Skip to content

fix(workspaces): slim CLI pane header on mobile — icons + short labels, desktop unchanged#26

Merged
miguelrisero merged 5 commits into
mainfrom
fix/mobile-cli-header
Jul 8, 2026
Merged

fix(workspaces): slim CLI pane header on mobile — icons + short labels, desktop unchanged#26
miguelrisero merged 5 commits into
mainfrom
fix/mobile-cli-header

Conversation

@miguelrisero

@miguelrisero miguelrisero commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Problem

The CLI pane header (CliMainPane) packs "Claude CLI" + "persistent tmux session" + the full LoopAutomationControl cluster ("Retrying at 14:32 · attempt 3/10" + cancel-X + ⟳ + "Keep going" + Switch + gear) + "Back to chat" into one non-wrapping h-8 flex row with zero responsive handling. Every ancestor is overflow-hidden, so on a 360–430px phone the right-hand controls (the switch, gear, back-to-chat) are clipped and unreachable. LoopAutomationControl (added in #23) is what pushed the row past viewport width.

What changed

Pure Tailwind breakpoint gating at md: (768px), which pairs with useIsMobile's 767px boundary — no JS hook. Desktop ≥768px renders computed-style identical for typical content; under extreme width pressure (narrow split pane + loop ON + pending wakeup) the title now truncates gracefully with an ellipsis where it previously wrapped into the clipped h-8 row.

  • CliMainPane.tsx: header row gets gap-2 min-w-0; title span gets min-w-0 truncate; mobile shows a new short title cliMode.titleShort ("CLI") while ≥768px keeps "Claude CLI"; the "persistent tmux session" hint is hidden md:inline; "Back to chat" text is hidden md:inline (icon-only on mobile) with an added aria-label; right cluster gets gap-2 md:gap-3 shrink-0.
  • LoopAutomationControl.tsx: root gap-1.5 md:gap-2 min-w-0; wakeup status keeps the moon/clock icon + a bare time (14:32) on mobile vs the full "Retrying at …"/"Waking at … UTC" on desktop, with whitespace-nowrap shrink-0; the "· attempt x/y" suffix and the standalone attempts span are hidden md:inline; "Keep going" label is hidden md:inline (icon + Switch remain, Switch already has an aria-label); toggle group and gear trigger get shrink-0.
  • Added cliMode.titleShort: "CLI" to all 7 locales (en, es, fr, ja, ko, zh-Hans, zh-Hant).

Review follow-ups (ship-it battery):

  • a11y: the mobile bare time is aria-hidden and the full localized "Waking/Retrying at …" string stays in the accessibility tree (sr-only md:not-sr-only), so screen readers never hear an orphaned time; "Back to chat" gains a ~29.5px mobile tap target via p-1.5 -m-1.5 (reset at md: so the desktop box is unchanged).
  • simplify: the desktop status span and mobile sr-only twin merged into one span; the triple nextWakeup.kind check hoisted into isUsageLimitWake; the 767/768 breakpoint tie documented in both files.

Result on a 375px viewport (worst case, loop ON + pending wakeup): CLI | [clock 14:32 ×] ⟳ [switch] ⚙ | [chat icon] — one row, nothing clipped.

How verified

  • pnpm run web-core:check, pnpm run local-web:check (tsc) — pass.
  • pnpm run local-web:lint (ESLint, max-warnings 0) — pass.
  • node scripts/check-unused-i18n-keys.mjs — "No unused i18n keys found" (all hidden labels stay in JSX via CSS, and titleShort is present in all 7 locales).
  • pnpm run web-core:format / local-web:format (prettier) — clean.
  • JSON validity of all 7 edited locale files verified.
  • CI frontend-checks green on every push.
  • Not verified: live device/emulator visual pass. The change is CSS-only gating at a breakpoint that pairs with the existing mobile boundary; reviewers verified the flex/gap arithmetic statically (worst-case mobile cluster ≈230px vs 328px available at 360px).

Risks

  • Mobile drops the "attempt x/y" datum from the row (still available via the gear popover) and the visible "UTC" qualifier on the wakeup time (the moon icon disambiguates kind; the sr-only string keeps "Waking at … UTC" for assistive tech).
  • Tablets/desktop ≥768px are intentionally untouched for typical content. In an extreme narrow desktop split pane (loop ON + pending wakeup), the now-incompressible right cluster clips before wrapping — accepted tradeoff of the mandated shrink-0/whitespace-nowrap protection; pre-PR behavior (text wrapping into the clipped h-8 row) was also degraded.
  • LoopAutomationControl is only mounted by CliMainPane today, so the md: classes are scoped correctly.
  • Sub-pixel viewport widths between 767 and 768px match neither media query — pre-existing app-wide trait of the 767/768 pairing, unchanged by this PR.

ship-it battery

Full review battery run against this PR (head 18b4a360):

  • Council round 1 (mixed: 4 Codex personas + 1 Claude cross-check on the dx seat): no P0/P1. Applied 2 cross-engine P2s in 9e6cfc14 — sr-only wakeup status for mobile screen readers; ≥24px back-to-chat tap target. Deferred P3s: visible "UTC" marker on mobile (locked design: bare time + icon, sr-only carries the qualifier), "byte-identical" PR wording (fixed in this body).
  • Simplify pass (4 parallel angles: reuse/simplification/efficiency/altitude): reuse + efficiency clean; applied in b6eb0492 — merged the status spans via sr-only md:not-sr-only, hoisted isUsageLimitWake, documented the 767/768 tie. Rejected all min-w-0/shrink-0/whitespace-nowrap removals (brief-mandated defensive classes) and an IconButton swap (would change desktop markup).
  • Codex review (xhigh reasoning, read-only): "No P0/P1/P2/P3 findings. The diff is clean within the requested scope."
  • code-review xhigh (10 finder angles + dedup/triage): no code-change findings survived; applied comment-accuracy fixes in 34fbc8b0 (dropped the "exact complement" overclaim — fractional 767–768px widths match neither query; scoped the sr-only claim to the Waking/Retrying string). Rejected with reasons: mobile bare-UTC (locked), desktop narrow-pane rigidity (brief-mandated classes, documented above), attempts counter hidden from mobile AT (sighted/AT parity, popover addition explicitly out of scope), 44px tap-target convention (infeasible in an h-8 header), 320px overflow claim (contradicted by arithmetic at the 8px/10px design-system font sizes).
  • Council round 2 (mixed, same panel, regression check on the fixes): 5/5 APPROVE, zero P0/P1. Non-blocking P3s noted and deferred: tap target erodes to ~23.6px only under the user-elected 0.8 mobile font scale (uniform app-wide rem scaling, pre-existing for all controls); wakeup consts computed while enabled=false (one string format per render, not worth the guard coupling).
  • CodeRabbit: CLI is deterministically unusable in this non-interactive environment (signed out, --api-key required; same result as the feat(executors): Fable/Claude-5 model family, ultracode effort level, free-text model entry + effort for preset models #29 runner). Substituted a codex CodeRabbit-style final sweep on the head diff. Sweep 1 found 1 actionable (md:whitespace-nowrap missing on the not-sr-only status span — not-sr-only resets white-space to normal, undoing the parent nowrap guard) → fixed in 18b4a360. Sweep 2 on the new head: "ACTIONABLE FINDINGS: 0". The CodeRabbit GitHub App is not installed on this fork, so no app review will post.
  • Commits: f001c9c8 (original fix) → 9e6cfc14 (council r1 a11y) → b6eb0492 (simplify) → 34fbc8b0 (comment accuracy) → 18b4a360 (nowrap hardening). CI frontend-checks green on every push.

…status, 24px back-to-chat tap target

Council review follow-ups:
- LoopAutomationControl: the mobile bare-time span is now aria-hidden and an
  sr-only twin carries the full localized "Waking at … UTC"/"Retrying at …"
  string, so screen readers no longer hear an orphaned time; the kind ternaries
  are hoisted into wakeupTimeText/wakeupStatusText.
- CliMainPane: back-to-chat button gets p-1.5 -m-1.5 (md:p-0 md:m-0) so the
  icon-only mobile target reaches ~24px without shifting layout; desktop
  rendering unchanged.
… breakpoint tie

Simplify pass:
- Merge the desktop status span and the mobile sr-only twin into one span via
  the sr-only md:not-sr-only idiom (same a11y tree at both widths, one less
  duplicated render of wakeupStatusText).
- Hoist the triple-checked nextWakeup.kind comparison into isUsageLimitWake.
- Comment the cross-file invariant: md: is the exact complement of
  useIsMobile's MOBILE_BREAKPOINT=767.
xhigh review follow-ups (comment accuracy only, no rendering change):
- LoopAutomationControl: the sr-only note now names exactly what stays in the
  a11y tree (the Waking/Retrying string) and records that the attempts counter
  is intentionally hidden on mobile for AT and sighted users alike.
- Both files: drop the 'exact complement' phrasing — fractional viewport
  widths between 767 and 768px match neither media query (pre-existing,
  app-wide trait of the 767/768 pairing).
…y reset

not-sr-only resets white-space to normal at md:, silently undoing the parent
span's whitespace-nowrap for the restored status text. Add md:whitespace-nowrap
on the same span (whitespace utilities are emitted after the accessibility
plugin, so it wins) — rendering-identical today, protects the h-8 row if the
status area is ever width-constrained. Flagged independently by three review
passes; final codex sweep marked it actionable.
@miguelrisero miguelrisero merged commit 59e6080 into main Jul 8, 2026
7 checks passed
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