Lane 3: Settings — Discord, and Recording, retention & AI (#729, CAMP-12, CAMP-13) #776
Loading…
Reference in a new issue
No description provided.
Delete branch "build/lane3-settings-b"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The second of three Settings PRs: Discord (CAMP-12) and Recording, retention & AI (CAMP-13) — the biggest section in the cupboard. Five of nine sections are now built; the
builtflag insettingsSections.jscarries the rest.The cap (D24) meets a GM, which is the part to read
#763 built the server. This is the first surface where a GM sees it.
IS-312's three states are all built and tested, including the one a naive build misses — "This instance keeps recordings 30 days. ⚠ 180 days is as long as it allows — an administrator can raise that." The test reaches that state from a number the GM types, not only from the stored one.CA-073's "Keep it" is absent under a cap, present without one, asserted both ways. The same rule is generalised to transcripts' "Keep them" — the drawing only ever draws that row on an uncapped instance, so the rule was never visible there, but offering "keep the words for good" under aretain_monthscap would be offering something the server is about to refuse.retain_indefinitelyon both sides offers all four modes and a test asserts the round trip with no question asked.One correction to my own brief
I told the delegate to use
settings_service's ordering helpers rather than re-derive the order. Those are Python and cannot be imported into a page, andPrivacy.jsxhas already faced this and deliberately refuses to copy an ordering into JS ("a second copy of it here is exactly the drift").What it did instead is better than either: "at the cap" is an equality of pairs, never a comparison; and "did this get shorter?" is derived from the thing the setting is actually about — a cutoff instant, where bin-on-success is now, N days is now − N, and for-good is never. That reproduces the server's order by construction rather than restating its rank table, and it catches the case a number-only model misses: 90 days → "bin it once transcribed" is a shortening with the same figure. Used for one thing only, deciding whether to ask a question; the server still refuses over-cap writes and the sweep still reads its own comparator.
D36: the drawing draws a dead button, and this does not
The drawing puts a bare
disabledon the channel test when the ID is malformed. That is precisely what D36 forbids. Both tests arearia-disabledwith a visible,aria-describedby-linked reason ("Fix the channel ID first.", "There is no webhook to test yet.") and stay in the tab order.IS-395/D67 is said twice over: in the blank-server hint as drawn, and as a banner when both are off — "Nothing is posted anywhere… There is no server-wide fallback: an instance's Discord settings never post for a campaign" — which appears the moment a GM presses "Remove it", before saving, which is when they need it.Two rows built partially, both because the server cannot answer honestly
IS-314(never set up / switched off / switched back on).GET /api/campaigns/{id}/ai-settingsis 403'd by the very switch D25 is about, so while per-campaign AI is off the page cannot read this campaign's stored endpoints — or even whether it has any. The drawing'skvlist of the GM's actual summariser and transcriber is therefore not built; the first two states are one panel, worded to be true of both. Caching the values in localStorage and claiming them back was rejected: a stale claim about somebody's stored API key is a worse failure than a sentence that covers two cases. Follow-up filed.IS-313(what a sweep would take). No endpoint previews one, andSessionListItemcarries nothing about whether a session still holds audio or a transcript. So the count is of sessions old enough to be caught — which is exact — and what they still hold is hedged. The drawing's "Three sessions have audio older than that" would have been a guess, andhas_summaryover-counts, since a summary outlives its transcript by design. Follow-up filed.Where else the drawing and the server disagreed
/settings/recording, not/settings/ai. My contract saidai; the merged frame saysrecording, and the id is the address segment. Paths are contracts, so the merged code won.IS-313's confirmation hangs off saving them, and D22's guard needs something to be dirty against. The page has its own Save/Cancel; the panel keeps its own (different endpoint, a PUT, andCA-091names it), labelled "Save the models" so two save buttons never share an accessible name.guild_idis validated digits-only and nothing may call Discord from the backend to resolve a name. The drawing's "The bot is in this server" is dropped — nothing in the app can know that — for what the field actually decides.What the render caught that reading the code did not
Both prototype sections in both themes, and the real built pages from a stub API, dark and light, including the shortening modal and the D25 panel. Four faults, all invisible in the markup:
Sectionlost the form's rhythm —.form's grid gap falls between its children, which on this page are the sections, so every hint ran into the next label..segis flex and shrank inside.ret__pick, wrapping "Bin it once transcribed" onto two lines and "The instance's" into a column one word wide.A fifth — a placeholder printed where the instance default goes, when that read fails or is in flight — was caught by a unit test first.
Checks
Frontend 1755 tests in 133 files (81 new), green.
npm run lint0 errors, 234 warnings — identical to the baseline; neither new file contributes one.npm run buildclean.coverage/lane2b.pyprints with no unclassified rows and no classification changed.Nothing depends on the wall clock: fixture sessions are dated 2019 (older than any window the file sets, and still older in ten years) or carry
confirmed_time: null. The "shortening that catches nothing" case is a 9,000-day window over a 2019 session — genuinely a shortening, and catches nothing until 2043.A player may not open either section: absence asserted, not a disabled control.
🤖 Generated with Claude Code