Modal close restores focus to the tooltip's trigger, which was firing
onFocus and re-opening the tooltip. Gate onFocus on input modality so
only keyboard-driven focus opens it, matching W3C tooltip behavior.
Also removes the key-based remount workarounds that existed solely to
sidestep this bug.
The source-control button rendered diff stats inline with no formatting
while the sidebar used toLocaleString, so identical numbers looked
different. Route both through DiffStat and switch to compact notation
(e.g. 87,700 -> 87.7k) so large counts stay readable.
Fan out agent close + terminal kill concurrently with Promise.allSettled
so one failure never leaves the worktree half-dead. Await real PTY exit
with SIGHUP→SIGKILL escalation before rm to avoid EBUSY races. Tolerate
missing repoRoot and retry directory removal with backoff. Purge stale
client workspace layout/tab state after successful archive.
The workspace screen was prefetching per-provider model lists via
`useProviderModels`, which fanned out N parallel
`list_provider_models_request`s that bypassed `ProviderSnapshotManager`
and spawned each provider fresh on every call (e.g. repeated copilot
ENOENT errors). The return value was never consumed.
Replace it with `useProvidersSnapshot(serverId, workspaceDirectory)` so
warmup goes through the shared, deduped, TTL-cached snapshot path that
consumers already read from.
Workspace creation landed on the setup tab because openTab implicitly
focused the tab it opened, and the setup auto-open effect ran after
the setup dialog's navigation, stomping on the agent tab's focus.
Split the store API into explicit openTabFocused and openTabInBackground
so every caller declares intent. The setup auto-open effect now opens in
background and no longer steals focus; user-intent call sites (sidebar,
kebab menus, file explorer, terminal creation, prepareWorkspaceTab)
use openTabFocused and drop the redundant paired focusTab call.
On iOS, tapping "+" → "Add image" in the Composer left an invisible
backdrop trapping touches after the native photo picker dismissed. Root
cause: the dropdown menu's onSelect fired while the RN Modal was still
completing UIKit dismissal, so PHPicker presented over a dismissing
Modal that never cleanly released.
DropdownMenu now defers the selected item's onSelect until Modal
onDismiss (UIKit-level completion) plus a short buffer on iOS, then
invokes it. Same DropdownMenu instance — no per-call opt-in.
Along the way, the Maestro repro surfaced a controlled-TextInput
character-drop race: fast inputText on a controlled input drops
characters mid-type. Direct-host and pair-link inputs are now
ref-backed uncontrolled inputs with stable testIDs.
New reusable E2E primitives under packages/app/maestro/:
- flows/land-in-chat.yaml — clearState + direct connect to
127.0.0.1:6767, lands in the composer. Compose on this for any
composer-level fixture.
- image-picker-repro.yaml — regression for the zombie-backdrop bug.
docs/MOBILE_TESTING.md gains the patterns: reach-the-composer via
land-in-chat, uncontrolled inputs for Maestro-typed fields, and the
dropdown → native-presenter dismissal timing on iOS.
Existing relay-pairing and relay-then-direct fixtures updated to use
stable testIDs instead of ambiguous text / point-based taps.
`window` exists on React Native (aliased to `global`) but `window.location` is
undefined, so reading `window.location.pathname` threw and killed the redirect
effect — leaving the host index route stuck rendering null (blank screen).
Use the `pathname` from `usePathname()` directly, restoring the pre-refactor
behavior that worked on both web and native.
Images and GitHub issues/PRs now live in one draft-store list (ComposerAttachment union) with cwd persisted on the draft. Paperclip becomes a + drop-up menu; queue button removed. Each pill uses a shared AttachmentPill wrapper with a corner-X that mirrors the auto-update toast spec. Pill body opens the attachment: GitHub via openExternalUrl, images via a new fullscreen AttachmentLightbox. GitHub picker auto-closes on select.
Tightens transparent icon gaps to 4px and gives filled action buttons
(send, interrupt) a 4px left margin so they sit 8px from neighbors.
Shrinks context-window meter SVG from 20 to 16 to match the visual
weight of surrounding icons.
The selector returned a freshly constructed authority object each call, so
useSyncExternalStore saw a new reference every render and force-rerendered,
producing a maximum update depth crash. Select the specific workspace
descriptor (stable reference) and derive the authority via useMemo.
The opaque-workspace-identifier refactor (e6044264) added a `b64_` prefix
to base64-encoded workspace IDs in route builders. Two app tests still
expected the unprefixed form and failed in CI.
Gate the GitBranch icon behind isGitCheckout, and keep the header
skeleton visible until the checkout status query resolves so the icon
doesn't pop in and cause layout shift.
* feat: add server-side TTL for provider snapshots and model list UI
Provider snapshots cached forever, causing newly available models
(e.g. OpenCode Go minimax, glm) to never appear in the picker.
Server: ProviderSnapshotManager now returns stale data immediately
and kicks off a background re-warm when the snapshot is older than
5 minutes. Injectable TTL/clock for testing.
App: Provider settings rows show model count and are tappable to
open a diagnostic sheet with a read-only model list (label + ID).
* feat: add per-provider refresh for models and diagnostic
* refactor: await refresh completion and clean up diagnostic sheet state
- ProviderSnapshotManager.refresh is now async; session.ts awaits it so
the RPC ACK reflects real completion instead of just queuing work.
- Preserve models/modes/fetchedAt on entries during targeted refresh so
the list no longer flashes empty mid-refresh.
- Show "Updated Xs ago" next to the Models count, plus loading and
error states for the list body.
- Split resetSnapshotToLoading into full vs targeted branches.
- Flatten nested ternary in model list rendering into renderModelsBody.
- Drop redundant local refreshing state and cargo useMemo wrappers.
* fix(app): widen isImeComposingKeyboardEvent type to accept optional fields
TextInputKeyPressEventData has isComposing and keyCode as optional, so
Pick<KeyboardEvent, ...> was too narrow and broke typecheck on main.
Add `selectable` prop to the code line Text component so users can
long-press to select and copy text on iOS. On web/desktop this is
already the default behavior via CSS user-select.
Markdown file preview has a similar issue but requires a different
fix (custom rules for react-native-markdown-display), left for a
follow-up.
Refs #238
Related #21
Co-authored-by: muzhi1991 <muzhi1991@limuzhideMac-mini-2.local>
* style(app): theme native scrollbars in tool call detail views
Apply CSS scrollbar-color to all ScrollViews in ToolCallDetailsContent
and DiffViewer so the browser scrollbar matches the app's dark theme
instead of showing the default white native scrollbar.
* refactor: split useWebScrollbarStyle into .web.ts/.native.ts platform files
- Native shim returns undefined without calling useUnistyles
- Web variant uses properly typed WebScrollbarStyle interface instead of `as any`
- Add .d.ts for TypeScript module resolution
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Detect .md and .markdown files in the shared file pane and render them as markdown instead of syntax highlighted code. This makes README-style files easier to read without changing the existing preview flow for other text files or .mdx.
Keep the change focused to the app renderer by reusing the existing markdown stack and adding a narrow extension check with targeted coverage.
hasInitializedRef was set to true before confirming the directory
listing request actually succeeded. On page refresh the WebSocket
client is still reconnecting, so requestDirectoryListing returned
early with "Host is not connected" and the ref stayed true —
preventing any retry once the client became available.
Fix: make requestDirectoryListing return Promise<boolean> and reset
hasInitializedRef on failure so the init effect re-runs automatically
when requestDirectoryListing is recreated after the client reconnects.
Fixes#441
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: file preview shows stale content when re-opening a file
The file content query inherits `refetchOnMount: false` and
`gcTime: Infinity` from the global QueryClient defaults, so
re-opening a previously viewed file never re-fetches its content.
Set `staleTime: 0` and `refetchOnMount: true` on the file preview
query so it always fetches the latest content when the component
mounts.
Fixes#351
* fix: keep original staleTime, only add refetchOnMount
staleTime: 5_000 is reasonable — avoids redundant fetches when
quickly toggling the same file. The actual fix only needs
refetchOnMount: true to override the global default of false.
---------
Co-authored-by: muzhi1991 <muzhi1991@limuzhideMac-mini-2.local>
The Create button on the new workspace screen was visible but clicking
it did nothing — submitAgentInput() still rejected empty payloads as
"noop". Thread allowEmptySubmit through Composer into submitAgentInput
so the callback fires and workspace creation + redirect completes.
Adds regression test for the empty-submit path.
Terminal tests (launcher-tab, terminal-performance):
- Terminal creation no longer silently no-ops when workspace metadata
hasn't hydrated. Early clicks are queued and flushed once the workspace
directory is ready, with toast feedback.
- Terminal button is disabled while waiting on workspace readiness.
- Removed isFocused gate from ?open= intent consumption in workspace
layout so deep-linked terminals open reliably on cold startup.
New workspace test:
- New workspace screen now shows a visible Create button even when the
composer is empty, via new allowEmptySubmit and
submitButtonAccessibilityLabel props threaded through Composer and
MessageInput.
Forward-port main's timeline epoch tracking, projected window selection,
and schema updates into dev. Remove aider/amp/gemini providers and
provider-history-compatibility layers that are no longer needed.
The prop represents whether the pane/panel is focused, not whether the
text input is focused. Rename across all 5 files to match the caller
names and reduce confusion.
Show a muted "⌘L to focus" hint in the top-right of the message input
when unfocused and empty on web. Registers Cmd+L (Mac) / Ctrl+L
(non-Mac) bindings for the existing message-input.focus action.
On mobile, the server drops live stream events for backgrounded or
unfocused agents. When the user returns, a seq gap triggers a
catch-up fetch whose canonical entries are appended to the tail —
but the head was never flushed. Stale live items from before the
gap stayed in the head and rendered after the newer catch-up entries,
breaking chronological order. Worse, subsequent live events of the
same kind (e.g. assistant_message) would append to the stale head
item, garbling message content.
Now the incremental path in processTimelineResponse flushes head →
tail before reducing canonical entries, keeping the timeline ordered
and the head clean for new live events.
Always render the GitBranch icon regardless of branch availability so
the layout doesn't shift when the branch name loads. Only the chevron
remains dynamic. Also removes vertical padding on mobile to tighten
the gap between the branch row and project subtitle.
The new-worktree button spinner was hidden when the cursor left the
project row. Force visibility when the mutation is pending so users
see the loading state throughout creation.
Route all git checkout queries through WorkspaceGitService instead of
shelling out to git on every call. This makes fetch_agents_request
instant when warm (cached in-memory snapshots) and deduplicates
concurrent cold-start refreshes via ensureWorkspaceTarget.
- buildProjectPlacementForCwd now uses workspaceGitService.getSnapshot()
- worktree-session call sites use WorkspaceGitService
- Deleted getCheckoutStatusLite and its 4 return types from checkout-git
- Fixed cold-start thundering herd: getSnapshot deduplicates via
ensureWorkspaceTarget instead of spawning parallel git processes
- Extracted checkout-diff logic into WorkspaceGitService
The hasSelectedAgent condition was redundantly gating Cmd+E (toggle right
sidebar) and Cmd+Shift+F (toggle focus mode) behind a pathname check that
Cmd+B and Cmd+. didn't require. The action handler already guards against
invalid states, making the when-clause check unnecessary and broken on the
dev branch's navigation flow.