mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-28 20:22:45 +00:00
fix/android-fold-orientation
131
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3a9304607c |
feat: sidebar header and footer plugin items, Usage in the sidebar footer (#5685)
* refactor(usage): remove agent-to-usage wiring
* feat(plugins): screens, sidebar header and footer items
Plugins register screens with addScreen and contribute component-based
sidebar items with addSidebarHeaderItem and addSidebarFooterItem. Items get
currentScreen, openScreen and openPopover, and render the SidebarRow and
SidebarButton kit. The footer shares the header's ordering model through the
new sidebarFooterItems preference, and Settings > Sidebar gains a Footer card.
addSurface, openSurface and addSidebarItem stay as aliases with their
settings keys and routes.
* refactor(plugins): share resolvePluginPlatform and keep header remembered-host key
Consolidate the five resolvePlatform copies into plugins/platform.ts.
Header sidebar items keep the <pluginId>/sidebar/<id> remembered-host key
so existing users keep their remembered host; footer items use
<pluginId>/sidebar-footer/<id>.
* feat(usage): global usage summary, pins and used/remaining display
- Device-local usage preferences (displayAs, pinned windows) in app settings.
- Sidebar footer usage summary: pinned windows in pin order, one per account;
opens the expanded view as a popover on desktop and a sheet on compact.
- Pin checkboxes on window rows and a used/remaining toggle shared by the
summary, popover, Usage screen and Settings usage section.
- Usage screen shows one host with a selector when several are connected.
- Extract HostSwitcher and the sidebar popover host for reuse.
* fix(usage): render each usage report as its own card
* feat(usage): row pinning, one title row for usage controls, even sidebar footer
- Window rows are the pin toggle (checkbox role, checked state); pinned rows
get a surface2 highlight, unpinned rows an interactionHighlight hover.
- Usage screen, Settings section, popover and sheet put the title left and
the host selector, used/remaining toggle and Refresh right (UsageControls).
PageLayout gains an actions slot; the menu sheet header a trailing slot.
- Sidebar footer rows share 28px height, spacing[1] gaps and one leading rail.
* fix(usage): keep row hover lighter than the pinned highlight
Hovering an unpinned window row shows the pinned surface2 at half opacity on
a separate layer, so it never reads stronger than a pinned row. Pinned rows
do not react to hover.
* fix(app): defer sidebar styles and preserve layout exports in timeline test
* fix(usage): open the Usage screen on desktop, drop the Usage footer icon, match git panel refresh
- Desktop presses on the usage summary open the Usage screen; only compact opens the sheet.
- The Usage footer icon is gone; with nothing pinned the summary renders a plain "Usage" row
in the footer row style. A stored `usage` footer preference is ignored.
- Refresh controls use the Changes panel's icon-only ToolbarButton with RotateCw.
* fix(sidebar): keep the footer icon row fixed; footer items are rows only
The footer's icon row (Hosts, Import session, Help and support, Settings) is back to main's
fixed implementation: not in Settings, not reorderable, and plugins can't add to it.
- Footer items are full-width rows between Add project and the icon row: the usage summary
and plugin `addSidebarFooterItem` rows. The Footer card in Settings lists only those rows.
- Stored `sidebarFooterItems` keys for hosts, import, help and usage are ignored.
- `SidebarButton` is removed from the plugin UI kit; a footer item renders `SidebarRow`.
* refactor(usage): call the sidebar footer item Usage
One concept, one word: the footer item is the Usage item, with key `usage`.
- Settings > Sidebar > Footer lists it as "Usage"; its preference key is `usage`.
- `usage/summary-item.tsx` becomes `usage/sidebar-item.tsx` (`UsageSidebarItem`); both states
use testID `sidebar-usage`.
- `usage/summary.ts` becomes `usage/pinned.ts` (`resolvePinnedUsage`, `PinnedUsageWindow`):
it resolves the pinned windows the item shows.
* feat(plugins): screen params, several rows per sidebar item, SidebarSeparator
openScreen takes { screenId, params }; params live in the screen route's
query so reload, history and links keep them. currentScreen carries the
params. A sidebar item may render a fragment or array of rows; a row press
anchors the popover to that row. SidebarSeparator joins the plugin UI kit.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(plugins): any screen param key; trailing presses anchor the row's popover
Screen params go in the query under a param. prefix, so keys like serverId
no longer collide with the route segments Expo Router merges into search
params. A press anywhere inside a SidebarRow, trailing content included,
makes that row the popover anchor.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(plugins): plugins title their screens
addScreen takes { id, title, Component }. title is a string or a function
of the screen's params; the screen header shows it and follows param
changes. A title function that throws or returns empty falls back to the
screen id. Legacy addSidebarItem screens keep their item's title and icon,
and the addSurface alias titles its screen with the id.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(usage): require discovery and remove unreleased agent resolver
* fix(plugins): restore legacy sidebar rows, share the item runtime, header hint presses its row
- Legacy addSidebarItem rows are their own group kind again: main's row, registered icon in
Settings, /sidebar/<id> route, host remembered per row.
- New-API items follow the host last used on any of the plugin's screens.
- /sidebar/<id> falls back to a same-id screen so pre-migration links resolve.
- params moves to PluginScreenProps; settings screens no longer receive it.
- Sidebar items, buttons and popovers share one runtime per installation, so no empty first frame.
- A header row's hint slot presses the row; labels wrap as on main.
- Skill and docs corrected.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(usage): one host resolver, sheet reuses the screen's controls, name the host
- The Usage row and screen default to the same host.
- The compact sheet carries the screen's controls, host selector included.
- The host selector shows with one host, and unavailable messages name the host.
- The row, sheet title and pinned label use the sidebar.footer.usage translation.
- Window rows read their percent and reset after the pin label.
- Drop the unread per-report cache writes and headline fixture fields.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(plugins): popovers, header buttons and composer pills own their runtime again
Only sidebar item rows use the installation's shared runtime. Popover content
disposes its runtime on unmount, so observations close with the popover.
* fix(plugins): a sidebar item releases its subscriptions when it unmounts
Each mounted sidebar item gets its own API scope over the host's client, created
during render so its first frame has content. The scope closes a microtask after
unmount (StrictMode's remount keeps it), when the item moves to another host, or
when the installation stops. Replaces the installation-wide shared runtime.
* refactor(plugins): one Paseo client per plugin; authors own their subscriptions
usePaseo() in every plugin surface, and client.paseo in setup, is the
installation's client, created with the installation in the registry. Authors
release their subscriptions in their own cleanup; plugin teardown disposes the
client and ends the rest. Nothing is created or disposed per mounted surface:
the per-surface runtimes, their boundaries, and the per-run Command Center and
slash command APIs are gone.
* refactor(plugins): one Paseo client per plugin; authors own their subscriptions
usePaseo() in every plugin surface, and client.paseo in setup, is the
installation's client, created with the installation in the registry. Authors
release their subscriptions in their own cleanup; plugin teardown disposes the
client and ends the rest. Nothing is created or disposed per mounted surface:
the per-surface runtimes, their boundaries, and the per-run Command Center and
slash command APIs are gone.
* fix(usage): show usage from released hosts
Normalize released-host usage responses at the client boundary so existing usage and pins remain available across app and daemon versions. Include account labels in legacy responses for older apps.
* fix(usage): keep the usage capability check out of the public SDK
* test(server): retry transient locks during runtime test cleanup
* test(usage): model hosts without either usage capability
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
cc744cc669 |
feat(app): let users set the chat and Markdown content width (#5680)
* feat(app): let users set the chat and Markdown content width Wide and ultrawide screens were stuck with an 820px reading column. The width is now an Appearance setting that flows through the Unistyles theme, like the font sizes, so every content surface picks it up. The stored value stays null until the user picks a width, so a future change to the default reaches everyone who never customized it. Reset writes null back. The native composer dock keeps plain React Native styles and takes the width inline because those styles land on Reanimated views, where theme-driven Unistyles crash on theme changes. * fix(app): pass the content width into the web stream viewport The viewport read app settings itself, which required a QueryClient in every render of the stream strategy. The agent stream view already reads settings, so it hands the width over with the rest of the render input. |
||
|
|
b9ac1d4982 |
fix(app): add a Back to plugins link to plugin settings screens on desktop (#5620)
On the wide settings layout, a plugin settings screen showed only its title and the plugin's content, so getting back to the Plugins page meant finding Plugins in the sidebar again. The compact layout already had a Back header and project settings already had a desktop "Back to projects" link. Plugin settings screens now render a "Back to plugins" link on desktop the same way, including when the screen is offline or unavailable. Closes #5518 Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
68e722ec2e |
fix(app): request a chat outline only for a timeline the daemon has served (#5602)
A new agent's draft pane rendered the chat outline with its local tab or draft id, so the web app asked the daemon for that id's prompt index before the agent existed and the daemon logged Agent not found. The outline now waits until the viewed timeline has an epoch, which only a daemon-served agent timeline has. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
b85b44aea1 |
Show a host's new version after its daemon restarts (#5351)
* Show a host's new version after its daemon restarts The app wrote a host's server info into the session store only when a client was first mounted, and a reconnect reuses the same client, so after a daemon upgrade or restart the host page and version checks kept the old version until the app restarted. The host runtime now projects the client's current server info into the session store on every connection change, keeping the last known value while disconnected. The React mount effect that duplicated this write is removed. Closes #5236 Co-authored-by: Leonid <127580858+bashrusakh@users.noreply.github.com> * Keep a caller-owned test daemon home across startup retries A failed start removed the home even with cleanup disabled, so a retry on a taken port came back with a new serverId. The host restart spec now drives a restartable host daemon helper that owns its port, home, and teardown. * Remove only the test daemon dirs a failed start created A failed attempt now removes the temp home and static dirs it created and leaves caller-supplied ones, so cleanup: false no longer leaks per retry. * Use function declarations in the test daemon helpers * test(app): cover host versions across daemon disconnect and reconnect The host settings badge retains its last known version during a transport failure; the help menu should show the replacement version after reconnect without changing another host. --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> Co-authored-by: Leonid <127580858+bashrusakh@users.noreply.github.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
792715e762 |
feat(usage): usage sources as built-in plugins, following the session's account (#5465)
* feat(server): run internal plugins in-process through the plugin runtime
* fix(server): harden internal plugin startup and boundary checks
* feat(usage): add plugin usage sources and Codex discovery
* fix(usage): share SDK helpers and bound source cache
* feat(usage): migrate usage fetchers to plugin sources
Serve the legacy usage RPC from discovered source reports so older apps retain the same host usage list while new clients use the source registry. Keep credential reads and normalization inside each source.
* refactor(usage): shape sources as fetch functions
Keep the plugin usage API limited to stable report and presentation helpers. Source-local parsers and timeout handling preserve credential and response parity while the cases move alongside each source.
* feat(usage): resolve reports from active session accounts
Resolve source inputs at fetch time so model and credential changes select the account each session spends. Keep the legacy discovered-account RPC for existing clients. Sync OpenCode model events outside foreground turns so usage follows model switches.
* fix usage source discovery and session report parity
* test adapter usage references through daemon RPC
* name usage reference contract across server
* refactor(usage): own usage RPCs and validate session auth
* feat(omp): resolve session usage from active OAuth credential
* fix(omp): match usage source input and valid credential fallback
* test(omp): use upstream credential database schema
* test(omp): verify live session usage references
* fix(usage): declare YAML parser and consolidate credential validation
* feat(app): show usage sources in the composer and a Usage screen
Replace the provider-matched usage in the context ring tooltip with a
composer pill driven by agent.get_usage_report, add a sidebar Usage
screen over usage.list_reports grouped by host, and move the host
settings Usage page to usage.list_reports. Hosts without
features.usageSources show an update message. Source icons come from
the report's catalog SVG. The app no longer calls provider.usage.list.
* fix(usage): declare the Paseo requirement on internal usage-source plugins
A manifest without requirements.paseo targets Paseo before 0.8, so the daemon
refused to start eight of the nine usage sources.
* fix(app): keep host connection statuses live under the React Compiler
The hook read a store version only as a memo trigger (void version); the
compiler dropped it, so pages that mounted before a host came online kept
showing it as connecting.
* fix(app): place the context ring slot before the usage pill
The ring reserves its slot before it has data. With the pill first, the empty
slot sat between the pill and the mic; now it falls in the flexible space.
* refactor(app): extract the settings page layout into PageLayout
Desktop puts the title in the page above a centered column; compact moves it
into a back header. Settings renders the same pixels through it.
* feat(app): lay out the Usage screen like a settings page
The title moves from the header bar into the page on desktop; compact gets the
settings back header, which returns home when the screen was opened by URL.
* refactor(app): drop the unused actions slot from PageLayout
* test(plugin): classify the usage source SDK entry
* fix(usage): preserve platform test contracts
* fix(settings): preserve page title test contract
* feat(usage): identify and cache reports by account ID
Keep report identity stable across input routes and token rotation, and make selective refresh operate on known account reports.
* feat(app): read usage reports through report IDs
Share report data between agent pills and host views while preserving the existing popover interaction for the next UI phase.
* fix(usage): keep account identity aligned with credentials
Read labels from local token claims and use one credential lookup for usage fetches and account identification. Bound cached profile identities across token rotation.
* fix(usage): isolate failure IDs and prune expired reports
Failure reports live outside the account-key namespace, and cache writes discard entries past the five-minute TTL.
* refactor(app): share the compact time-ago prose formatter
Move the tab tooltip's "3m" -> "3m ago" wording into utils/time so the
usage card can reuse it.
* feat(usage): show when each report was fetched
The usage card reads fetchedAt as "Updated 3m ago": a tooltip on the
card header where hover exists (web, desktop), printed in the card
footer on native and compact. Shared by the pill popover and the Usage
screen.
* feat(usage): open the pill popover from cache and refresh on demand
Opening the popover no longer forces a vendor fetch; it shows the cached
report. A Refresh control force-refreshes only this report
(usage.list_reports with its single ID and forceRefresh), shows
Refreshing... while it runs, keeps the previous report visible, and says
"Unable to refresh usage" if the request fails.
* feat(app): let ToolbarButton show a tooltip other than its label
An icon action can now say a status in its tooltip ("Updated 3m ago")
while keeping its label for accessibility.
* feat(usage): refresh a report from its card header
Refresh is an icon button at the right edge of every usage card header,
in the pill popover and on the Usage screen. It force-refreshes only that
report, writes the result to the report's cache and its host list, shows
a spinner and disables itself while pending, and prints "Unable to
refresh usage" in the card on failure. Its tooltip carries the
freshness; without hover the freshness stays in the card footer. The
status indicator sits just left of the button, and the popover's
separate refresh row is gone.
* fix(server): include internal plugin assets in built output
* feat(server): built-in plugins in /plugins (#5544)
* feat(server): load compiled built-in Codex plugin
* test(server): guard built-in plugin loading and installs
* test(server): verify built-in plugins from dist in CI
* refactor(server): migrate remaining usage sources to built-in plugins
* test(server): guard all packaged usage source registrations
* docs: describe usage validation across plugin hosts
* test(ci): include built-in plugins in routing contract
* test(server): import moved Codex usage source
* fix(server): retain ACP updates received with session creation
|
||
|
|
d7b7016cc9 |
feat(settings): polish navigation and controls (#5459)
* feat(settings): polish navigation and controls * test(settings): update navigation assertions |
||
|
|
e1c769c018 |
Make password-protected daemons work across local and remote connections (#5393)
* feat(auth): authenticate daemon clients through hello Give local CLI and desktop clients a per-run credential while remote clients send the host password inside the protocol. Keep relay passwordless admission for this mobile release so existing saved hosts keep working until the follow-up enforcement release. Move password recovery for saved hosts into host settings and keep pairing credentials in the add flow. Include isolated web QA evidence for both flows. * fix(auth): preserve Hub bootstrap and local credential expectations Hub-owned sockets already carry admission; keep their hello handling synchronous through session bootstrap while resolving credentials asynchronously for ordinary sockets. Keep the resolver out of lightweight configuration imports. Update CLI expectations for same-home credentials, let a rejected connection yield to another usable connection, and clear pairing secrets when add-flow targets change. * fix(pairing): retain passwords across edits to the same target Track the last valid pairing host while a URI is edited. This preserves an entered password for whitespace and partial edits but clears it when a different host becomes the target. * test(auth): align local credential checks across platforms Expect the CLI to authenticate through the credential in its selected home, and verify POSIX file modes only where Node exposes them. * feat(app): move host password into a settings row with a modal The host page rendered an always-visible password form above Name on every host. Replace it with a Password row under a Security section below Appearance; tapping it opens a modal with Save and, when a password is saved, Clear. The connection-error callout keeps its guidance and gains a Set password button that opens the same modal. Drop the unused pairing.hostPassword body/reopen/saveError strings. * chore: remove committed QA screenshots QA evidence stays out of the repository; the PR body describes it in words. * feat(app): keep the host password out of the host page The saved password is connection data, entered only when a host is added. A Password setting on the host page reads as the daemon's own password, so the row, modal, and callout button are gone. A rejected host shows the daemon's reason with guidance to remove the host and add it again with the password. useHostMutations no longer exposes setHostPassword; relay imports still set it inside the runtime. * feat(app): show host connection errors in an error alert HostConnectionError renders the existing Alert with the connection error as its title. Auth rejections add the re-add guidance as the description; other errors show the title only. The Overview and Connections pages share it. * style(app): soften alert borders to half-opacity accent * feat(app): size alerts on the button scale Alert takes a size prop (xs/sm/md/lg, default md) whose geometry lives next to the button's in control-geometry: one font size for title and description, the button icon size centered on the first text line, button padding and radius, and a one-line alert as tall as the button of the same size. Consumers pick sm among settings rows, cards, panels, and modals, and md when the alert is the whole page or sheet state. * style(app): roomier, rounder alerts with a tight title block Alert sm padding returns to the original 12/16 and every size is at least borderRadius.xl. Title and description sit a small gap apart with no line height override. The icon slot carries a zero-width text strut in the alert's font so the icon centers on the first line for titled, title-only, and description-only alerts. * refactor(app): align the alert icon with a centered lead row Drop the zero-width strut. The icon and the alert's first text (title, else description) share one row centered on each other; the description and actions sit below, indented by the icon slot width plus gap so they start at the lead text's left edge. * refactor(app): drop the unused Alert icon override |
||
|
|
04c3e003f8 |
Keep model picker rows free of nested buttons on web (#5404)
* Keep the model row's profile action out of the row button on web On web the model row renders as a <button>, and the create/edit profile action inside it rendered a second <button>, so React logged "<button> cannot be a descendant of <button>" for every row. The action now sits beside the row button inside the same hover boundary, positioned over a slot the row reserves, so layout and hover reveal are unchanged. Closes #5385 * Check the create-profile action leaves the composer's model unchanged --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> |
||
|
|
83f9fba209 |
Keep the draft-create handoff until the new chat is current (#5168)
* fix(app): keep the draft-create handoff until the new chat is current Creating an agent from the draft composer on web flashed the "Updating messages" panel toast for 40-200ms. That toast means "this chat may be out of date, we are catching up" — a chat this client just created cannot be. The timeline reducer released the create handoff at the first synchronized page, but the visibility catch-up flag only clears once catch-up settles, and a fresh chat always has a parked follow-up "after" page because live rows arrive during the tail fetch. Between the two releases the state machine saw a hydrated chat with no handoff and a pending catch-up, which resolves to the resume toast. #4863 turned that branch from silent to status, which is what made the window visible. Both signals now flip in one synchronous call: createViewedTimelineSync gains an onTimelineCurrent port, called from setVisibilityCatchUpReady — the single place the sync declares a chat current, already covering the parked follow-up and the fetchLatestTail overflow path. The owner implements it by clearing the handoff. The optimistic "running" override therefore lasts one extra round trip. The state machine and sync-status rules are unchanged; they were correct. * fix(app): release the create handoff when a chat leaves timeline demand Closing a new chat's tab before catch-up settled left its sent attempt in useCreateFlowStore forever. Reopening it in the same session then supplied optimistic-create continuity for an authoritative chat: the panel showed it as running and suppressed "Updating messages" while the real catch-up ran — the state this fix exists to protect. An offline or slow host widens that window from milliseconds to minutes. Leaving the demanded set ends the owner's obligation to a chat exactly as reaching current does, so onTimelineCurrent becomes onCatchUpEnded and fires on both. It reports after the membership commit, so the sync never describes itself mid-transition. Disconnect and backgrounding preserve demand and therefore preserve the handoff. |
||
|
|
ee7949ae25 |
fix(pi): honor per-model thinking configuration (#4413)
Derive Pi thinking options from each model's thinkingLevelMap, read Pi's effective level back after create, resume, set_model and set_thinking_level, surface failed state reads instead of persisting the requested level, and show the thinking control for single-level models. Closes #4382 Co-authored-by: therainisme <therainisme@qq.com> Co-authored-by: trim21 <i@trim21.me> |
||
|
|
9696f42264 |
Subscribe only the chats this session opened (#5040)
* fix(app): subscribe only the chats this session opened Open-chat membership was sourced from the persisted workspace layout, which is restored from disk at launch and carries a tab for every chat the user has ever opened on the host. Every member got an authoritative timeline catch-up at session start, and each of those fetches resumed its agent on the daemon: one provider session per historical tab, every launch. Two things followed from each resume. Registration stamped `updatedAt` over the restored timestamp, and the resumed agent started with no attention because the resume path never read the stored flag. Both were persisted, so restarting Paseo rewrote every workspace's last-used time in the sidebar and cleared Ready to review on chats the user never looked at. The timestamp loss was permanent: workspace `statusEnteredAt` is re-derived from `updatedAt` on every daemon start. Keep the open set on the session instead. A chat joins it the first time the user opens it and stays until its tab closes or the session ends, so hiding, switching workspaces, evicting a retained view, backgrounding and reconnect all preserve the subscription, with no recent-agent limit. Layout keeps its release role and loses its source role. Harden the producer as well, so a resume from any caller stays inert: registration adopts restored timestamps rather than stamping now, and resume carries the stored attention through. * fix(agents): keep a resumed agent's updatedAt from moving backwards Dropping the registration stamp exposed a skew the stamp had been erasing. `extractTimestamps` read `lastActivityAt ?? updatedAt`, but the two diverge: renaming, labelling, restoring from archive, marking unread, and clearing attention all move `updatedAt` on an unloaded agent without touching `lastActivityAt`. Eight of 854 agent records on one dev machine carry that skew, up to eight days of it. A resume then handed clients a timestamp older than one they already held, and `acceptAgentDirectoryUpdate` drops any state update that goes backwards. Marking a chat unread without opening it, then opening it, left the unread dot stuck in History, the agent list, Command Center and the tab: the daemon cleared attention and the client refused the update. Read both fields and take the newest, which is already the rule `resolveStoredAgentPayloadUpdatedAt` applies to the list payload. Move that function beside the other stored-record readers as `resolveStoredAgentUpdatedAt` so one rule covers both paths. Also replace the `!options?.updatedAt` inference in `registerSession` with an explicit `restoring` flag. The inference was correct for all four call sites, but it made a second meaning out of a field, and the next caller to seed a timestamp would have opted out of activity stamping silently. * test(app): assert a reloaded layout subscribes only the chat it restores into No e2e reloaded the page with a populated workspace layout and asserted which agents the daemon acknowledged, so the regression this branch fixes had no end-to-end guard. Opening four chats across two workspaces leaves a tab per chat in persisted layout; after a reload only the restored chat may be subscribed, because subscribing resumes that agent on the daemon. Verified it fails when session-context's layout callback is pointed back at replaceVisibleAgentIds: the three restored tabs nobody opened join the acknowledged set. |
||
|
|
5d70ab2ab8 |
fix(app): restore character-paced streaming replies (#5013)
* fix(app): restore character-paced streaming reveal Restore the v0.8 character reveal model and remove the word-level fade implementation so streamed assistant and reasoning text remains opaque, grapheme-safe, and immediately completes when a turn ends. * fix(android): preserve inverted timeline selection React Native's prebuilt Android library does not consume source patches under node_modules. Register the compiled scroll manager before the core package so inverted child-reveal calculations leave focus and text selection in place while normal and explicit scrolling remain unchanged. |
||
|
|
047f40e623 |
Install npm plugins and review source updates (#4975)
* feat(plugins): unify source installation and simplify plugin settings * feat(plugins): install npm sources and review CLI updates Treat install selectors as one-time choices. Keep source identity separate from the installed revision, and resolve ordinary updates from the current upstream default. npm only acquires directories; plugin loading remains independent of npm. * fix(plugins): preserve CLI diagnostics and exercise reviewed updates Move new source capability coverage to real browser journeys. Update the requirements regression to exercise preview and apply, and preserve the existing duplicate-selector diagnostic across positional option scopes. * test(plugins): remove unused pending helper * test(plugins): pack registry fixtures with the Windows command runner * test(plugins): assert failed client contribution setup * docs(plugins): consolidate guides and document npm publishing * docs(plugins): introduce publishing choices before npm instructions * test(plugins): own browser journey cleanup in a fixture * feat(plugins): prepare scaffolded packages for publishing |
||
|
|
6ce6f4c961 |
Let plugins discover hosts and use host-targeted SDK clients (#4971)
* feat(plugins): expose hosts and host-targeted SDK clients * test(plugins): verify exact cross-host observation cleanup * fix(plugins): reacquire explicitly disposed host clients * docs(plugins): clarify host client usage and lifecycle * docs(plugins): use theme colors in host discovery example |
||
|
|
46529146ac |
Show pending file attachments and keep uploads responsive (#4958)
Show a spinner attachment while a selected file is read and uploaded, yield between 128 KiB chunks so the UI stays responsive, validate every file in a batch before sending any of them, and leave one SelectedFile shape across the picker, drag-and-drop, and the upload path. |
||
|
|
a78949d1d4 |
Preserve committed changes when restoring archived workspaces (#4926)
* fix(server): preserve comparison base when restoring worktrees Restore archived worktrees from the workspace's saved comparison base instead of using the feature branch as its own base. Preserve the branch HEAD and allow restore when an exact comparison ref no longer exists. * test(app): isolate draft preference checks from menu animation |
||
|
|
64b1a62ed3 |
Fix voice reply latency and hide internal prompt wrappers (#4927)
* fix(voice): speed up replies and hide prompt wrappers Keep voice prompt cleanup in the client presentation path so provider history and live messages render consistently without rewriting canonical input. Correct native TTS scheduling and threading, and reconnect interruption to speech detection and session cancellation. * test(voice): clarify playback and timeline regression journeys * test(voice): centralize native worker cleanup and repair fixture path * test(app): exercise workspace eviction without reloading the app * test(app): preserve streamed output during workspace eviction Hold the turn until the workspace is evicted instead of dropping all stream events during navigation. Slow CI runners can finish the entire response before navigation ends; waiting for producer completion before release makes that ordering deterministic. * test(app): stabilize subscriptions during workspace eviction Opening another chat replaces the timeline subscription, making buffered frames from earlier generations unroutable. Preopen all chats before holding the stream, finish production before eviction, and assert both early and final response content with no subscription replacement. |
||
|
|
4be34f05ef |
fix(plugin): continue one timeline item across ACP chunks without messageId (#4701)
* fix(plugin): continue one timeline item across ACP chunks without messageId The plugin ACP reducer minted a new fallback id for every agent_message_chunk / agent_thought_chunk that carried no messageId, so a streamed reply rendered as one block per chunk, splitting words and Markdown delimiters (#4699). Keep one fallback id per chunk kind and end it on a chunk of the other kind, a tool call, a plan, a user message, or a turn boundary, matching the server-side ACP provider. * fix(plugin): end the other chunk kind's item on any chunk A chunk of one kind now ends the other kind's fallback item before the content or id is looked at, so an explicit-id text chunk between two id-less thoughts yields three items instead of merging the thoughts. The test declares the streamed updates through the connector harness instead of handling protocol frames itself. * test(plugin): verify ACP message boundaries and rendered Markdown * test(app): synchronize rewind setup and menu selection --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
d8dd189b94 |
ci: simplify and de-flake the test suite (#4868)
* chore(ci): audit packaging costs and repeated test execution Read-only audit: Linux packaging and smokes consume 12-14 minutes; all three CLI shards repeat the 294-test source suite and build the server twice. Preserve the 17 required contexts, move packaging to main, run CLI source tests once, and consolidate repeated E2E setup into journeys. Historical logs and the complete audit were reported outside the repository. * ci: move packaging to main and remove repeated CLI suites PR runners were rebuilding distribution artifacts and executing the entire CLI source suite on all three E2E shards. Keep packaging coverage after merge, let the local E2E runner own its dependency build, and execute source tests once in an existing required check. Root typecheck already validates plugin examples. * test(ci): consolidate journeys and remove repeated startup work Run user journeys with shared setup, retain one Hub CLI contract, and weight the existing Playwright shards using observed costs. Remove implementation-only and ad-hoc measurement checks. Await root-watch closure and publish creation failure commands atomically so teardown and failure injection have completion boundaries. * test(hub): remove incidental observation and speculative shutdown changes Hub control RPCs need a connected client without an agent observation. Preserve the failed-create subscription contract and remove product shutdown changes whose Windows cleanup cause was not reproduced. * fix(client): clear the scheduled retry when a connect attempt opens a transport An explicit connect() during reconnect backoff opened a new transport but left the scheduled retry armed. When it fired on the healthy connection it disposed the transport and reconnected, dropping requests in flight. This is why owned-subscriptions-old-daemon could still fail after #4858 added the explicit reconnect. attemptConnect now owns the rule ensureConnected already applied; the new unit test fails on the old client. * test(e2e): keep the completed-block, font-readiness, and live-page guards inside journeys - agent-stream-ui: the auto-scroll journey anchors on the completed intro block by content and requires its Markdown root to stay the same mounted node while later blocks stream (#1989 guard, reshaped off the tail block). - changes-pane: the code-typography journey holds font loads and requires no diff geometry before they resolve, then applies the font change. - agent-timeline-pagination: the live-output page load runs against a thirty-minute mock stream and gates the release on the running turn, so the status assertion no longer races a ten-second turn. * test(desktop): install the Electron binary once before the unit suite Electron 44 downloads its binary on the first Node require("electron") instead of at npm ci. Vitest workers importing electron-log/main raced that download and extraction (File exists on dist/electron, then "Electron failed to install correctly" in a sibling worker) on both Ubuntu and Windows desktop-tests jobs. Run the package's idempotent install-electron in one process before vitest forks. * ci(desktop-packages): pin actions and smoke packaging-path pull requests Pin checkout, setup-node, and upload-artifact to full commit SHAs. Keep packaging off ordinary PR CI, but run the Linux package smoke on pull requests that touch packages/desktop, nix, or the workflow so a packaging or sandbox regression cannot land on main unchecked. The desktop release workflow already builds and smokes its own artifacts on all platforms. |
||
|
|
0474c3e0af |
fix(app): stop the streaming Markdown spec at a word boundary (#4875)
The mock provider splits "**Bold" into two frames, "**Bo" and "ld". The helper forwarded both back-to-back and asserted the strong span reads "Bold", which only holds when the browser commits both frames to the store in one reducer flush and the message first mounts with "**Bold". When a frame boundary lands between the two tasks the message mounts with "**Bo" and word pacing holds "ld" until the whitespace after it arrives, so the span reads "Bo" until the gate releases. That is the designed behaviour of the reveal, and the batching is up to the browser's task scheduling. Stop after "**Bold text" instead: the space after "Bold" ends the word, so every batching reveals "Bold" and never more than "text". Forcing the frames into separate commits reproduced the CI failure before the change and passes after it. |
||
|
|
b21c004ffe |
Keep open chats current and show reconnection progress (#4863)
* fix(app): keep open chats current and show resume progress Keep open-chat delivery independent of mounted views, freeze hidden chat rendering, and catch up visible chats before hidden ones. Distinguish host reconnection from timeline freshness so cached messages do not silently appear current after resume. * fix(app): prevent sync status from overlapping action errors Share the existing panel toast slot so action errors remain readable during reconnect and catch-up. Verify the interaction and draft retention in the real browser, preserve the tested freeze transition, and refresh the Nix dependency hash for react-freeze. * test(hub): expose complete creation and message retry failures Partial response assertions hid the creation error and allowed duplicate responses with incomplete agent data. Check complete success envelopes, the shared agent snapshot, and null message errors so future failures print the actual error payload. * fix(app): dismiss retained chat menus before freezing An async fork navigates before its closing state commits, so freezing the source chat could leave its portal backdrop blocking the destination. Remove the shared menu surface in the inactive commit before suspension. Keep regression assertions aligned with open-chat subscriptions, per-agent incremental recovery, plugin contribution lifetimes, and rendered image completion. * test(app): expect the reloaded plugin to contribute its pill again A full reload discards the pressed pill with the page, then evaluates the plugin again and its agents snapshot re-adds the pill. The old count-zero assertion only passed on main because the chat was not ready yet when the URL settled; open chats now subscribe from session start, so the pill is rendered by then. * fix(app): suspend hidden chats on web only react-freeze is Suspense with a never-resolving thenable. Suspending a committed subtree hides its host nodes, tears down layout effects, and detaches refs, which changes Fabric ownership of the retained chat. Split the boundary by platform: the web file keeps the two-commit freeze, the native file keeps the chat mounted and live, and the retained-panel signal remains the only thing that stops background work. Restore the mobile-panels rule the branch had rewritten. * fix(creation): never read a receipt while replacing it Admission reads the creation receipt while the runner replaces it atomically. On Windows a rename over a file this process still has open for reading fails with EPERM, and that error marked an otherwise completed creation as failed (#4865). Queue reads and writes per receipt so one owner never overlaps them, and serialize the record when the write is queued because the runner mutates it afterwards. No retry on EPERM: the conflict is removed rather than tolerated. Linux cannot reproduce the rename failure, so the Windows server-tests job (creation/index.test.ts and hub/execution-session.websocket.test.ts) is the verification for this change. |
||
|
|
6215e08efe |
Smooth streaming replies with a continuous left-to-right fade (#4859)
* feat(app): reveal streaming text as one continuous fade front Streaming assistant text is released by word and every character fades in over the same 150 ms envelope from a start time owned by the message. Start times never decrease in reading order, so a line paints as a single front moving left to right, however the text is chunked. Per-word envelopes with an internal stagger let the next word's first letter overtake the previous word's last letter, which read as holes and flashing words at the real frame rate; 100 ms frame strips hid it. Native renderers receive the fade ranges as a prop on a host view wrapping the root Text, so ranges and text land in the same Fabric mount transaction. The earlier binding sent ranges through a separate async module call keyed by view tag and had to reconcile them against the view's text; on a fast phone the UI thread lags JS by several commits, mounts and calls interleave, and every reconciliation heuristic guessed wrong. Native code no longer infers newness or creates start times. The mock load-test provider now separates cycles with a blank line so a cycle heading is not appended to the previous paragraph. * fix(ci): refresh streaming dependencies and isolate E2E setup * test(app): distinguish fade cleanup from Markdown replacement * test(app): detect restarted fades during Markdown updates * docs(app): keep streaming guidance focused on constraints and QA * test(app): control animation time in Android fade assertions Fixed wall-clock sleeps let emulator scheduling select the sampled animation frame. Inject the animator time source and drive paint at exact timestamps within one UI-thread operation; production keeps the system clock. Verify the mid-fade sample and the exact cleanup boundary without sleeps, polling, or retries. * fix(test): compile the desktop renderer before lifecycle checks The lifecycle fixture treated Metro /status as app readiness. Its first Settings click therefore spent its 90-second UI budget compiling the cold bundle. An isolated cold-cache run restricted to one CPU reproduced the click timeout before the app runtime had loaded. Reuse the browser suite's existing bundle warmup before launching Electron, and preserve Metro output in the lifecycle artifacts. The UI timeout, build-request timeout, lifecycle assertions and product behavior are unchanged. CI did not preserve Metro output, so its precise startup timing is not recoverable from the failed run. The retained log makes subsequent startup failures diagnosable without relying only on an empty renderer text dump. Validation: three complete lifecycle E2E runs under private Xvfb displays; existing Metro readiness tests; app typecheck; targeted lint; root format. * test(file-observer): await burst delivery through native recovery The deletion phase required every event within 10 seconds, but native coalescing recovery uses a 30-second safety audit followed by the existing reconciliation debounce. An idle diagnostic excludes that delayed audit and therefore cannot act as a completion barrier. Await the required delivered events instead, preserving every deletion assertion and the existing overall test watchdog. A real-filesystem test with controlled native notifications reproduces 95 outstanding paths at the old deadline and verifies completion when the safety audit delivers them. No production observer behavior changes. * test(file-observer): verify burst recovery through public events The controlled-clock regression constructed a private native backend, bypassing the interface used by consumers. Consolidate recovery coverage into the public observer integration test instead: drive real writes, deletes and directory renames, then await every required public event, including the 450 surviving files at their renamed paths. No public capability was added: subscribe callbacks already express completion. No production behavior or test timeout changes. * test(app): drive Android fade tests through the native host The instrumentation suite constructed TailFadeInAnimator directly, advanced its private clock, and counted FadeInSpan instances. It now mounts the WordFadeHost that React mounts, feeds it the ranges prop the bridge sends, and asserts rendered pixels: hidden before a start time, a monotone front inside and across words, settled frames equal to the baseline, and no frame callbacks left after settling. Span counts and internal timing state are no longer asserted. Choreographer cannot be advanced deterministically from a test and the fade epoch is the message's wall-clock start time, so the host takes a WordFadeFrameClock (time plus frame callbacks) as its smallest public seam; production keeps the Choreographer-backed default. Fixture text is sized in pixels so every sample stays on one line at any density; at 420 dpi the 32sp fixture wrapped and the column helper summed glyphs from the first line. |
||
|
|
c3bcad687d |
fix(app): prevent and recover workspace layout crash loops (#4844)
* fix(app): prevent and recover workspace layout crash loops * fix(desktop): capture resized browser tabs without hanging A resident guest can paint fresh pixels after resize while capturePage's native surface-copy request remains pending. The same failed guest returns a fresh frame through frame subscription, so waiting for resize or another animation frame does not correct the failing copy request. Capture one subscribed frame within the existing five-second budget and release the subscription on completion or cancellation. Preserve temporary frame production and its original throttling policy. Remove viewport copy retries and E2E screenshot polling; assert the resized image dimensions. Refs #4866. Three full real-Electron browser journeys passed under Xvfb, including physical clicks and fresh screenshots while the window is hidden. * test(app): choose Agent before exercising the runtime catalog The newly merged catalog journey assumed an empty workspace opened an agent draft tab. This branch opens the New launcher, so create the agent draft tab explicitly before checking its composer and model choices. * fix: settle closed browser captures and validate persisted layout fixtures Destroying a guest during frame subscription left its screenshot pending until the capture deadline and blocked queued captures. Reject at destruction, remove the listener on every exit, and return browser_tab_closed. The regression proves immediate settlement and queue release. Reuse the unchanged persisted layout schema from a UI-independent module so the recovery fixture validates storage before editing it. * test(app): encapsulate Explorer layout setup and cleanup Keep persisted layout schemas and corrupted-state construction inside the seedCorruptedWorkspaceLayout helper. Use the existing withWorkspace fixture for cleanup in all four Explorer journeys so test bodies follow a straight setup, action and assertion flow. * test(app): target the selected workspace in layout recovery The corruption helper selected the first persisted entry, so a multi-workspace journey could leave the intended workspace untouched. Resolve its exact persistence key from the requested workspace ID and fail setup when absent. The failing-first journey now preserves an earlier workspace with an agent draft tab while recovering the selected workspace. |
||
|
|
0aca3b605a |
fix(plugins): transform whole timeline items before grouping and Markdown splitting (#4675)
Send every original tool call and each whole accumulated assistant message through plugin transformers before display grouping or Markdown splitting. Preserve streaming callbacks, original payloads, native Markdown block identities, and chat outline reading position. Reject legacy fragment caches and retain failing-first plugin-interface and browser regressions. Fixes #4673. Fixes #4789. Consolidates #4820. Co-authored-by: Matt Cowger <matt@cowger.us> Co-authored-by: Jegor Kitskerkin <jegor.kitskerkin@gmail.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
77c5c8f17e |
Prevent duplicate catalog choices from crashing agent drafts (#4839)
* fix(providers): keep duplicate catalogs from breaking agent drafts Normalize model identities before publication and when materializing older catalogs, preserving the first definition and configured overrides. Keep explicit default variants separate from the base model selection. * fix(providers): preserve saved variant identities across resumes Persist the selection encoding so legacy names remain literal. Refresh provider persistence when a selection changes, before another prompt is sent. * test(app): let fixtures own catalog journey cleanup * fix(providers): preserve the existing default variant contract Keep one Default catalog choice and let the provider resolve an omitted variant. Remove the encoding and persistence machinery; other variant IDs retain their literal meaning. * test(creation): expose failed completion after conflicting requests Creation returns a failed snapshot instead of rejecting. Assert the result before its side effects so platform failures report the underlying error. * test(app): wait for the seed client after daemon restart The browser and seed client reconnect independently. Wait for the seed connection before issuing post-restart RPCs instead of assuming browser readiness covers both clients. * test(app): isolate visible prompts and checkout diff fixtures Inactive tabs retain message DOM, so prompt assertions must inspect visible rows. Ignore the local fixture remote before observation rather than deleting it after the daemon has started tracking the checkout. * fix(file-observer): preserve final deletions when batching events A duplicate creation notification followed by deletion was collapsed into an update, hiding final absence from consumers that distinguish deletes. Keep final deletes while retaining update semantics for atomic replacement. * fix(desktop): wait for Metro HTTP readiness before launching Electron Expo briefly listens while probing port availability. A successful TCP connection can therefore launch Electron before Metro starts and leave it exiting on connection refusal. Require the configured renderer origin to report the running packager status. * test(ci): consolidate fixture and readiness corrections Await the connection and UI states each journey owns, isolate Git fixture contents before observation, and hold real Markdown frames at asserted checkpoints. Keep creation completion diagnostics explicit without claiming the original Windows failure is fixed. |
||
|
|
836f1a9c23 |
fix(ci): consolidate E2E repairs and wait for Metro readiness (#4858)
* fix(desktop): wait for Metro HTTP readiness before launching Electron Expo briefly listens while probing port availability. A successful TCP connection can therefore launch Electron before Metro starts and leave it exiting on connection refusal. Require the configured renderer origin to report the running packager status. * test(ci): consolidate fixture and readiness corrections Await the connection and UI states each journey owns, isolate Git fixture contents before observation, and hold real Markdown frames at asserted checkpoints. Keep creation completion diagnostics explicit without claiming the original Windows failure is fixed. * test(desktop): describe Metro readiness through HTTP states Keep protocol mechanics in the fixture and assert readiness only after a successful packager response. Cover non-success status and unrelated HTTP content without inline synchronization in the test body. |
||
|
|
799c91b39d |
fix(app): restore Android tablet model picker interactions (#4845)
Restore provider/model selection in Android tablet modal windows by giving both native modal hosts their own gesture-handler root. Preserve phone sheet and web portal behavior. Include native regression scripts, test dependency resolution, and verified CI test corrections. Generated media and logs are excluded. Refs #4475 and #4377; consolidates #4721 and #4771. Co-authored-by: Mike P. Kuhl <mkuhl@softmachine.at> Co-authored-by: Cameron Chin <cjcrjc@users.noreply.github.com> |
||
|
|
d63132a37d |
fix(creation): prevent duplicate workspaces and agents on retries (#4442)
* fix(creation): prevent duplicate workspaces and agents on retries * test(creation): cover plugin hooks across receipt replays * fix(creation): recover draft retries without accepting partial workspaces * refactor(creation): name receipt inputs and tag compatibility gate * fix(file-observer): track files announced by native change events * fix(creation): keep initial prompts in daemon-owned creation requests * fix(creation): reuse connection-owned progress and recovery * test(creation): adapt handoff gates to combined requests * ci(desktop): avoid xz compression for smoke packages * ci(desktop): pass valid RPM compression through FPM config * refactor(creation): give legacy and modern requests one identity owner * fix(creation): log observer failures without interrupting creation |
||
|
|
2d7733cd65 |
fix(app): keep incomplete Markdown formatted while streaming (#4742)
* fix(app): keep incomplete Markdown formatted while streaming Treat unfinished formatting at the growing assistant-message tail as provisional so delimiters and link destinations do not flash while tokens arrive. Completed messages retain ordinary Markdown parsing. * test(app): express streaming Markdown checks as user actions |
||
|
|
a7ce602c83 |
fix(agents): restore archived Codex workspaces without losing focus (#4736)
* fix(agents): restore archived Codex workspaces without losing focus History selection must survive without cached agent records, and reading an archived Codex thread must not acquire an interactive writer. Keep workspace restore explicit, serialize native lifecycle transitions, and leave transcript lifetime with its owner. * test(app): express Codex restore specs as user journeys * test(app): finish restore fixture cleanup after teardown errors * test(app): align restore coverage and await fixture readiness Workspace restore leaves agents archived until explicit Unarchive. Keep that distinction covered for multiple agents. Compile the recovery fixture before its interaction test starts, and wait for sidebar agent hydration before asserting tab presence. |
||
|
|
0110302b6f |
fix(daemon): keep lifecycle operations on the selected instance (#4575)
* fix(daemon): keep lifecycle operations on the selected instance Restart reconstructed launch inputs and address fallback could redirect a home-selected shutdown to another daemon. Let supervisors publish bound endpoints, retain their worker restart contract, and share captured local lifecycle ownership between CLI and Desktop. * test(daemon): verify bounded desktop readiness and cancellable artifacts * test(cli): verify reload target hints and durable offline pairing * test(daemon): align lifecycle fixtures with ownership and targeting * fix(daemon): preserve reachability when status details time out Provider discovery can outlast a status request after authentication succeeds. Keep local connection facts separate from unavailable details, while explicit remote queries retain their error outcome. Migrate CLI and packaged Desktop fixtures to persistent managed configuration and explicit targets. Isolate generic Git observation from forge discovery and reuse disposable CLI homes on Windows. * test(daemon): observe restart through the worker status RPC The bounded CLI status observation may omit worker details while an authenticated connection remains healthy. Exercise the app-style restart through its full RPC and retain the replacement observation, with a real slow provider probe covering the distinction. |
||
|
|
4006f974fb |
Reload to the project picker after an app error (#4598)
* fix(app): reload to the project picker after a render error Keep the recovery boundary above the router so Reload starts a new navigation tree at the picker instead of remounting the failed workspace. Preserve saved layouts and normal startup restoration. * test(app): exercise reload through fixture routes |
||
|
|
6f2195904b |
fix(subscriptions): isolate observers and silence unrequested feeds (#4596)
Give each observation a server-issued ID owned by its physical connection. Preserve snapshot-only reads and combined list-and-subscribe, source-bound replies, lifecycle cleanup and legacy wire compatibility. |
||
|
|
bc385b5930 |
fix(app): don't steal focus when workspace creation finishes late (#2986)
Preserve the selected workspace when delayed creation finishes, while still starting the requested agent or terminal. Keep normal navigation when the initiating screen remains active, and preserve newer draft text and context. Verified with before/after recordings, ten passing real-browser journeys using isolated daemons, focused regression tests, green CI, and independent review. QA: https://github.com/getpaseo/paseo/tree/qa/workspace-focus-2986 Co-authored-by: Christoph Leiter <christoph@leiter.io> |
||
|
|
0b3d82a469 |
feat(plugin): add shared header buttons and composer controls (#4577)
* feat(plugin): add shared header buttons and composer controls * fix(plugin): isolate button registration imports and settle captures * test(plugin): express button journeys through domain helpers |
||
|
|
509a3e17bd |
feat(app): read the changelog in the app instead of the browser (#4576)
What's new opens an adaptive sheet — bottom sheet on compact, centred card on desktop — that fetches CHANGELOG.md from the repository and renders it with the app's own chrome. The desktop update callout, the sidebar help menu, and a new row in Settings → About all open it; the sheet header links out to paseo.sh/changelog. The parser commits to two facts only: `##` starts a release and `###` starts a section. Section titles are read from the document rather than matched against a list, and everything under a section passes through verbatim to the Markdown renderer, so a future callout, block quote, code fence or image needs no parser change. Fences are tracked because a fenced sample is the one thing that can fool a line scan. The document is fetched rather than bundled because the app has to describe a release that shipped after it was built, which is the update callout's whole purpose. It goes straight to the repository rather than through a host: the notes describe the app, and routing them through a daemon would make them depend on which host happens to be connected. Nothing is fetched or parsed until the sheet opens. The latest release renders as soon as there is one and the rest of the first page follows in a transition, so React slices that work around the opening animation instead of committing hundreds of list items at once. |
||
|
|
96ea6698d7 |
fix(app): preserve plugin provider icons in model buttons (#4549)
* fix(app): preserve plugin provider icons in model buttons Pass the selected host to the composer glyph so it resolves the same plugin SVG as the model picker. Require the nullable glyph host prop to catch omitted identity at compile time. * test(app): isolate plugin icon regression setup |
||
|
|
85cd969321 |
Allow finished workspaces to be marked unread (#3603)
Restore unread attention on finished workspaces and preserve the reminder when leaving the current workspace. Cover desktop and compact read flows, and retain change-only file paths for deletion observation. Co-authored-by: Edi Hasaj <34984294+edihasaj@users.noreply.github.com> |
||
|
|
0859a67ab1 |
fix(app): show workspace plugin panels in Explorer menu (#4446)
* fix(app): show workspace plugin panels in Explorer menu Let the host-filtered launch catalog identify toggleable views and their targets. Explorer uses the same identity comparison for every view while plugin panels remain workspace-scoped. * test(app): express Explorer panel regression as user steps |
||
|
|
c46aee4e6a |
Revert #4421 and narrow the cache restore fix (#4436)
* Revert "Restore cached conversations before reconnecting (#4421)"
This reverts commit
|
||
|
|
a5d2b7d863 |
Declare and enforce plugin Paseo version requirements (#4430)
* feat(plugins): enforce declared Paseo version requirements Treat manifests without a Paseo requirement as pre-0.8 plugins so the first breaking plugin release offers an explicit migration path. Check the daemon and app independently before plugin execution, and preserve installed Git revisions when updates are incompatible. * fix(nix): refresh dependency hash for plugin requirements * refactor(plugins): validate test manifest inputs and name compatibility input |
||
|
|
1c51b52d07 |
Restore cached conversations before reconnecting (#4421)
* fix(app): restore cached conversations before reconnecting Give each host one timeline write authority so cache restores, live events and fetched pages publish consistent rows and coverage. Persist a moving 40-entry restart page independently of loaded scrollback, and keep native navigation mounted while startup settings hydrate. * fix(app): recover cache writes after initialization failures Keep accepted writes pending and retry initialization without another UI update. Publish the storage index only after loading succeeds, while queued host identity changes still settle in order. * test(app): wait for sidebar and workspace transitions Viewport intersection and leaving the creation route can precede completed UI transitions. Wait for the layering hit test and selected agent tab before asserting or navigating again. * fix(app): restore provider loading on native clients The native AbortSignal lacks throwIfAborted, so successful provider responses failed before reaching every model selector. Preserve both cancellation boundaries using the supported aborted property and the existing query cancellation error. |
||
|
|
dd7671be20 |
Remember model selections across workspace creation (#4401)
* fix(providers): remember model selections across workspace creation Keep saved model intent independent of catalogue readiness and use the caller-owned host and directory from the first render. Share discovery through provider-owned keys while preserving execution context. Give each target one committed snapshot, publish actual transitions atomically, and share catalogue content across client directory associations. Regression coverage exercises successful repeated creation and provider lifecycle boundaries. * fix(providers): preserve shared cache bodies during eviction Admit directory references with their bodies, retain reference recency during eviction and recovery, and remove the unused persisted index. Refresh known workspace catalogues concurrently. Make repeated-creation picker states explicit and seed mode tests from the installed provider catalogue, preserving assertions across Codex versions. * fix(providers): bound discovery independently per provider Settings refresh repairs all remembered targets, which otherwise starts an unbounded number of discoveries. Admit work after catalogue-key deduplication and keep each provider’s budget independent so a stalled provider cannot block another. Preserve active accounting across configuration changes and settle obsolete queued requests during replacement and shutdown. |
||
|
|
ae80ec4252 |
Let plugins contribute settings screens (#4357)
* feat(plugins): contribute settings screens and shared controls * fix(plugins): localize settings and verify shared UI consumers * test(composer): unmount draft test roots after each case * test(app): keep launch terminals alive until cleanup The fixed ten-second profile lifetime could expire before a busy CI browser attached or finished revisiting the composer. Keep the real process waiting for input until project teardown terminates it. * test(server): await the initial snapshot before a buffered ref event Watcher setup can read checkout facts before the initial refresh publishes a snapshot. Wait for publication before injecting the event so the test consistently exercises a second refresh. |
||
|
|
f4b209be4d |
Preserve nested subagent ownership (#4321)
* fix(subagents): preserve recursive provider ownership Provider events retained enough lineage to identify nested work, but the canonical descriptor discarded the direct owner. Preserve that relationship at ingestion so child timelines and hierarchy remain attached to the process that emitted them, including after history replay. * test(e2e): clarify nested ownership journey * test(e2e): separate nested ownership actions * fix(subagents): retain nested ownership across replay Wait and interaction events can reference descendants without spawning them. Preserve the original spawning owner and route persisted background notifications through that same relationship after history reload. * test(subagents): hide provider turn plumbing * fix(subagents): scope nested tracks to their host Plugin-provided icons are host-specific. Carry the panel host through nested child tracks so the rebased ownership UI resolves the same provider presentation as its parent. * fix(workspaces): preserve launched terminal focus |
||
|
|
f75dff2322 |
Require explicit setup for fork pull requests (#4215)
* feat(workspaces): gate fork repository automation Cross-repository change requests can execute contributor-controlled package hooks even when the setup command comes from the base repository. Persist the checkout provenance and require one explicit workspace-level setup action before repository automation runs. * fix(workspaces): clarify blocked script errors * fix(app): keep long toasts readable * test(server): expect fork repository provenance * test(server): make setup marker portable * test(app): retry transient sidebar layout |
||
|
|
53d824a320 |
feat(plugins): split client and server plugin entries (#4206)
* feat(plugins): phase 1 split runtime entries after green gate The compiler now builds explicit client and server entries, so runtime boundaries are source-owned instead of registration-name filtering. Compiler and runtime tests prove the entry split, directory and suffix boundaries, client Node import errors, migration failure, and client-only operation without a subprocess. * feat(plugins): phase 2 run client entries after green gate Explicit client and server contexts replace the mixed plugin context. The app runner owns every client registration and its idempotent removal, including late contributions and composer pills; focused contribution tests prove the removal contract, and all five migrated examples reached running on the isolated worktree daemon. * feat(cli): phase 3 scaffold runtime split after green gate The generated project demonstrates the required shared RPC contract, server handler, client surface, and sidebar wiring. The scaffold test and an isolated init, typecheck, install, and running check prove the phase acceptance path. * docs(plugins): phase 4 publish migration after green gate Gate: current docs and the paseo-plugin skill describe only explicit client/server entries. The standalone migration guide maps every former registration, and migration-doc.test.ts proves every client add* method remains represented. * fixup! feat(plugins): phase 1 split runtime entries after green gate Gate: compiler 13, runtime 24, typecheck, lint, and format passed. The dependency fixture proved node_modules/client was incorrectly treated as a plugin boundary. * fixup! feat(plugins): phase 2 run client entries after green gate Gate: client runtime and registry tests, typecheck, lint, format * fixup! docs(plugins): phase 4 publish migration after green gate Gate: migration docs test, paragraph audit, typecheck, lint, format * fixup! feat(plugins): phase 1 split runtime entries after green gate Gate: compiler organization tests, typecheck, lint, targeted format * fixup! feat(plugins): phase 2 run client entries after green gate Gate: shared SDK tests, runtime tests, typecheck, lint, targeted format * fixup! feat(cli): phase 3 scaffold runtime split after green gate * fixup! docs(plugins): phase 4 publish migration after green gate * fixup! feat(plugins): phase 1 split runtime entries after green gate * docs(plugins): rewrite public plugin docs for runtime entries and mobile guardrails * fixup! feat(cli): phase 3 scaffold runtime split after green gate The scaffold now keeps DOM globals out of the program and declares only window.open inside client/web.ts. The scaffold test invokes tsc in a fresh process, proves the generated project passes, and proves a stray document access fails. * fixup! docs(plugins): phase 4 publish migration after green gate The internal guide and plugin skill now forbid both the DOM lib and triple-slash DOM references. They direct web adapters to declare only the globals used by client/web.ts. * fixup! feat(plugins): phase 1 split runtime entries after green gate Removing the obsolete Babel parser changes the locked npm dependency graph. The macOS Nix desktop gate reported the new fixed-output hash, which this commit records. * fixup! feat(plugins): phase 1 split runtime entries after green gate Reject relative imports that escape the plugin root while continuing to skip resolved node_modules internals. The compiler regression test proves the escaped import is rejected and the dependency-internal fixture remains accepted. * fixup! feat(plugins): phase 1 split runtime entries after green gate Classify absolute imports and reject plugin-authored paths that escape into node_modules. Dependency internals remain exempt based on their importer path. Red-first compiler regressions cover both bypasses, and the existing dependency fixture remains green. * fixup! feat(plugins): phase 1 split runtime entries after green gate Classify canonical esbuild resolution results so dependency-relative and symlink imports cannot escape client/server boundaries. The two compiler regressions failed before the fix and pass afterward. * test(ci): synchronize flaky state transitions Wait for repository watcher registration before emitting buffered ref events, let sidebar order polling retry unlaid-out rows, and wait for the inactive browser parking state before screenshot capture. These changes directly address the three observed CI failures; the affected server, Playwright, and desktop browser tests pass locally. * test(ci): synchronize Mermaid completion layout assertion The Playwright streaming acceptance test exposed a completion remount between visibility and layout sampling. Check the existing completion promise around the measurement so that transition is not reported as diagram loss. * fixup! feat(plugins): phase 1 split runtime entries after green gate Resolve bare package imports before boundary classification so symlinked dependency entries cannot expose server modules to the client bundle. The focused regression proves the bypass and the compiler file passes 20/20. * fixup! feat(plugins): phase 1 split runtime entries after green gate Allow canonical paths only within a matching linked package root while preserving runtime and containment checks for imports that leave it. The linked-dependency regression fails before the fix and compiler tests pass 21/21 afterward. * fixup! feat(plugins): phase 1 split runtime entries after green gate Reject matching package manifests that contain the plugin or live inside it, so linked-dependency roots cannot exempt plugin or workspace files. The ancestor-manifest regression fails before the fix and compiler tests pass 22/22 afterward. * fixup! feat(plugins): phase 1 split runtime entries after green gate Validate plugin-local lexical boundaries before canonical linked-root exemptions and require remembered-root imports to originate inside that root. The linked-root bypass regression fails before the fix and compiler tests pass 23/23 afterward. |
||
|
|
fa1f01ebbc |
feat(app): add fullscreen viewer for Mermaid diagrams on web (#4107)
* feat(app): add fullscreen viewer for Mermaid diagrams on web Add a fullscreen action to the diagram toolbar that opens the diagram in a modal ZoomableViewport with free zoom/pan, closable via the close button, Escape, or clicking outside the diagram. The sandboxed iframe runtime moves into a shared MermaidIframeRuntime so the inline host and the fullscreen viewer drive the same renderer. Fixes uncovered while verifying the feature end to end: - The runtime measured the rendered SVG with getBoundingClientRect(), which depends on the iframe size the host derives from that very measurement. Each streaming re-render shrank the reported size by the diagram box padding, ratcheting diagrams down to a few pixels. Measure the viewBox instead, which is the diagram's own size, and let the SVG fill whatever frame the host provides. - ZoomableViewport's root is flex: 1, so as a flex item its 0% basis overrode the inline diagram box height and the box collapsed, scaling the diagram tiny and clipping the toolbar unclickable. Size the box from its height and keep a minimum tall enough for the toolbar. - react-native-web's Modal focus trap focuses the first descendant that takes focus, which was the diagram iframe; every keystroke then landed in the sandboxed frame and Escape never reached the window listener. Mark the presentational iframe inert and focus the viewer's content layer on mount. Closes #4106 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(app): exercise Mermaid fullscreen interactions Cover zoom, pan, outside-click dismissal, and streaming layout through the real browser path. Remove the mocked DOM test that duplicated those behaviors without exercising the viewport or iframe runtime. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com> |
||
|
|
ac085d50c6 |
Make import session one tap away and make the sheet find your session (#4216)
* feat(app): make import session a first-class entry point * feat(server): improve import session discovery resilience Search up to 500 persisted candidates per provider and isolate listing failures behind eight-second deadlines so healthy results remain usable. Preserve native persisted title precedence while keeping discovery read-only. Parsing now continues through each transcript so native titles and the true last prompt are retained; the old early exit made lastPromptPreview equal firstPromptPreview. A cheap record prefilter avoids JSON.parse for unrelated lines during the full scan. * feat(app): searchable, grouped import session sheet with per-provider errors The sheet now carries a debounced search field gated on the host's importSessionSearch feature, grows its per-provider page on demand (15 -> 45 -> 90 -> 200) instead of capping at the 15 most recent, and groups unscoped rows by folder under the project name the app knows for that directory. Worktrees of one project would otherwise all head up as the same bare name, so a directory below its project root carries the path under that root beside the name. A workspace-scoped sheet says so and offers "Show all sessions". Importing a row from another directory once that is on drops the workspace from the request, which the daemon would otherwise reject, and routes the user to the agent's own workspace rather than opening a dead tab in the current one. That decision lives in resolveImportTarget: a scoped listing is trusted wholesale because the daemon already matched it realpath-aware, which the client cannot. A provider that fails now gets its own error row with a Retry that refetches only that provider, sourced from both a failed request and the daemon's providerErrors. React Query no longer retries these, so the header spinner settles once every provider has answered or given up (#2512). * test(e2e): import session flow with fixture Claude sessions * fix(app): shorten import sheet scope copy so it fits at phone width At 390px the scoped subtitle rendered "Sessions in this wor..." — the label, its action, and the sheet's refresh and close buttons do not share one line at phone width. The label already sits under the sheet title "Import session", so "Sessions in this" was repeating context the header had: it becomes "This workspace" and the action becomes "Show all". The unscoped label still names the host, which nothing else on the sheet does. * refactor(app): flat recency-ordered import list with per-row folder label Grouping by folder buried recency: a busy directory's block of fifteen pushed a two-minute-old session in a quiet directory below the fold, and the axis a user is scanning on is when they last touched the session, not where it lives. The unscoped sheet is now one list in the order aggregateSessionEntries already produces, and each row carries its folder as a muted third line. resolveDirectoryLabel stays — it is what keeps two worktrees of one project apart — and formatDirectoryLabel renders its two parts as the single ellipsised line a row has room for. A scoped sheet lists one directory, so its rows carry no folder line at all. * fix(app): close import sheet before refreshing sessions Provider-list invalidation waited for every active query before the sheet could close. Mark the disabled picker queries stale after close and navigation so reopening refreshes without delaying the imported agent. * fix(server): reuse workspaces for session imports Host-wide imports should join the oldest active workspace for their directory, or restore its archived workspace, instead of minting duplicates. Rollback remains limited to workspace records created by the failed import. * test(e2e): express the import session journey through a helper DSL * test(e2e): align sidebar journeys with import entry point |