feat: move the focus bar into the deadline rail past lg

Past `lg` the page is a pinned rail of instructions beside the lists, and
the focus bar was still a full-width strip above both columns: it spent
the whole width of a wide screen on a row of chips and pushed "next to
expire" — the thing the reader opened the page for — down behind it, while
sitting nowhere near the panels it narrows.

The top of the rail is still above everything it affects, and now it pins
with the deadlines and dailies it filters. On a phone, and on the
timeline, which has no rail, nothing moves: the same rule puts it back at
the top of the page. One element, rendered once per view.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-18 22:12:43 +02:00
co-authored by Claude Opus 5
parent 7a90158139
commit 16fcfed9b3
+32 -14
View File
@@ -353,6 +353,30 @@ export function App() {
); );
} }
/**
* Working through games one at a time, which is how someone with four of them
* actually plays: clear one, move on.
*
* It goes at the top of the column it filters, and past `lg` the checklist's
* column is the rail: the bar rides with the deadlines and the dailies it
* narrows, pinned and still above every one of them, instead of spending the
* full width of a wide screen on a row of chips and pushing "next to expire"
* — the answer the reader came for — down the page to make room. On a phone,
* and on the timeline, which has no rail, that same rule puts it back at the
* top of the page. Rendered once per view, never twice on one page.
*/
const focusBar = (
<GameFocus
games={enabled}
focus={focus}
counts={perGame}
total={scopedTodo.length}
next={advanceFocus(focus, enabled)}
onFocus={(focusGame) => update({ focusGame })}
onAdvance={() => update({ focusGame: advanceFocus(focus, enabled) })}
/>
);
return ( return (
<GameMetaProvider value={gameMeta}> <GameMetaProvider value={gameMeta}>
<Shell> <Shell>
@@ -401,19 +425,6 @@ export function App() {
</div> </div>
</header> </header>
{/* Working through games one at a time, which is how someone with four
of them actually plays: clear one, move on. Above everything it
filters, so what it is doing to the page is never a mystery. */}
<GameFocus
games={enabled}
focus={focus}
counts={perGame}
total={scopedTodo.length}
next={advanceFocus(focus, enabled)}
onFocus={(focusGame) => update({ focusGame })}
onAdvance={() => update({ focusGame: advanceFocus(focus, enabled) })}
/>
{view === "soon" ? ( {view === "soon" ? (
/* /*
* Two columns once the screen has room for them, and the split is the * Two columns once the screen has room for them, and the split is the
@@ -421,7 +432,8 @@ export function App() {
* *telling* the reader to do on the left, what it is *showing* them on * *telling* the reader to do on the left, what it is *showing* them on
* the right. The deadlines and tonight's dailies are instructions, they * the right. The deadlines and tonight's dailies are instructions, they
* are short, and they are what the reader came for — so on a wide * are short, and they are what the reader came for — so on a wide
* screen they stop scrolling away and stay pinned beside the list. * screen they stop scrolling away and stay pinned beside the list, with
* the focus bar that narrows them at the top of the same column.
* *
* Below `lg` this is one column in exactly the old order, because on a * Below `lg` this is one column in exactly the old order, because on a
* phone the same argument produces the same answer: put them first. * phone the same argument produces the same answer: put them first.
@@ -433,6 +445,8 @@ export function App() {
full-height divider would spend most of its length walling off full-height divider would spend most of its length walling off
an empty gap. It travels with the panel as that pins. */} an empty gap. It travels with the panel as that pins. */}
<div className="scroll-pane lg:sticky lg:top-0 lg:max-h-screen lg:overflow-y-auto lg:border-r lg:border-hairline"> <div className="scroll-pane lg:sticky lg:top-0 lg:max-h-screen lg:overflow-y-auto lg:border-r lg:border-hairline">
{focusBar}
<NextUp <NextUp
rows={headline} rows={headline}
focused={focus === null ? null : gameMeta(focus).name} focused={focus === null ? null : gameMeta(focus).name}
@@ -510,6 +524,9 @@ export function App() {
</div> </div>
</div> </div>
) : ( ) : (
<>
{focusBar}
<Timeline <Timeline
rows={visible} rows={visible}
now={now} now={now}
@@ -521,6 +538,7 @@ export function App() {
onOpen={setOpenId} onOpen={setOpenId}
isDone={isDone} isDone={isDone}
/> />
</>
)} )}
<Controls <Controls