diff --git a/AGENTS.md b/AGENTS.md index 432057e..706f0c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -937,6 +937,16 @@ to an open page). Four things hold it up: Native `
` for the reason the reorder arrows are ordinary buttons: keyboard and screen reader reach it without a second implementation. `summary` is not an `a`, `button` or `[tabindex]`, so it needs its own `:focus-visible` rule in `styles.css` — the shared one does not reach it. +- **The panel is one column of groups, and it uses the page's width.** Those are two claims and only + the first survived a desktop read: the rows were held to `max-w-3xl` inside a shell a third wider, + so every rule inside the panel stopped short of the panel's own, and it was the one block on a wide + page not using the width — between a two-column checklist and a three-column footer that both do. + A name at one end of a row and its state at the other is what "running now" already does with a + title and its countdown, so full width is the house pattern rather than a stretch. Prose inside a + group keeps its own measure, and the game list — the one thing tall enough to need it — takes **two + columns of its own past `lg` once there are nine or more games**, flowing down then across so the + numbers still read in a straight line and an arrow still swaps a row with its neighbour. Eighteen + rows in one column was a 1,000px ribbon with two thirds of the width empty beside it. - **A game's dailies stay together, and grouping never re-sorts inside a group.** `dailyGroups` (`Dailies.tsx`) emits a game's standing chore followed by that game's repeating events, in the order those arrived; it replaced `[...chores, ...repeating]`, which put a game's chore and its own diff --git a/docs/PRD.md b/docs/PRD.md index 0f559a5..71361af 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -217,6 +217,15 @@ points at: the empty states name both the switch and the group it is in, which i a checkbox in a wall was. And **the summary states are derived, never stored** — they are a reading of `prefs`, so they cannot disagree with the controls inside. +**The panel uses the page's width**, like every other block on it. The rows were held to a narrower +measure on the argument that a name and its state stop reading as one line across a wide screen, but +the panel's own rule spans the shell, so the rules inside it stopped a third of the column short and +the panel became the one thing on a desktop page not using the width — sitting between a two-column +checklist and a three-column footer that both do. A title at one end of a full-width row and its +countdown at the other is what "running now" does directly above it. Prose inside a group keeps a +readable measure, and the game list takes two columns of its own on a wide screen once there are nine +or more games, because eighteen rows in one column is a thousand pixels of ribbon down the left. + **F4a — Focus one game at a time.** Switching games on and off says *which games the reader plays*, and is set once. It is the wrong tool for the thing a player of four games actually does while reading: clear one game, move to the diff --git a/src/client/components/Controls.tsx b/src/client/components/Controls.tsx index 93d9728..17de708 100644 --- a/src/client/components/Controls.tsx +++ b/src/client/components/Controls.tsx @@ -61,11 +61,11 @@ const SPLITS: Array<{ id: boolean; label: string; hint: string }> = [ * report of how the app is configured, and makes opening one a deliberate act * rather than the price of reading it. * - * One column rather than the two this replaced. The two-column split was there - * because the panel was tall and a wide screen had room to halve it; with the - * groups closed the whole thing is shorter than the header above it, so the - * argument is gone, and full-width rows give the summary state somewhere to sit - * on the right of the name it belongs to. + * One column of groups rather than the two this replaced: the split was there + * because the panel was tall, and with the groups closed the whole thing is + * shorter than the header above it. That is a claim about the *groups*, not + * about the panel's width — the rows run the full column, and the one list long + * enough to need it takes two columns of its own inside its group. */ export function Controls({ games, @@ -104,11 +104,16 @@ export function Controls({ you only have to open the one you came to change.

- {/* Held to a readable measure rather than the container's full width. Each - row pairs a name on the left with its state on the right, and across a - wide screen those two ends stop reading as one line — which is the whole - point of putting the state there. */} -
+ {/* Full width, like every other row on this page. This was held to + `max-w-3xl` on the argument that a name and its state stop reading as + one line across a wide screen — but the section's own rule spans the + shell, so every rule inside it stopped short of that by a third of the + column, and the panel became the one block on a desktop page not using + the width, between a two-column checklist and a three-column footer + that both do. The pairing itself is what the page already does: a + deadline and its countdown sit at opposite ends of a full-width row + directly above this. Prose inside a group keeps its own measure. */} +
-
    + {/* Two columns once there are enough games to make one tall, and once the + screen is wide enough to hold them — eighteen rows was a 1,000px ribbon + down the left of a desktop page with two thirds of the width empty + beside it. It flows *down* then across, not across then down, so the + numbers still read 1‑2‑3 in a straight line and an arrow still swaps a + row with the one it is next to. Below nine games it stays one column: + splitting a list short enough to take in at a glance only strands a + couple of rows in a second column. */} +
      = 9 ? "lg:max-w-4xl lg:columns-2 lg:gap-x-10" : "" + }`} + > {games.map((id, i) => { const game = gameMeta(id); const on = !hidden.includes(id); @@ -517,7 +534,7 @@ function GameOrder({ setDragging(null); }} onDragEnd={() => setDragging(null)} - className={`flex items-center gap-2 rounded-lg py-1 ${ + className={`flex break-inside-avoid items-center gap-2 rounded-lg py-1 ${ dragging === i ? "opacity-40" : "" }`} > diff --git a/src/client/styles.css b/src/client/styles.css index ac5c7a2..6be3e45 100644 --- a/src/client/styles.css +++ b/src/client/styles.css @@ -448,6 +448,15 @@ body { border-radius: 3px; } +/* + * The last group closes the list, so its rule would be a second hairline a few + * pixels above the footer's own — two parallel lines with nothing between them, + * which reads as a mistake rather than as a boundary. + */ +.settings-group:last-child { + border-bottom-width: 0; +} + .settings-chevron { transition: transform 150ms ease; }