feat(control): one question per screen on a phone - #483
Merged
Conversation
/control put both full-width panes on a phone at once — the project detail and
the 21-row roster, stacked. Whichever is second is off screen, so the layout
was really a choice of which half to hide, and both orderings had been tried:
list first → the one working agent sat under 20 idle rows, ~3 screens down
detail first → (the previous fix) the page OPENED on a project you had not
chosen, with the chooser below it
The second is what shipped, and using it is how the cost shows up. Tapping a
row changed a highlight and nothing else, because the pane it drives was
already three scrolls above — so the tap read as broken. And getting back to
the composer meant scrolling up THROUGH a nested scroller: measured at 671px
tall, a 336px window holding 1835px of rows, inside a page with 1746px of its
own scroll. A drag near the list scrolled the list; a drag elsewhere scrolled
the page. Two scrollers, one gesture, unpredictable result.
Ordering was standing in for the real fix: show ONE pane at a time.
roster → "which project?" (the fleet, by name, prioritised)
tap → detail → "what should it do?" (state, composer, one action)
‹ All projects → back
From `lg` up nothing changes: both panes side by side, no back button, the
rail keeps its capped scroll so it cannot run past the detail.
WHY A NEW PIECE OF STATE. `selectedTab` cannot express "the operator opened a
project": ControlPanel deliberately forces a selection whenever it is null,
because the desktop two-column layout must never render an empty detail pane.
Correct there, and exactly what made the phone open on someone else's choice.
So `projectOpenOnPhone` answers the phone's own question and desktop ignores
it. A `?focus=` deep link sets it too — arriving from a push notification or
the failure banner and being shown the roster instead would make the link
useless exactly when it matters.
The 50dvh cap on the rail list is lifted below `lg`. It was right while the
list shared the screen; now the list IS the screen when it shows, so the page
scroll is the only scroll — one gesture, one result.
Verified in a browser at 624px and 1742px: phone shows hero + roster with no
detail card, tapping opens the project with a back control, back returns; ONE
scroller throughout (app-main), down from two, and page content drops 2173px →
1335px because only one pane renders. Desktop: both panes visible, back button
absent. audit:responsive clean at 320/390/768/1440; pnpm run verify passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UvjGNAS9CMfEGNW26tUR4P
Collaborator
Author
|
Rebasing onto the green main (#484 unblocked it). |
github-actions Bot
pushed a commit
that referenced
this pull request
Sep 4, 2026
…#485) Follow-on to #483. That gave the phone one PANE per screen; this reduces what the remaining pane puts in front of you. Measured on the live detail screen at 390px before touching it: 1088px tall, 114 words, 22 controls — and SIX different ways to start the same action (type + Send, tap Suggested next, tap Next best, tap a history chip, open Prompt library, open More). Six entry points to one job, with nothing saying which is the normal one. Two changes, both removing duplication rather than hiding function: 1. THE "SEND TO AGENT" HEADING IS GONE. The composer directly above already asks the question — its placeholder is literally "What should the agent work on?" — so the kicker restated it, and worse put a section break between the two halves of ONE decision. Measured: the composer ended at y=779 and Next best began at y=924, reading as a separate feature rather than the other way to answer the same prompt. The honesty chip stays; it says something the button cannot ("Builder online"). 2. ONE REUSE AFFORDANCE INSTEAD OF TWO. RecentPromptChips and ProjectPromptLibrary do the same job with the same handler — `onPick={onCustomChange}` and `onSelect={onCustomChange}`, both "put text in the composer" — yet rendered as two blocks with two headings, 4 of the 22 controls and ~150px, below the fold, competing with the composer they feed. They now share one labelled disclosure, closed by default: a convenience does not get to open itself on the screen where space is scarcest. CSS-only (`max-lg:hidden` + a `lg:hidden` toggle), so there is no hydration branch and desktop is untouched — that card has the room and never had the problem. Result, same environment, same project, before → after: 1088px → 888px (-18%) 114 words → 91 (-20%) 22 controls → 19 NOT DONE, and worth naming rather than implying otherwise. Of the 19 that remain, six are header context/navigation (streak, Focus terminal, Open here, branch, project details, agent setup) that you pass before reaching the question — that is the next lever. And "Autopilot on" and "Pause automatic continuation" are two adjacent controls about whether it keeps going by itself; they are genuinely distinct (automationMode vs per-project auto-continue), so merging them is a semantics change I am not making blind. pnpm run verify passes. Claude-Session: https://claude.ai/code/session_01UvjGNAS9CMfEGNW26tUR4P Co-authored-by: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
github-actions Bot
pushed a commit
that referenced
this pull request
Sep 4, 2026
Today a dependabot bump turned main red and stopped the merge queue. The gate was right; its TIMING was racy, and the race is the whole bug. What happened, in order: #482 merges, mints fleet-runner-v0.8.16. #411 (dependabot, touches ONLY desktop/package-lock.json) had already run its CI while v0.8.15 was newest. Against v0.8.15 its base ALREADY read 0.8.16, so "version is ahead of released" held — CI PASSED. #411 merges. Newest tag is now v0.8.16, the only desktop change since it is that lockfile, and the identical check goes RED on main. Both evaluations were correct. The gap is between them, and everyone else pays: the sweep merges nothing onto a red base, so every unrelated PR stalls behind a bot's dependency bump. #483 failed CI on a diff that touched no desktop file. The existing question is cumulative — "has anything changed since the last tag" — and its answer moves when a tag is minted, i.e. for reasons outside the branch. So ask a second question that has no race in it: does the diff of THIS BRANCH touch desktop/, and if so does THIS BRANCH bump desktop/package.json? Decided entirely within the branch, identically before and after any tag appears. It fires on the PR — one blocked bot PR a human can see — instead of after the merge, where it is a red main and a stopped queue. On main itself the branch diff is empty and the rule does not apply; the cumulative check still guards there. Mutation-proven: desktop change, no bump -> exit 1, "this branch changes desktop/ but does not bump desktop/package.json", naming the file + bump, no changelog -> exit 1 on the pre-existing changelog rule (the two compose; a bump alone is still not publishable) neither -> passes, rule correctly silent This is the fix I would rather have than the automation I had offered — opening a bump PR after the fact repairs the outage; this prevents it. pnpm run verify passes. Claude-Session: https://claude.ai/code/session_01UvjGNAS9CMfEGNW26tUR4P Co-authored-by: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
/controlput both full-width panes on a phone at once — the project detail and the 21-row roster, stacked. Whichever is second is off screen, so the layout was really a choice of which half to hide. Both orderings had been tried:Using it is how the second one's cost shows up. Tapping a row changed a highlight and nothing else — because the pane it drives was already three scrolls above, so the tap read as broken. And getting back to the composer meant scrolling up through a nested scroller: measured at 671px tall, a 336px window holding 1835px of rows, inside a page with 1746px of its own scroll. A drag near the list scrolled the list; a drag elsewhere scrolled the page.
Ordering was standing in for the real fix: show one pane at a time.
From
lgup nothing changes: both panes side by side, no back button, the rail keeps its capped scroll so it cannot run past the detail.Why a new piece of state
selectedTabcannot express "the operator opened a project".ControlPaneldeliberately forces a selection whenever it is null, because the desktop two-column layout must never render an empty detail pane — correct there, and exactly what made the phone open on a choice you didn't make. SoprojectOpenOnPhoneanswers the phone's own question, and desktop ignores it.A
?focus=deep link sets it too: arriving from a push notification or the failure banner's "Open on Control" and being shown the roster instead would make the link useless exactly when it matters.The
50dvhcap on the rail list is lifted belowlg. It was right while the list shared the screen; now the list is the screen when it shows, so the page scroll is the only scroll.Verified in a browser, both breakpoints
624px — hero + roster, no detail card; tapping opens the project with a back control; back returns to the roster. One scroller throughout (
app-main), down from two, and page content drops 2173px → 1335px because only one pane renders.1742px —
listVisible: true, detailVisible: true, backBtnVisible: false. Unchanged.audit:responsiveclean at 320/390/768/1440.pnpm run verifypasses.🤖 Generated with Claude Code
https://claude.ai/code/session_01UvjGNAS9CMfEGNW26tUR4P