mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 01:14:52 +00:00
ab8e1e61ff7c0bbad08c95e15a09f3ec42333410
206
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ab8e1e61ff |
fix(app): keep the composer ready after clicking Send (#9015)
Pointer submission blurs the editor before sending and clears retained focus intent. Restore focus through the existing default send action for mouse-driven layouts, while keeping touch keyboard dismissal intact. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
0e40022519 |
Preserve linked resources when pasting prompts (#8972)
* fix(app): preserve linked resources when pasting prompts Rich clipboard links carried their destination only in HTML, while the composer let textarea default paste insert the plain label. Convert linked fragments to Markdown at ingestion and replace the selected draft range through the existing editor command. * fix(app): keep pasted links undoable while drafting offline Use the browser edit command for converted paste so selection replacement participates in Undo and normal input publication. Keep complete Markdown clipboard text when it already carries the link destinations, and limit connection gating to image uploads. * fix(app): preserve copied Markdown with normalized links Copy renders normalized URLs in HTML while retaining original Markdown in plain text. Compare parsed destinations using the same Markdown parser so pasting complete responses keeps tables and formatting intact. * fix(app): retain external linked tables when pasting prompts Recovering missing link destinations must retain table rows and columns. Use the existing table converter and keep cell delimiters and line breaks inside their cells. * test(app): name rich clipboard paste arguments * fix(app): preserve pasted links around rowless tables The table converter requires a first row. Convert rowless table content separately so empty markup cannot abort recovery of surrounding destinations. * test(app): wait for workspace restore before undo teardown The directory appears before git finishes recreating the worktree. Wait for the host to publish its active workspace before cleanup removes the source repository. --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
161076a853 |
Show loading and errors in the slash command menu, and stop defaulting to /exit (#8905)
* feat(app): show loading and errors in the composer command menu Rows are single lines with a kind icon, the matched part of the name, the argument hint, the description and a source tag. The detail card is gone. The menu used to stay hidden while commands loaded and then show "Error: ..." over a list whose options Enter could still pick, so a failed load plus Enter archived the agent through a hidden /exit. Loading and error are now status rows with no options; the error row names the reason and offers Retry. Loading appears after 300ms so a warm cache paints the results first, as before. An empty query used to select /exit. Provider commands now come first, so the bottom row the input selects is a provider command. useAutocomplete derives the selection during render. When results replace the loading row, the selection is valid in the same render, so the popover stays mounted at its anchor instead of remounting. Claude Code's `__`-prefixed commands (/__remote-workflow) only run in its own server-launched sessions, so the Claude provider no longer lists them. * fix(app): never default the command menu to a Paseo action An arrow-key choice was tied to the query it was made for, so returning to that query brought it back: arrow to /exit on `/`, type `com`, delete it, and Enter archived the agent. A choice now clears whenever the query changes. An empty query still selected /exit when the agent had no provider or plugin commands, because Paseo's actions were the only entries. With nothing else to list, an empty query now lists nothing and waits for a search; typing `/ex` still finds /exit. * fix(app): sort empty-query commands without toSorted Hermes has no Array.prototype.toSorted, so typing `/` threw "undefined is not a function" on iOS and Android and the app showed its error screen. Browsers and Node have it, which is why web and unit tests passed. * test(app): cover the stale command choice in Playwright and wait out the loading row on devices The renderHook test mounted React under JSDOM, which docs/testing.md forbids. The Playwright test drives the same sequence in a real browser and fails without the reset in useAutocomplete: Enter chose the stale /exit. The composer-keyboard harnesses treated a visible popover as a ready list. The popover now shows a loading row first, and a cold Claude command list takes several seconds, so both harnesses wait for the rows. * fix(app): size the command menu loading spinner to the row icons Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
77798b2e46 |
Show and stop agent background work (#6220)
* feat(server): add an in-memory store for agent background work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(claude): report background tasks as background work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(server): track agent background work in the agent manager Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(protocol): add agent background work messages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(server): serve and push agent background work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(client): expose agent background work on agent handles Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(sdk): document agent background work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(claude): cover background shells in background work end to end Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(client): resync background work subscriptions on every snapshot subscribe() now lists the agent's background work each time the event observation is established, so the listener starts with the current list and catches up after a reconnect. A list answered after a newer update is dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): answer background work lists without loading dormant agents Background work lives only in memory, so a stored agent that is not loaded has none. The list handler now answers [] from storage instead of resuming the agent, and still rejects unknown, internal, and archived agents. A new test pins that old apps never receive agent.background_work.update. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server): read Claude background tasks defensively The user's claude binary can differ from the SDK typings, so a task's description is read as unknown and an empty or missing one becomes null. Drop the monitor kind: Claude Monitor watches report as local_bash, so kind is "shell" or "other" today. The store now copies only the wire fields of each input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(server): prove internal background work was emitted before checking globals Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(sdk): describe background work resync and feature gating Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(claude): cover background work reported by a retired query The daemon stops reading a Claude query it no longer owns, so work that a retired process still had queued cannot reach the background work list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(client): let background work subscribers handle a failed list read When the list read that follows each subscription snapshot failed, the error was only logged and the caller got neither the list nor a failure. subscribe() now takes an optional onError that receives it; without one the error is still logged. The read is not retried, since the next reconnect reads the list again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(agents): show background work as active Keep background activity visible after a foreground turn ends while preserving independent send and interrupt behavior. * fix(agents): protect background work subscriptions Keep internal work out of public updates and restore observation after a subscription failure. * feat(agents): show Codex and OpenCode background work Keep provider-owned shell activity visible after a turn ends while leaving foreground turn controls independent. * feat(agents): stop individual background work Wait for provider confirmation so stopping one background item leaves sibling work and foreground turn controls independent. * refactor(agents): name background stop parameters * test(website): isolate browser tests from live metadata * test(agents): clarify background stop journeys * fix(sdk): report background work subscription failures Report setup failures so subscribers can replace an observation that the host released. * fix(app): restore background work after reconnect Wait for the host handshake before selecting the retained client so an offline pane starts observing when the connection returns. * fix(agents): retain background shells across rewind Preserve known live shell ownership when conversation history is rewound, and revoke it when the provider runtime exits. * fix(website): launch test seeder across platforms Invoke Wrangler through Node so local website test startup does not depend on an executable npm command shim. * feat(agents): clarify background task details and tracks Keep launch descriptions and commands distinct so background work remains identifiable, and allow composer tracks to scroll when space is limited. * fix(agents): preserve active background work during archive Keep background-running children out of bulk cleanup and warn before an individual archive. Preserve first taps on composer tracks while the keyboard is open. * fix(codex): retain background terminals across rewind Keep live terminal inventories attached to their originating threads so a fork does not hide or misroute their stop actions. * fix(codex): scope background terminal identities to threads Prevent commands on separate threads with the same provider process ID from sharing a stop target. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
4b285a113d |
Space the touch composer toolbar by what it paints (#8909)
* feat(app): tighten the touch composer toolbar onto the text column
On touch, the toolbar's icon-only glyphs were 20pt muted outlines at a 1.5px
stroke in loosely spaced slots, and the row sat on a flat -6 margin, so the
attach + and the trailing control floated inside the text column. Touch now
places 44pt slots edge to edge, draws icon-only glyphs at 24pt with a 2px
foreground stroke, keeps label-paired glyphs at 20pt, and runs the context
ring at 26pt with a 3px stroke. The row overhangs into the composer padding
by each end control's ink inset (composerToolbarOverhang), so the + ink and
the trailing glyph or send/stop disc sit on the text column's edges.
Pointer sizing is unchanged.
The folded model control now drops its thinking label before the model
label truncates, and the model label truncates on a word boundary
("Ten second…") instead of leaving a space before the ellipsis. Both are
decided from measured widths. The web reports layout widths rounded to whole
pixels, so a tie counts as not fitting.
* fix(app): measure replacement model labels instead of hiding them
The folded model label and TailEllipsisText kept their measuring views mounted
when the label text changed, while discarding the cached widths. A retained
measuring node whose size does not change emits no new layout event on web or
native, so after switching to a model whose label (or a shortened prefix of it)
has the same width, the widths stayed unknown and the label stayed transparent.
Keying each measurer by what it measures remounts it, so every width is
measured again.
Also move the label width sweep into a named helper so the test body states
the expected label order.
* feat(app): space the touch composer toolbar by what it paints
- Lay each control out at its painted width, 20pt apart, with a disc-sized circle that is both its frame and its fully rounded pressed highlight. Pickers are 36pt pills.
- Match the header icons: 20pt glyphs at 1.5px, an 18pt context ring at 2px.
- Always end the row in a disc (voice mode, send or stop) so it does not shift, nested into the composer's corner, with a concentric 26pt corner radius on touch.
- Stop the picker row's clip from flattening the picker highlight's left edge.
- Budget the density ladder on laid-out widths.
* fix(app): disable the stand-in send disc and keep form model fields in their box
- An empty new chat's stand-in Send is disabled until there is content, as in fullscreen.
- A custom model selector trigger, such as the schedule form's field, drops the toolbar slot's margins.
|
||
|
|
1e2ab3d596 |
Keep the first prompt on screen when a new agent starts on Android (#8898)
* refactor(app): stop resetting the chat timeline when its agent id changes A mounted timeline only ever shows one agent: every host keys its panel by tab id, and a tab keeps its id across a target change only when a draft becomes the agent it created. The per-agent resets inside the timeline therefore never had a different agent to reset for. They only fired at that create handoff, where they hid and re-placed a timeline that was already on screen. Delete them: LegendList's dataKey and the placed agent id, the web viewport key, the bottom anchor's resetForAgent, the history window's agent id, and the expansion and path-opener resets in the stream view. * fix(app): keep one chat timeline across the draft-to-agent handoff A draft tab and the agent its first prompt creates rendered two different panels: DraftPanel mounted its own dock and list, and the retarget swapped it for AgentPanel's. On Android, with the LegendList timeline, the swap blanked the first turn and re-placed it. Render both from the agent panel. A draft supplies its timeline from the pending create in the create-flow store (composer/draft/timeline.ts) and its composer in the composer slot. The retarget then re-renders the same stream view with the agent's timeline, so the list instance and its measured rows stay mounted. Agent gating is unchanged. Float the compact header over draft tabs as it already floats over agent tabs. The header sat in flow above a draft and over an agent, so the list viewport grew 92 dp at the retarget and the turn moved. The handoff E2E now fails if the first row disappears, hides, or shifts down while the draft becomes the agent. * fix(app): load subagents and tasks only for an agent, not a draft tab |
||
|
|
6355b339c8 |
fix(app): paint a smaller send and stop disc inside the touch hit frame (#8891)
Under touch sizing the composer's send and stop buttons filled their whole 44pt hit frame with color, so they outweighed every unpainted control in the toolbar. The trigger keeps the 44pt frame and now paints a 36pt disc (TOOLBAR_DISC_SIZE) centered inside it. Pointer sizing paints 28 inside 28, so desktop is unchanged. The voice button's recording fill follows the same rule. |
||
|
|
e9f897a66a |
fix(app): rest the iPhone composer at the screen's bottom clearance (#8823)
* fix(app): rest the iPhone composer at the screen's bottom clearance The composer sat 50pt above the bottom edge on every iPhone: the dock padded the rectangular home-indicator safe area (34pt) and the composer stacked another 16pt under it. On iPhone Duo that safe area is also taller than needed; iOS 27.1's bottom-bar layout guide, which follows the screen's corners, rests 26pt from the edge. The native window-layout probe now publishes that clearance, and WindowLayoutProvider overrides the safe-area context's bottom with it, so every useSafeAreaInsets() consumer reads one bottom value and null falls back to the system inset. The docked composer applies max(bottom, 16) once and keeps 16pt above the keyboard through the translate view's transform; the gap is not animated as padding because layout updates through the keyboard transition are expensive on Android Fabric. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep the Import session gap under the wide new workspace composer The composer's own bottom spacing used to separate it from the Import session pill on the wide form. With that spacing moved to the dock, the centered slot padded below the pill instead, so the pill touched the composer. The pill now owns its gap above. Co-authored-by: Elsa Granger <zeyugao@outlook.com> --------- Co-authored-by: Elsa Granger <zeyugao@outlook.com> |
||
|
|
ac775a2904 |
Reveal collapsed navigation as a touch overlay on wide layouts (#8739)
* feat(app): swipe open collapsed tablet navigation Reuse the retained sidebar and mobile panel motion for a partial-width navigation overlay when the wide dock is hidden. Preserve the dock preference, project gestures through the sidebar width, and dismiss the overlay when revealing workspace content. Reset the animated frame width explicitly across dock transitions and keep the resize origin on the UI thread. Validated on Duo portrait and landscape, iPad portrait and landscape, iPhone full-width navigation, and browser sidebar interactions. Typecheck, lint, targeted formatting, and 31 relevant tests passed. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): refine touch navigation overlays Enable navigation swipes only on touch-capable runtimes and reject mouse and pen input per gesture event. Allow swiping from anywhere in the workspace instead of requiring an edge start. Cover the full application window with the backdrop, including safe-area padding, and remove the wide overlay's inline close button. Preserve swipe, backdrop, and destination-selection dismissal. Validated interior swipes on Duo and iPad, mouse exclusion in a browser, and full-window dimming and dismissal in Duo portrait and landscape. Typecheck, lint, and targeted formatting passed. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): let the panel store drop the overlay target on dock Docking the sidebar, or any layout that no longer presents it as an overlay, now drops the transient agent-list target inside the panel store. App chrome publishes one left sidebar presentation (overlay, dock, hidden) instead of an effect in SidebarChrome watching flags. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): answer fine pointer and touch screen from one module Merge use-fine-pointer and use-has-touch-screen into hooks/pointer-capabilities, which reads both answers from the media queries it subscribes to. The touch answer now comes from (any-pointer: coarse) instead of navigator.maxTouchPoints, which that query's change events never reported. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep the phone web drawer on its edge swipe Restore the 32px left-edge start for the compact web navigation drawer, so interior rightward swipes over terminals, code blocks, and diffs stay with their horizontal scrollers. The wide navigation overlay still opens from anywhere. The edge rule moves into resolveMobilePanelGestureIntent so both drawers share it and it has a test. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): one sidebar handler per destination Settings, host settings, add host, add project, and every navigation row now use one handler that closes the navigation overlay before it navigates. Closing is idempotent, so the docked sidebar shares it and the overlay ? mobile : desktop handler pairs go away. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): resolve the left panel width in one place MobilePanelsProvider now resolves the left sidebar width (full window on compact, the saved width clamped to the safe viewport on wide) and exposes it through the panel runtime. The open and close gestures, the overlay frame, and the docked sidebar all read that value, so gesture travel and panel width cannot disagree. MobileGestureWrapper no longer recomputes it, and the panelWidth/width parameters go away. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * docs: fold the wide navigation overlay into the mobile panel rules Move the facts the overlay PR appended into the sections that own them: the destinations intro, store ownership of the agent-list target, the projection widths, gesture pointer and edge rules, and the sidebar presentation bullets. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep the sidebar pans live across dock and overlay switches On an Android tablet, opening the dock with the header toggle and collapsing it again left the touch swipe unable to open the overlay until the app reloaded. Swipe-to-close from the sidebar also stopped working after the same toggle cycle. Both pans took the dock/overlay state through RNGH's `.enabled()`. On Android, RNGH 2.32 cancels the native handler when `isEnabled` changes and resets its config on every update. After a false -> true flip the handler stayed inert. `.enabled()` now carries only the device capability (touch screen present), which does not change at runtime. The dock/overlay state moves to a UI-thread derived value that fails the gesture in onTouchesDown, next to the existing non-touch pointer check. The wide sidebar's workspace list now always receives the close gesture ref, as the compact sidebar already does. Toggling it between undefined and the ref rewired the list's simultaneous handlers on every dock switch, and that also left the close pan dead after a toggle cycle. This is a native RNGH handler behavior, so there is no unit repro. It was verified on the Android fold emulator: toggle open, toggle closed, swipe opens the overlay, swipe on an empty sidebar strip closes it, no reload. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep the workspace full width after collapsing the dock On an Android tablet, opening the dock with the header toggle and collapsing it again could leave the workspace at the docked width, with a blank strip on the right, until the overlay was next opened. Elsa's original head showed the same. The sidebar's panel host flips between an in-flow dock and an absolute overlay. Its width came from a Reanimated style that read the React `overlay` flag, so the width reached the native view on the UI thread after React had already committed the new presentation. Layout logs on the fold emulator show the lag on every toggle-open: the workspace laid out at width 0 while the docked host still had the overlay's full window width, then a second pass laid out the 320dp dock. When the late update was lost on collapse, the workspace kept its dock width. The host no longer has a width of its own: - The dock sizes to the inner sidebar view, which already carries the animated resize width. - The overlay host is absoluteFill, which covers the whole row including the safe-area padding. With no animated layout prop on the host, a presentation switch is one React commit, and the host and the workspace lay out in the same pass. Device evidence on the fold emulator: - Toggle open and collapse cycles: the host and the workspace lay out together (320/521dp, then 841/841dp). - A swipe opens the 320dp overlay with a full-window backdrop, and a swipe on the sidebar closes it. - Dragging the dock edge still resizes the dock and the workspace together. There is no unit repro: the defect is the ordering of Reanimated's UI-thread layout updates against React commits. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): cancel pending sidebar swipes when docking An opening swipe still has the agent target until release. Leaving overlay presentation must advance its revision even then, so motion ownership and queued commits reject the obsolete swipe. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep mouse selection free of the touch-only pan's capture On a touch-capable browser the navigation pan registers pointer listeners over the whole workspace. Gesture Handler's web pointer manager captured the pressed element before the handler saw the pointer, and captured it again on every move, so a mouse drag that the pan rejects stayed captured and the browser's selection stopped at the starting paragraph. The patched manager now captures after pointer-down, and only a pointer the handler still tracks. The resize handle and touch swipes keep capturing. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): drop the compact Explorer target when the layout widens Opening the Explorer on a compact layout selects the `file-explorer` mobile panel target. Widening the window (unfolding a foldable, rotating a tablet) kept that target, because the store only heard the left sidebar presentation, which is `overlay` on both compact layouts and wide layouts with a collapsed dock. The navigation swipe begins only from `agent`, so on the wide layout every swipe was rejected until something else reset the target. App chrome now publishes the whole panel layout (compact or wide, plus the left sidebar presentation) and the store drops any target that layout does not present. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep wide overlay sidebar content inside the left safe area The overlay starts at the window's left edge, over the workspace's safe-area padding, so its content now takes the left inset itself. The dock already sits inside app chrome's padding and stays unchanged. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): return an interrupted swipe to its anchor when docking cancels it Docking mid-swipe bumps the panel revision while keeping the agent target. The command treated the unchanged target as motion already in progress and skipped the animation, leaving the dragged position stranded between anchors. A command that interrupts a drag now always animates to its target. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): synchronize startup panel selection and gate pending swipes A child layout effect can publish panel layout before the motion provider subscribes. Establish the current selection when subscribing, then observe later commands. Recheck presentation eligibility before gesture activation so docking also cancels touches that have not started dragging. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): cover panel subscription through its public contract Verify initial snapshot delivery, later commands, and unsubscription directly in the existing panel-store suite instead of mounting test-only React components. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): reopen compact navigation after leaving settings Destination navigation clears the transient overlay selection. Reopen the compact drawer before asserting its configured footer rather than relying on a stale open target surviving a wide layout. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): open navigation by keyboard in composer focus checks Navigation pans intentionally reject mouse drags. Use the keyboard sidebar command to exercise focus and fullscreen Escape behavior without relying on the former mouse swipe helper. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): complete sidebar integration after main rebase Retain the resize width resolver and format the shared division-width and hybrid touch-sizing integration. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(desktop): give split browser panes room for tab rails After #8740, narrow panes replace tab rails with compact dropdowns. The desktop browser journey counts and selects rail tabs, so widen the real Electron content window before splitting instead of treating missing narrow-pane rails as missing panes. Co-authored-by: Elsa Granger <zeyugao@outlook.com> --------- Co-authored-by: Elsa <zeyugao@outlook.com> |
||
|
|
cab5eedfe3 |
Keep workspace layouts usable in narrow columns across platforms (#8740)
* fix(app): retain Explorer resize width until acknowledged Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): choose workspace header by measured column width Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): separate iOS composer clipping from its shadow Keep explicit coverage of the existing keyboard rotation guard. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): choose workspace chrome within each desktop pane The outer width wrapper added a compact header while the desktop split container kept its full header and pane rails. Each pane now owns the complete choice, replacing its header and rail together. Each split pane uses its own measured width: below 500 points it shows its own tab dropdown, even if the neighboring pane is wide. Pane width determines usable header space. Keep new-tab keyboard ownership outside the switched chrome and retain positive measurements while hidden. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): reserve readable width for the compact tab popover Native scroll content does not contribute intrinsic width to the anchored frame. The count badge alone allowed the popover to compress its tab list and footer labels. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): preserve compact pane actions and window clearance Carry the owning pane into compact launch actions, keep split and restore controls in its menu, and distribute window corners through visible split children. Keep tab rails below header-owned window controls. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): retain window dragging in compact panes Keep the shared titlebar drag region when pane width replaces full desktop chrome. Cover two compact panes with both sidebars closed through the desktop host adapter. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): align pane journeys with measured workspace chrome Give rail-specific split journeys enough column width, assert launched Diff content independently of header shape, and retain pane identity across compact transitions. Expect the compact selector in the half-screen Explorer workflow. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * refactor(app): share pane tab-switcher rendering after header rebase Keep compact and vertical-bar header actions on the same pane-aware switcher contract while preserving the merged control geometry and script ownership. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): retain Explorer acknowledgements across consecutive drags Keep earlier released widths as expected allocations while a newer gesture owns the frame, and restore the latest release if that gesture is canceled. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): keep focus mode exit in the compact workspace menu Carry the workspace focus state into its compact action menu so a narrow header retains an exit without keyboard shortcuts. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): distinguish resize rollback from pending acknowledgements Restore the last released width when repeated widths precede a canceled gesture, and retain ownership when an unchanged old allocation matches a newer release before its predecessors arrive. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): allow software WebGL time to finish atlas growth coverage The 256-batch pixel regression crosses more than 500 render boundaries and exceeds its 30-second total budget under CPU contention. Keep the corpus, every pixel assertion, and the two-second per-frame deadline; allow 60 seconds for the complete workload. Reproduced the timeout with Chromium confined to one CPU and verified two successful constrained runs after the adjustment. Co-authored-by: Elsa Granger <zeyugao@outlook.com> --------- Co-authored-by: Elsa Granger <zeyugao@outlook.com> |
||
|
|
fcbfb4f0d3 |
Size controls for touch on iPad and other touch screens (#8748)
* feat(app): size controls for touch on any touch device, not just compact layouts
One gate picks touch or pointer sizing: touch when the layout is compact or
the device has a touch screen. The touch fact is read once, so attaching a
mouse or keyboard to a tablet never shrinks its controls. Stylesheets take
`bySizing({ touch, pointer })`, which resolves to a breakpoint value, so the
existing `{ xs, md }` consumers follow the gate without call-site changes.
Touch values follow the iOS HIG 44pt hit region: controls, icon buttons,
rows, bars and menu rows are 44 on touch; `sm` controls are 32. Glyphs move
to a touch ladder (14/18/20/24). Pointer sizes are unchanged.
Merges duplicated header control heights, density's private icon ladder and
the compact-only icon button sizes into control-geometry.
* feat(app): size the composer for touch through control geometry
The composer toolbar, attach / dictation / realtime / send buttons, context
meter and model triggers take TOOLBAR_CONTROL_SIZES (28 pointer, 44 touch)
instead of private 28 constants. The density budget, button glyphs and the
fullscreen glyph rail offset resolve the same gate, replacing the isWeb /
isNative glyph branches. Track rows join the menu row height.
Desktop geometry is unchanged. On touch the toolbar steps down density
earlier: iPad portrait goes from condensed to tight.
* feat(app): fold composer controls before the model label truncates
The composer toolbar walks one width budget: full, condensed, tight,
compact, then icon-only. Tight keeps the model at its family label;
below that, thinking, mode and features fold into the model control's
sheet (the phone toolbar), and only then does the model drop its label.
A touch iPad in portrait with an attachment now reads
"GPT-6-Astra Medium" instead of "GP…".
The context meter glyph and combobox rows and icons take the density
ladder. Desktop is pixel-identical in the content area.
* refactor(app): keep the sizing gate free of the theme
constants/layout sized its bars through control-geometry, which loads the
theme and runs Platform.select at import. The gate (resolveControlSizing,
bySizing, TOUCH_TARGET) moves to control-sizing.ts with no theme import;
control-geometry re-exports it, so its callers are unchanged.
* test(app): expect touch sizing in the compact Changes and plugin sidebar specs
The compact Changes options button is a 44pt icon button with a 20pt glyph,
and its framed actions trigger is 32 tall. In the compact sidebar the nav
block is capped at half the height, so 44pt rows put the plugin header row
below the cap; the spec scrolls it into view as a user would.
* fix(app): close composer menus when the controls fold or unfold
Folding swaps the toolbar controls for the model sheet. A menu opened on the
separate controls stayed marked open across the fold, so widening the
composer again reopened Thinking without a click.
|
||
|
|
ca6b54fc2e |
feat(app): mark composer pills that open a menu and keep the pill track's space (#8673)
Plugin composer pills with a menu or popover now show an upward chevron at every width, so a pill that opens a surface reads differently from a one-tap action pill. Built-in Tasks and Subagents pills are unchanged. The transcript's bottom inset used to be reserved only while some pill existed. Plugins that add pills when a turn ends and remove them when the next starts moved the transcript every turn. The inset is now reserved whenever the agent has an active composer. |
||
|
|
1c4400194a |
Support hardware keyboard shortcuts across native and desktop apps (#8625)
* feat(keyboard): support hardware keyboards on iPad Register app shortcuts as iOS UIKeyCommands so a paired hardware keyboard drives the same bindings as web and desktop. Overlays (menus, comboboxes, modals, command center) get first refusal on bare keys, and a focused terminal keeps the keys it needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(keyboard): record iOS hardware key observation spike With a UITextView focused on iPad Pro M4/iOS 18.6, window and controller observers received Cmd+B, Cmd+P and Escape. Escape also invoked UIKeyCommand when registered, so the stream must be the sole emitter. Use observation for shortcuts and commands only for text-control ownership. Simulator intercepted Cmd+K as HID184; input capture blocked synthetic typing entirely. Keep capture off and verify the toggle via a rebind during simulator QA. * refactor(keyboard): give overlays one key registration API Overlay ownership now follows one layer-ordered registry on every runtime. Web retains focus trapping and restoration through its adapter; native registers the same key handler without a DOM scope. Picker navigation and dismissal no longer require a parallel native stack. Co-authored-by: jeroenfrenken <16974549+jeroenfrenken@users.noreply.github.com> * feat(keyboard): route native hardware keys through the shared resolver Observe each physical press once, including native modal windows, and derive focus from declared composer and terminal surfaces. Native bindings and rebinds now use the same resolver and overlay precedence as browser and Electron input. Co-authored-by: jeroenfrenken <16974549+jeroenfrenken@users.noreply.github.com> * feat(keyboard): show shortcuts when hardware keyboards are present Keep routing independent of compact layout and detect hardware capability for hints and settings. Rebind capture uses the shared overlay interface on native too. Keep the filtered sidebar model available for keyboard navigation while its panel is hidden. Co-authored-by: Les Stroud <public@lesstroud.com> Co-authored-by: jeroenfrenken <16974549+jeroenfrenken@users.noreply.github.com> * test(keyboard): allow shortcut hints in compact menu assertions Compact menus now display hardware keyboard hints. Assert action labels, order and uniqueness without treating a trailing shortcut badge as part of the action label. * fix(keyboard): retain an overlay's opening layer in state Capture the registry's current top when an overlay opens and retain that layer through rerenders. React Compiler removed the previous useMemo cache after the registry getter moved across modules, allowing a parent modal to rise above its child and start an infinite focus loop. Compile this hook in Vitest as Expo does. The new hook regression failed first with layer 220 instead of 20, then passed with explicit opening state. Focused unit and browser tests pass, and the previously failing onboarding E2E completes through the real Expo compiler. Typecheck, lint and format pass. * refactor(keyboard): let overlays own native hardware input Remove the TextInput key handlers that duplicate declared overlay arrows and Escape in Add Project and Command Center. Remove Add Project's hidden responder now that native window sources observe keys without it. Keep soft-keyboard submit editing behavior. * fix(keyboard): close Add Project outside its state updater Root Escape previously called the parent close action from a React state updater, producing a cross-component render warning when navigation was still queued. Compute the previous page in the event handler and close outside the updater. The existing Escape/Back E2E now batches navigation and dismissal through the DOM key source. It failed first with the AddProjectFlowHost render warning, then passed after the correction. Typecheck, lint and format pass; native Escape checks pass on Android and iPad. * fix(keyboard): preserve Android dead-key composition * fix(keyboard): recognize focused native text inputs * test(keyboard): replace JSDOM coverage with browser tests * refactor(menu): spell out arrow navigation branches * fix(composer): claim fullscreen Escape on native * refactor(composer): one fullscreen Escape module for every platform The shared registration lived in fullscreen-exit.shared.ts with two one-line re-exports (fullscreen-exit.ts and fullscreen-exit.native.ts). Metro needs no native override now that the Android back button is its own adapter, so the implementation moves into fullscreen-exit.ts and the browser test imports the module every platform resolves. --------- Co-authored-by: Ubuntu <jeroenfrenken@icloud.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: jeroenfrenken <16974549+jeroenfrenken@users.noreply.github.com> |
||
|
|
336e7d1b89 |
Add first-run setup that walks new users through Paseo (#8363)
* feat(app): add first-run setup modal New users landed on an empty "Add a project" screen and never found providers, Paseo tools or phone pairing, which all live in Settings. A one-time setup modal now walks them through Appearance, Providers, Projects, Orchestration and Phone. Every step renders the same component Settings uses, so changing something in setup changes it in Settings too. The modal opens only when the setup host is connected, its workspaces have loaded and it has no projects. Existing users already have projects, so they never see it after upgrading and no migration is needed. Finishing or skipping writes `@paseo:onboarding` on this client. It uses its own key because it records feature state, and e2e specs replace app settings wholesale. Settings > General > "Show setup again" reopens the modal on step 1. Native phones skip the modal for now. Shared pieces moved out so setup and Settings render the same thing: - ThemePicker is a tile grid and replaces the Settings theme dropdown. - PaseoToolsCard is extracted from the host page. - The skeleton pulse moves to components/ui. Provider rows show it while the first snapshot loads instead of "Loading…". - The pair-device direct-connection hint links to the Tailscale docs. The e2e fixture marks setup completed by default so existing specs keep their starting state. The desktop smoke and lifecycle scripts, which start from a fresh daemon, mark it before they continue. * feat(onboarding): suggest projects from agent history The setup's Projects step lists the repos the user already ran agents in, with busy recent ones preselected, so a new user adds their projects in one click instead of browsing for folders. With rows checked, the footer's main button adds them and only moves on once every one was added; a checked row always means "this will be added". The daemon resolves each session folder to its project root (main checkout for worktrees, repo root for subfolders) because only it can read the git state; the app asks once per provider so a slow or broken provider never holds up the others and can be retried on its own. Hosts without the projectSuggestions feature show only the Add project button. Onboarding e2e hosts read an empty Claude history with other providers off, so suggestions never depend on the machine running the tests. * fix(onboarding): keep late project adds on their step and show Retry early - A finished add only advances setup while the Projects step is still open. - A failed provider shows its Retry as soon as any provider answers. - The desktop lifecycle check no longer reloads the window: setup never opens there because no host is connected. * fix(onboarding): bind late project adds to their step visit and freeze rows while adding - Each step visit has an id; a finished add only advances the visit that started it, so leaving and returning to Projects, or reopening setup, ignores it. - Suggestion rows are disabled while their batch is being added. * test(app): drive theme selection through the tile picker Settings → Appearance no longer has a theme dropdown. The Escape test opens the syntax theme dropdown instead, and the Android system-bars check taps theme tiles. * fix(server): return project suggestion roots in the form project.add stores Git reports Windows repo roots with forward slashes, so an added suggestion kept showing because its path never matched the new project's root. * feat(app): two control sizes, orchestration switches and mobile connect choices in setup Controls had four sizes picked per call site, so a button in a dialog, a settings row and a setup step came out at different heights. Controls now have two: md (default; 32px with a pointer, 44px in the compact layout so phone controls are full touch targets) and sm (28px, for tight chrome and nested settings rows). The breakpoint picks md's height, not the caller. Setup: - Orchestration is three switches (Paseo tools, agent skills, CLI), all on, applied on Continue. Turning one off undoes it; the desktop CLI uninstall removes only the file this app installed. - The Mobile step has store buttons and a "Choose how to connect" section: Relay opens the existing pairing modal, Tailscale opens the docs. Settings section docs links are one pattern, SettingsSection's `link` prop, and modals sit on surface0 like a screen. The vitest unistyles stub now resolves breakpoint values by window width, as Unistyles does at runtime; without it md controls had no height in browser tests. * fix(app): finish setup on its last step and confirm undoing orchestration In the compact layout Orchestration is the last step, and Finish closed setup without applying its switches. The last step's work now runs before Finish, the same as Continue. Turning off skills now asks first, as Settings does. Removing the CLI keeps a paseo command this app didn't install and says so. A failed Paseo tools update shows a toast. Adding suggested projects no longer advances when a row was checked while the add ran. Chat sender attribution and the message preview toggle go back to the sm button: md padding is a breakpoint value, and on web its media-query rule beat the zero right padding that aligns the sender with its bubble. * fix(app): ignore a late Finish once the user left the last setup step Finish called complete(), which ignored the step visit, so pressing Back while Orchestration applied its choices still closed setup and recorded it as done. next(visit) now finishes setup from the last step, so a late Finish is dropped the same way a late Continue already was. * fix(app): hold the skills switch while setup applies its choices The Paseo tools and CLI switches were disabled while Continue or Finish applied them, but the skills switch only locked while a skills operation ran. With skills needing no change, it stayed editable during a pending tools update and the new value was silently dropped. * fix(app): read setup's skills choice as off when no skill is picked Saving an empty selection in the skills picker left setup's skills switch on. Continue then asked the host to install nothing, the host reported skills as not installed, and the step stayed put with no error. Setup now reads skills as off with nothing picked, as Settings does. |
||
|
|
b92a34ab3d |
Keep reconnecting and updating status visible in native chats (#8568)
* fix(app): keep chat status toasts visible below floating headers Compact native chats extend beneath the floating header. Panel toasts need measured header clearance and a layer above its fade; placing them below the fade leaves an excessive gap. Keep dock overlays outside keyboard translation so opening the keyboard cannot move them offscreen. * test(app): verify rendered chat status spacing on native devices Measure the real header and toast bounds with the software keyboard closed and open. The former arithmetic helper test passed even when the viewport stopped consuming header clearance; the native regression fails against that broken production wiring. * test(app): exercise toast placement through host disconnection Observe the public reconnecting state with host connectivity interrupted externally. Keep native bounds and software keyboard assertions while removing runtime evaluation and private timeline-owner mutations. |
||
|
|
1008e9fffc |
Insert suggested commands into the composer (#8371)
* feat(composer): insert suggested commands into drafts * fix(composer): close suggestions after command insertion * fix(composer): place caret after inserted commands |
||
|
|
0e5bcbd9f5 |
fix(app): open relative chat links inside nested repositories (#8361)
* fix(app): open a nested-repo file when the workspace-root path is missing A workspace that contains another git repo often gets paths written relative to that nested repo. `schema/x.md` is really `repo-a/schema/x.md`. A markdown link was joined onto the workspace root and opened a missing file. Inline code already asked the daemon for a suffix match. Resolved workspace-relative paths that contain a slash now use that same lookup before opening. An exact hit stays on the original path. A suffix hit replaces it and keeps the line range. A failed lookup still opens the original path. Bare names, absolute paths, and `~/` paths are unchanged. Example: - Input: workspace `/repo`, link `schema/x.md`, file at `repo-a/schema/x.md` - Before: open `/repo/schema/x.md` (missing) - After: open `/repo/repo-a/schema/x.md` * fix(app): preserve explicit destinations when finding nested files Relative chat links can name a file inside a nested repository. Retain the suffix lookup while using the original href and document base to preserve absolute paths, preview links, and folder destinations. Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com> * fix(app): keep network share links on their absolute destination Use the shared absolute-path classifier so network share hrefs retain their destination alongside drive and POSIX paths. * fix(app): keep nested-file lookup inside ordered path opening Resolve the original directory before considering suffix matches, and allocate click ordering before asynchronous lookup. The existing opener now owns the complete operation so a slow earlier click cannot replace a later selection. * test(app): load UI modules before test lifecycle hooks Cold module transformation exceeded the lifecycle hook deadline before these suites could run. Complete module initialization before registering the tests, retaining their assertions and timeout settings. --------- Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
d6d5a266ad |
Open a subagent in place of the subagents list on mobile (#8093)
* fix(app): seat the subagent sheet's parent callout like the composer The sheet tab body padded the bottom safe-area inset, and the composer dock padded it again, so the parent callout in a subagent sheet floated a full navigation-bar height above where the composer sits in a tab. The sheet body (SheetBottomInset) now owns the inset: it pads it once and tells its content none is left, so the dock stays host-agnostic. Overlay sheets (adaptive modal sheet, menu sheet, combobox sheet) read the inset on the far side of their portal, so ones opened from sheet content still clear the screen edge. The callout takes the composer's rail and shows the parent's provider icon through StatusIcon: no icon box, with the running ring, attention dot or needs-input alert on the icon. * fix(app): give the attachment lightbox its own safe area An RN Modal passes its call site's context straight through, so a lightbox opened from a sheet tab read the bottom inset the sheet body had already spent (0) and drew over the navigation bar. The modal is its own window: it now gets its own SafeAreaProvider, which measures that window, and the image area reads its insets below it. floating-panels.md records the rule for overlays: read insets where the overlay draws. * feat(app): open a subagent in place of the subagents list on compact On phones, choosing a subagent from the subagents list now replaces the list inside the same sheet instead of stacking a second sheet over it. Back returns to the list, Close closes both, Android Back steps subagent -> list -> closed, and Open parent lands on the pane the list was opened from (for a nested subagent, its parent's sheet). - Menu engine: fill pages (`fill: true`) take the full sheet height, Back pops a page via `goBack`, and a page whose definition disappears falls back to the list. - One sheet pane (`screens/workspace/sheet-pane`) renders the header, body, bottom inset and focus for both containers: the workspace's detached sheet tabs and the in-list menu page. Only the container differs. - The header drags the sheet through `SheetDragArea`; compact menu sheets use the shared `SheetBackground`/`SheetHandle` chrome. - `selectSheetTab` describes the sheet a reveal would show without opening it. * fix(app): put the parent callout on one row The Open parent button already says what the callout is, so the Parent label goes. The parent's title shrinks to one line before the button does, so Open parent never wraps or leaves the row. * refactor(app): register panels once in the workspace screen Every reader of the panel registry called ensurePanelsRegistered, which imports every panel module. The panels render those readers (the agent panel's subagent track and parent callout, the new-tab panel's launcher, the diff panel's review surface), so each reader closed an import cycle through register-panels. The workspace screen, the only route that renders a registry reader, now registers the panels before anything under it renders, and the readers import only the leaf panel-registry. The pane content test registers a probe panel for real instead of mocking the registry. * test(app): transform the markdown WASM factory when Vite versions its URL Importing the sheet pane barrel in retained-chat-content.browser.test.tsx made Vite request md4c.js with a ?v= query, which the CommonJS-to-ESM transform did not match, so the test file failed to import. |
||
|
|
ce412d04c3 |
Keep docked navigation sizes consistent in narrow windows (#8052)
* fix(app): unify layout and density breakpoints Align md styles with the existing 600pt compact/wide boundary so 600-719pt tablet windows no longer mix wide layout with compact metrics. Keep the 500pt composer threshold local to its measured content width. Let Settings navigation shrink before its detail pane falls below the existing 400pt target, while retaining the preferred 720pt split budget. Validated workspace typecheck, lint, formatting, real Chromium resize checks, Duo portrait and landscape, and existing sidebar geometry tests. * test(app): cover consistent docked navigation and readable Settings Compare narrow and wide navigation density and Settings detail space through the real browser journey, including compact Settings after resize. The regression fails with main's split layout and density boundaries. Co-authored-by: Elsa <zeyugao@outlook.com> --------- Co-authored-by: Elsa <zeyugao@outlook.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
7f6a805e0e |
feat(app): choose the composer send shortcut (#5503)
Add a client-local Send shortcut selector with Enter and Command/Ctrl+Enter choices. Preserve Enter as the default and route the selected shortcut through the existing send behavior. Cover selection, persistence, editing, and running-agent send behavior with browser tests. Co-authored-by: Luke LaBonte <contact@lukelabonte.com> |
||
|
|
e958540b0a |
fix(app): remove SVG prop and nested button warnings on web (#7915)
* fix(app): prevent web SVG prop and nested button warnings Consume Unistyles mapping props at the wrapper boundary and use web accessibility attributes for decorative rings. Register composer track rows as menu items so nested actions stay valid and keyboard selection remains available. * test(app): select archive actions by their menu role |
||
|
|
033b2cdfc2 |
fix(app): keep command suggestions visible as the composer grows (#7910)
Long command descriptions exhausted the available space above a tall draft, leaving no complete selectable row. Bound the preview and reserve list space. Let the popover own its composer anchor so layout changes refresh its position while suggestions remain open. |
||
|
|
88bd1cf05a |
feat(app): long-press Send to queue or send now on touch screens (#7897)
Touch users had no way to reach what Cmd/Ctrl+Enter does: queue when Default send is Steer or Interrupt, send now when it is Queue. Holding the Send button now runs that same action on every platform, with a haptic on native and an accessibility hint naming it. Tapping is unchanged. Both paths go through runAlternateSendAction, which now falls back to the default send when no turn is running, so holding Send while idle still sends and the keyboard path no longer carries its own gate. Co-authored-by: bjspi <43368639+bjspi@users.noreply.github.com> |
||
|
|
6f07b0de48 |
feat(app): recall sent messages with Up and Down in an empty composer (#7710)
Fixing a prompt after interrupting it, or retrying one after a provider error, meant scrolling up and copying it out of the chat. Up in an empty agent composer now fills the last message sent to that agent; Up walks older, Down walks newer and past the newest back to empty. History is read from the loaded timeline on key press, not kept in a separate store, so it follows rewinds, reloads and other devices and costs nothing while a turn streams. Recall only starts from an empty composer, so it never discards a half-written draft. While a recalled message is unedited the autocomplete stays hidden, otherwise a recalled `/command` would open the picker and Enter would pick an option instead of sending. |
||
|
|
2a890208ce |
feat(app): show a subagent's parent and a way back to it (#7784)
A subagent pane now names its parent and takes you back to it. With a composer, a pill leads the rail above it; without one, a parent box takes the composer's place. Both open the parent through navigateToAgent. |
||
|
|
582beeb10b |
fix(app): preserve compact sidebar context under sheets (#7693)
Keep compact sidebars open while users inspect Search, Import session, Add project, or file sheets. Reveal the resolved destination when navigation commits, and place compact workspace sheets above both sidebars while preserving shared editing and cache state. |
||
|
|
55a7590952 |
Sort sidebar workspaces and keep active overflow visible (#7635)
Order ordinary sidebar workspaces by Created or Last prompt, preserve project and pin ordering, and keep later active or unacknowledged work visible beyond the first seven rows. Preserve reveal state through live updates and verify sorting, visibility, compatibility, and rendering behavior. |
||
|
|
9d908d678b |
feat(terminal): support image paste into agent TUIs, local and remote (#3795)
* feat(terminal): support image paste into agent TUIs, local and remote (#3725) Codex and OpenCode receive image pastes by binding Ctrl+V (\x16) and reading the OS clipboard of the machine they run on; no image bytes travel through the pty. Paseo's terminal swallowed that keystroke and only ever pasted text, so image paste silently did nothing. - web/Electron runtime: forward \x16 when the clipboard holds no text, keep the existing text-paste path otherwise (bash quoted-insert stays intact) - new terminal.clipboard.write_image RPC ships client clipboard bytes to the daemon, which writes them onto its host clipboard (osascript / wl-copy / xclip / Set-Clipboard) so remote clients work like local ones; gated by server_info.features.terminalClipboardImage with a bare-\x16 fallback for older daemons - clipboard tools are waited on via their exit event, not stream close: wl-copy and xclip fork a selection owner that inherits our pipes, so waiting for stream closure hung every Linux write until timeout - headless hosts without a clipboard get a temp-file fallback: the daemon materializes the image under os.tmpdir() and returns its path; the client injects it as bracket-pasted text - all four renderers covered (web DOM, Electron, native grid, WebView bridge); mobile Paste button enables for image clipboards and toasts failures in all 10 locales * fix(terminal): bind image paste to private retained files * fix(terminal): recover interrupted image metadata safely * fix(terminal): retire image files with background lifecycle maintenance * fix(terminal): check live paste modes and refresh native bridge * fix(terminal): order image paste input and isolate failures * fix(terminal): normalize paste and release failed creation reservations * fix(terminal): validate missing Windows launch paths before spawn * fix(terminal): keep input live during cancellable image uploads * test(terminal): follow streamed input-mode updates * feat(terminal): upload dropped images through the paste flow * feat(terminal): paste and drop images through file uploads Terminal image paste and drop now reuse the existing upload path (client.uploadFile -> file.upload.request -> FileUploadStore) and paste the quoted host path through the pane's input, bracketed when the app asked for it. This replaces the PR's daemon-side image store, RPCs, paste permits and file lifecycle, so the daemon, protocol and client diffs for this feature are empty. - terminal/image-paste owns a per-pane serial queue: gesture order, keystrokes stay live, Ctrl+C and pane/connection teardown cancel pending pastes, and a cancelled path is never pasted. - Clipboard text wins over an image; any image/* type is accepted up to the composer's 50MB upload limit. - Host paths are quoted for the host OS by path shape. - One failure toast; i18n keeps two keys. - Browser e2e covers two panes pasting distinct PNGs, drop, ordering, Ctrl+C cancel and disconnect against a real daemon. Co-authored-by: Gerard Balaoro <miogerard@yahoo.com> * fix(terminal): translate the disconnected image upload error * feat(terminal): report image uploads through toasts Show a lasting "Uploading image…" toast while an image uploads, replace it with "Image uploaded" or a failure toast that carries the reason, and dismiss it when Ctrl+C cancels the upload. Toast show now returns a dismiss scoped to that toast, so the pane can clear its own toast without removing a newer one. * build(terminal): regenerate the WebView HTML for the toast events * fix(terminal): keep i18n out of the terminal WebView bundle The image paste queue imported i18n for its size-limit message, which pulled every translation into the WebView HTML. The queue now reports a typed TerminalImageTooLargeError and the pane words it. * refactor(terminal): route dropped files by host locality and share the drop overlay A dropped file pastes its own path only when the host is the desktop app's own daemon (useIsLocalDaemon); anywhere else the host cannot read the client's disk, so the file uploads through client.uploadFile and the host path is pasted. File type no longer plays a part: images and other files take the same route, and clipboard images always upload because they have no path. The terminal pane now sits in the shared FileDropZone, so dragging files over it shows the agent panels' "Drop files here" overlay. The emulator's own drag listeners, blue tint and 180 ms timeout are gone. FileDropSink.onFiles is optional so a sink can take raw files without persisting raster images as attachments. terminal/image-paste becomes terminal/file-paste; the queue, callbacks, toasts and i18n keys say "file". * test(terminal): keep the fake desktop bridge in an e2e support helper The CI contract test forbids browser specs from installing paseoDesktop directly; the local-host drop scenario now imports it from support. --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
d0568f9f1b |
Batch agent reports and completion notifications (#7561)
* fix(server): batch agent reports and completion notifications Admit agent-originated messages immediately so a child can report and finish before one recipient-owned delivery. Keep human prompts direct, retain individual provenance through replay, and complete send receipts only after provider dispatch. * test(server): wire queued delivery into bridge integration fixture * test(server): resolve batching CLI fixture independently of cwd Workspace CI runs from packages/server, while the original regression was verified from the repository root. Resolve the CLI entrypoint relative to this test file in both contexts. * test(server): run batching CLI fixture with source exports Server integration CI builds only the client dependency stack. Match the existing CLI subprocess fixtures so the send regression exercises workspace sources without requiring compiled server output. * test(server): await persisted batch receipts within integration deadline The provider-start callback precedes asynchronous receipt commits. Use the same explicit integration deadline as dispatch instead of the matcher default of one second on Windows CI. |
||
|
|
30464cf234 |
Translate plugin content and labels in each app language (#7478)
* feat(website): serve paseo.sh in Simplified Chinese
Every page of the website now has a Simplified Chinese version under
/zh-CN: the interface, docs, blog posts, comparison pages, legal pages,
all changelog releases and the homepage mockup. English URLs are unchanged.
The URL alone decides the language. There is no browser-language redirect
and no stored preference, because either would have to redirect the
unprefixed English URLs that people share and search engines index.
Internal links carry the page's language instead, and hreflang alternates
point Chinese search results at /zh-CN.
Translated headings pin the English anchor with {#id}, so section links
work in both languages. content-translations.test.ts fails when a page
has no translation or a translation's headings drift from the English.
Changelog releases and plugin listings without a translation render
whole in English under a notice, so a release never waits on one.
Plugin manifests and the registry gain optional translations and
OVERVIEW.zh-CN.md fields; older clients and registries ignore them.
* feat(website): replace the native language select with an EN/ZH menu
The header showed an OS-native select whose popup did not match the site.
The trigger now shows the language abbreviated (EN, ZH) and opens a menu in
the app's dropdown style that names each language in itself, with its name
in the page language underneath. Rows are real links to the same page in
that language; arrows, Escape, outside presses and taps behave as a menu
button, and the menu keeps itself on screen when a narrow header pushes the
trigger toward an edge.
Also drops the stray space after the full-width comma in the Chinese
homepage subtitle.
* docs(website): translate main's new docs and 0.12.0 release notes
Main added Keep run workspaces to the schedules docs, rewrote plugin icons
around inline SVG, and promoted 0.12.0 with new entries. Bring the Chinese
pages and changelog in line so no section falls back to English.
* fix(server): leave plugin listing translations to the registry
The daemon rejected manifests whose translations the registry would refuse,
so a plugin main installs could fail to load here. Paseo never reads
translations; the registry validates them at publish. Docs now say which
checks are automated at publish and which are human review.
* docs(plugins): describe the registry's final overview translation discovery
* style(server): format manifest test
* feat(plugins): translate plugin UI in each app language
Keep translation resources and subscriptions inside each installation. Resolve host labels against the presentation locale so live language changes preserve mounted drafts and RPC state. Existing descriptor types retain string defaults for source compatibility.
* test(plugins): dismiss developer chrome in second translation client
* test(app): measure sidebar rows one at a time
After a reload the developer error toast appears while expectVerticalOrder
measures every row at once. Each concurrent boundingBox runs the toast
locator handler, and only one of them resumes, so the poll timed out on
every attempt since #7424. Measuring sequentially keeps one action in flight.
* test(app): return sidebar row measuring to main
#7480 hides the developer toast instead of running a locator handler, so
measuring the rows concurrently no longer races it. Reverts
|
||
|
|
adf27de7d0 |
Keep Explorer consistent across phone, tablet and desktop layouts (#7424)
* Upgrade to Expo 57 and unify Explorer across layouts * feat(app): retain Explorer across layout and appearance changes Keep one workspace Explorer host and destination-aware navigation so folding, theme updates and compact sheets preserve the current panel. Scope pull request discovery and timeline demand to active surfaces. * fix(app): keep Explorer resize handles stretched through the dock The shared frame moved the divider into an absolute column, leaving its horizontal handle without height. Restore the row so pointer drags reach it, preserving the existing resize and performance contract. * fix(app): keep Explorer drag widths out of the shared stylesheet Carry the existing inline sizing policy into the shared frame so pointer previews do not accumulate CSS rules. * fix(test): align isolated test runtimes with Expo 57 Hoisted React consumers need the matching DOM renderer at the repository root. Automated Metro processes must not install native DevTools into disposable homes, and development toast overlays must not cover compact actions. Keep Explorer journeys on the shared host and synchronize setup fixtures on daemon output rather than a one-second file-marker window. A delayed real child start reproduces the readiness failure on every platform. * fix(test): await retained shell layout after composer resize The gesture host now stays mounted across breakpoints, so its presence no longer identifies compact layout. Report the current layout on the retained host and synchronize composer journeys with that state. * fix(app): preserve browser interactions in retained workspace hosts Initially disabled gesture hosts still registered pointer and wheel listeners, while hover cards used window positions inside a workspace-local sheet portal. Respect initial gesture enablement and position cards relative to their measured floating host. Keep developer-toast dismissal independent of pointer input and replacement toast visibility so modal, hover, and focus checks retain their input state. * fix(test): share developer toast handling with Electron browser runs The standalone Electron browser runner bypassed the browser fixture, allowing developer errors to cover Settings. Use the same non-pointer dismissal in both runners while retaining their console logs. |
||
|
|
b8bcd204a9 |
Dismiss touch composer focus when switching tabs (#7352)
* fix(app): dismiss touch composer focus when switching tabs A retained tab can hide and return before Chromium paints, preserving the focused textarea without another focus call. Blur web touch editors when their pane becomes inactive so returning leaves the conversation ready to read. Exercise the same mounted editor through a second edit and keyboard navigation cycle. * test(app): reproduce touch tab focus race before paint --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
d61dcaf1df |
fix(app): give subagent rows room on compact and standard press feedback (#7364)
The subagent track rows were as dense on a phone as in the desktop popover, and the archive and detach actions were hand-rolled pressables that only recoloured the glyph, so a tap or hover never showed the rounded highlight every other icon action has. Rows now take the compact menu-row height and padding below the `md` breakpoint, decided once in the track row style; the desktop popover keeps its density. The actions are `ToolbarButton`, which owns the hitbox, the rounded highlight on hover and press, the compact size, and the tooltip. A repo oxlint rule, `paseo/no-raw-pressable`, reports `Pressable` and `Touchable*` imports outside `components/ui/` so the next tap target reaches for a primitive. Existing importers are on a burn-down list they can only leave. The rule is its own name rather than another `no-restricted-imports` entry so its override cannot interact with the existing burn-downs. Its test runs the real oxlint binary and is wired into the CI quality job. |
||
|
|
559c0200e0 |
Reuse workspace and subagent lists while replies stream (#7342)
* perf(app): skip workspace agent visibility rebuild on stream ticks The workspace screen derived agent visibility on every session-store update: it copied every agent and agent detail into a new Map, walked all agents, and allocated two Sets, then compared them. Stream ticks update the store but leave the agent maps untouched, so this was pure waste at stream rate. createWorkspaceAgentVisibilitySelector returns the previous result until session.agents or session.agentDetails changes, and keeps it when a recompute yields the same sets. Parent lookups read the two maps directly instead of merging them. Fixes #4817 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * perf(app): skip subagent row rebuild on stream ticks Every mounted agent panel selected its subagent rows by walking all session agents, allocating rows, sorting, and deep-comparing on every session-store update, including every stream tick. createSubagentsForParentSelector returns the previous rows until session.agents changes, and keeps them when a recompute yields equal rows. The hook rebuilds the selector when the parent or the pending archive ids change. Refs #4817 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * perf(app): check pending submissions without allocating selectAgentTurnPresentation runs from several session-store selectors and filtered the submission list only to read its length. Use some(). Refs #4817 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Ivan Malison <IvanMalison@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
60fa12c4de |
Report disconnected hosts in new agent drafts (#7303)
* fix(app): report offline hosts in new agent drafts A disconnected client still exists, and pending form resolution and retained loading catalogs outlive the connection. Use host connection state for draft readiness and model loading while keeping known models available for reconnect. Co-authored-by: Ivan Malison <IvanMalison@gmail.com> * test(app): confirm draft recovery on a fresh host connection --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> Co-authored-by: Ivan Malison <IvanMalison@gmail.com> |
||
|
|
4a32f95848 |
fix(app): soften the compact composer shadow in light mode (#7214)
The compact composer used a hardcoded box shadow shared by both themes, so it read as a heavy band on light backgrounds. It now uses the shared sm shadow token, whose light value is raised from 0.02 to 0.08 opacity so the lift stays visible. Dark sm is unchanged. |
||
|
|
8d9a7123ba |
feat(app): polish compact header, composer, and settings menu (#7129)
Header islands lose their filled background and shadow; the round tap highlight stays. The floating header's fade now carries the icon contrast the island fill used to give, so it stays solid through the header and runs a longer tail into the content. The fade lives inside the overlay because Android clips children that overhang their parent, and keeps three gradient stops because Android rendered a four-stop version far more transparent than specified. Single-line header titles read one step larger. The compact composer gets a soft drop shadow. The compact settings root groups App and Host into islands of tiles, driven by breakpoint style values so desktop keeps its plain list. |
||
|
|
6017706cc1 |
fix(app): avoid restoring composer focus on touch navigation (#7130)
Limit implicit pane and remount focus replay to the existing fine-pointer policy. Fullscreen keeps one editor mounted, so touch navigation does not need the restoration introduced for the former portal handoff. |
||
|
|
8d424e039e |
Reduce rendering cost in long chats (#7052)
* perf(app): stop re-walking the timeline on every streamed delta Every coalesced delta rebuilds the stream render model with the same tail. The model re-sliced the tail at the history-window start on every call, so the rendered tail was a fresh array per delta and every WeakMap keyed on it (ordering, history split, turn timing, the layout history cache, row revision) missed each time. Cache the slice by tail identity and window start. Turn timing then still walked the whole rendered tail per delta and allocated a new assistant-timing Map, which the layout memo depends on. Split the walk: the tail is walked once per tail identity and the head is walked from the carried open turn per delta. The Map keeps its identity while the head adds no completed turn. Adds identity-contract tests and a vitest bench for the per-delta pipeline. 200 deltas over a 2,000-item timeline, mean per run: default mounted window 5.68ms -> 2.82ms, history fully revealed 16.95ms -> 1.81ms. Fixes #4816 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * perf(app): keep the stream view's per-delta work bounded by the head The chat outline concatenated tail and head, reduced over the result in the render body, and rebuilt jumpToPrompt on every delta even when the outline was disabled, which is every native viewport. Memoize the tail's contribution, scan only the head per delta, do nothing while disabled, make jumpToPrompt a stable event so the rail's memo holds, and republish the active prompt only when the index or reading position changes. The view built a Set of every mounted id plus the live head per delta for the outline's pending-jump check; key it on mounted history only and treat live head rows as always mounted. Split plugin projection so a head change does not re-project the tail, memoize the history-window boundary lookup, and read the turn presentation's submission flag with some() instead of allocating a filtered array. Refs #4816 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(app): confirm full history before measuring streaming --------- Co-authored-by: Ivan Malison <IvanMalison@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
74282dac12 | fix(app): return to chat after fullscreen submission (#6987) | ||
|
|
e5124b039a |
Put sheets on one stack and show an agent's facts in its sheet (#6958)
* Put sheet tabs on the one sheet stack Sheets are one primitive with one stack. A sheet opened from any sheet stacks on top of it, and closing it uncovers the sheet underneath. The compact workspace's file and subagent sheets were an in-tree Gorhom BottomSheet with their own stacking, so a subagent chosen from the subagents list sheet replaced the list instead of covering it. - SheetHostScope: a subtree hosts the sheets presented inside it, so the sheet tabs draw inside the compact column and read the workspace providers while still joining the app-wide stack. Needs Gorhom's BottomSheetModalInternalProvider, which the patch exports. - IsolatedBottomSheetModal takes enableDismissOnClose; false keeps the content mounted through a close so the unsaved-changes guard can veto it. Its backdrop then closes the sheet rather than dismissing the top modal. The Gorhom patch also keeps a dismissing modal's status through its final onChange(-1), without which such a modal unmounting while presented never left the portal. - SheetTabs (was WorkspaceSheetHost) renders one IsolatedBottomSheetModal per store entry. The store never reorders sheets; a reveal of a covered sheet drops the unmodified sheets above it. - Subagent rows that open a sheet keep the list open (closeOnSelect false); rows that open a tab still close it. - A menu closes when its retained panel goes inactive, instead of hiding and reappearing with the panel on the next tab switch. Co-Authored-By: Claude <noreply@anthropic.com> * Show an agent's model and thinking in its sheet, and fit Open as tab in the header rail A Paseo agent shown in a sheet has no composer, so the facts the composer's model and thinking controls would show now sit in one line under the sheet header, the same row provider subagents already get from the daemon. The line resolves exactly as the controls do (resolveAgentModelSelection), from the session agent and the providers snapshot, and shows wherever the pane host is a sheet. Open as tab becomes the full-screen glyph beside Close, drawn by the shared SheetHeaderIconButton, so the title keeps its width. The tab switcher sheet is titled "Workspace tabs" so it reads as what it lists. Co-Authored-By: Claude <noreply@anthropic.com> * Show mode and context usage in the sheet facts line The line under a sheet's header now carries everything the composer's controls and context meter show: model, thinking, mode, and used/max context tokens with the share, each resolved as its control resolves it. Co-Authored-By: Claude <noreply@anthropic.com> * Re-render subagent rows when the workspace's tabs change A row decides at render time whether choosing it opens a sheet or reveals a tab, and keeps the list open only for a sheet. The hook behind it only subscribed to the layout mode, so a row rendered while its agent was a full tab kept closing the list after that tab was gone: on iOS, promoting a managed child, closing its tab, and reopening it from the list dropped the list under the sheet. Subscribe to the workspace's layout too. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6db47b3c37 |
fix(app): keep the keyboard closed after mobile sidebar dismissal (#6945)
Separate composer keyboard eligibility from pane focus restoration. A sidebar dismisses the keyboard without hiding the pane, so its close must not restore editing focus. |
||
|
|
f3ddf99fe3 |
fix(app): restore fullscreen composer keyboard motion and clearing (#6902)
* fix(app): keep fullscreen composer in the keyboard dock * fix(app): preserve composer caret and scope keyboard actions |
||
|
|
17562fcb66 |
fix(app): stop New workspace sending a mode the provider doesn't offer (#5847)
The New workspace screen submitted the composer's remembered mode as-is, so a saved mode for a provider that offers no modes (or no longer offers that one) failed creation with "Invalid mode '<mode>' for provider '<provider>'". A new chat tab and the workspace setup dialog already reconciled the mode against the provider's modes, each with its own copy of the rule. The draft composer state now exposes effectiveModeId, the mode the picker shows, and all three create paths submit it. While the provider is still loading, the selected mode is kept because its modes aren't known yet. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> Co-authored-by: Matt Cowger <matt@cowger.us> |
||
|
|
750cb82fbb |
Keep long mobile drafts readable with fullscreen editing (#6531)
* feat(app): add fullscreen editing to the compact composer * fix(app): preserve read-only terminal command previews Static command previews must render their published source; the live editor buffer is updated after render and has no mounted input to repaint read-only content. * fix(app): import React for composer editing scope rendering Workspace pane unit tests use the classic JSX transform, so the editing-scope provider needs React in scope when mounted. * fix(app): preserve fullscreen composer controls and editing Keep the fullscreen frame inside the visual viewport, gate hidden autocomplete keys, apply replacement commands to local line measurement, and return to inline before starting voice so its controls stay accessible. * test(app): cover draft replacement through the production composer Replace the measurement-hook fixture with profile switching and typing through the real composer. Keep measurement state inside its production surface; Android manual red/green evidence covers the native replacement flow. |
||
|
|
01f9bc8291 |
Modernize compact headers and sidebar affordances (#6431)
* feat(app): modernize compact headers and sidebar affordances Use a shared island header with quick tab creation and keep sheet footers inside their visible layout. Opt sidebar rows into touch sizing without changing global appearance settings. * test(app): match darker light-theme workspace selection * test(app): align plugin journeys with compact header |
||
|
|
2f0cb2f54b |
Show the Codex speed bolt in yellow while Fast or Ultrafast is selected (#6152)
* fix(app): fill the speed icon for accelerated tiers * fix(app): highlight the Codex speed bolt when Fast or Ultrafast is selected Codex speed moved from the Fast toggle to a Speed selector, and the composer only colored toggle features, so the bolt stayed muted for every speed. A select feature is now active when its selected option is not the default, and the Codex speed takes the same yellow highlight as the Fast toggle, on the desktop toolbar and in the compact sheet. Closes #5939 Co-authored-by: Nurshot <85260051+Nurshot@users.noreply.github.com> * test(app): assert the Codex speed bolt is yellow on desktop and compact --------- Co-authored-by: Nurshot <85260051+Nurshot@users.noreply.github.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
a50e14b69e |
fix(app): keep the centered new workspace form above the keyboard on tablets (#6093)
* fix(app): keep the centered new workspace form usable with the keyboard open The centered tablet dock translated the whole form by the full keyboard shift and reserved the keyboard twice to keep its top below the header. On short tablet viewports such as an unfolded foldable, that left about 20pt for the setup fields and composer, which then overlapped. Translate the centered form only by the part of the keyboard that reaches it, and size it to fit above the keyboard. Fixes #6012 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(app): rest the centered new workspace form on the keyboard The keyboard shift excludes the bottom safe area, but the keyboard also covers it. Measuring the centered form's clearance and capacity down to the screen edge left the form one navigation-bar height under the keyboard, hiding Import session on Android tablets. Measure both above the bottom safe area so the form ends at the keyboard's top edge. Fixes #6012 Co-authored-by: Ivan Malison <IvanMalison@gmail.com> --------- Co-authored-by: Ivan Malison <IvanMalison@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
fa0f04058d |
Keep account usage accessible before the first turn and restore compact sheets
* fix(usage): keep details hoverable and restore compact sidebar sheets Share hover-card mechanics with workspace details so context usage can stay open while users press Refresh. Honor Retry-After for rate-limited usage requests, and let both sidebar entry points share the compact sheet. * fix(usage): restore settings cog with a fitted popover and compact sheet Keep shared Usage preferences in the title controls on every surface. Settings rows use the menu surface directly, avoiding the trailing margin and duplicate card border from the page layout. * fix(app): show account usage before the first agent turn Keep the context meter available for existing agents without context readings so account usage can be opened before a turn. Show an empty ring and an explicit no-data state until readings arrive. * fix(usage): preserve keyboard access and hover card positioning Context details must remain reachable by keyboard after replacing the tooltip. Keep focus within the card, restore side flipping, and dismiss detached cards when the viewport moves. Prune expired cooldowns even after credential rotation. * chore: remove QA screenshots from the source tree * fix(app): let hover cards claim Escape before agent shortcuts Register hover surfaces with the shared overlay keyboard owner without moving or trapping focus on pointer hover. Component-local Escape handling ran after the global interrupt shortcut and cancelled the underlying turn. * fix(app): reopen focused hover cards with button keys Keep focused triggers registered with overlay arbitration after dismissal so activation keys are handled before composer shortcuts. * fix(app): release overlay ownership when hover cards close Closed triggers handle activation locally, so a dismissed card does not block pane Find. Open cards retain shared Escape arbitration to protect running turns. * test(app): pin Linux keyboard identity for composer Find * test(app): synchronize context usage hover journeys Return focus to the composer before asserting pointer dismissal, wait for the card to reach the viewport, and park the pointer before reloading so it cannot consume the next scripted usage response early. |
||
|
|
febe9611bf |
fix(app): stop composer autofocus on touch screens (#5477)
A tablet or an unfolded foldable is wide enough for the desktop layout, so opening an agent autofocused the composer and raised the on-screen keyboard over the conversation. Autofocus on open now also requires a fine pointer, the same signal the app already uses for hover. A mouse-driven window still focuses the composer when an agent opens, and sending a message still keeps the composer focused. Co-authored-by: Phieu Dinh <phieu@fiu.one> |