Skip to content

feat(control): one question per screen on a phone - #483

Merged
github-actions[bot] merged 2 commits into
mainfrom
feat/control-one-question-per-screen
Sep 4, 2026
Merged

feat(control): one question per screen on a phone#483
github-actions[bot] merged 2 commits into
mainfrom
feat/control-one-question-per-screen

Conversation

@catomean

@catomean catomean commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

/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. Both orderings had been tried:

ordering what it cost
list first the one working agent sat under 20 idle rows, ~3 screens down
detail first (what shipped) the page opened on a project you had not chosen, with the chooser below it

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.

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 a choice you didn't make. 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's "Open on Control" 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.

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.

1742pxlistVisible: true, detailVisible: true, backBtnVisible: false. Unchanged.

audit:responsive clean at 320/390/768/1440. pnpm run verify passes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UvjGNAS9CMfEGNW26tUR4P

/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
@catomean

catomean commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Rebasing onto the green main (#484 unblocked it).

@github-actions
github-actions Bot merged commit 07410fe into main Sep 4, 2026
3 checks passed
@github-actions
github-actions Bot deleted the feat/control-one-question-per-screen branch September 4, 2026 13:01
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant