mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-05 16:04:43 +00:00
78669b1f5b2fadbeb2fbe2a0114b07946a1cad1c
717
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
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> |
||
|
|
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. |
||
|
|
a880c9619d |
feat: background agents for hidden helpers (#8683)
* feat(server): add background agents An agent can be created background in any workspace. It stays a durable, addressable agent, but default discovery, workspace status and attention leave it out. Effective background is agent.background || workspace.background. - Protocol: `create_agent_request.background` now marks the agent itself and stays the workspace intent for a workspace the request creates. Snapshots carry `background`. The daemon advertises `features.backgroundAgents`. Clients advertise the `background_agents` capability. - Daemon: the flag persists in the agent record, so restart and resume keep it. AgentDirectory owns visibility. Inclusive reads and subscriptions from sources without `background_agents` see only agents in background workspaces, because older apps would otherwise open a tab for each one. - Inheritance: omitted on agent-scoped creation, children and new workspaces take the caller agent's background. - MCP: create_agent accepts `background`. - SDK: placing a background agent in an existing workspace, or under a caller, fails fast with "Update the host to use background agents." on older hosts. - CLI: `ls -a` and `ls --background` add a BACKGROUND column. * feat(app): hide background agents from tabs, track and status * docs(plugins): point hidden helpers at background agents * fix: keep background agents hidden across cache reload and connected hosts * fix: keep remote creation working on older hosts and inherit foreign parents' background |
||
|
|
6740d41fab |
Open plugin settings from a cog, with a tab per settings screen (#8675)
* feat(app): open plugin settings from a cog with a tab per screen The plugin row shows a settings button next to its switch when the plugin registers settings screens. The actions menu keeps Logs, Reload and Remove. The settings page shows one tab per registered screen; a plugin with one screen shows no tabs. Tabs swap screenId in place, so existing plugins/[pluginId]/[screenId] links and openSettings keep working. * fix(app): render settings tab icons inside the plugin's runtime Component icons now mount under the installation provider, so plugin SDK hooks such as useTranslation work. Each icon has its own error boundary; an icon that throws leaves its tab with the label alone. * feat(app): name only the plugin in its settings page header The header shows the plugin's manifest name, or its id when it has none, for single- and multi-screen plugins alike. The selected tab names the screen. The Plugins page and the header share one plugin list query. * feat(app): show plugin display names and a Settings tooltip on the cog Plugin rows on Settings → Plugins show the manifest name, falling back to the id; when a name is set, the id stays visible beneath it. The row's controls, logs sheet, confirmations and feedback use the same name. The cog is a ToolbarButton labeled Settings, with a matching tooltip. |
||
|
|
3084f32852 |
Show effort controls for Claude settings models (#8699)
* fix(claude): show thinking options for models from settings.json Models appended from Claude's settings.json were pushed to the catalog without thinkingOptions, so the composer had no effort selector for them. Resolve them like profile and additionalModels entries: manifest options for built-in IDs, the standard Claude effort levels otherwise. * docs(claude): clarify effort selection for settings models Describe explicit effort selection without promising how every client initializes an unsaved value. Co-authored-by: Okan YILDIRIM <63511519+ragokan@users.noreply.github.com> Co-authored-by: wangyahao <wangyahao@fxiaoke.com> --------- Co-authored-by: Okan YILDIRIM <63511519+ragokan@users.noreply.github.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> Co-authored-by: wangyahao <wangyahao@fxiaoke.com> |
||
|
|
40b95f70f4 |
Fix Android chat mouse-wheel direction (#8693)
* fix(android): correct inverted chat wheel direction * test(android): run wheel regressions with current React Native Provide the native-only test context with React Native’s event dispatcher for an absent JavaScript instance, retaining real scroll handling. Co-authored-by: Seth Jeon <paseo@sethjeon.com> --------- Co-authored-by: Seth Jeon <paseo@sethjeon.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
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> |
||
|
|
e546497a21 |
fix(workspaces): keep merged pull requests visible
* fix(workspaces): keep merged pull requests visible A remote update before merge can leave the checkout behind the PR head. Match terminal requests by branch for display and require an exact fresh head match before automatic archive. * fix(forges): retain pull requests targeting a fork A fork checkout can have its own pull request as well as one targeting its parent. Compare both candidates before choosing which request to display. * fix(forges): rank closed drafts as terminal GitHub retains the draft flag after closing a pull request. Selection must use the request state so a closed draft does not override an open or exact-head request. |
||
|
|
85be2121b8 |
Link #123 issue and pull request references in chat (#8636)
* feat(app): link #123 issue and pull request references in Markdown Agents cite issues and pull requests by number; reading one meant looking up the URL by hand. Markdown in a workspace now links forge shorthand against the workspace's origin remote: `#N`, GitLab and Gitea `!N`, and cross-repository `owner/repo#N` on the same host. Each forge's URL grammar declares which sigil maps to which path, so the syntax stays per forge: GitHub has no `!`, and its `#` targets /issues/, which GitHub redirects to the pull request. Recognition goes through the Markdown engine's text link regex so code spans, fences and authored links stay plain without a second Markdown parse while streaming. The reference alternative sits before path recognition so `group/project#12` is not read as a file path. * fix(app): keep http:// for self-hosted forge web links A self-hosted forge cloned over http:// serves its web UI over http on the same origin. Forcing https made reference, blob and tree links fail against an HTTP-only port. Cloud hosts and SSH remotes stay on https. |
||
|
|
fc710be1af |
fix(app): keep linked badges inline in rendered Markdown on web (#8530)
The library's web renderer marked an image inline only when it was a direct child of a paragraph with other content. A linked image sits under a Link node, so badges such as a review bot's `[](url) Confidence Score` header rendered as 100%-wide, 320px-tall block images in the PR panel and file preview. Images nested in links (or emphasis) now count as inline when their paragraph has other content; a paragraph made only of images or image-only links still renders them as blocks. Inline images now take the line height with automatic width instead of a line-height square, so wide badges and photos keep their aspect ratio instead of squashing. |
||
|
|
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. |
||
|
|
6f6712be62 |
Make interrupted-agent recovery opt-in (#8619)
* fix(agents): require opt-in for restart recovery Keep automatic interrupted-turn resumption off during further dogfooding. Preserve explicit opt-in and custom resume prompts. * test(settings): verify restart recovery opt-in persists * docs(agents): describe restart recovery opt-in |
||
|
|
d4ac64f335 |
Let plugins stream live events and binary data to the app (#8527)
* feat(plugins): stream events and bytes between plugins and the app
Plugins can declare a subscription with defineSubscription, handle it with
server.handle(contract, (input, { push, signal })), and listen from a surface
with useSubscription or client.subscribe. A top-level Uint8Array in an event,
an RPC output, or an RPC input crosses the wire as a binary frame (opcodes
0x20-0x2f) without JSON or base64; a nested one fails validation on the sender.
Each subscriber runs its own handler, owned by the session that subscribed,
so release, disconnect, plugin stop and reload need no plugin code. The
runtime buffers pushes until the subscribe response has gone out, so a client
never sees an event for a subscription it does not know yet.
Gated on server_info.features.pluginSubscriptions; binary outputs reach only
clients that advertise plugin_binary_payloads.
* fix(plugins): close subscription gaps found in review
- Losing daemon.manage now releases the session's plugin subscriptions;
binary events bypass the outbound permission check, so streams would
otherwise outlive the revocation.
- A subscribe that times out sends unsubscribe, since the plugin may
still start the handler after the daemon gave up.
- The byte limit leaves room for the largest frame header and routing,
and lives in assertPluginWireValue so events, outputs and inputs share it.
- A release right after the host accepted keeps `ready` resolved, so a
handler that returns at once reports closed rather than an error.
- Session trace logging tolerates loggers without isLevelEnabled.
* fix(plugins): withhold binary plugin results after plugin access is revoked
A binary RPC output or event skipped the outbound permission check its
JSON form passes, so an RPC in flight when daemon.manage was revoked still
delivered its bytes. Each frame now checks the permission of the JSON
message it stands in for.
* test(plugins): cover subscriptions in a real browser instead of JSDOM
The app subscription tests mounted hooks in JSDOM, which docs/testing.md
rules out. The adapter and contract behavior is now a plain unit test,
and a Playwright spec drives the diff-watch and media-stream examples:
JSON events following file changes, streamed bytes, and an upload.
* test(plugins): add the plain subscription unit test and the browser spec
* fix(plugins): apply subscription schema transforms once
Both ends parsed each subscription value, and the sender sent its parse
output, so the receiver parsed a schema's output as fresh input. An event
schema like z.string().transform((s) => s.length) closed the stream as
invalid. The sender now validates and sends the value as given; the
receiver's parse produces what the handler or callback sees.
* fix(plugins): parse a subscription's input once in the plugin process
server.handle wrapped subscription handlers in a second input parse, so a
transforming input schema rejected its own parsed output before the
handler ran.
|
||
|
|
7c2812dde6 |
fix(app): add hosts without leaving the current screen (#8512)
* fix(app): add hosts without leaving the current screen A retained Settings route replayed its addHost query after appearance remounts. Own the connection flow outside screen lifetimes and let each consumer choose its completion navigation. * fix(app): release the project dialog before adding a host * fix(app): size the QR scanner viewport on wide layouts * test(app): align creation and desktop setup with in-place host flow |
||
|
|
9b0542d127 |
fix(server): retry a connected host that failed the capability check (#8423)
* fix(server): retry a connected host that failed the capability check Every failure in a connected host's acceptance step was terminal: the row went to "error" with no retry, and the status listener ignored later reconnects. A host rejected with unsupported_capability stayed unusable after it was updated, until the registration was saved again or the managing daemon restarted. Capability and other non-identity acceptance failures now go "offline" with nextRetryAt and retry with capped backoff (1.5 s doubling to 30 s), so an updated host comes online by itself. Self, duplicate, changed-identity and authentication failures stay terminal. Fixes #7971 * fix(server): keep a retried host's relay security floor and update guidance A scheduled capability retry built a new transport factory, which dropped the relay security floor learned from a protected handshake, so the retry could negotiate legacy encryption and send credentials. Retries now reuse the registration's transport factory (createHostClient accepts one). The update guidance also stays visible while a retry connects, the backoff resets after a successful acceptance, and disabling or removing a host cancels its pending retry. |
||
|
|
da75aa7a4a |
Fix terminal emoji size and alignment (#8375)
* fix(terminal): render emoji at their intended width The native grid scales glyphs to the columns assigned by the terminal buffer. Use grapheme widths across current hosts and clients so emoji fit their slots and replay preserves cursor positions. * fix(terminal): make width regression test portable * test(terminal): wait for replay fixture output to finish A trailing newline could arrive after the readiness marker and change the retained history between replay captures. |
||
|
|
56c051503b |
fix(server): keep promptless Codex agents usable after reopening (#8393)
* fix(server): report a Codex thread only once Codex can resume it Codex writes a new thread's rollout at its first turn, but the adapter starts a thread when the agent is created and reported its ID at once. AgentManager stored that ID as the persistence handle, so a Codex agent that was reopened before its first prompt (idle runtime release, daemon restart, plugin tool reload) failed every prompt with "no rollout found". The adapter now reports a thread's ID, as runtime info and as a handle, only once it is resumable: after Codex announces the thread's first turn, or when the thread was resumed or forked. Until then a reopened agent starts a new session, as an unprompted Claude agent does. Document that a persistence handle must be resumable. * test(server): reopen promptless Codex agents without model requests Exercise the real app-server before any first turn, so publishing an unsaved persistence handle fails at the actual resume operation. Co-authored-by: Jeong-Uk,Kim <dnrtoki@gmail.com> --------- Co-authored-by: Jeong-Uk,Kim <dnrtoki@gmail.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
74613e4bc3 |
feat(opencode): reuse running OpenCode v2 services (#7435)
* feat(opencode): connect v2 client to an existing server * feat(providers): reuse running agent servers Keep shared server sessions isolated and retain native tools while refreshes reuse the running service. * fix(providers): recognize formatted bridge manifests Managed plugin ownership must survive harmless JSON formatting changes. * test(providers): distinguish isolated session bindings Use separate session identities to verify that dedicated helpers keep their environments isolated. --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
fd65985e9d |
fix(android): resize header fades after rotation (#8349)
Clear Android SVG descendant geometry caches when the canvas resizes. Cover growth and shrinkage with a native pixel-rendering regression. |
||
|
|
9b3f1bfffd |
Start custom Codex providers with reserved names (#6954)
* fix(codex): start custom providers with reserved names Codex rejects custom provider keys such as ollama before a turn starts. Namespace the generated keys without changing user-facing provider ids. Keep older sessions discoverable and select the new key explicitly when resuming them, so the startup fix does not strand existing conversations. Closes #6874 Co-authored-by: Egg3901 <rainfordmason@gmail.com> * fix(codex): keep native sessions out of custom provider imports * fix(providers): align custom provider example with generated config Correct the documented internal provider key and simplify import filtering while preserving native and legacy session selection. * fix(codex): exclude native GCP sessions from custom imports * test(codex): seed sessions through the portable launcher --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> Co-authored-by: Egg3901 <rainfordmason@gmail.com> |
||
|
|
9c926ed126 |
fix(terminal): preserve 10,000 rows with direct ANSI replay (#8282)
Restore configured terminal history on reconnect, retaining 10,000 rows plus the screen. Serialize the worker-owned xterm buffer directly to ANSI and forward negotiated bounded replay chunks while preserving ANSI-era clients and per-viewer live ordering. Preserve captured geometry through replay, bound pressure recovery to 500 rows, and keep the omitted visible-history default at 200. Remove the full cell-grid copy and obsolete JSON rendering. All 21 CI checks pass. Real browser, native Debug emulator/simulator and Linux Electron coverage and platform limits are documented in the PR. The Android Debug invalid native delegate dispatch also reproduces on upstream main; its cause and any change in frequency remain unproven. Credit Manav Dhindsa (DrakenKor), #6139 and #6148, and Lennart Schoch, #6743, for prior ANSI replay and retention work informing this change. Closes #6137. Closes #6147. |
||
|
|
0a549945b5 |
fix(terminal): keep native text aligned with terminal cells (#8336)
Shape rows in native Canvas and CoreText, then place clusters on xterm columns so fallback font advances cannot separate text from its cursor. Wait for measured dimensions before attachment and repaint overhanging ink with affected rows. Use Android platform ICU to avoid bundling approximately 14 MB of data; retain and document the Android 10 conjunct limitation. |
||
|
|
0be49c0620 |
feat(transport): keep large messages intact across relay connections (#8302)
Negotiate shared binary chunk framing below application messages and routing, with bounded logical assembly and paced writes that respect encrypted relay limits. Preserve legacy admission, reconnect state, sibling route isolation, and native single-frame upload behavior. |
||
|
|
a4f696620b |
Keep big agents loadable by bounding every timeline item (#8274)
* fix(server): bound every timeline item so one fetch cannot close the socket A timeline page is one WebSocket message. Only three tool-call fields were capped, so MCP results, file contents, errors and merged assistant rows could make a 20-entry or even a 1-entry fetch pass the outbound high-water mark. limitAgentTimelineItemContent is now one budgeted walk over any item: 64 KiB for tool calls, 256 KiB for other items. Over budget, the largest strings are cut to a shared length with a visible marker and oversized data URLs become a marker. Fetches also apply it to merged assistant and reasoning rows. * fix(server): keep short strings whole and typed arrays valid when limiting items Strings up to 1 KiB are never cut, so a tool call keeps its callId and an item limits to the same result twice. Arrays drop trailing elements without inserting a marker element, which typed arrays such as todo items would reject. |
||
|
|
787edb1bf9 |
Respect proxy variables throughout the daemon (#8177)
* fix(server): respect environment proxies across daemon traffic Install proxy routing at daemon and plugin process startup, with a separate HTTP agent for outbound WebSockets. Always exempt loopback so local provider bridges remain reachable. * test(server): run usage source regressions in CI * fix(server): validate proxy settings without blocking startup Normalize scheme-less proxies and share HTTP-only validation across fetch and WebSockets. Record ignored variables without credentials in the daemon log. Separate built-in and directory plugin regressions through a typed fixture. * refactor(server): treat ignored proxy variables as unset * fix(server): read Windows proxy environment entries once * fix(server): apply proxy policy to connected host links * test(server): isolate proxy fixture credential directories * fix(server): keep proxy routing and authentication consistent Normalize Windows environment names and wildcard bypass rules at the network boundary. Compare bypass ports numerically and keep Basic proxy credentials on the CONNECT pool, including empty credential fields. |
||
|
|
9477fe0c00 |
fix(app): refresh Android layout after background fold changes (#8285)
Backport the Unistyles 3.5.3 Activity configuration fix while keeping 3.2.4 and its existing fixes. Application resources could leave the responsive shell on the previous fold size after resume. Cover the real device-state sequence with a native UI regression. |
||
|
|
e3682ba18c |
Usage fetches only when shown, and can be turned off (#8181)
* fix(usage): fetch only when shown and add a host-wide off switch * fix(usage): restore policy defaults and isolate usage test state * refactor(usage): give the sidebar footer sole ownership of its summary |
||
|
|
946b6e58fd |
Keep transcript colors in sync after a hidden theme change (#8199)
* fix(app): preserve adaptive theme events while hidden Read the current scheme through fresh matchMedia lists so hidden runtime reads do not consume the pending events on listener-owned lists. Carry Dan Sabin's fix from #7172 into the current combined Unistyles patch, and cover both transition directions with a real Electron regression in the existing desktop browser suite. Co-authored-by: Dan Sabin <dan@freethinking.it> * test(desktop): run adaptive theme regression on Linux Electron retains --no-sandbox in argv, so the positional lookup used the script filename as the artifact directory. Read the final argument so the same real Electron regression exercises Linux as well as macOS. Co-authored-by: Nurşat Bülbül <85260051+Nurshot@users.noreply.github.com> Co-authored-by: Dan Sabin <dan@freethinking.it> --------- Co-authored-by: Nurshot <85260051+Nurshot@users.noreply.github.com> Co-authored-by: Dan Sabin <dan@freethinking.it> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
75f58c4fe7 |
fix(release): upload only en-US screenshots to Google Play (#8195)
supply uploads every locale folder under fastlane/metadata/android in one
edit. Play rejects the F-Droid-only ja locale, which failed the whole edit in
the 0.14.0 release ("ja - Invalid request"), so no screenshots were replaced.
Stage only the en-US images, add a standalone workflow to rerun the upload for
the current release, and document the App Store associatedErrors gotcha.
|
||
|
|
7d74ae5d6c |
docs(release): release from tags with selected cherry-picks while main stays open (#8188)
Replace the next-branch discipline with the main-first workflow: normal PRs always merge into main, each release builds in a short-lived release workspace from origin/main or a release tag plus approved cherry-picks, promotions take an inventory that defaults to defer, and a reconcile PR brings the shipped changelog, version and F-Droid files back into main. |
||
|
|
61841d1ed5 |
fix(app): stop web menus snapping back to their entering-animation rect (#8180)
Reanimated's web path saves a custom keyframe's element rect when the animation ends, and writes it back as inline top/left/width/height when it cleans the keyframe up 750ms after the start. A menu whose content resized in between, such as a plugin popover whose data had just arrived, was left clipped to its loading-state size or hanging over its anchor. Play the same keyframes through the Web Animations API on web, which leaves no inline style, and drop the height release that only undid part of that write. Native keeps the Reanimated Keyframe. |
||
|
|
7c88a48be4 |
Publish store screenshots from the repo on each stable release (#8186)
* feat(release): publish store screenshots from the repo Stable releases now upload the committed store renders: the App Store review lane attaches the iPhone and iPad sets to the version it submits, and a new Play lane replaces the phone and tablet screenshots after the Android submit. F-Droid reads the same Android files, which now link to the store renders instead of the website images. The Play lane needs its own copy of the submit service-account key (GOOGLE_PLAY_JSON_KEY in the EAS production environment) because the key Expo stores for eas submit is not available to custom jobs, and EAS Metadata does not handle screenshots for either store. * fix(release): rewrite the Android screenshot links on every render Google Play and F-Droid read numbered links to the Google renders. The renderer now rewrites them from the panels it exported, so adding or renaming a panel reaches all three stores. |
||
|
|
83ed52669d |
Resume interrupted agents after host restart (#8127)
* feat(server): resume interrupted agents after host restart Continue saved running conversations after graceful restart or crash, with host opt-out and a configurable continuation prompt. Preserve explicit launch overrides when reopening the same provider session. Co-authored-by: coolwind <45729206+coolwindX@users.noreply.github.com> Co-authored-by: suntianc <suntc8985@gmail.com> * fix(app): translate restart recovery host settings * test(app): synchronize sidebar drags and clean up added projects Wait for the drag sensor click blocker to expire before the next user action. Resolve UI project keys to daemon IDs for cleanup so empty projects do not contaminate later sidebar tests. * fix(app): tuck the resume prompt behind an edit sheet Match the System prompt row: an Edit button left of the restart recovery switch opens the prompt in a sheet, and is disabled while resume is off. * fix(server): resume interrupted agents without holding startup Startup now only chooses resume candidates before anything else reads or repairs persisted running state. Reopening and prompting run in the background after connections open, so a provider that is slow to reopen holds back only its own agent. Finish observation waits for a pending child's resume, so parents are not told the child was closed meanwhile. * style(server): format restart resume e2e test * test(server): wait for the fetch result before advancing the narrow refresh debounce The test released the held fetch, flushed six microtasks, then advanced the 1s refresh debounce. When the fetch result landed after that advance (slow Windows runners), the debounce started late and vi.waitFor's auto-advance reached it only at its real-time timeout. Wait on the service's in-flight fetch count instead, as the sibling test does. * test(server): retry stalled OpenCode fixture downloads within the test budget npm abandons a stalled registry download only after its 300s default fetch timeout, past the install's 180s budget. The killed install left the fixture directory locked on Windows, so cleanup failed with EBUSY and hid the timeout. Against a registry proxy that stalls the first tarball, a default install hung 225s; with --fetch-timeout it retried and finished in 16s. --------- Co-authored-by: suntianc <suntc8985@gmail.com> |
||
|
|
5cfe8f1218 |
Remove the out-of-date daemon sidebar notice (#8133)
The notice arrived with the terminal worker change (#7644) but is a separate product change. Remove it with the native/compact callout filtering it needed, restoring the sidebar callout slot to web-only. Restart and close terminals in Settings stays. |
||
|
|
33faacc055 |
fix(server): release retained plugin streaming snapshots (#7675)
Release cumulative plugin snapshots after adapter attachment and retain canonical replay items. Preserve Unicode text, root/child ordering and current tool state; release history on close. Co-authored-by: suntianc <suntc8985@gmail.com> |
||
|
|
9fb7808191 |
feat(worktrees): let plugins choose creation options and placement (#8117)
* feat(worktrees): let plugins choose creation options and placement Await the worktree creation request hook before Git source planning so plugins can change placement, branch and base without undoing a fetch. Manage arbitrary destinations through marker and registry ownership, and restore them at their recorded paths. Co-authored-by: Matt Cowger <matt@cowger.us> * fix(worktrees): compare repository paths across Windows aliases Deletion must recognize the same repository through Git native and long path spellings. Reuse ownership path equivalence instead of comparing canonical strings, and let the recovery fixture accept Git platform line endings. Co-authored-by: Matt Cowger <matt@cowger.us> * fix(worktrees): respect recorded ownership and creation intent Keep branch checkout distinct from branch-off, validate plugin placement once at the daemon boundary, and share default path collision resolution. A restored external worktree's marker must never override its recorded non-ownership. Co-authored-by: Matt Cowger <matt@cowger.us> * test(worktrees): record ownership in session archive fixture The fixture creates a Paseo-owned worktree, so its durable record must say so. Archive now treats a recorded false value as authoritative instead of replacing it with filesystem discovery. Co-authored-by: Matt Cowger <matt@cowger.us> * test(plugins): retry temp cleanup after worktree archive on Windows Windows releases directory handles after the daemon closes, so rmdir on the source repository can hit EBUSY. Retry like the other git temp-dir tests. * test(plugins): compare plugin-chosen worktree paths in canonical spelling Windows temp directories come back as 8.3 aliases while the daemon records the resolved spelling, so normalize the expected root the same way. --------- Co-authored-by: Matt Cowger <matt@cowger.us> |
||
|
|
41582eb5fd |
Terminals survive daemon restarts, crashes and updates (#7644)
* M1: terminals survive daemon restarts: stop reasons, detached terminal worker, kill cleanup
restart_server_request.killTerminals / shutdown_server_request.keepTerminals and server_info.features.terminalsSurviveRestart; CLI daemon restart --kill-terminals and daemon stop --keep-terminals (capability-gated). The terminal worker runs detached over an owner-only, secret-authenticated socket and is adopted on daemon start. One kill cleanup contract (the reaper) by birth time, used by the daemon, the supervisor and stopDaemonInstance. Settings offers Restart and close terminals.
* M1: fix review findings: reaper session/table retry, short-command creation order, remote kill-restart cleanup report, stop test port
* M1: fix review findings: reaper-owned cleanup record, session member identity by terminal id, unreadable cleanup records, Settings restart error rendered in the card
* M2: worker protocol versions and draining, worker runs from its own copy, desktop upgrades keep terminals, out-of-date daemon notice, docs/terminal-worker.md
- Daemon classifies live workers by protocol: adopts current, drains one version old, shuts
down older, leaves a newer one running and answers list/create with
terminal_worker_incompatible (daemon status prints it). Never more than two workers.
- esbuild worker bundle (build:lib); a built daemon copies bundle, shell integration and
node-pty into $PASEO_HOME/terminal-worker/<version>-<hash>/ and launches from it; copies no
record names are pruned.
- Desktop app_update/version_mismatch stop with keep terminals on macOS/Linux; Windows and a
daemon without the capability close them.
- Out-of-date daemon sidebar notice with Update button, worded by terminalsSurviveRestart.
- PASEO-13: unreadable worker records fail status and kill-restart retry.
- PASEO-14: a session member without a terminal id is unknown, not unrelated.
- Smoke test 46 with A/B installation fixtures and per-fixture CLI shims (PASEO-9 fold).
* M2: track in-flight worker requests without leaving an unhandled rejection
detach({ afterPendingRequests }) tracked each request with request.finally(), which rejects
with the request; a fire-and-forget send that timed out surfaced as an unhandled rejection
(worker-terminal-manager.test.ts). Track settlement only.
* M2: CI round 1 fixes: worker terminal manager in the MCP key test, status cleanup field, shared socket dir
- mcp-server.test.ts (new upstream test) built the worker manager with the old fork API;
it now starts a real detached worker in a throwaway home.
- daemon-session.test.ts expected status without terminalCleanupUnconfirmed (added in M1).
- paseo-daemon.test.ts: a home under the scratch temp dir is too long for a unix socket, so
the worker listens in the per-user paseo-<uid> directory, which outlives workers by design;
the test now requires it to be empty instead of absent.
* M2: run Windows PowerShell by its install path for process identity
server-tests (windows-latest): bootstrap-provider-availability.test.ts starts a daemon with a
PATH that holds only git, and the terminal worker failed to start: 'cannot read its own process
identity: spawn powershell.exe ENOENT'. Process inspection (managed-processes.ts inspect and the
terminal worker's process table) now runs %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe.
* M2: lifecycle e2e plain stop uses the shutdown RPC on Windows
CI round 2, server-integration (windows-latest): 'stop and stop --force never signal processes
a stale or earlier-boot record names' called stopDaemonInstance without a shutdown channel,
which Windows refuses ('has no graceful shutdown channel'). Plain stops in the file now pass
one, as the CLI does.
* M2: native notice outlet, Windows desktop-update wording, worker cap counts unconfirmed cleanups, create recovers after a drain
* M2: phone sidebar mounts the callout slot (iOS notice verified), worker record reads retry transient errors
* M2: native notice preconditions use explicit working directories, one block per terminal
* M1: compact sidebar shows only native callouts on every platform; PTY round trips in the MCP key test get a 5 s window
* ci: pin Playwright browser jobs to ubuntu-24.04
The pinned Playwright (1.58) has no ubuntu-26.04 build, and forks whose
ubuntu-latest already resolves to 26.04 fail at browser install. Job names
are unchanged, so required-check contexts still match.
* ci: use ubuntu-latest for Playwright jobs, same as upstream
Reverts the ubuntu-24.04 pin; upstream main's Playwright shards pass on ubuntu-latest.
* fix(server): keep terminal worker record IO off the daemon event loop
* fix(server): restore terminal creation after worker failure
A disconnected terminal worker remained the current manager indefinitely, leaving dead sessions listed and every later create bound to a closed socket. Confirm process death before retiring the member and use the existing single launch path for replacement. Reconnect live workers and replay their screens while preserving terminal identity.
* test(server): verify orphan cleanup during worker recovery
Recovery reaps orphaned programs before a later daemon stop. The previous fixed-delay assertion raced that cleanup and failed when it completed promptly. Verify orphan removal, same-daemon replacement creation and cleanup on normal or forced stop instead.
* test(server): isolate Windows dependency fixture setup
Build the local package registry in setup so its npm packing processes do not consume the installation journey deadline. Install the platform package used by Windows runtime fixtures directly, avoiding an unused wrapper postinstall probe without a timeout.
* test(server): wait for pending watchers before disposal
Observation admission starts before asynchronous path setup reaches watcher subscription. Disposing at the admission counter races setup and never exercises late watcher cleanup. Wait for the subscriptions to be pending before disposing.
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
|
||
|
|
dee0d138e9 |
feat(worktrees): copy included local files before setup and agents (#8100)
* feat(worktrees): copy included local files before setup and agents Keep local-file materialization in one built-in lifecycle hook so workspace setup and the first agent see the completed copy. Reuse the community plugin algorithm with the source checkout supplied by the worktree hook. Co-authored-by: Carlos Precioso <github@precioso.design> * test(plugins): make worktree lifecycle checks portable on Windows Compare source checkout filesystem identity across Git and Windows path spellings. Exercise copy rollback with a destination file blocking a directory, since Git for Windows accepts directories as exclude files. --------- Co-authored-by: Carlos Precioso <github@precioso.design> |
||
|
|
986687b8fd |
feat(plugins): unify lifecycle hooks and await worktree initialization (#8081)
* refactor(plugins): make lifecycle hook timing explicit Use one registration method and subject.operation.step names so plugin authors can read whether an operation waits. Preserve old registrations through a single SDK adapter and keep timing and payloads unchanged. * fix(plugins): align hook notes and example fixtures with release contracts Keep the changelog inside its versioned beta entry for app parsing. Run current plugin examples against the checkout version so their declared requirements remain valid. * docs(plugins): align translated hook dependency guidance * fix(plugins): align hook exports and release documentation * feat(plugins): use lifecycle positions and await worktree initialization * test(plugins): cover first-agent worktree initialization * test(plugins): verify rollback against workspace catalog entries |
||
|
|
8e7e8cce21 |
fix(app): retain plugin state across host disconnects (#8105)
Keep client plugin installations and local UI state alive through transient host disconnects. Reconcile changed bundles and removals on reconnect and refresh settings changed while offline. Connection switching still replaces the client and reinstalls plugins. Preserves the implementation from #6731, adds a failing-before/passing-after real browser regression, and corrects the English and Chinese lifecycle documentation. Verified with focused tests, browser reconnect journeys, Android debug offline interaction, root checks and green current-head CI. Co-authored-by: luke <luopeihuan@gmail.com> Co-authored-by: RoundRedDot <roundreddot@gmail.com> |
||
|
|
36fc4f0b43 |
chore(ci): remove Android module tests from ordinary CI (#8102)
Native unit tests require an Android prebuild and Maven downloads that block otherwise completed app checks. Keep the explicit local test command while removing its CI invocation and dedicated Java setup. |
||
|
|
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> |
||
|
|
78f580f9e8 |
perf(plugins): reuse verified bundles and fix repeated timeline preparation (#8040)
Reuse verified host-scoped plugin bundles across client launches and reconstruct complete catalogs when unchanged code is omitted. Keep store ownership on the connected client, including adopted bootstrap clients, and persist in the background with bounded writes. Remove catalog catch-up deferral and fix repeated timeline preparation that promoted cached completed history into a live head. This preserves completed plugin-rendered chat when catalog installation precedes timeline catch-up. Carries forward #6732 on an upstream branch, with the original hash/cache contribution and integration attribution preserved. Co-authored-by: luke <luopeihuan@gmail.com> Co-authored-by: RoundRedDot <roundreddot@gmail.com> |
||
|
|
50623965b4 |
Make line wrapping responsive in large diffs (#8006)
* perf(app): make diff line wrapping responsive on large changes Wrapping eagerly shaped every expanded line before the viewport could paint. Estimate off-screen heights and measure rows as they enter the window, preserving the scroll anchor as geometry becomes exact. Add shared layout and browser benchmarks to track toggle latency and shaping cost. * fix(app): preserve wrapped diff anchors across bounded relayouts Resizing can move the viewed row outside the old materialization window. Keep its estimated wrap geometry available to anchor resolution, and measure the anchored row in the same layout pass so inaccurate estimates cannot discard the exact fragment or pixel offset. |
||
|
|
ef58ace424 |
Fix history compatibility and turn presentation regressions (#8010)
* fix(client): preserve history loading on older hosts Whole-turn paging is optional, but requiring its capability blocked existing history reads before any request was sent. Negotiate the paging enhancement at the client boundary while preserving item-based requests for older daemons. * fix(app): preserve row ownership during late turn updates Late lifecycle events retagged the newest row even when they updated an earlier call, causing the next update to append a duplicate tool ID. Assign turn membership only to rows written by the event before finalizing unrelated reasoning. Co-authored-by: luke <luopeihuan@gmail.com> * fix(app): preserve reasoning continuity and turn expansion controls * fix(app): hide reasoning inside collapsed turns --------- Co-authored-by: luke <luopeihuan@gmail.com> |
||
|
|
8a542048b6 |
Let plugins contribute MCP servers on every agent session open (#7983)
* feat(plugins): contribute launch-only MCP servers at session open * docs(plugins): explain saved configuration and launch-only MCP servers |
||
|
|
26ee20b371 |
fix(ci): hoist expo-module-scripts so root overrides reach its Expo peers (#7998)
`npm audit signatures` in the quality job fails on some runners with EMISSINGSIGNATUREKEY for @react-native/babel-preset@0.88.0. Nothing in the repo uses React Native 0.88: the package came from a second Expo SDK that npm placed under packages/expo-two-way-audio/node_modules because expo-module-scripts was declared there. Root `overrides` are not applied to packages nested under a workspace, so jest-expo's `expo@*` peer resolved to the latest SDK and pulled 200+ duplicate packages into the lockfile. The failure itself is a CDN cache problem: one Cloudflare edge (ORD, which serves GitHub runners in Azure northcentralus) cached the attestations document for that version seconds after publish, before the publish attestation existed, and keeps serving the provenance-only document for a year. Every quality job that lands in that region fails; every other region passes. The audit code path checks the registry signature before attestations, so the key list is fine and reruns only succeed by landing elsewhere. Declaring expo-module-scripts in the root devDependencies puts it under the overrides, which removes the nested SDK and the poisoned URL from the lockfile. The module's tsconfig points its typeRoots at the hoisted copy; its scripts resolve `expo-module` from the root `.bin`. Closes #7730 |