Skip to content

Search the phone case picker, and drop the duplicate toolbar stepper - #428

Open
Ark0N wants to merge 2 commits into
masterfrom
feat/mobile-case-search-and-toolbar-cleanup
Open

Ark0N wants to merge 2 commits into
masterfrom
feat/mobile-case-search-and-toolbar-cleanup

Conversation

@Ark0N

@Ark0N Ark0N commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Two small UI changes, both from the same "make the shell strip and its
neighbours calmer" thread as the tab strip redesign brainstorm in
Discussion #426. That
discussion is where the bigger directions (A Clusters, B Ledger, C Triage,
D Focus strip, E Mission control, F Segments, G Header right side) are still
open for feedback; this PR is only the two pieces that did not need a decision
first.

1. The phone case picker can be searched

Tapping the case button on a phone opens a bottom sheet listing every case.
With more than a handful of cases that is a long scroll, and there was no way
to narrow it, even though the desktop toolbar has had a filtering combobox
(#quickStartCaseSearch) for a while.

The sheet now has a search field that runs the same matcher the desktop
combobox runs (filterCasePickerOptions), so both pickers answer a query
identically: every whitespace-separated term has to appear in the option's
searchText, which already carries the case name, the rendered label, the
path and the remote/docker fields. Searching mac-mini finds the case on that
remote host, not just cases with mac in the name.

Behaviour worth calling out:

  • The filter resets on every open. The sheet is a one-shot picker, and a
    query left over from last time would present a truncated list as if it were
    the whole one.
  • No autofocus. Focusing the field raises the keyboard over a sheet that is
    anchored to the bottom of the screen, so the user asks for it rather than
    getting it on every open.
  • Enter takes a single remaining match and closes the sheet. With several
    matches it just dismisses the keyboard. Escape drops the filter first and
    only closes the sheet on a second press.
  • No match renders "No cases match" rather than a blank sheet.

The one non-obvious piece is the keyboard. The sheet is a third
position: fixed bottom-anchored surface, so it joins the toolbar and the
accessory bar in KeyboardHandler's existing lift. iOS does not shrink the
layout viewport when the keyboard opens, so an unlifted sheet would sit
behind the keyboard with its own search box out of sight. resetLayout()
clears that offset unscoped (not only for an open sheet), or a sheet closed
while the keyboard was still up would slide in already displaced the next time.

Two CSS traps that cost a round of screenshots each, noted in the code:

  • The clear button needs an explicit [hidden] rule. The UA's display: none
    is specificity (0,0,0) and loses to the button's own display: flex, so the
    clear button was visible over an empty field.
  • The input has to drop the global input:focus-visible ring, which inside an
    already-bordered row painted a second border a few pixels in.

2. One instance stepper in the toolbar, not two

The desktop toolbar carried two identical − 1 + steppers side by side, one
after Run and one after Run Shell:

before:  [▷ Run][v]  [− 1 +]  [Run Shell]  [− 1 +]  [case v] [+] [⚙]
after:   [▷ Run][v]  [− 1 +]  [Run Shell]  [case v] [+] [⚙]

The second one (#shellCount) is gone. Run Shell keeps the capability:
both launch paths now read the remaining #tabCount control through a shared
_toolbarInstanceCount(), so setting the counter to 3 and pressing Run Shell
still starts three shells. That helper also makes an absent stepper read as 1
instead of throwing, which matters twice: the group is display: none on
phones and tablets, and the Run dropdown's Terminal / Shell entry routes
through runShell() as well, where the visible counter used to be ignored
entirely.

Desktop only. Both steppers were already hidden under 1024px.

Testing

  • npm test green: 383 files, 7257 passed, 12 skipped.
  • 7 new unit tests in test/run-mode-ui.test.ts covering the sheet's
    reset-on-open, the rendered rows for a query, the remote-host match, the
    empty state, clearing, the Enter shortcut in both the single-match and
    ambiguous cases, and the counter fallback.
  • Driven for real in a browser against an isolated instance (its own
    CODEMAN_INSTANCE, never prod) with 31 cases: Pixel 7 viewport for the
    sheet on both a dark and a light skin, 1440px for the toolbar. Run Shell
    with the shared counter at 2 produced s1-mobile-tests and s2-mobile-tests.
  • typecheck, lint, format:check, check:frontend-syntax,
    check:public-assets all clean.

The phone case sheet listed every case with no way to narrow it, while the
desktop toolbar combobox has filtered for a while. The sheet now carries a
search field that runs the same matcher (filterCasePickerOptions), so both
pickers answer a query identically: every term has to appear in the option's
searchText, which already carries the name, the rendered label, the path and
the remote/docker fields.

Details worth keeping:

- The filter resets on every open. The sheet is a one-shot picker, and a
  leftover query would present a truncated list as the whole one.
- No autofocus. Focusing raises the keyboard over a sheet anchored to the
  bottom of the screen, so the user asks for it.
- The sheet is a third position:fixed bottom-anchored surface, so it joins the
  toolbar and the accessory bar in KeyboardHandler's keyboard lift. iOS does
  not shrink the layout viewport, so an unlifted sheet would sit behind the
  keyboard with its own search box out of sight. resetLayout() clears the
  offset unscoped, or a sheet closed while the keyboard was up would slide in
  already displaced next time.
- Enter takes a single remaining match and otherwise just dismisses the
  keyboard; Escape drops the filter before it closes the sheet.
- No match renders an empty state rather than a blank sheet.
- The clear button needs an explicit [hidden] rule: the UA's display:none is
  specificity (0,0,0) and loses to the button's own display:flex.
- The input drops the global input:focus-visible ring, which inside an already
  bordered row drew a second border a few pixels in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The desktop toolbar carried two identical "minus 1 plus" instance steppers side
by side, one after Run and one after Run Shell. The second (#shellCount) is
gone for a cleaner strip.

Run Shell keeps the capability: both launch paths now read the remaining
#tabCount control through _toolbarInstanceCount(), which also makes an absent
stepper read as 1 instead of throwing. That matters because the group is
display:none on phones and tablets, and because the Run dropdown's
Terminal / Shell entry routes through runShell() too, where the visible counter
was previously ignored.

Desktop only: both steppers were already hidden under 1024px.

Co-Authored-By: Claude Opus 5 (1M context) <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.

2 participants