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;
}