The composer refactor changed the agent-mode button's accessibility
label from the raw mode id ("load-test") to the formatted label
("Load test"). The user-facing change is intentional. Bring the test
in line with the new label.
handleSelect only ran setIsOpen(false) on the success path. When the
rewind action failed, the catch block swallowed the error but the menu
stayed open, blocking subsequent interactions with the page.
Close in finally so success and error both dismiss the menu.
The tab testID was reading from tabId, which intentionally stays as the
draft id after a draft -> agent handoff so the panel React key remains
stable and the component does not remount. The data-testid was riding
along on that identity and stayed workspace-tab-draft_xxx forever, even
though the tab semantically became an agent. Tests that look for
workspace-tab-agent_* timed out.
Read testIDs from tab.target via buildDeterministicWorkspaceTabId so the
testID reflects what the tab represents. React key is unchanged.
Position portal content relative to its render host so the composer anchor stays correct when surrounding layout shifts. This preserves the Portal surface needed for Android scrolling and touch behavior.
config.title is now strictly the caller's creation intent and is never
mutated. The prompt-derived placeholder lives only in record.title at
creation, so the metadata generator's upfront gate (hasExplicitTitle) is
the only one and writes unconditionally when it runs. Drops the two
projection fallbacks to record.config.title that were the second
hidden path for the prompt to surface as a title.
Move optimistic user message reconciliation into the stream reducers and keep draft-created agents in a handoff lifecycle until authoritative history lands.
Replace the full-screen rewind sheet with a dropdown anchored to the
trigger, with a muted warning header and inline pending state on the
chosen action. Also realign the user message action row so timestamp,
rewind, and copy share a single 24px baseline — the copy button was
inheriting assistant-footer paddings that pushed the row taller than
the icons.
decodeURIComponent throws on '%' not followed by two hex digits. The two
calls inside parseAssistantFileLink and normalizeFileUrlPath had no
try/catch, so any timeline message containing strings like '100% packet
loss' (ping output), '%PATH%' or '0% off' could escape into the React
tree and unmount the agent route, leaving the renderer blank.
Wrap both calls in a local safeDecodeURIComponent that returns the input
unchanged when decoding fails, mirroring the helper already used in
host-routes.ts.
Closes#1148
* Add Pi extension-launch plumbing for in-process tree primitives
* Add Rewind controls for agent sessions
* Preserve SDK checkpoint env var so Claude file rewind works
The SDK injects CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true when file checkpointing is enabled. The earlier parent-env stripping from c4782ec71c was too broad and removed that child flag along with real parent-session markers.
Keep stripping parent markers, but preserve the checkpoint flag so the SDK rewind path can run directly. With that boundary fixed, the JSONL file-rewind fallback is no longer needed.
* Update OpenCode history replay tests for rewind state
* Show Rewind actions in an adaptive sheet
* Fix rewind semantics across providers
* Refactor Pi IDs through extension capture
* Restore user message to composer after rewind
* Fix Claude rewind tool result mapping
* Fix Claude rewind session switch roundtrip
* Fix rewind and optimistic message regressions
* Tighten rewind test discipline
* Fix OpenCode optimistic message reconciliation
* Reconcile optimistic user messages by marker
* Reorder user message actions
* Unify provider user message contract
* Add Claude rewind flow Playwright contract
* Add Codex rewind flow Playwright contract
* Add OpenCode rewind flow Playwright contract
* Add Pi rewind flow Playwright contract
* Remove real-provider rewind Playwright contracts
* Gate real-provider rewind specs from CI Playwright
The popover was positioned `bottom: 100%` of its parent inside the composer
and worked on iOS/web; Android's hit-test by parent bounds drops touches on
children that overflow their parent, so taps and scrolls went through to the
chat behind it. Move the popover into a `<Portal>` at the app root and apply
the same Reanimated keyboard SharedValue as the composer so it tracks the
keyboard in lockstep. New `docs/floating-panels.md` captures the gotchas
(Android hit-test, Portal lifecycle/transforms, status-bar offset, the
two-measurement flash) and the canonical files.
The model picker search input rendered text in the light-theme
foreground color on a dark surface, leaving typed characters invisible.
@gorhom/bottom-sheet mounts header subtrees before the sheet is visible
and keeps them mounted across theme changes, so any color read from
StyleSheet.create((theme) => ...) at the caller goes stale. Move text
color and placeholder color into AdaptiveTextInput itself via
withUnistyles, with the leaf's color appended last so callers cannot
re-introduce a stale read. Drop the now-redundant placeholderTextColor
props at the header search call sites.
Also ban useUnistyles() in docs/unistyles.md. The hook subscribes to
every runtime change and forces a re-render each cycle; new code uses
withUnistyles or StyleSheet.create instead.
The key matcher only consulted event.code when a binding explicitly
opted in via codeFallback. macOS rewrites event.key whenever Option
is held (Option+T -> "†", Option+[ -> "“", Option+Shift+W -> "„",
etc.), so the comparison against combo.key always failed and every
Alt-bound letter / bracket shortcut silently stopped firing on Mac:
Cmd+Alt+T (cycle theme), Alt+Shift+[/], Alt+[/], Alt+Shift+W.
Fall back to event.code when combo.alt is set. Keep the existing
key-first behaviour for non-Alt bindings so Dvorak users still get
logical-character matching (Cmd+V on physical Period keeps pasting
instead of triggering Cmd+. for sidebar toggle).
The Shortcut component computed its colors with a hex-only opacity
helper that received unistyles' CSS-var theme strings on web (e.g.
"var(--colors-foreground)"), fell through both regex branches, and
returned the rgba(255,255,255,...) fallback. Badges and chord text
rendered as near-white on white, so the keyboard shortcuts settings
page looked blank. Use the existing surface2 and foregroundMuted
tokens directly, which work in both themes without per-platform
color math.
Replace the Expo DOM-backed terminal on iOS and Android with a managed
WebView running xterm.js. Web and desktop continue to use the existing
DOM implementation.
Mobile mounted tabs switch from display:none to opacity:0 for the hidden
slot on purpose: terminals stay mounted under the LRU tab cache, and
keeping the WebView in the layer tree avoids cold-starting it every time
the user returns to a terminal tab.
EAS native builds rebuild the WebView bundle post-install so the
generated HTML stays in sync with the entry source.
This does not fix the WS 1006 / NSPOSIXErrorDomain Code=54 host-disconnect
symptom that prompted the investigation. After restoring the baseline,
that symptom could not be replicated; do not read this commit as its
root cause or fix.