feat(mobile): unify the terminal and home composers

Merged
SA
3mo ago
+941-658

Why

The terminal composer was a stripped copy of the home glass pill — no voice, no attachments, and a send button that hard-popped in and out of a zero-width container instead of morphing. Two divergent copies of a fiddly SwiftUI component.

What

Both surfaces now build on one GlassComposer (screens/(authenticated)/components/GlassComposer), with slots for the surface-specific chrome (header, toolbarLeading, above). NewChatWidget and TerminalComposer are thin wrappers.

The terminal inherits the mic (dictation → recording pill → finalizing chip), the animated mic→send swap, focus-expand, and attachments. Project/branch/agent pickers stay home-only.

Also:

  • Attachments sheet moved from (home)/attachments to /(authenticated)/attachments so both surfaces can open it.
  • Quick keys are now SwiftUI, inside the composer's Host. As RN siblings their gap to the pill was a two-value guess (pb-[26px] / pb-2.5) at a height that varies continuously, animating on a third curve — it visibly drifted whenever the pill grew. SwiftUI now owns that spacing.
  • Terminal attachments upload into the worktree's .superset/attachments/ and ride along as worktree-relative paths appended to the prompt, mirroring the desktop terminal adapter. Agent sessions only — a plain shell tries to execute the path (zsh: permission denied: …IMG_0006.HEIC), so + is gated on agentId != null.

Net −340 lines.

Notable gotchas encountered

Documented inline, since each cost real debugging time:

  • The SwiftUI TextField sits outside RN's responder chain. Programmatic focus() doesn't fire onFocusChange; iOS restores first responder after a sheet dismisses without firing it either; and Keyboard.dismiss() can't lower the keyboard for it (tap-outside must call the field's own blur()).
  • transitionEnd only fires on the navigator owning the animating screen — moving the attachments route to a parent stack silently killed the refocus callback.
  • The liquid-glass material paints past its layout frame. onGeometryChange reports the frame accurately; the bleed still needs explicit padding inside the Host.

Verification

Driven on an iOS 26 simulator against production: expand/collapse, voice control, send insertion, tap-outside dismiss, attachment thumbnails, and an image round-tripping to Claude Code — which read the HEIC and described it correctly.


Summary by cubic

Unifies the home and terminal input into a shared SwiftUI GlassComposer, so the terminal now supports voice, attachments, animated mic→send, and consistent expand/collapse. Previously the terminal had no voice/attachments and a hard-swapping send button; now both surfaces share one component to prevent drift.

  • Adds screens/(authenticated)/components/GlassComposer with header, toolbarLeading, and above slots; NewChatWidget and TerminalComposer wrap it. Exposes focus(), blur(), and clear() on a ref, plus keepExpanded, onActiveChange, onSubmit, isSending, and showAttachments.
  • Moves the attachments sheet to /(authenticated)/attachments; both composers open the same sheet. Quick keys render in SwiftUI inside the composer's Host for exact spacing and native animation.
  • Terminal attachments write to .superset/attachments/ via the host service and are sent as worktree‑relative paths appended to the prompt. The + button shows only for agent sessions. Adds useWriteTerminalAttachments.
  • Measures composer height via onGeometryChange and pads for glass bleed to avoid keyboard clipping. onActiveChange enables backdrops/tap‑to‑dismiss in callers.

Migration

  • Update any links or router.push calls to the attachments screens from /(authenticated)/(home)/attachments[...] to /(authenticated)/attachments[...].

Summary by CodeRabbit

  • New Features

    • Added photo and screenshot attachment selection with permission guidance, previews, ordering, and multi-select support.
    • Attachments can be added to chats and terminal sessions.
    • Added voice dictation with recording controls, live levels, and transcript insertion.
    • Introduced a unified composer with attachment previews, quick actions, focus controls, and send-state handling.
    • Improved attachment presentation in authenticated mobile flows.
  • Refactor

    • Standardized chat and terminal input experiences around the shared composer.