* fix(app): dismiss touch composer focus when switching tabs
A retained tab can hide and return before Chromium paints, preserving the focused textarea without another focus call. Blur web touch editors when their pane becomes inactive so returning leaves the conversation ready to read. Exercise the same mounted editor through a second edit and keyboard navigation cycle.
* test(app): reproduce touch tab focus race before paint
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Grouped sidebar projects chose the first eligible host even while a workspace on another host was active. Prefer that active host when online and eligible, keeping the existing fallback for other placements.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Michael Whitman <michael.whitman@getsteward.ai>
* feat(app): rank, highlight, and timestamp Command Center results
Workspace and agent rows now show when they were last active, rank by
match quality and then recency, mark the query terms in every result
kind, and lead workspaces with their project icon.
Ties used to be broken by where the match landed in the string, so
"Deliver sidebar usage" beat "expose usage data" on sentence position
and recency almost never decided anything. Built-in rows now compare
only which fields matched and the matcher tier, then the most recent
row wins. The empty query follows the same rule, so it lists the most
recent workspaces and agents instead of alphabetical workspaces and
status-first agents.
Row and section heights live in list-metrics: a layout picks one row
inset and every height derives from it plus shared line boxes, which
the styles also read. The layout is chosen once at the top, so rows
never branch on it, and two-line rows are now vertically centered.
Desktop is denser (32/46); compact keeps 44pt touch rows (44/58).
findHighlightRanges takes the matcher's options so the marks match
what the list matched: the Command Center is not typo-tolerant, the
history list is.
* fix(app): keep the project icon out of Command Center row names
The fallback project icon draws the project's initial as text, which
became the first word of the row's accessible name ("C Command Center
..."), so screen readers announced it. The icon is decorative.
The scroll spec now reads the newest seeded workspace: the empty query
lists the most recent workspaces, so the first one seeded is no longer
among them.
* fix(usage): retain last successful data when polls fail
Keep the latest poll outcome separate from its successful snapshot so errors remain visible without discarding known usage or advancing its freshness. Preserve attempt timestamps for response ordering and isolate retained values by scoped login set.
* fix(usage): preserve history through discovery failures
A failed account-store read does not confirm that an account was removed. Retain its existing host mapping until successful discovery replaces it, without carrying mappings across agent launch environments.
* fix(app): group native assistant prose into selectable runs
* fix(app): render selectable native prose with enriched markdown
* docs: explain native prose selection boundaries and tradeoffs
* fix(app): preserve markdown semantics and render limits in prose runs
* docs: record native prose compatibility fixes and heading limits
* fix(app): allow additive task lists and admonitions in native prose
* fix(app): preserve inline image formatting and reference scope
* fix(app): preserve markdown source and file actions in native prose
Prepare original source blocks with source-position edits from the assistant parser. Keep reference scope and live/history projection consistent, and retain link text when dispatching workspace file actions.
* fix(app): preserve native prose entities and links while bounding preparation cost
* fix(app): preserve provisional syntax at cached reveal boundaries
* fix(app): bound prose append work and preserve markdown edge cases
* fix(app): preserve home paths and normalized prose links
* fix(app): preserve parser-owned links and streaming tilde tails
* fix(app): keep growing markdown on the streaming block renderer
* fix(app): preserve bracketed IPv6 links in native prose
* fix(app): skip prose classification for blocks over the render cap
* fix(app): share selectable native prose across Markdown surfaces
* fix(app): preserve Markdown containers before grouping prose
* revert(app): remove shared prose-run renderer changes
Reverts ab4737bb2 and ac47e1063 before replacing the Markdown engine across all platforms.
* feat(markdown): use enriched renderer across all platforms
* test: verify library image controls and isolate website config
* fix(markdown): preserve file links and image revisions
* fix(markdown): restore diagrams and preserve link origin
* fix(markdown): retain diagram identity and reference scope
* fix(markdown): avoid empty web history measurements
* fix(markdown): preserve authored empty link titles
* test(markdown): exercise find across whole-message history
* fix(markdown): preserve encoded image paths after rebase
Keep the shared renderer image normalization and file-read regression coverage that originally lived in the main integration merge.
* fix(markdown): derive table and task list colours from the theme
* fix(markdown): restore selection highlight on web
* fix(markdown): restore file link hover tooltips on web
* fix(markdown): normalize inline HTML before rendering
* fix(markdown): create renderer types when applying package patch
The published nightly omits both MarkdownRenderers type files. Represent them as new files so patch-package succeeds on a fresh npm install, matching the already patched development package.
* fix(markdown): keep the renderer tree stable while images resolve
* fix(markdown): size inline images to the line and collapse failed loads
* fix(markdown): normalize only parsed HTML nodes
* fix(markdown): keep the first image reference definition
* fix(markdown): preserve complete assistant messages on native
* fix(markdown): classify web links explicitly and complete renderer types
* fix(markdown): preserve spacing between HTML elements
* fix(markdown): normalize HTML following code elements
* fix(markdown): preserve literal image fallback labels
* fix(markdown): retry transient image decode failures
* docs(markdown): record native selection and image limitations
* fix(markdown): preserve first definitions across Mermaid segments
* fix(markdown): reserve pending images and harden web rendering
* test(markdown): cover state retention during image arrival
* fix(markdown): preserve reader position while responses grow
Keep the same message component boundary when a live row becomes retained history, preserving Mermaid runtimes and selection nodes. Observe paced child text commits at the web viewport so sticky-bottom follows before the next frame.
* test(markdown): require growth while holding a streaming paragraph
* test(markdown): isolate image arrival from reload positioning
Mounted row totals change as history virtualizes, so observe the new response and tool directly. Hold the real PNG response after reload until the reading position settles, then verify its reserved height and viewport instead of attributing initial bottom-follow positioning to image decoding.
* fix(markdown): preserve adjacency around inline HTML
* fix(markdown): keep summary block boundaries outside bold text
* perf(markdown): scan HTML block boundaries incrementally
* test(markdown): gate delivery while establishing a held reader
* test(markdown): name shared renderer setup and file actions
* test(markdown): assert linear HTML boundary work without timing
* test(markdown): check large HTML table output without observing built-ins
* fix(markdown): restore accentBright link colour
* fix(markdown): require letters and known extensions in bare path recognition
* fix(markdown): match main's blockquote, bullet and h6 colours
* fix(android): keep inverted chat still during text selection
React Native decomposes Android list inversion into a 180-degree rotation with positive scaleY, so the existing visibility-scroll guard never recognized the inverted viewport. Inspect the composed matrix and reproduce the actual list transform through the native view manager in regression tests.
* fix(markdown): linked inline code keeps inline code size
* fix(markdown): reserve a placeholder box with an image glyph while images load
* fix(markdown): bare prose paths need an extension, line suffix or depth
Slash-separated English pairs were recognized as file links in prose. Require stronger path intent there while preserving the existing inline-code recognition rule.
* fix(markdown): horizontal blocks own their drags on compact layouts
* perf(markdown): keep native streaming updates in the unfinished tail
Native markdown reparses each updated view in full. Keep parser-closed blocks in a memoized head during paced reveal, then reunite completed prose for cross-paragraph selection. Preserve trailing spacing and source indentation at the split.
* perf(markdown): reuse Android measurements across view mounts
* test(markdown): bare Fabel/model and daemon/client stay plain text when copied
a502cfd0d stopped linking bare two-segment prose paths; the clipboard
expectation still carried the links from the earlier loose pattern and
failed on CI (playwright shard 1/4 on 60b4e2d05). main expects the plain
text, so restore that.
* perf(app): limit native chat mount batches
Whole-message Markdown rows measure synchronously during Fabric layout. Render fewer history rows per batch and retain a smaller offscreen window so opening a chat does not measure twelve history rows at once.
* fix(app): keep populated markdown in sync with native layout
Fabric can collapse streaming blocks before asynchronous rendering replaces the old text, producing a blank frame. Commit populated-view updates synchronously while retaining worker preparation for first mounts to avoid moving large history parsing onto the UI thread.
* perf(app): preserve unchanged sidebar workspace projections
Reopening a retained sidebar rebuilt its entry collection and invalidated every project row despite unchanged workspace data. Preserve collection identity as well as row identity, while keeping membership and ordering changes observable.
* Revert "perf(app): preserve unchanged sidebar workspace projections"
This reverts commit a3919faed4.
* feat(app): run and watch workspace scripts from the compact header menu
The compact header redesign dropped the mobile scripts button, so phones had
no way to run, stop, or restart workspace scripts. Scripts are now a page of
the ⋯ sheet: the root row shows what is running, tinted by health, and the ⋯
carries a dot while a running service is unhealthy. Desktop keeps its popover;
both surfaces share one set of script actions and one status model.
Script status also froze on native whenever the sidebar was closed and no
agent tab was viewed. DirectorySynchronizer releases its subscriptions,
including script_status_update, when nothing demands the directory, and the
workspace screen never demanded the workspace it shows. Web hid this because
the favicon sync holds demand for the whole session. The focused workspace now
holds directory demand, which also lets Restart's second half (start once the
daemon reports the script stopped) complete.
* perf(app): keep workspace script status live without syncing the directory
The focused workspace held full directory demand so the scripts menu saw
script_status_update on phones with the sidebar closed and no agent tab.
That fetched the host's agent and workspace lists on every focus and
streamed every agent and workspace update for the whole host.
Script status now has its own event subscription, which a workspace
screen can hold alone. It fetches the workspace's scripts once when the
subscription lands, because the subscription only carries changes.
Directory demand also holds it, so the sidebar and agent tabs see the
same updates as before.
* fix(app): let Escape close an open popover before it interrupts the agent
The agent.interrupt shortcut listens on the window in the capture phase, so
it sees Escape before anything else. Registered overlays get first refusal
through dispatchTopWebOverlayKeyDown, but plugins cannot register, and draw
their floating panels with the Popover API instead. With such a panel open
and the composer focused while an agent runs, Escape closed the panel and
interrupted the agent in the same press.
While a popover is open, Escape is left to it and no app shortcut runs. A
second Escape, with nothing open, interrupts as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
* test(app): cover Escape with a popover open through the shortcut path
The first test only checked hasOpenDomPopover and would still pass with the
guard removed from useKeyboardShortcuts. The check is now keyBelongsToDomPopover,
called by the hook, and a browser test drives it the way the app does: a
window capture listener for shortcuts, a popover closed by its own document
Escape listener, focus in a textarea. The first Escape closes the popover
with no interrupt; the second interrupts. Both new tests fail with the guard
removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
* test(app): exercise plugin Escape through the real composer
Keep the contributor fix and replace the substitute shortcut-listener test with an installed-plugin browser regression. The first Escape must close the preview without cancelling the turn; the second must cancel it.
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
* test(app): check dismissed model pickers leave the viewport
---------
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): scroll the Add Project results to the keyboard-selected row
The results list had no scroll ref, so ArrowUp/ArrowDown moved the selection off-screen; the autocomplete reveal math is now shared through useRevealActiveItem.
* test(app): verify the directory selected beyond the results viewport
Check scrolling in both directions and across wrapping, then confirm Enter opens the selected directory. The previous regression used an aria-selected attribute that result buttons do not render.
Co-authored-by: superheher <heh@vivaldi.net>
* test(app): verify scroll reveal through browser behavior
Keep pure offset checks and cover keyboard navigation, wrapping, and replacing search results in the real Add Project flow.
* fix(app): measure retained Add Project rows when revealing selection
* fix(app): measure scroll content through a native View reference
---------
Co-authored-by: superheher <heh@vivaldi.net>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): focus chat feed when clicking message content
* test(app): wait for new chat output before checking scroll position
Co-authored-by: Ivan Pantic <panta@talentkit.io>
* test(app): capture the keyboard paging regression
* test(app): verify paged chat text stays in place
---------
Co-authored-by: Ivan Pantic <panta@talentkit.io>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): preserve usage provider order across refreshes
* test(usage): keep refresh and scrolling journeys stable
Usage reports can follow the long fixture card, so a fixed wheel distance
can scroll past its final window. Target the named windows with wheel
input while preserving the visibility checks. Assert refreshed provider
order through visible names and keep viewport setup in journey helpers.
Co-authored-by: Sleroq <hireme@sleroq.link>
---------
Co-authored-by: Sleroq <hireme@sleroq.link>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Preserve the bundled Hoplite ACP adapter and provisioning transformer, activate it only after Add, and provide a searchable provider grid. Restore the contributor's original icon without the copyright mark.
Co-authored-by: Odin Glynn-Martin <odin.glynn@icloud.com>
* Put sheet tabs on the one sheet stack
Sheets are one primitive with one stack. A sheet opened from any sheet
stacks on top of it, and closing it uncovers the sheet underneath. The
compact workspace's file and subagent sheets were an in-tree Gorhom
BottomSheet with their own stacking, so a subagent chosen from the
subagents list sheet replaced the list instead of covering it.
- SheetHostScope: a subtree hosts the sheets presented inside it, so the
sheet tabs draw inside the compact column and read the workspace
providers while still joining the app-wide stack. Needs Gorhom's
BottomSheetModalInternalProvider, which the patch exports.
- IsolatedBottomSheetModal takes enableDismissOnClose; false keeps the
content mounted through a close so the unsaved-changes guard can veto
it. Its backdrop then closes the sheet rather than dismissing the top
modal. The Gorhom patch also keeps a dismissing modal's status through
its final onChange(-1), without which such a modal unmounting while
presented never left the portal.
- SheetTabs (was WorkspaceSheetHost) renders one IsolatedBottomSheetModal
per store entry. The store never reorders sheets; a reveal of a covered
sheet drops the unmodified sheets above it.
- Subagent rows that open a sheet keep the list open (closeOnSelect
false); rows that open a tab still close it.
- A menu closes when its retained panel goes inactive, instead of hiding
and reappearing with the panel on the next tab switch.
Co-Authored-By: Claude <noreply@anthropic.com>
* Show an agent's model and thinking in its sheet, and fit Open as tab in the header rail
A Paseo agent shown in a sheet has no composer, so the facts the composer's
model and thinking controls would show now sit in one line under the sheet
header, the same row provider subagents already get from the daemon. The line
resolves exactly as the controls do (resolveAgentModelSelection), from the
session agent and the providers snapshot, and shows wherever the pane host is
a sheet.
Open as tab becomes the full-screen glyph beside Close, drawn by the shared
SheetHeaderIconButton, so the title keeps its width. The tab switcher sheet is
titled "Workspace tabs" so it reads as what it lists.
Co-Authored-By: Claude <noreply@anthropic.com>
* Show mode and context usage in the sheet facts line
The line under a sheet's header now carries everything the composer's
controls and context meter show: model, thinking, mode, and used/max context
tokens with the share, each resolved as its control resolves it.
Co-Authored-By: Claude <noreply@anthropic.com>
* Re-render subagent rows when the workspace's tabs change
A row decides at render time whether choosing it opens a sheet or reveals
a tab, and keeps the list open only for a sheet. The hook behind it only
subscribed to the layout mode, so a row rendered while its agent was a
full tab kept closing the list after that tab was gone: on iOS, promoting
a managed child, closing its tab, and reopening it from the list dropped
the list under the sheet. Subscribe to the workspace's layout too.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Separate composer keyboard eligibility from pane focus restoration. A sidebar dismisses the keyboard without hiding the pane, so its close must not restore editing focus.
Decode assistant Markdown image destinations before resolving a filesystem read. Keep plain filesystem paths and direct image URIs unchanged so Markdown Preview and literal percent filenames are decoded only once.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Stack workspace sheets and tighten sidebar density
- Workspace sheets stack: opening a target from a sheet covers it, Back and
drag close only the top sheet, and the header is the sheet's drag handle.
- The workspace sheet reuses the shared sheet handle, background and header.
- Fix the shared sheet handle indicator rendering black.
- Sidebar text returns to semantic sizes; Pinned and Workspaces share one
section header.
* Pause covered workspace sheets and mock the shared sheet handle in tests
* Export BottomSheetHandle from the browser test Gorhom stub
* Let a stacked sheet's backdrop catch taps on iOS
* Bring a revealed sheet to the top instead of duplicating it as a tab
* Let readFile callers disable the request timeout
readFile resolves only when FileEnd arrives, so the default 60 s request
timeout bounds the whole transfer. A large file over a slow relay fails
before it finishes even though every chunk is arriving. Thread an optional
timeout through requestFileExplorer; 0 disables the timer, as the
workspace.create request already does, and a dead connection still rejects
on disconnect.
The relay e2e case reads a 1 MiB file through a real daemon and a local
relay with timeout 0 and compares SHA-256 digests with the source.
* Reject a binary file transfer whose byte count differs from FileBegin.size
concatByteChunks allocated FileBegin.size bytes and copied the chunks in:
a short transfer resolved as a zero-padded file, and a long one threw a
RangeError inside the transport callback and left the request pending.
Count the received bytes at FileEnd and reject the read when they differ
from the announced size.
* Download files over the session socket when no direct connection is active
The download store only knew the HTTP token route and picked the first
directTcp connection in the host profile, whatever connection the host
runtime was using. A relay-only host has none, so every download ended in
"Download host is unavailable." (#543). SSH, socket and pipe hosts failed
the same way, and a stale directTcp entry won over a working relay (#3753).
Choose the transport from the active connection: directTcp keeps the HTTP
token download unchanged; anything else reads the file over the session
socket with the binary frames previews already use, then hands the bytes
to the browser's download manager on web and Electron or writes them to
the cache directory and offers the share sheet on native. Files over
64 MiB are refused before any byte moves, using the existing fileTooLarge
string, and a daemon response without content fails instead of saving an
empty file.
* fix(app): enforce the session download size limit during the read
The token size is sampled before the file is read. Pass the download byte budget through the actual read so a changed file cannot bypass the in-memory limit.
Co-authored-by: Michael Vandesompel <mv@droggol.be>
Co-authored-by: YASICJUNWOO <joonoo3@naver.com>
Co-authored-by: JrDw0 <JrDw0@users.noreply.github.com>
* test: verify TCP downloads and own relay fixture cleanup
---------
Co-authored-by: Michael Vandesompel <mv@droggol.be>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: YASICJUNWOO <joonoo3@naver.com>
Co-authored-by: JrDw0 <JrDw0@users.noreply.github.com>
* fix(app): keep long custom models removable
Custom model labels and IDs could consume the row width and push Remove outside the sheet. Allow those text cells to shrink while preserving the button width and short-name alignment.
* test(app): wait for legacy agent creation before checking prompts
Agent-list membership includes initialization. Waiting for finish at that point can return on the initial idle transition before the initial prompt starts. Wait for the matching creation response before checking each completed turn, preserving the existing exact-once assertions.
* test(app): wait for directory search keyboard focus
The search page becomes visible before its deferred focus callback runs. Wait for the input to own keyboard focus so the journey types its query rather than dropping it during navigation.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Show Claude option previews in the question card
* Wrap long lines in question option previews
* Check option previews under the option that owns them
* Show the whole option preview on native so every line is reachable
On Android, a slow drag inside a preview capped at 240px did not scroll
it, so lines past the cap could not be read. The preview's nested
scroll sits inside the option's Pressable; the same drag scrolls it
once the preview is outside the Pressable. Native now renders the
preview at full height so the chat scroll reaches every line, and a tap
on it still selects the option. Web keeps the 240px cap.
The mock agent's rollout preview is now taller than the cap so the
Android replay can check the last line.
---------
Co-authored-by: Gabriel Giordano <hello@gabrielgiordano.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(app): open files and subagents in a closable sheet on compact
On phones, opening a file, a provider subagent, or a Paseo subagent now shows it
in a bottom sheet over the current tab instead of adding a tab, so the tab strip
stays for terminals and root agents. Paseo subagents get "Open as tab" to move
the same tab (same tabId and state) into a full tab with a composer; agents in a
sheet hide the composer.
The decision lives in the workspace layout store's openTab, driven by a
per-kind `compactSheet` rule in the panel manifest, so every open path (chat
links, Explorer, subagents track, notifications, plugins) follows it without
call-site branching, and a new kind opts in with one manifest entry. The app
shell publishes the layout mode (compact / single-pane / split-panes) once, so
callers stop passing isCompact for placement.
A sheet holding unsaved edits is never implicitly displaced; later opens go to
tabs underneath, and closing it goes through the existing discard guard. The
editor host is position: sticky because CodeMirror's scrollIntoView otherwise
scrolls the bottom sheet's container and drags the sheet to full height.
* test(app): fail the compact sheet snap check when the sheet is oversized
---------
Co-authored-by: paseo-bot[bot] <paseo-bot[bot]@users.noreply.github.com>
* Keep pairing QR code within compact tile
* Move pairing screenshot after viewport assertion
* test(pairing): verify the complete QR code on Android
The native regression failed with the fixed-size SVG because the code was clipped at both screen edges. Verify the complete square and visible margins through the Android app, and keep browser geometry assertions in the pairing helper.
Co-authored-by: fdkevin <25277753+fdkevin0@users.noreply.github.com>
---------
Co-authored-by: fdkevin <25277753+fdkevin0@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): jump the chat outline to prompts a timeline plugin draws
A timeline transformer replaces a stream item with plugin rows whose ids
are "<pluginId>/<itemId>". The chat outline, chat find and history reveal
look a message up by the source item's id, so once a plugin drew the
prompts the outline found no row: the rail showed the prompts and
clicking one did nothing, for mounted, virtualized and unloaded prompts
alike.
Projected plugin rows now carry the id of the item they replaced, and
getStreamItemMessageId answers with it. Row wrappers, the virtualized
index lookup, the visible-message set and history reveal all read that
function, so they now find the plugin row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(app): verify outline jumps to virtualized plugin prompts
Replacing every prompt keeps all loaded rows mounted, so the original plugin regression never exercised a virtualized target. Retain newer native prompts and verify that a loaded unmounted plugin prompt lands without fetching.
Co-authored-by: luopeihuan <luopeihuan@roundreddot.com>
* test(app): retain outline navigation screenshots
---------
Co-authored-by: luopeihuan <luopeihuan@roundreddot.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(app): add fullscreen editing to the compact composer
* fix(app): preserve read-only terminal command previews
Static command previews must render their published source; the live editor buffer is updated after render and has no mounted input to repaint read-only content.
* fix(app): import React for composer editing scope rendering
Workspace pane unit tests use the classic JSX transform, so the editing-scope provider needs React in scope when mounted.
* fix(app): preserve fullscreen composer controls and editing
Keep the fullscreen frame inside the visual viewport, gate hidden autocomplete keys, apply replacement commands to local line measurement, and return to inline before starting voice so its controls stay accessible.
* test(app): cover draft replacement through the production composer
Replace the measurement-hook fixture with profile switching and typing through the real composer. Keep measurement state inside its production surface; Android manual red/green evidence covers the native replacement flow.
* feat(app): modernize compact headers and sidebar affordances
Use a shared island header with quick tab creation and keep sheet footers inside their visible layout. Opt sidebar rows into touch sizing without changing global appearance settings.
* test(app): match darker light-theme workspace selection
* test(app): align plugin journeys with compact header
* feat(review): edit compact diff comments in a sheet
* feat(review): send diff feedback to workspace agents
Send saved reviews from the diff while retaining file navigation and unrelated composer drafts. Clear acknowledged versions only so comments edited or added during submission remain available.
* fix(review): preserve drafts and coordinate feedback across surfaces
Share pending submission ownership by draft while keeping recipient menus local. Retain editable text across presentation changes and acknowledgement, and reserve safe-area clearance for floating actions.
* fix(review): center a small primary feedback button on desktop
* fix(review): restore focus to feedback button after closing menu
* feat(server): let clients create internal agents
The daemon's own branch-name and commit-message generators run as
internal agents: never persisted, listed, flagged, announced, or seen by
plugin lifecycle hooks. Nothing outside the daemon could ask for one,
because the wire config schema strips `internal` and the agent.create
transform re-pins it. A plugin that runs a one-turn helper per event
therefore left an archived session in History every time.
Add an optional `internal` flag to `create_agent_request` (inherited by
`agent.create.request`), copy it onto the config in the session's create
path, and skip provider session persistence for it like the generators
do. The client SDK forwards the flag on both creation paths and gates it
once on `server_info.features.internalAgents`.
Two daemon paths assumed every agent is public and had to follow:
`resolveAgentIdentifier` now accepts a live agent by exact id, since the
id set it matched against came from listings that exclude internal
agents and left `wait_for_finish` answering "Agent not found"; and
archiving an internal agent now closes it without writing the record
that `registry.applySnapshot` used to leave behind, so `autoArchive`
does not accumulate hidden files on disk.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(server): keep internal agents whole through failure, archive, and wait
Review follow-ups for internal agents:
- A create that fails after the runtime is registered now closes the
internal agent before rethrowing, so a hidden runtime cannot outlive
the failed request. Public agents keep today's behaviour.
- Archiving an internal agent cascades to attached children like any
parent. cascadeArchiveChildren takes the parent record instead of
reading it back from storage, where an internal agent never is.
- The archived snapshot and last message of an internal agent stay
readable by exact id for ten minutes, so create-then-waitForFinish
returns the finished turn whether the wait lands before or after an
auto-archive. Storage plays that role for public agents.
- The internal-agent e2e scenarios live in the existing
create_agent_request lifecycle suite rather than a new file.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(server): cascade to live internal children and keep a failed internal create's worktree until it closes
Internal children never reach storage, so the parent archive cascade scans
live agents for them as well. A failed internal create now clears the
created id only after the runtime is archived, so worktree cleanup cannot
delete the directory under a runtime that would not close. Stored agent
payloads carry lastError, so a retained failed helper reports its error
through wait_for_finish instead of the generic message.
* feat(cli,mcp): expose internal agents and document --no-wait
paseo run gains --internal and create_agent gains internal, both forwarded
to the create request. --no-wait becomes the documented spelling for
returning immediately, matching paseo send; --background and --detach stay
as hidden aliases under COMPAT tags.
* test: follow the --no-wait rename and stub getRetiredInternalAgent
* feat: list internal agents on request and add internal workspaces
Internal agents stay hidden by default, but a caller can now ask for
them: `fetch_agents_request` takes `filter.includeInternal`, snapshots
carry `internal: true`, and `paseo ls --internal` shows them in an
INTERNAL column. The app never passes the flag.
Workspaces get the same flag. `workspace.create.request` with
`internal: true` persists a hidden workspace: listings, update
subscriptions, sync reads, empty-project counting, open-by-path, MCP
`list_workspaces`, and plugin workspace hooks all skip it unless the
caller opts in with `filter.includeInternal`. Every agent created inside
an internal workspace is internal and unpersisted; AgentManager applies
that from the workspace id before the plugin hook so creators cannot
opt out. Archive and shutdown enumerate internal agents so nothing
inside an internal workspace outlives it.
Surfaces: `paseo workspace create --internal`, `paseo workspace ls
--internal`, `paseo run --internal --new-workspace`, MCP
`create_workspace({ internal })`, SDK `workspaces.create({ internal })`
and `list({ filter: { includeInternal } })`. Clients gate on
`features.internalWorkspaces`.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(workspaces): add background visibility with inherited creation defaults
* fix(cli): resolve workspace callers on the selected daemon
* test(workspaces): keep caller integration coverage in server suite
* test(cli): exercise workspace callers through CLI integration harness
* test(mcp): remove obsolete execution flags from browser fixtures
---------
Co-authored-by: marvin-ambrozio <marvin@gustavo.eng.br>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(agents): preserve creation when the first prompt fails
A rejected initial prompt must leave a created agent with a turn error instead of returning the user to a draft whose creation key is already committed. Preserve receipt replay without repeating the prompt, and bound fork history to 100,000 characters before it becomes an attachment.
* test(creation): isolate user agent counters from background naming
Workspace naming uses internal provider sessions. Counting and rejecting those sessions made creation assertions depend on installed provider discovery timing. Keep discovery within the test adapters and scope observations to user-created agents.
* test(creation): retry temporary directory cleanup on Windows
Windows CI completed the rejection assertions but failed teardown with EBUSY. Match the daemon test harness bounded removal retries so transient directory locks do not fail creation tests.
* fix(agents): record rejected prompts before their errors
Startup rejection recorded only the system error, leaving the client to append its local prompt after it. Record the rejected submission at the terminal failure boundary so stream and fetched history agree on prompt-before-error ordering.
* fix(agents): require acceptance for delivery notifications
Retain rejected human submissions before their errors without projecting rejected inter-agent prompts as completed deliveries. Reproduces and fixes the notification regression exposed by merge CI.
* test(agents): retain rejected submissions across retries
The existing browser retry test assumed terminally rejected prompts disappeared. Assert the intended prompt-before-error history and a separate accepted retry while preserving draft, attachment, and ambiguous-steering checks.
* test(creation): allow integration latency in raw RPC responses
The peer inherited a one-second polling timeout despite exercising Git and provider startup. Reproduce the timeout with delayed startup and wait within an explicit integration budget while retaining all response and identity assertions.
* fix(app): show PR panel state, activity verbs, and thread badges in the app language
The PR panel's state label, the verb after each activity author, and the
Resolved and Outdated thread badges were English literals, so they stayed
English in every other language. The state and verb translations already
existed and were unused; the badges get new keys in every locale.
Closes#6214
* fix(app): translate every PR panel verb and state, and phrase them after the author
The zh-CN state and review labels still held English ("Open", "Draft",
"已 review"), and the Spanish and Portuguese verbs were adjectives that
do not read after an author's name. The translation test now checks
each verb and state on its own, so one English label fails it.
Co-authored-by: Dillon <dillonzq@outlook.com>
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Dillon <dillonzq@outlook.com>
* feat(plugins): enable registry installs by default
* fix(plugins): explain explicit Git source for missing registry IDs
* fix(plugins): tailor missing registry guidance to installation
* test(app): sample footer geometry in one browser frame
* fix(plugins): make registry source resolution deterministic
Directory existence must not change the meaning of a registry identifier shared by CLI, RPC, and UI installs. Classify explicit host paths in the source parser and explain recovery from registry transport and revision-selection failures.
* fix(plugins): recognize dot and Windows directory sources
* test(cli): wait for readable daemon ownership before cancellation
The cancellation test raced PID-file creation against its first write and parsed empty JSON in CI. Use the public daemon-instance reader to wait for and capture the supervisor PID before asserting cancellation cleanup.
* test(app): expect missing directory install guidance
* Open Usage as a modal over the current screen
The sidebar Usage icon and summary now open Usage in the adaptive modal
(a dialog on wide layouts, a bottom sheet on compact ones) instead of
navigating to the /usage screen, so closing it leaves you where you were.
The /usage route and UsageScreen are removed.
* Reset Usage modal cards when the host changes; keep scroll test body linear
* fix(app): close hover cards when the pointer leaves a clicked trigger
A hover card stayed open while any focus sat inside its trigger or content,
so clicking a sidebar workspace row (which focuses the row's button) pinned
the card after the pointer moved away, until Escape or a click elsewhere.
Only keyboard-visible focus now keeps the card open, which preserves the
keyboard entry path added with the shared hover card.
* fix(app): keep a hover card open when keyboard focus arrives during its close delay
Recheck keyboard focus when the close timer fires, and move the hover card e2e steps into sidebar helpers.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(muse): show usage only for the agent account
Usage discovery ignored session scope and exposed all remembered accounts to unrelated agents. Resolve session accounts from their own launch environment while preserving global discovery.
* test(muse): own the usage scope regression in the plugin suite
* fix(usage): scope agent reports to their own logins
Account grouping also merged fetch inputs and cache entries across agent and host queries, hiding the agent login failure behind another harness. Preserve scoped login sets, collect every host failure with source-owned harness labels, and keep refresh results within their requesting view.
* test(usage): expect harness labels in discovered accounts
* fix(usage): keep details hoverable and restore compact sidebar sheets
Share hover-card mechanics with workspace details so context usage can stay open while users press Refresh. Honor Retry-After for rate-limited usage requests, and let both sidebar entry points share the compact sheet.
* fix(usage): restore settings cog with a fitted popover and compact sheet
Keep shared Usage preferences in the title controls on every surface. Settings rows use the menu surface directly, avoiding the trailing margin and duplicate card border from the page layout.
* fix(app): show account usage before the first agent turn
Keep the context meter available for existing agents without context readings so account usage can be opened before a turn. Show an empty ring and an explicit no-data state until readings arrive.
* fix(usage): preserve keyboard access and hover card positioning
Context details must remain reachable by keyboard after replacing the tooltip. Keep focus within the card, restore side flipping, and dismiss detached cards when the viewport moves. Prune expired cooldowns even after credential rotation.
* chore: remove QA screenshots from the source tree
* fix(app): let hover cards claim Escape before agent shortcuts
Register hover surfaces with the shared overlay keyboard owner without moving or trapping focus on pointer hover. Component-local Escape handling ran after the global interrupt shortcut and cancelled the underlying turn.
* fix(app): reopen focused hover cards with button keys
Keep focused triggers registered with overlay arbitration after dismissal so activation keys are handled before composer shortcuts.
* fix(app): release overlay ownership when hover cards close
Closed triggers handle activation locally, so a dismissed card does not block pane Find. Open cards retain shared Escape arbitration to protect running turns.
* test(app): pin Linux keyboard identity for composer Find
* test(app): synchronize context usage hover journeys
Return focus to the composer before asserting pointer dismissal, wait for the card to reach the viewport, and park the pointer before reloading so it cannot consume the next scripted usage response early.
A tablet or an unfolded foldable is wide enough for the desktop layout,
so opening an agent autofocused the composer and raised the on-screen
keyboard over the conversation. Autofocus on open now also requires a
fine pointer, the same signal the app already uses for hover.
A mouse-driven window still focuses the composer when an agent opens,
and sending a message still keeps the composer focused.
Co-authored-by: Phieu Dinh <phieu@fiu.one>
* feat(usage): stream reports for live agent accounts
Resolve account scope from the running harness environment without invoking lifecycle hooks. Keep the client aggregate result while delivering report updates as each fetch settles; the app presentation follows separately.
* fix(usage): retain account scope for open agent sessions
Capture the launch environment once per session, independently of lazy child-process startup or exit. Use a session UUID for registry identity. Update remaining global discovery fixtures to the scoped SDK contract.
* test(usage): update browser fixtures for scoped report streams
* feat(usage): let a usage card show window rows without pins
UsageCard takes a required pinnable prop. When false, a window row is a
plain bar: no Pressable, hover highlight, or pin glyph. The settings
Usage list passes true.
* feat(usage): show the agent's usage in the context window popover
Reports stream into the query cache as each update arrives, for the
host list and for a new per-agent query keyed by agent. The context
window popover fetches its agent's reports while open and renders one
unpinnable card per report as it lands, a loading sentence before the
first, the request error as a sentence, and nothing on hosts without
usage reports.
* feat(usage): hide the Refresh button on popover usage cards
UsageCard takes a required refreshable prop. The popover passes false
because its content takes no pointer events, so the button could not
be pressed; the card then shows its freshness inline. The settings
Usage list keeps the button.
* feat(usage): make the sidebar Usage summary opt-in
The sidebar Usage item starts hidden on every layout, not only on
compact ones, so resolveSidebarNavItems drops its compact input. While
the summary is off, the Usage list renders window rows without pins;
saved pins stay in preferences and return when it is turned on. Specs
that read the summary now turn it on first.
* docs(glossary): describe the popover usage card and the opt-in sidebar summary
* fix(usage): show a popover request error without retrying
A daemon error for an agent's usage, such as an unknown agent, does not
heal on retry, and reopening the popover fetches again. Retrying held
the loading sentence for about seven seconds before the error.
* test(usage): cover agent usage in the context window popover
The usage fixture records agentId, streams reports held behind
promises, and can switch host support between reloads. The meter spec
walks loading, streaming, a report problem, a request error and a host
without usage reports, on desktop and compact in both themes.
* fix(usage): keep the plain context window tooltip on hosts without usage
The popover widened to fit usage cards on every host. Hosts that cannot
report usage now keep the tooltip's default padding and width, as
before, and mount no usage section.
* fix(usage): let a report's Refresh replace it in the agent's list too
Refresh wrote the refreshed report only into the host's list, so a card
rendered from an agent's list kept the old report. Both lists now sit
under one host key and Refresh replaces the report in each that holds it.
* feat(usage): open the context window details in a sheet on compact screens
Wide screens keep the tooltip. On compact screens pressing the meter opens
a content-sized bottom sheet titled Context window. Both render one
ContextWindowDetails body; only the sheet's usage cards have Refresh,
since the tooltip cannot be pressed. Pins stay off in both.
* test(usage): cover the compact context window sheet
On the compact viewport the meter opens the sheet; its cards have Refresh,
a Refresh replaces the card, and Close dismisses the sheet. The glossary
names the sheet.
* feat(ui): give tooltips and the context window sheet accessible roles
Tooltip content is role=tooltip, and the context window sheet is a dialog
named Context window, so assistive tech and tests find them by role. The
agent usage message drops its test ID; it is found by its text.
* test(usage): reshape the context window and Usage switch specs as journeys
The context window spec runs one tooltip journey and one sheet journey per
theme, with no layout branches. Its fixture script, meter actions and
report builders move to a context-window helper, and it finds the meter,
tooltip, sheet and Refresh buttons by role. The theme comes from the
system colour scheme. The Usage switch test uses named helpers for the
Usage icon and the Summary in sidebar switch.
* fix(usage): say an agent's usage request failed instead of showing older reports
A failed request keeps whatever reports the query held: those from an
earlier open, or those that streamed in before the failure. The context
window details showed them as if they were the agent's current usage.
The request error now wins over held reports.
* test(usage): let a fixture clean up the context window spec's agent
The tooltip and sheet tests no longer wrap their steps in try/finally;
a spec-local fixture seeds the agent and removes it after the test.
* fix(usage): keep a refreshed report when the list it is in finishes
Pressing Refresh on one card while another source was still streaming
put the pre-refresh report back: the finished list replaced the query's
data with the copies the request had fetched when it started. A report
now keeps whichever copy was fetched later, both when a streamed copy
arrives and when the finished list replaces the streamed one.
* fix(usage): show only the login a resumed agent runs under now
An agent resumed under another login keeps its ID, so its usage query
kept the previous login's reports: reopening the context window showed
them while the new login's reports loaded, then both side by side until
the request finished. The agent's usage is now dropped as soon as its
tooltip or sheet closes, so each open loads the login the agent runs
under at that moment.
* fix(usage): stop a closed agent usage request from writing reports
Closing the context window details cancels the agent's usage request and
drops reports still on their way, so reopening after a resume under
another login sends a new request instead of joining the old one.
* fix(app): smooth chat history pagination and reserve image space
Select the range at the pending reading offset before a prepend mounts rows. Reuse committed geometry for scroll input so a layout correction cannot hide the user's movement. Reserve image dimensions before loading and measure transient mounts in the browser regression.
* fix(app): scope cached image dimensions to their source
Raw file-path aliases let one workspace supply dimensions for another. Keep file metadata tied to its daemon and workspace, and close the delayed-image test server even when setup fails.
* fix(app): retain local image context in row height estimates
Pass the rendering server and workspace through height estimation so scoped image metadata remains usable before an older row mounts.
* refactor(app): name row height estimate inputs
* fix(app): render linked pictures in PR markdown
Consume image-only headings, anchors, and picture wrappers together so splitting at the fallback image does not leave raw opening tags or discard its link.
* fix(app): preserve picture block boundaries in markdown
* fix(app): parse nested image wrappers without recursion
* test(app): account for delayed wheel input in scroll assertions
CI recorded a 480px wheel scroll delivered after its event alongside 41px of image growth below the new reader. Use the same input budget to locate the reader and account for intervening growth, while continuing to reject added viewport displacement.
* test(app): locate the scroll reader from frame movement
* fix(app): keep chat reading position stable while scrolling
Prepend restoration froze the reading offset while wheel input and delayed image measurements changed layout. Give the viewport one reading anchor, preserve concurrent user movement, and apply virtual row measurements before paint. Suspend automatic pagination while explicit message navigation settles.
* fix(app): type the reading anchor scroll intent explicitly
* fix(app): retain the reading anchor through virtualization startup
Keep the reader pinned while the first virtual range mounts. Strengthen the scroll regression detector to compare shared rows when the original anchor unmounts, and consume fixture replies only when they are emitted.
* test(app): distinguish delayed wheel input from idle scroll drift
CI frames can arrive long after their wheel events. Start idle assertions after the input driver finishes, and account for an image entered by wheel input expanding below the new reading line. Replayed all six CI captures without false positives while retaining the original 76 baseline jumps.
* test(app): bound image growth allowance to displaced reading rows
Require the entered image top to follow wheel input before attributing movement of a later row to its growth. Calibrate the detector against simultaneous viewport jumps and jumps of the growing row itself.
* test(app): clamp scroll expectations at the history boundary
* feat(app): unify Explorer tabs and refine interface styling
Give Explorer the workspace tab controls while keeping launcher choices separate from drag-and-drop support.
Upgrade Lucide and set a shared icon stroke width for consistent styling. Reduce the desktop composer bottom inset to align the action row with its corners.
* fix(app): show active Explorer tabs in foreground
The shared row coupled foreground styling to keyboard shortcut ownership. Separate those concerns so Explorer can highlight its active tab while Cmd+T remains with the workspace pane.
* fix(app): refine Explorer close controls and launch options
* test(app): respect singleton Files in Explorer plugin workflow
* test(server): trim unused setup from no-track checkout regression
* test(app): count visible main tab rows after pane moves
* fix(usage): derive window names and unify pin selection
Quota slots do not identify a period. Derive numeric windows and IDs from reported duration and quota scope; keep unknown lengths neutral. Resolve source defaults and explicit pins through one model, with a persisted empty selection distinct from defaults.
* fix(plugins): declare the duration helper host requirement
* fix(usage): verify persisted selections at the storage boundary
Chromium bounding boxes differ by millionths of a pixel for equal-width footer buttons. Allow less than 0.01px in width and grouped-gap comparisons while preserving alignment and Help/Settings separation checks.
* fix(usage): keep existing logins visible and explain unavailable quota
Credential discovery must preserve accounts whose login exists even when it has expired. Keep vendor failures on the discovered account and isolate discovery failures without inventing provider cards.
* test(usage): align credential route tests with account discovery
* fix(app): hide the sidebar Usage item while it has no summary
Without a summary window with data, the item fell back to a plain "Usage"
row that duplicated the footer's Usage icon. It now renders nothing, and the
footer rows leave it out so an otherwise empty rows block and its separator
disappear with it.
* fix(desktop): restore built-in usage in packaged apps
The external plugin compiler cannot traverse app.asar, and dependency packaging drops declarations needed by import validation. Ship complete built-in resources beside the archive and verify registration in the packaged desktop smoke check.
* fix(usage): restore login discovery across harnesses
Discover subscription accounts independently of agent sessions and retain each account’s logins in preference order so an unusable credential can fall back to another login for the same account.
* fix(skills): install one Codex-discoverable orchestration copy
* fix(skills): keep installed skills when agent skill roots are symlinked together
Retiring the old Codex copy deleted the skills just installed when
~/.codex/skills resolved to ~/.agents/skills or ~/.claude/skills (or the
reverse), and status reported drift forever. The Codex skill directory is
now treated as a leftover only when it is a separate directory from the
shared and Claude copies.
Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
* test(e2e): expect agent skills in the shared and Claude roots only
The skills settings spec still expected every skill in ~/.codex/skills.
It now checks the two install roots and that ~/.codex/skills holds no
Paseo skill, which fails against the previous installer.
---------
Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
Co-authored-by: Bae Seongmin <83870598+3ae3ae@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(app): streamline sidebar usage and stacked sheets
* fix(app): restore compact sidebar navigation clicks
The close button container emitted box-none through Unistyles as invalid CSS and intercepted New workspace clicks. Let React Native apply click-through behavior at the view boundary. Keep the import journey on its existing New workspace screen and assert only the visible transcript.
* fix(app): cap menu sheets at eighty percent height
Keep long sidebar usage reports recognizably sheet-sized while allowing the content to scroll. Short menu sheets retain dynamic sizing.
* fix(app): show default usage windows when no windows are pinned
* fix(app): show only percentages in sidebar usage summaries
* fix(client): restore usage source logos for released hosts
* fix(app): restore the labeled footer line below sidebar items
* fix(app): use History's host filter to pick the usage and plugin host
The Usage screen, the compact usage sheet and plugin screens used a separate
HostSwitcher trigger. They now use HostFilter without "All hosts", as the
label manager does, and hide it with one host, as History does.
* fix(app): save the usage host pick and show it in the sidebar summary
Picking a host on the Usage screen or usage sheet was view state: it reset when
the view closed, and the sidebar summary never read it. The pick is now a
device-local usage preference. The summary and the screen show the picked host
while it is connected, else the active workspace's host, else the first host.
The usage fixture answers by request, since the summary and the screen each
load reports and only a card's Refresh forces one.
* test(app): expect plugin footer rows above the footer bottom line
Add project moved to the bottom line with the icons, so footer rows sit above
it, spanning Add project to Settings.
* test(app): expect the saved usage host in stored settings
* test(app): let usage settings specs share reports with the sidebar summary
The sidebar summary loads reports on app start, so the settings specs answer
by request instead of by order and read percents from the settings card.
* Add confirmation for new hosts
Opening a pairing link (an `#offer=` URL, a scanned QR code, or a pasted
link) now asks the user to confirm before the host is added or connected.
The prompt shows the host id, its key fingerprint, and the relay. Cancel
leaves no new host and no connection.
The host runtime owns the check: `upsertConnectionFromOfferUrl` and
`probeAndUpsertConnectionFromOfferUrl` ask a registered confirmer before
doing anything. `HostTrustPromptModal`, mounted at the app root, registers
the confirmer and renders the prompt with the shared confirm sheet. Without
a registered confirmer, pairing links are declined.
Adds a test that an offer link with the confirmation declined adds no host
and attempts no connection.
* Only ask for new hosts or changed connections
A pairing link now connects without the confirmation when it matches a
saved host exactly (same server id, key and relay). It still asks for a new
host, and for a saved host whose key or relay differs from what is saved,
with the "This link changes how you connect to this host" text.
After the user approves a link that then needs a password, the password
retry no longer asks again. The approval is an `OfferApproval` owned by one
pairing flow (one QR scan, or one open pair-link or add-host modal) and
only covers the exact offer that was approved.
Adds tests for a matching saved host, a changed key or relay, and the
password retry.
* Move host confirmation into the host runtime
The host runtime now owns the pending host confirmation and the decision to
ask. The sheet reads the pending confirmation and sends the answer; nothing
registers into a module variable.
A pairing flow is a LinkPairing the runtime hands out, so the password retry
for a confirmed link doesn't ask again without views creating or resetting
approval objects. Cancel is a result instead of an error. Pairing waits for
saved hosts to load before deciding whether to ask.
* Stop QR scanning after an attempt ends
The scan screen now keeps its workflow in a small model. When the user
cancels the host confirmation, closes the password prompt, or the attempt
fails, scanning stops until they tap Scan QR code again. Before, the code
still in front of the camera started a new attempt on the next frame.
A host confirmation answer is now tied to the request it answers, so a tap
on one sheet never answers a newer one. Relay hosts can no longer be added
through HostMutations without the confirmation, the add-host modal drops
the reset its per-open mount made redundant, and the fingerprint comment
says what the shortened form shows.
* 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>