From 35b3ab8e897eecc620d65afa2f62dfbe2e88d09c Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Thu, 20 Aug 2026 07:24:38 +0200 Subject: [PATCH] fix: let the settings panel use the width of the page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looked at on a 1440px screen it was the one block on the page not using the column it sits in. The rows were held to max-w-3xl inside an 1150px shell, but the section's own rule spans the shell — so every hairline inside the panel stopped a third of the column short of the one above it, between a two-column checklist and a three-column footer that both go full width. The measure was there to keep a name and its state reading as one line, but that pairing is what "running now" already does with a title and its countdown directly above, so full width is the house pattern rather than a stretch. Prose inside a group keeps its measure. The game list was the worse half: eighteen rows of four controls at max-w-md is a thousand pixels of ribbon down the left with two thirds of the screen empty beside it. It takes two columns 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. Below nine, and on a phone, it is unchanged. The last group also loses its bottom rule, which was a second hairline a few pixels above the footer's own. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 10 ++++++++ docs/PRD.md | 9 +++++++ src/client/components/Controls.tsx | 41 +++++++++++++++++++++--------- src/client/styles.css | 9 +++++++ 4 files changed, 57 insertions(+), 12 deletions(-) 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; }