mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 16:34:36 +00:00
161076a853e9858b3edfb89b17c32511b428d2b7
6378
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
14845d3ff3 |
Keep ACP mode selections valid after config updates (#8912)
* fix(acp): preserve session mode identity across config updates * test(acp): verify mode identity through public sessions Exercise initialization, queued notifications, mode selection, and rejection over stdio instead of replacing private connection state. Co-authored-by: a-sh <846704+a-sh@users.noreply.github.com> --------- Co-authored-by: a-sh <846704+a-sh@users.noreply.github.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
616f5e155c |
fix(app): show branch loading and fetch errors (#8938)
* fix(app): distinguish branch loading from empty results Preserve branch query pending and error states through the switcher so first open does not report missing branches before the response. Show request failures with a retry action. * fix(app): keep branch retry progress and keyboard focus Verify failed cached refresh and keyboard retry through the real browser and daemon. Keep focus in the footer while Retry is disabled so repeated Enter cannot select a cached branch. * fix(app): preserve picker interaction when retrying branch loads Use the existing close-and-reopen retry behavior with clear feedback rather than introducing an action that competes with native list keyboard ownership. |
||
|
|
471db4bdb5 |
Keep workspace tabs predictable and clear attention on deliberate focus (#8935)
* fix(app): preserve workspace tabs and acknowledge deliberate agent focus Workspace navigation no longer chooses an attention agent. Preserve repeated focus actions so an already selected chat can be acknowledged, and stop consuming attention on departure. * fix(app): retain deliberate focus until fresh agent data arrives Connection readiness permits a pending focus acknowledgement without creating a new focus action. Keep that intent across client changes and wait for the current timeline synchronization generation before consuming it. * fix(app): preserve reconnect feedback during attention acknowledgement |
||
|
|
68fc16b855 |
fix(terminal): run terminal workers without process identity queries (#8818)
* fix(terminal): run terminals when process identity cannot be read On some Windows machines Windows PowerShell cannot run Get-CimInstance, or cannot be started at all. The terminal worker refused to start without its own birth time and command line, so those machines had no terminals. Process identity only authorizes signals. The worker now starts anyway and records its identity as unavailable, and programs without a birth time. Cleanup never signals what it cannot identify. It asks such a worker to shut down over the socket the secret authenticates, and reports survivors as unconfirmed. So records of crashed workers do not block launches, the process table falls back to process.kill(pid, 0) when the query fails. When a live pid cannot be identified, a refused connection on the worker's socket marks it as another process, because a worker listens until it exits. * fix(terminal): ask unidentified workers to shut down on every cleanup Retries of an unconfirmed cleanup and startup cleanup run without a grace period, which skipped the shutdown request. For a worker whose identity is unavailable that request is the only way to stop it, so it is now always sent. A gone program recorded without a birth time is no longer walked through the process table: the table was unreadable when it started, and the walk only kept the cleanup unconfirmed. Also test the socket probe on every platform, restore a test of the daemon starting when the worker cannot, and keep one pid liveness helper. * test(terminal): fail the worker launch the same way on every platform Windows opens a directory sitting at the launcher's log path, so the worker started there and the #8810 test never saw a failed launch. An invalid protocol override makes the worker exit before it writes its record on every platform. * fix(terminal): identify workers through their recorded sockets * fix(terminal): authenticate legacy sockets before forced cleanup * test(terminal): exercise closing records without process queries * test(terminal): identify stale lifecycle records by their sockets * refactor(terminal): drop the unreleased unavailable-identity record shape No release wrote identity: { unavailable }; it existed only between commits of this change. Records keep reading the identity workers up to 0.15.1 wrote, and nothing consults it. |
||
|
|
ae157dbfb6 |
Even out chat footer spacing and show the Changes base only on worktrees (#8934)
* fix(app): space the turn footer like the rest of the timeline The footer sat 13px under the last line of text while the text sat about 21px under a tool or thinking row, so the footer looked glued to the message. The message drops its bottom padding when a footer follows, so the footer's top margin alone sets that gap; 20 makes the centre-to-centre distance match the tool-to-text gap. * fix(app): show the Changes base only on Paseo worktrees The header rendered "branch -> base" whenever checkout status carried a baseRef, and the daemon sends one for every git checkout, so plain checkouts showed "-> main" with no way to change it. Only Paseo worktrees store a base, so only they show it. The arrow is extra muted so it reads as a separator, and the base no longer shrinks, so a long branch name truncates instead of the base. * fix(app): let a long Changes base truncate instead of overflowing |
||
|
|
dc51a3b6a9 |
fix(app): preserve plugin-rendered replies when collapsing turns (#8811)
* fix(app): preserve plugin-rendered replies when collapsing turns * test(app): exercise plugin reply folding in the browser |
||
|
|
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> |
||
|
|
c8bd4717c9 |
test(server): wait for Git ref observation before injecting events (#8924)
Zero in-flight fetches also describes observation that has not started. Wait for the first fetch to start and settle, and exercise delayed watcher setup so missing callbacks cannot silently skip the test events. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
6c1b48d296 |
Archive a workspace from its sidebar row, with undo (#8904)
* feat(app): archive a workspace from its sidebar row and undo it Hovering a workspace row on desktop shows an archive button next to the row menu. Archiving shows a toast with Undo and a Cmd/Ctrl+Z shortcut so a mis-click costs one keystroke instead of a trip through History. The daemon serializes archive and undo per workspace across every connection. An undo that lands while an archive is still tearing down aborts it at the next checkpoint and restores only the agents that archive took down; deleting the worktree directory is the point of no return, after which undo recreates the worktree. * fix(server,app): close archive and undo races found in review - An archive request now claims the workspace's archive queue before looking up the workspace, so an undo sent right behind it cancels it instead of running first and losing to the archive. - A repeat archive of an already archived workspace no longer wipes the agents the earlier archive saved for undo. - Undo no longer brings back an agent that was archived before the workspace archive and only loaded to read its history. - Each local archive or undo request owns its own intent token, so a late response to an earlier request cannot release a newer request's hold on the row. An archive canceled by another client's undo puts the row back. - The undo failure toast shows translated copy; the cause goes to the log. * fix(app): keep a workspace's layout when another client restores it during the undo window The undo window purged tabs and layout whenever it closed after a finished archive. If another client restored the workspace before this client's toast closed, the restored workspace lost its saved arrangement. The archive now commits only if the workspace is still gone when the window closes. * fix(app): show the archived toast's Undo as a button * fix(app): keep a workspace's agent tabs when it is opened while its undo runs * fix(app): keep a row whose undo is running when a directory snapshot omits it |
||
|
|
1a626230d9 |
Keep clients current when parent archive detaches an unloaded child (#8903)
* fix(server): retain archived parent provenance on detach * fix(server): publish stored child detach updates on parent archive Keep subscribed clients current when an unloaded child survives its parent archive. Narrow the contributor change to the missing update; archived-parent provenance remains a separate feature. Co-authored-by: luke <luopeihuan@gmail.com> * test(server): clean up the detach fixture static directory Keep the test daemon static directory inside the fixture root so cleanup removes all test-owned directories. * fix(server): retain runtime settings in stored agent updates Parent archive publishes stored survivors through the manager. Preserve their saved runtime model and thinking settings so the update matches stored fetches and subscription filters. --------- Co-authored-by: luke <luopeihuan@gmail.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.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.
|
||
|
|
4281372bee |
fix(app): keep the chat readable with the keyboard open on iOS 26+ (#8918)
iOS 26 adds a scroll edge effect, a blur at each edge of every scroll view, and places it in the scroll view's own coordinates without applying transforms. The keyboard lifts the chat with a translateY, so on iPhones the blur lands over the readable conversation and stays until the next scroll. The app draws its own header fade, so the app's ScrollView component class, which backs every React Native ScrollView, hides all four edge effects. Co-authored-by: David Greenspan <155814+dgreensp@users.noreply.github.com> |
||
|
|
e79e826e23 |
Stop re-rendering every chat message on each agent update (#8916)
* fix(app): keep unchanged agent fields when an agent update is accepted * fix(app): share unchanged agent fields at the store write, not in the update policy |
||
|
|
4928500e1f | fix(app): keep chat rows their size while their tab is hidden on Android (#8911) | ||
|
|
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 |
||
|
|
2716bd96de |
test(terminal): observe original process completion after exit (#8892)
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
5abfdf7107 |
Record workspace creation parentage (#8890)
* feat(workspaces): record workspace creation parentage Retain the caller workspace as informational ancestry for future sidebar nesting, independently of workspace placement and lifecycle. * fix(app): preserve workspace parentage in replica cache |
||
|
|
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. |
||
|
|
8b9c42c266 |
Keep every sidebar footer action reachable in a narrow sidebar (#8885)
* fix(app): move sidebar footer actions that don't fit behind a caret Touch sizing makes each footer icon a 44pt target, so five of them plus the row's padding need 236pt. A docked sidebar can be 200pt and a fold column about as narrow, so Settings was cut off at the sidebar's edge. The row now measures itself and keeps as many full-size buttons as fit; the rest move behind a caret that opens a menu-engine popover. Help and Usage leave first, Settings and Add project last, and Hosts and Help open as pages of the overflow menu rather than second popovers. * fix(app): keep the sidebar footer empty until it has measured Laying the footer out before its first onLayout put all five buttons in the row, so a narrow sidebar painted Settings clipped for a few frames before the caret replaced it. The unmeasured row now places nothing and holds its height, so the first painted row is already the measured one. * fix(app): keep the footer overflow menu on a narrow, short screen The overflow menu asked for 240pt, and edge clamping can move a surface but not shrink it, so on a 220pt screen its right edge and submenu arrows hung off the display. Anchored surfaces now cap their width to the display inside the same edge padding that clamping uses. The Hosts page lists every host, so the overflow menu is scrollable: on a short screen the last hosts and Add host were below the fold with no way to reach them. |
||
|
|
a68dc55490 | fix(app): refresh visible usage summaries periodically (#7806) | ||
|
|
3ed57e9dd7 |
Change a worktree workspace base from Changes (#8642)
Change a worktree workspace's base from the Changes pane. The choice persists and diffs, counts, commits and Git actions follow it. Supersedes #6872. Co-authored-by: Tianyu Yang <yangtianyu.cl@bytedance.com> |
||
|
|
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> |
||
|
|
b0a8a5db6d |
Stop the double reconnect when returning to the app (#8855)
* fix(app): keep the selected connection when it reconnects during a probe cycle When the app returns from the background with more than one saved connection and the selected socket was closed meanwhile, the probe cycle marks the selected connection unavailable at probe start, while its client is still reconnecting. The alternative probe lands later, after the selected client has already reconnected, and finalizing the cycle switched to the alternative using that stale verdict. That disposed the live client (rejecting the in-flight timeline catch-up) and reconnected a second time. The cycle now reconciles the selected connection's verdict with its client's current state before deciding to fail over. A client that is still down at that point still fails over as before. * test(app): cover reconnect with and without a heartbeat RTT |
||
|
|
412550c36f |
Keep active transfers connected when the app returns to foreground (#8651)
* fix(client): keep active transfers alive during foreground checks Foreground used an independent three-second probe that closed live connections while a pong waited behind bulk data. Share the heartbeat clock and refresh its outstanding deadline on validated inbound frames, including incomplete chunk assemblies. * test(client): deliver bulk response before the queued pong * fix(client): preserve latency deadlines when sharing heartbeat pings * fix(client): keep heartbeat cadence during active downloads |
||
|
|
e6064bb374 | fix: update lockfile signatures and Nix hash [skip ci] | ||
|
|
59be8975a4 |
Dismiss a failed agent's red dot by reading it (#8831)
* fix(sidebar): clear the failed status once the error is read Reading an errored agent already cleared its error attention, but the state bucket also mapped lifecycle status "error" to failed. Lifecycle only leaves "error" when the next turn starts, so the red dot and the Failed group stuck until the agent was prompted again. The failed bucket now follows unread error attention, the same way the green dot follows unread finished attention; lifecycle status stays "error". Because failed now depends on attention, a failure must replace unread finished attention, otherwise a reload that fails on an unread agent would show green. * fix(sidebar): keep failed subagents red and let read failures be marked unread The failed bucket now follows unread error attention, which broke three places that relied on lifecycle "error" alone: - Subagent track rows and provider-subagent tabs have no read state of their own, so failed children lost their red mark. They now map the child's lifecycle directly. - A read errored workspace became "done" and offered Mark as unread, but the daemon only accepted idle or closed agents. Marking it unread now restores error attention. - A reload that failed again while the lifecycle was already "error" raised no new attention. A failed reload now raises error attention directly instead of relying on a lifecycle transition. |
||
|
|
cdd7800fed |
test(app): wait for workspace hover cards to settle (#8846)
Keyboard focus can briefly mount the next card while the previous card closes. Poll uniqueness and visibility together so the assertion tolerates that transition but still rejects persistent duplicates. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
902d43da7c |
chore(release): reconcile v0.15.2 into main (#8845)
* fix(app): hold the loading screen until first-run setup is decided A new user saw the empty home, the sidebar and a logo jump before setup popped in. Startup now keeps the splash while setup may still open over the empty home, for at most 3 s and only while the setup host is reachable. Remembered-workspace restore does not wait. Web dialogs now fade in as they already fade out. (cherry picked from commit |
||
|
|
92c8c55f31 |
Fix registry plugin installs through npm mirrors (#8756)
* fix(plugins): install registry npm pins through the configured registry Registry npm pins carry an npmjs.org tarball URL. Passing that URL to npm made it rewrite the host onto the configured registry and drop any path, so mirrors such as https://mirrors.tencent.com/npm/ returned 404. Install the pinned version instead and identify the reviewed artifact by version and integrity, since a mirror records its own tarball URL. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(plugins): address review on npm artifact comparison Take an object parameter in isSameNpmArtifact, and clean up the mirror fixture when test setup fails. Co-authored-by: Cursor <cursoragent@cursor.com> * test(plugins): verify mirror artifacts with a warm npm cache A shared npm cache lets the mirror regression reuse tarballs from earlier fixture installations, so successful installs made no tarball requests. Check the installed artifact's mirror URL, version and integrity instead of requiring network transfers. Co-authored-by: finofliu <finofliu@tencent.com> --------- Co-authored-by: finofliu <finofliu@tencent.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
b920243c74 |
docs(release): dispatch the instant-admit flip only after the release is published (#8816)
A draft release has no tag, so dispatching desktop-rollout.yml before finalize-rollout publishes it fails with "release not found". Seen on v0.15.1. |
||
|
|
9667ec5576 | fix: update lockfile signatures and Nix hash [skip ci] | ||
|
|
72955cef60 |
chore(release): reconcile 0.15.1 into main (#8814)
Brings the shipped 0.15.1 changelog entries, version, and F-Droid changelog files from release/v0.15.1 into main. |
||
|
|
8dfdb479ab |
fix(terminal): start the daemon when the terminal worker cannot start (#8810)
On 0.15.0 the daemon launches the detached terminal worker while it is being created, and the worker refuses to start unless it can read its own birth time and command line (Windows PowerShell 5.1 `Get-CimInstance`, `ps` elsewhere). Any failure of that query used to escape startWorkerTerminalManager and take the whole daemon down with "Daemon failed to start (exit 1)", as two Windows machines hit after the update: one whose PowerShell 5.1 cannot load CimCmdlets, one where powershell.exe cannot be spawned at all. 0.14.0 never ran that query at startup. The launch error is now logged and the daemon starts without a current worker. The pool already launches one lazily on the first terminal creation, so that request reports the same error while the machine still cannot run the query, and terminals come back once it can. The identity requirement itself is unchanged: a worker without a verified identity is never written or adopted. |
||
|
|
5d1a2c10f8 |
test(app): wheel the thinking group into view before opening it (#8791)
The "reasoning follows Expand thinking (false)" spec clicked a tool-call group above the viewport. Playwright's scroll-into-view is programmatic, and the web timeline only leaves sticky-bottom on user input (wheel, key, touch, scrollbar), so it snapped back to the bottom while the click was in flight and the group never opened. Wheel the group into view first, reusing the helper the timeline pagination specs already use for prompts. |
||
|
|
a91bb4d406 | fix: update lockfile signatures and Nix hash [skip ci] | ||
|
|
15997a06d0 |
fix(ci): pin semver away from incomplete attestations (#8787)
The pkijs lockfile regeneration bumped tiny-async-pool's semver to 5.7.3 and app-builder-lib's @electron/get semver to 6.3.2, both published 2026-11-02 in one batch. Some Cloudflare edges cached the attestation document for 5.7.3 before the publish attestation attached, so `npm audit signatures` fails with EMISSINGSIGNATUREKEY depending on the runner's region. Pin both back to the 2023 releases with per-parent, range-keyed overrides so global-agent's semver ^7 under @electron/get is left alone. |
||
|
|
2d5b8eb1fd |
Handle web terminal finger scrolling once (#8776)
* fix(app): scroll web terminal history with a finger swipe A phone browser claims the vertical drag and drops it, because the canvas covers the scrollable viewport and the page itself does not scroll. Keep the gesture in the touch handler and move one line per cell. * fix(app): handle terminal finger scrolling once Keep the vertical gesture in the existing terminal handler so xterm does not also scroll it at document level. Retain only the confirmed propagation correction from the contributor patch and cover the actual phone-sized app with trusted touch input. Co-authored-by: sino <sino2322@gmail.com> * fix(app): let xterm own terminal touch input Remove the duplicate gesture implementation so history scrolls once and terminal applications retain arrow-key and mouse-report input. Cover live application modes with trusted touch gestures and a real raw-stdin process. --------- Co-authored-by: sino <sino2322@gmail.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.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> |
||
|
|
0e7f452c2f |
Keep duplicate-path setup out of the Git filter CI budget (#8753)
* test(server): prepare duplicate paths before measuring Git filtering * test(server): clarify the observer path preparation boundary --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.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> |
||
|
|
4407283a13 | fix: update lockfile signatures and Nix hash [skip ci] | ||
|
|
4f7813d3aa |
Keep the tapped line in place when chat disclosures expand or collapse, with the native timeline on LegendList (#8717)
* Keep a disclosure's trigger in place on web Expanding or collapsing a turn summary, tool-call group, tool call details or todo badge now holds the row that owns the trigger. Rows inside the timeline get a disclosure context; each toggle runs its state change through `useDisclosureHold()`, and the viewport's new `holdRow(rowId, apply)` replaces `holdReadingPosition()`. On web the hold pins the reading anchor to that row, stops following output, and resumes following when the change leaves the list at its end. Native keeps its previous behaviour (detach, then apply) until the anchored toggle lands. * Render the native chat timeline with LegendList in forward order The native timeline was an inverted FlatList. Disclosure toggles moved their trigger, and a cold open settled visibly. It is now a forward-order LegendList that stays virtualized, and one owner moves the scroll offset at a time: - Before placement: LegendList's initial scroll, behind its hidden reveal. - While following: the end pin (native-end-pin.tsx). This is a zero-height first child at the bottom of the content, which native maintainVisibleContentPosition keeps in place. A content shrink or growth never scrolls from JS; only a viewport resize or an explicit jump does. - During a disclosure hold and while detached: LegendList MVCP with shouldRestorePosition (native-disclosure-hold.ts), so a toggle keeps its trigger at the same y and paints one layout change. - Every newly mounted row starts at a per-kind estimate (new-row-estimates.ts). PaseoScrollView defers React Native's content clamp while MVCP is set. A shrink below an unmoved anchor no longer draws a frame past the end (RN #58578, PR #58695). VisibleContentPositionClampTest covers this. LegendList 3.7.1 patch, one "Paseo patch" comment per hunk: - The content height comes from React state on the new architecture. - Rows get a per-row size estimate. - The MVCP anchor lock runs on native too. Upstream gated it to web in d24a9d93 without a native reason. Without the lock, the size pass for prepended history re-anchors on a prepended row and moves the rows on screen. Native ends the lock when its row leaves the view, not on the 300 ms clock. - Header and footer reveal together with the rows. - Only lists that keep their end preserve the initial end. - A remeasure within one pixel plus the 1/8 dp floor of the known size is noise. Upstream compares the raw size with the floored one, so the live turn's assistant row (1380 -> 1381 px) read as a resize and moved every row a frame after the reveal. - The Android silent initial scroll retry sends its 1 dp nudge and the return to the target in one task. Upstream returns on the next frame, but its 16 ms check can finish the scroll first and cancel that frame, so the list revealed 3 px short of its end and moved into place a frame later. - The top-padding shrink hold applies on iOS only. Android, emulator. Evidence is in .artifacts/legend-impl. - J1, J2 at the end, J3, J4, J5, J6, J7: pass. - J2 while streaming: not reachable on the Ten second stream fixture; its turns never fold. - J8: no blank frames over 10 fresh flings, at host load 32-37. J4: still. Before these two hunks, 6 of 7 cold opens moved every row one frame after the reveal. After them, 9 valid cold opens show no shift. 5 of those were traced through the nudge path. Runs that hit the dev client's startup SIGSEGV in MountingCoordinator::pullTransaction were left out. That crash is in the emulator's dropbox from before this work. * Revise a history row when its turn summary line changes LegendList renders a row again only when its item changes. The forward-order timeline in d1f2ae352 folds a finished turn into its host user message, but the host's item does not change when the fold appears, so a turn that folded live lost its summary line until something else revised the row. collapseTurns now reports the hosts whose summary line appeared, changed or went away since the previous fold (updatedHostIds), and the history row revision treats them as changed content. * Stop following the end while a user scroll owns the viewport The bottom anchor controller kept sticky-bottom for the whole of a drag and its momentum and only decided at the end. While it is sticky the native list mounts the end pin, so a drag started during a stream had every new line move the rows under the finger: on the iOS simulator the offset jumped by each line's height (+22, +35, +22 pt) mid-drag, and Android does the same with the same code. beginUserScroll now detaches. endUserScroll resumes following when the gesture ends at the end (scrolling there without animation) and otherwise stays detached and drops any pending anchor, as before. The re-stick is not animated: on Android a line that mounts during a smooth scroll cancels it, and the list then followed 40 pt short of the end until the controller detached it. Measured after the change, frame by frame: - iOS and Android, drag up mid-stream: the content tracks the finger and momentum with no line steps; the stream grows below; tapping the pill re-sticks and follows again. - A short drag at the end, mid-stream: following resumes; the release lands on the end in one frame (the drag distance, here 8 pt on iOS and 21 dp on Android). A line already mounting when the drag begins can still move the rows once in its first frame, because the detach reaches native one commit after the touch. * Correct the iOS scroll view under maintainVisibleContentPosition Phase 2 of the LegendList timeline. The native timeline relies on maintainVisibleContentPosition for the end pin and the disclosure hold, and React Native 0.86 on iOS gets two cases wrong. modules/paseo-scroll now registers PaseoScrollViewComponentView, a subclass of RCTScrollViewComponentView, for the iOS scroll view, like PaseoScrollView on Android. - A mount that removes its own anchor: detaching removes the end pin in the same mount that opens the tapped disclosure, and the offset jumped by the removed view's frame. The subclass keeps the offset when the anchor's tag is no longer registered, the check React Native 0.88 adds in https://github.com/react/react-native/pull/57294. That PR's extra superview check is not copied because it skips anchors detached by removeClippedSubviews (https://github.com/react/react-native/issues/58910). - A shrink at the end: UIKit clamps the offset to the new end during the mount, then React Native adds the anchor's movement, so a turn folding while following landed above the end (https://github.com/react/react-native/issues/58578). The subclass restores the pre-mount offset for the adjustment and clamps after it, as https://github.com/react/react-native/pull/58695 does. The dev client was rebuilt with the module. Verified on the iPhone 16 Pro simulator, frame by frame: the summary far up and the latest turn's summary while following each keep the trigger at 0 px with one layout change per toggle, the todo card the same, the cold open lands still, older history pages keep the visible rows in place, and the keyboard, send-time shrink and streaming stay pinned to the end. Freshly mounted markdown on iOS paints one frame after its layout, because enriched-markdown renders asynchronously (https://github.com/software-mansion/enriched-markdown/issues/1131). A synchronous first render removed that frame but raised fling frame time at p90 and p99, so it is not part of this change. docs/agent-stream-disclosure.md now describes the clamp correction on both platforms. * Keep the rows under the finger at a drag's start and its release Two one-frame row movements remained around a user scroll while the timeline streams. A drag's first frames. The end pin stays mounted until JS stops following, one round trip after the drag begins, so a line mounting in those frames scrolled the rows under the finger by its height. Both scroll view subclasses now skip the content-position correction while the user drags and the anchor is the end pin, which they recognise by its native ID ("paseo-end-pin"). Android overrides scrollToPreservingMomentum, the position keeper's only scroll call, from the begin-drag event until the finger lifts; iOS skips the adjustment while the scroll view is dragging. The skip is limited to the pin: LegendList's own anchoring for the detached list also uses the native correction, and a history page landing mid-drag needs it. It starts at the drag rather than at touch-down: a tap or long press while following would otherwise leave the list stranded short of its end, still following, once the finger lifts. The release. A gesture ending within 32 pt of the end re-stuck with an instant scroll, a snap of up to 56 px. Following now resumes only when the finger lifts at or past the end, or momentum stops there, within 1 px, the distance web resumes at (strategy-web.tsx). Anything else stays detached with the pill. The check runs when the gesture is decided, so output that arrived below meanwhile counts. A release past the end counts as at the end: on iOS it bounces back, and a line landing during the bounce scrolls in as following would. endUserScroll takes isAtEnd, like releaseDisclosure. Tests: VisibleContentPositionClampTest gains growth at the end during a drag (red before the change: the offset moved by the 120 px growth, 352 -> 472) and growth without a touch (the pin still follows); the module suite passes on emulator-5558, 11 tests. bottom-anchor-controller.test.ts: a release near but short of the end stays detached. Device, frame scans while streaming (dev clients rebuilt): - Android, 8 slow drags with a pill tap between: no frame moves against the finger once a drag starts; the stream follows between drags. Three short releases 50 px short of the end stay put until the pill; three drags back past the end resume following. - iOS simulator, 6 drags: no line step during any drag or its momentum. Two short releases stay detached with no snap; three drags past the end resume following within 0.5 s of the bounce. * Document the LegendList timeline and remove what the inverted list left The native section of docs/agent-stream-disclosure.md described the FlatList hold (anchor neighbour, native child index, frame spacing, render window). It now covers why the timeline is forward order, who owns the offset in each state, the hold and its sync layout trigger, user scroll and release, draw distance numbers, the LegendList patch and the scroll view corrections. Draw distance stays at 2000. The spike measured it on a chat with a 15,000 px turn expanded: 500 blanked the viewport 1.0 to 1.5 s per fling and 2000 not at all. A re-measure on the collapsed chat (500, 1000, 1500, 2000, interleaved) blanked at no value and its jank followed host load, which stayed between 19 and 40, so it could not separate 1000 or 1500 from 2000. The patch file now opens with a header listing its eight hunks by purpose and linking LegendApp/legend-list#593, which reports each one upstream. The markdown doc records the iOS first-paint delay (software-mansion/enriched-markdown#1131). Removed with the inverted list: PaseoScrollView's child-reveal override (a no-op without the inverting transform) and ScrollSelectionTest, the invertedWrapper style, and the inverted-list passages in android.md, native-text-selection.md, mobile-testing.md, floating-panels.md and agent-stream-performance.md. model.test, layout.test and footer-spacing.test still asserted the inverted native order and failed; they now assert forward order, and footer-spacing uses the real strategies instead of an inverted stub. * fix(app): show a reply's footer when the next prompt arrives on native LegendList re-renders a mounted row only when its item or extraData changes. When a new prompt arrives, the previous reply's footer moves from the live auxiliary slot into that reply's layout item (completedFooter). The reply's StreamItem keeps its identity, and renderItem is a stable event, so the row never rendered its new layout and the footer (copy, timing, fork) disappeared until the row remounted. FlatList had the same gap. The history renderer is now LegendList's extraData. Its identity changes with the history layout map, and StreamRow's memo limits the pass to rows whose layout item changed. * test(app): run the web disclosure hold cases in a real browser The three hold cases simulated layout in jsdom: hand-written row heights, scroll clamping and resize callbacks. They could pass while Chromium paints the trigger somewhere else. They now run in Vitest browser mode with real layout, ResizeObserver and scroll clamping. The two hold cases fail when the hold is bypassed. The browser project pre-bundles @tanstack/react-virtual, so its first import no longer reloads the test mid-run. The lucide stub gains ArrowDown, which pane-find imports. * fix(app): let a drag end a disclosure hold on native A drag detached the bottom controller but kept the disclosure hold. Every item size change restarts the hold's quiet frames, including a streaming row's, so the hold can outlive the toggle. While it lasts, shouldRestorePosition accepts only the held row. Once the drag scrolls that row away, a history page arriving mid-gesture gets no anchor, and the rows the reader was on move by the page's height. The drag now cancels the hold without releasing it. The controller keeps no hold state of its own beyond detached, and the gesture's end decides whether following resumes. * perf(app): keep the scroll-to-bottom button's state out of the timeline Opening a tool call or turn at the end of the chat now holds its trigger in place, so the timeline detaches and the scroll-to-bottom button appears. That visibility was useState in AgentStreamView, so showing the button re-rendered the whole timeline view for a change only the button reads. The viewport now writes near-bottom into a per-agent store that only the button subscribes to. * fix(app): show the scroll-to-bottom button on native whenever following has stopped A gesture now resumes following only when it ends within 1 px of the end, but the button still hid within 32 px. A release 2-32 px short of the end left the list detached with no button to return. While detached the button now hides only at the end; while following it keeps the 32 px tolerance so content growth that the offset trails for a frame does not flash it. The button also updates when following starts or stops without a scroll. * fix(app): stop the timeline jumping when the keyboard opens or closes on native The keyboard's settled height was committed to the list after the motion, as paddingTop on Android and contentInset.top on iOS, to keep the top of the history reachable under the translated dock. While following, the end pin absorbed it in the same mount. While detached or holding a disclosure, nothing did: Android's native anchor is LegendList's ScrollAdjust view, which padding does not move, and LegendList compensates a padding change only on iOS. Every row jumped by the keyboard height 0.2-1 s after the keyboard settled, and back after it closed. On send, the same commit raced the jump to the end. The keyboard now only translates the list with the composer. The cost: while the keyboard is open, the composer viewport clips the top keyboard-height of the list, so the start of a short conversation is hidden until it closes. * chore(app): describe the native timeline as LegendList in stream comments * fix(app): keep a disclosure's trigger in place when its row is cut off at the top on native LegendList's idsInView starts at the first fully visible row. When a turn host's prompt bubble sat above the viewport, a disclosure hold found no anchor in the data pass (shouldRestorePosition rejects every other row), and the size pass anchored on the row below the host instead, so the trigger moved by the host's size change. The patch appends the partly visible rows to idsInView after the fully visible ones, so the held row anchors while every pass that does not restrict the anchor keeps the first fully visible row. The browser test renders the patched native build, which vitest now aliases instead of the package's unpatched web build. * fix(app): let a turn's summary line collapse again after it opens The React Compiler cached StreamRow's `renderStreamItem(layoutItem)` on the layout item and the renderer. The revised item that useRevisedHistoryRows hands a row whose display state changed only fed the row id, so the row reused its cached content. Opening a turn usually inserts its rows directly below the host, which also gives the host a new layout item. When another row sits between them, such as a compaction marker, the host kept its layout item, its summary line stayed collapsed, and every later tap asked to open the turn again. The renderer now takes the item it renders, so the compiler's cache keys on the revision. StreamRow moves to its own module, which the tests compile the way Expo does. * test(app): alias the patched LegendList build in browser tests only Unit tests load @legendapp/list outside Vite, so the native build's react-native import skipped the react-native-web alias and reached React Native's Flow source, failing every unit suite that imports the native strategy. * fix(app): keep a size-only disclosure's trigger in place when its row is cut off at the top on native LegendList's prepareMVCP picks the anchor for a size change with no data change as the first row in view, without asking shouldRestorePosition. A disclosure that only grows its own row (a todo card) while that row's top was above the viewport therefore held the row below it, and the trigger moved by the row's size change. The patch now applies shouldRestorePosition to size changes as it already does to data changes. * test(app): render the timeline row revision test in a real browser The repo's unit tests do not mount components in JSDOM. The test still compiles stream-row.tsx with the React Compiler, so it fails when a revised row ignores the item it was handed. |
||
|
|
4991efddf8 |
feat(app): follow iPhone Duo vertical bars and fold divisions (#8741)
* feat(app): follow system vertical bars and fold divisions Keep custom navigation controls outside system content areas and use active division regions for temporary panel allocation. Normal sidebar preferences resume when the division disappears. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): show window actions in the active portal scope The bottom-sheet provider creates its own portal context. Register the window action host within that context so symbols reach the root host. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): hide workspace bar actions behind compact panels Compact panels keep the workspace route focused while covering its content. Hide its portal actions until the workspace panel is active again. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): continue the sidebar surface across divisions Keep the fold free of controls while painting it as part of the sidebar, with the shared divider at the workspace edge. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): omit tab count beside the full tab rail The full tab rail already exposes navigation. Use the same compact/full header selection for the vertical action list. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): preserve folded navigation and bar action lifetime Use resolved sidebar visibility for desktop activity, and keep bar controls mounted while compact panels cover them so pending menu work survives. Carry circular button chrome inside the portal and exercise the rendered boundary in Chromium. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): own script actions across header placement changes Keep one script action instance on the workspace screen so pending restarts survive removal of either menu. Pass activity through the bar portal so covered menus close their overlays without taking workflow state with them. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): exercise script ownership through the workspace Replace synthetic ownership fixtures with a real workspace, script menu and isolated terminal worker. Hold terminal exit until the compact header is removed so moving pending state back into the menu fails the regression. Exercise portal activity with production menu and header controls. 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.
|
||
|
|
6221b0c8b7 |
Fork from earlier replies when a plugin card was updated later (#8746)
The fork checkpoint check refused a fork when any projected row spanned the selected reply. A plugin card re-appended with the same id keeps its first position and stretches to its latest update, so it spanned every reply in between, although the fork never copies plugin cards. The check now looks only at the row kinds the fork copies, taken from the same constant as the copy. Closes #8745 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bfbd4d5175 | fix: update lockfile signatures and Nix hash [skip ci] | ||
|
|
52b84b7835 |
fix(ci): avoid incomplete pkijs signature attestations (#8749)
Pin pkijs to 3.4.2: SEA serves only provenance for 3.4.3. Hoist electron-builder so root overrides apply to its dependency tree. |
||
|
|
d68271565a |
Read agent history after its working directory is removed (#8737)
* fix(server): read agent history after working directory removal * fix(server): preserve interactive residency when reading agent history * fix(server): serialize history session upgrades * fix(server): upgrade history sessions before changing prompt mode * fix(server): keep archived history out of interactive residency * fix(server): admit configuration changes through interactive residency * fix(server): establish interactive residency before rewinding * fix(server): preserve immediate rewind locking for interactive sessions |
||
|
|
ce7b14a143 |
Keep context details open while chat streams (#8742)
* fix(app): keep hover cards open while unrelated content scrolls Hover cards closed on any scroll outside their content. A streaming chat scrolls on every new line, so the context window details closed while an agent was responding. Close only when the scrolled element contains the trigger, which is the case that moves the card's anchor. * test(app): confirm the turn is still streaming once the details open * test(app): cover context clicks and trigger scrolling Exercise the reported click path without replacing usage transport, and preserve dismissal when a sidebar scroll moves its trigger. Co-authored-by: 3ae3ae <ogyshjsm@naver.com> --------- Co-authored-by: 3ae3ae <ogyshjsm@naver.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |