Lane 3: Settings, the frame and its first three sections (#729, CAMP-9, CAMP-10, CAMP-11) #769
Loading…
Reference in a new issue
No description provided.
Delete branch "build/lane3-settings-a"
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?
Settings is nine surfaces and 109 features — bigger than the whole rest of this lane — so it is built in three PRs. This is the first: the frame every later section hangs off, plus General (CAMP-9), Invites & joining (CAMP-10) and Ownership (CAMP-11).
The frame, which is the part with rules in it
shell/settingsSections.jsis the nine sections in one list — id, label, summary,who, and abuiltflag. The index (pages/CampaignSettings.jsx) and the Contents (shell/useContents.js) both read it, so a later PR adds a section by flipping one word. The addresses are settled (/settings/general,/settings/invites,/settings/ownership); paths are contracts.Two decisions live in it:
IS-199: the index says whose each section is. Not every section is the table's business — some are the GM's and some the owner's — and the index says so before you press rather than after. A section the reader may not open is absent, not disabled, which is D57's rule on the member page applied again.IS-310: the way back out, which the first draw never gave. On a desktop the Contents highlights Settings; on a phone there was no exit at all. There is now, and each section is a page with its own address so Back works, a section can be sent to somebody, and D22's unsaved-changes prompt fires at all —SH-036only triggers on a change of pathname, so nine sections built as tabs of one page would have lost an edit with the guard sitting right there.Not built, each for a reason
CA-017/CA-019, the About card. Its home is This week (IS-355), not Settings — D45 is precisely the decision that moved a member's reading of the campaign off this page, which is whyCA-016is classifiedreplaced. Thegendrawing shows the card only to say where it went. It is already built on This week (#767). A second copy here would duplicate it and put a reading card back in the GM's cupboard.CA-223).InviteCreate.expires_in_daysisint = Field(default=7, ge=1, le=30)— required, capped at a month. The control would 422. 1 / 7 / 30 days are offered and the code says why. Making it real isint | Noneon the schema plus a null branch ininvite_service.create_invite; the UI already renders "never expires" correctly if it ever arrives.CA-222, D12 tier one). There is no inverse of a revoke, and remaking the link hands back a different address. A deferred delete with an undo window was considered and rejected: leaving the page inside the window would silently drop the DELETE, which is a worse lie than no Undo.navdraws "Stay here / Leave without saving / Save and leave";useUnsavedChangesWarningis stillwindow.confirmwith two buttons, and its own comment says a design-system dialog is a later step. Replacing it changes a hook every editor in the app uses — WikiArticle, SessionDetail and others — and does not belong in a Settings PR. General arms the existing guard. D22 is not a coverage row in these three blocks.Where the drawing and the server disagreed
create_invitealways setsexpires_at, so the drawn "For Ade · never expires" row is a state today's server cannot produce.CampaignSummarycarriesmy_rolebut notis_owner, so the Contents entry can only be GM-gated, as drawn — but #644 lets the owner be a plain player, and Ownership is theirs. The pages are gated on "a GM, or the owner" so a player-owner is not locked out of their own section; they reach it by address. Closing that properly is one field onCampaignSummary.Where I think the drawing is wrong
sysRow({unlinked:true})replaces the select with a bare input, stranding a GM with no visible way back to the registry.CA-216/CA-217describe the right shape — a dropdown defaulting to "Free text / Other" with the text box shown only while it is selected — andNewCampaign.jsxalready built it that way in lane 1. Followed the catalogue, and gave the box a visible label for the same reason lane 1 did.genFoothas no "Saved". A form with a Save button and no acknowledgement is a defect, and its finished siblingaiFooton CAMP-13 draws exactly that state (CA-091). Added a quiet "Saved" banner.One row added at the merge, by me, that neither drawing has
Settings' sub-list ends with "Everything else" →
/campaigns/:id/manage. Six of the nine sections — Discord, AI & recording, the wiki's rules, content packs, Foundry, and archive, export & delete — are still on the old campaign page, and that page has no address per section: it draws them inline and its?open=takes only the first-run actions. Without the row they would leave the navigation altogether the day Settings arrived, and archiving or deleting a campaign is not something to reach only by typing an address. It goes with the last of them. Owner, 2026-09-23: fine.Checks
Frontend 1672 tests in 131 files, green on the merged tree; eslint 0 errors, 234 warnings — the same count as main;
vite buildgreen.lane2b.pyandlane2.pyboth print with no unclassified rows and no classification changed.Rendered
lane2.html?only=setandlane2b.html?only={nav,gen,camp10,camp11}headless in both themes and read every slice — which caught two faults in this PR's own CSS, including the index row's "who" line landing under the icon rather than the name. The built CSS was probed separately against hand-written markup at two widths, becauseindex.css's cascade-layer note is about exactly the difference between the dev server and the emitted bundle.One honest limit: jsdom cannot evaluate a container query, so no test asserts what is visible at a given width. The way back out is tested in two halves — the desktop exit as the Contents, the phone's back bar by presence and address — and its phone-only-ness is a CSS fact verified by the render, not by a test.
🤖 Generated with Claude Code