* docs(hub): document required output contracts
* docs(hub): link faq to output contract
* docs(hub): document materializable required outputs
Explain that required declarations resolve through registered capabilities, failed deliveries can be retried, and command-line repository comments use the scoped credential path.
Hub configuration resolves a normalized friendly slug to an immutable daemon ID. Document that model consistently so enrollment terminology matches hub.yml.
* fix(terminal): clear activity when turns are interrupted
Some terminal agents do not emit a stop lifecycle hook after a user interrupt. Treat exact interrupt key frames as cancellation at the terminal input boundary so running state cannot stick, and clear rather than finish to avoid completion attention.
* test(terminal): use platform shell in interruption tests
Let the terminal test fixture select the platform default shell so the interruption cases reach their assertions on Windows.
* feat(server): show provider workflows in subagent track
Normalize workflow tasks into the provider-subagent contract so shared clients need no workflow awareness. Persisted nonterminal runs replay as failed because their original runtime can no longer deliver a terminal update.
* fix(server): preserve workflow replay and tool cards
* fix(server): suppress workflow notification cards
* fix(server): replay workflow child timelines
* fix(server): fail tasks when Claude exits
* fix(server): preserve workflow replay parity
* fix(server): fail tasks on Claude restart
* fix(server): hide workflow child prompts on replay
* fix: terminalize workflows on session reload
* fix(server): own workflow notification routing
* feat(server): show workflow results in detail pane
* fix(server): preserve unrestored task notifications
* fix(server): normalize replay task notifications
* fix(app): debounce reconnect status toast
Keep reconnect transport immediate while delaying the visible status toast so brief outages stay quiet. Scope the toast to the active retained panel, keep its copy and status dot consistent across locales, and add a manual real relay deployment journey that verifies streaming recovery without user action.
* fix(app): isolate manual relay deployment check
Keep the deployment journey out of the provider-wide real test project so it runs only when its local relay prerequisites are explicitly requested. Also keep the relay teardown active when packaged daemon startup or later cleanup fails.
* fix(app): preserve reconnect toast across tabs
Keep reconnect presentation timing at the host level so switching between retained panels does not restart the debounce or flash the toast. Add a browser regression covering a visible outage across retained tab switches.
* fix(app): anchor reconnect debounce to host status
Record host connection status transitions independently of mounted agent panels and preserve the outage start across reconnecting states. Use that host-level timestamp when a visible agent panel appears, with runtime coverage for the transition lifecycle.
* fix(app): reset reconnect toast state per outage
* test(app): build relay journey prerequisites
Retained tabs stay mounted under display:none while inactive, which
reports a zero-width onLayout. ControlledAgentControls fed that
straight into its density resolver, so a hidden tab's toolbar
collapsed to its tightest density and painted one frame collapsed
before re-expanding when the tab became visible again.
Extract the zero-width guard that useContainerWidthBelow already had
into a shared readMeasuredWidth helper, and use it in the composer
toolbar's own layout handler so a display:none measurement can never
drive its density.
Also bump the chat outline rail's MIN_PANEL_WIDTH breakpoint (818 ->
918) so the rail disappears earlier under a narrower panel, and widen
the chat-outline e2e spec's viewport so its panel keeps healthy margin
over the new threshold.
* fix(app): respect assistant copy selection boundaries
Selection serialization reintroduced semantic ancestors even when the user selected only their inner text, causing list markers and code delimiters to expand beyond the visible range. Preserve semantics only for complete selected nodes, and keep rich list payloads compatible with text composers that prefer HTML.
* fix(app): preserve loose list copy breaks
* fix(desktop): preserve browser tabs across focus changes
Electron can replace a guest WebContents when a webview is reparented, which reloads the page and can race automation against a destroyed target. Keep each browser guest on one permanent paintable surface and make user and agent resizing share one canonical viewport.
* fix(desktop): recover browser surfaces after lifecycle changes
* fix(desktop): preserve resident browser lifecycle state
* fix(desktop): seed inactive browser viewport
The running dot no longer animates, and the machinery behind it is gone
rather than switched off: the shared refcounted clock, the theme-scheme prop
mapping, and the extra wave color all existed only to drive it.
Four affordances were tried and each failed for its own reason, which is
worth writing down before someone reaches for one of them again. Fading the
dot blends it toward the row's surface rather than toward black, so the
bottom of every cycle was a washed-out blue that read as the success dot one
row up. Dropping chroma holds brightness but turns the low point grey.
Raising chroma has nowhere to go: blue at these lightnesses is already within
11% of the sRGB gamut edge, so the only real headroom is at the hue-250 cusp,
which costs lightness. An expanding ring solved the color problem outright —
motion cannot be confused with another hue — but stayed too subtle at a size
that suits an 8pt dot sitting beside a title.
If the ring is revisited: the project badge shell clips its contents to 12pt,
which is what made the first attempt invisible on exactly the rows most
likely to be on screen. The clip is not what makes the shell read as a
knockout; its fill is.
The badge icon moved from `identityColor` to `identityForeground` when the
status palette was unified, because a stroked glyph on a surface has to clear
contrast against that surface rather than behind white letters. The helper
still expected the fill table, so it read teal as #368080 where the icon now
draws #3e6d6c.
The variant is per-scheme and there is no scheme to read from a Playwright
locator, so the expectation pins "light" and says why.
The sidebar row narrowed to services only and its display-preferences item is
now labelled Services, while the panel and menu that list both types are still
Scripts. That is two words in the UI for things a reader will assume are one,
which is exactly what this file exists to settle.
Checks stopped being a toggle. Showing the icon without its word is a real
third answer rather than half of "off" — the state stays on the row for a
glance and a passing workspace stops paying a word for it on every line — so
it left the boolean row-item record instead of widening it, and a stored
`sidebarRowItems.checks === false` migrates to the new "none".
The services item narrowed to actual services. It used to fall back to any
running command, but a test watcher running says nothing about whether a
workspace is up, and the item now says "Services" so drawing a terminal glyph
under that label was a control lying about itself. It reports the name rather
than the port because the name is what you called it. `health` was already on
the wire and nothing in the sidebar read it, so a failed check now turns the
whole item danger; where several services run, the failing one takes the
single slot, since a healthy one is the unremarkable case and any of them
says "this is up".
That narrowing is why the rename went through the code and the persisted key
rather than stopping at the label: the concept genuinely changed, and a UI
saying "Services" over a `scripts` key is the synonym docs/glossary.md calls a
design error. Both key migrations are tagged COMPAT.
The rest is the sidebar row's geometry. The show-more and new-workspace rows
each carried their own smaller, inset, less-rounded version of a workspace row
— 24pt of left margin, a 14pt icon column, `borderRadius.md`, one fill for
hover and press — which under a column of real rows reads as a mistake rather
than a distinction. They now take the workspace row's geometry, and the
show-more row asks its caller whether it is ending an indented list, because
the two groupings disagree.
Group spacing moved from margin to padding so a collapsed group gives it back
instead of staying spaced for content that is gone. SPACING gained a 0.5 rung
for the 2pt gap between rows.
Three systems were reporting the same states: a full and a muted status
tier in the theme, plus raw palette steps in StatusBadge and
getStatusDotColor. A PR icon was muted in the sidebar and full-strength
in the PR panel, and the Resolved badge used a theme-independent green
so it painted a dark block in light mode. The muted tier is deleted and
every status surface now reads the one token per signal.
Dots keep a band of their own. A dot is a few points of solid colour
with no shape to read and no label attached, and the running one pulses,
so at the status band's lightness it sat below the check icons and host
badge on its own row — backwards, since the dot is the row's state.
Chroma caps at 90% of gamut rather than 100% because at the edge green
reaches C=0.215 while blue manages 0.116 and the set stops reading as
one family; dark lightness caps at 0.72 because red desaturates to pink
above it.
Host badges needed a foreground variant of the identity table. That
table is tuned for a white letter on the colour as a fill; used as text
it cannot clear 4.5:1 on both themes at once, since light needs
luminance <= 0.183 and dark needs >= 0.212. The new set is generated on
the status family's exact lightness and chroma fraction, so a host name
and a CI check in the same meta row carry equal weight instead of the
brighter one winning the row.
Also: PR avatars had their own eight-hex palette and hash duplicating
identity-colors; workspace rows under a project group now indent to the
same rail as status-grouped rows; group headers are muted; and the
pulsing dot is faster and fades further.
Settings lost the workspace host when entering the app-wide route, while desktop bootstrap could restore a removed local connection before the saved registry was ready. Use the remembered workspace host for Settings and only register the managed daemon when its server is unknown after registry hydration.
Beta testers had to find prerelease assets on GitHub by hand. The
download page now carries a Stable/Beta switch, with stable staying the
default and the canonical URL.
The channel lives in the URL (?channel=beta) rather than client state so
maintainers can link someone straight to it. That link outlives the beta
it was shared for, so the switch only renders while the newest
prerelease leads stable on its core version, and a stale ?channel=beta
falls back to stable instead of serving a promoted version twice.
"What's new" deep-links the minor group anchor (/changelog#release-0.3),
not the exact entry: promotion rewrites the -beta.N entry in place, so
that anchor dies while the group survives — and the group is the
stable-to-beta diff a beta downloader came to read. A version missing
from the bundled changelog links plain /changelog rather than a dead
anchor, which covers the window between a tag going public and the site
redeploying.
Homebrew, the app stores, and the web app have no beta, so the Beta view
drops those rows instead of showing inert placeholders.
The mobile terminal stream readiness work made live focus and renderer state recreate the stream controller. A replacement controller could remain untargeted, so input reached the PTY while output stopped reaching the terminal. Keep the controller scoped to the client connection and expose current render state through stable callbacks.
* feat(website): nest docs pages under landable groups
The docs sidebar could already render groups from subdirectories in
public-docs, but nothing used it because every doc was a flat file, and
two things stopped it being usable.
A group had no page of its own, so its label was only a toggle and its
name came from the directory name via formatLabel. A directory's
index.md now supplies the group's label, order, and href, which makes
the group landable and lets the label be authored rather than derived.
Categories and top-level directories named the same thing, so a
categorised doc at hub/index.md produced a group nested inside its own
category. Only directories below the top level become groups now.
Also fixes two behaviours in the sidebar:
- The open/closed effect assigned containsActive on every change, so a
group you expanded to browse collapsed the moment you navigated
anywhere else. Navigating in now opens a group and navigating away
leaves it alone.
- The sidebar scrolls independently of the page and never scrolled the
active entry into view, so loading or refreshing a page far down the
list left the sidebar parked at the top with no visible position.
The group row is a single target: it navigates and expands, and
collapses when clicked again from the page it points at. A separate
chevron button would put two actions a few pixels apart.
* docs(hub): document Hub
Adds a Hub section covering what it is, how it works, daemons,
triggers, configuration, activity, and the two ways to run it.
The section separates using Hub from running it. Overview, quickstart,
daemons, triggers, and configuration hold regardless of who operates
the deployment; the self-hosting branch owns deploying the service and
creating the GitHub, Slack, and Discord apps it talks to. That split
falls on a real seam, because those apps are exactly what a hosted Hub
would own on your behalf. Hosted is a placeholder until it ships.
Each provider gets a trigger page carrying its events, filter
semantics, and the full paseo.event.* reference for the data a prompt
can read. That data is the thing a config author actually needs and
the thing they cannot get from anywhere else.
Every YAML block was parsed against the Hub config schema rather than
eyeballed, which kept several plausible-looking things out of the
docs: worktree prNumber takes a literal number and not a merge
variable, only daemon environments run today, and github.push has no
actor so it can never satisfy the required from_users filter.
Troubleshooting moves to order 90 so it stays last.
* refactor(website): let the active docs entry scroll itself into view
The layout queried the sidebar for the active entry after every
navigation, which coupled it to when that entry mounts. A link inside a
collapsed group only mounts once the group's own effect opens it, so
the query could run against a DOM that does not contain it yet.
Moving the scroll to the entry removes the ordering question rather
than answering it, and drops the ref and the cross-component query.
Reported by Greptile on #2864. The failure it described does not
reproduce: navigating from a page outside a collapsed group into one of
its children still scrolls correctly, verified with the group confirmed
collapsed and the sidebar forced to the top. The coupling was real even
though the bug was not.
* docs(hub): stop listing BETTER_AUTH_URL as required
It falls back to PASEO_CLOUD_PUBLIC_URL, so a deployment that sets the
public origin already has a correct auth base URL. Listing it as
required made the Fly recipe look incomplete when it was not.
Moved to optional with its default, and recorded what actually breaks
without BETTER_AUTH_SECRET: the auth routes close and nobody can sign
in.
Reported by Codex on #2864, which read the same inconsistency the other
way round and proposed adding the variable to the Fly example.
* docs(hub): attach a database in the Fly recipe
The Fly section showed inline config and a secrets command, neither of
which supplied DATABASE_URL. It defaults to localhost:5432, which on
Fly reaches nothing, so the documented deployment could not start.
fly postgres attach sets DATABASE_URL as a secret, so the step is the
attach itself rather than another variable to copy.
Reported by Codex on #2864.
* docs: align Hub self-hosting contract
The archive response already waits for durable archival and owned-worktree cleanup. Assert those final states directly instead of racing a separate filesystem watcher with its own fixed timeout.
Beta tags now reach external testers without per-release dashboard work. Reserve distinct native build slots so successive betas and the promoted stable build can coexist under one app version.
Scale custom glyph paths in measured cell coordinates so box-drawing runs fill their cells on Android. Debounce daemon resize claims at the terminal pane boundary so keyboard layout animation sends only the settled dimensions.
Make provider catalogs compact and content-addressed so repeat launches can reuse the persisted snapshot instead of transferring it again. Remove eager global catalog loading and the stopped-agent defensive tail fetch, while keeping sequence-gap and reconnect recovery as the correctness paths. Add an opt-in production-ID Android profiling build for local startup traces.
Live Markdown block promotion discarded timeline positions, so merging a disjoint history window could sort the final block ahead of earlier blocks. Preserve the source position on every promoted block so timeline merges keep the response intact.
* fix(server): pin workspace file watchers to native backends
Backend auto-detection could select an IPC watcher whose subscription lock deadlocked the Node worker pool during watcher reconfiguration. Select the host-native backend explicitly so filesystem work, logging, and relay DNS cannot be starved by that path.
* test(server): cover native watcher production boundary
Route the existing real filesystem observation test through the same subscription function used by the daemon. This proves native watcher selection and reconfiguration with actual file events instead of only testing the platform selector.
* test(server): synchronize native watcher readiness
Replace fixed startup sleeps with a real tracked-file event barrier. Native backends can resolve their subscription before the initial stream is fully drained, so the integration test now begins assertions only after an event has been delivered and refreshed.
* fix(server): measure native workspace watchers
Route composer attachments through the shared compact menu sheet so headers and rows use the same alignment rails. Base the final-row padding on the device safe area to keep actions clear of system navigation.
The highlight is an overlay inside the pressable rather than a background on
it, because the node Reanimated animates cannot also carry a themed Unistyles
style. An overlay inherits nothing, so a rounded sidebar row flashed square.
Take the corners from the pressable's own style. The row already declares its
shape once, and a highlight that restated it would be a second copy free to
drift; highlightStyle is left meaning only what the highlight is painted with.
Corners apply before it, so a caller can still depart from the row's shape.
The trigger was a vertical ellipsis on compact and a horizontal one elsewhere.
Use the horizontal glyph everywhere: it is the wider shape, and it is the one
sitting above a menu that now presents from the bottom edge.
The button draws at 32pt, under the 44pt touch minimum, and three of them sit
flush beside the title, so the slop can only grow vertically — widening it
would put two buttons' hit areas over the same pixels.
Step the title and project name down one rung of the scale on compact, and put
the host on the project line. On a phone this header is the only thing on
screen that names the machine, because the sidebar carrying that badge is
closed. It follows each host's own badge setting, so a local-only setup stays
quiet, and it yields space before the project name does.
A project name that only repeats the workspace name is still dropped where the
two sit side by side, and kept on compact, where the line exists for the host
anyway. That made shouldShowSubtitle a verdict the header source was not in a
position to reach, so it now reports the fact: isSubtitleDistinct.
Drop the 60% cap on the project name. It resolved against a box whose own width
came from that same text, so it clipped while there was still room beside it;
flexShrink already gives up space only when there is none.
The server glyph, its identity-colour table and the muted label lived inside
the sidebar meta row, and the three stores that decide whether a host has a
badge at all were assembled at one call site. Both are about to have a second
caller in the workspace header.
Move the badge to hosts/host-badge and the resolution to useHostBadges. The
badge now owns its glyph size, and the meta row matches it, since it is the one
item on that line that also appears off it.
Selecting an item parked its action in a ref and waited for the Modal's
onDismiss to flush it. MenuOverlay returns null as soon as the menu closes, so
the Modal unmounted and onDismiss never fired: every close-on-select action was
dropped. Creating a terminal from the compact workspace menu did nothing at all.
Wait on a timer instead. The grace period was always about giving UIKit time to
tear the surface down before a native presenter launches, and a callback fired
from inside a subtree being removed is racing its own removal. The sheet had no
flush at all, so it was broken the same way for a different reason.
Rows are 40pt below md and 28pt above it. The split is on breakpoint rather
than presentation because the compact popover, which compactMode defaults to,
is worked with a thumb just as a sheet is.
Move vertical spacing off the row onto a new MenuPage, which also provides a
page's depth so the popover and the sheet cannot disagree about what a page is.
A row carrying vertical margin would serve both the edge inset and the
inter-row gap at once, and margins do not collapse.
Native font glyph bounds do not reliably fill terminal cells, and long box-drawing runs accumulate advance drift. Paint block and box glyphs from cell geometry while preserving the original Unicode cell model for selection and copying.
Sidebar rows previously waited for JavaScript-driven selection or navigation before showing feedback. Keep the transient highlight on the UI worklet while leaving activation, cancellation, and drag ownership with the existing pressable.
Clearing the hidden terminal input after multi-character commits can leave the keyboard's composing buffer intact, causing later changes to replay committed text. Keep the accumulated native value so subsequent changes emit only their appended suffix.
Pin the keyboard controller to the release that prevents stale keyboard positioning from leaving the composer underneath a keyboard that is still visible.
* fix(workspaces): default new worktrees to upstream refs
Use the daemon-resolved upstream as the untouched starting ref so unpushed local commits do not leak into new workspaces. Preserve exact base identity for fork remotes while keeping local-first behavior across version drift.
* fix(workspaces): preserve exact base refs in git actions
Resolve accepted display aliases back to the stored ref before comparisons and updates. Fail closed when a non-origin remote base has no explicit mutable merge target.
* fix(workspaces): reject conflicting qualified base refs
Display aliases remain accepted for compatibility, but a qualified ref carries remote identity and must not be canonicalized to a different stored upstream.
* test(server): make upstream fixtures portable
Keep the checkout-status fixture aligned with the optional upstream field, and select main explicitly when cloning a bare test remote whose default HEAD varies by platform.
* fix(workspaces): preserve exact upstream refs
Qualified remote refs now fail closed when pruned instead of switching lineage. Metadata accepts Git-valid upstream names rather than applying a narrower character whitelist.
* fix(workspaces): preserve qualified local base refs
Local tracking branches are exact upstream identities too. Keep heuristics only for legacy unqualified metadata so origin cannot replace a stored refs/heads base.
Replace time-based subscription expiry with a bounded hot set so switching among a small working set and backgrounding the app do not force stale timeline catch-up. Visible chats always retain priority, while disconnect and legacy delivery preserve their existing authoritative boundaries.
* fix(app): preserve chat scroll across workspace switches
Hidden retained panels can emit geometry-driven scroll events while collapsed, which previously looked like user intent and disabled output following. Keep the retained DOM mounted, suspend its inactive scroll work, and only detach from the bottom after explicit input.
* fix(app): classify chat scroll intent at input
A held pointer is not evidence that a geometry-driven scroll was user initiated. Detach output following directly from upward wheel, key, touch, or mouse-drag input, and keep ordinary scroll events responsible only for metrics and bottom reattachment.
* fix(app): keep editing keys out of scroll intent
Question inputs render inside the chat viewport, so their caret-navigation keys bubble through the same keyboard boundary. Treat keys from editable descendants as editing intent while preserving viewport-level keyboard scrolling.
* fix(app): exclude zoom gestures from scroll intent
Ctrl-wheel and trackpad pinch events can carry a negative wheel delta without scrolling chat content. Keep those browser zoom gestures from disabling output following.
* fix(app): require pointer-driven chat movement
Mouse movement alone is not viewport scroll intent. Correlate actual upward scroll with short-lived content-drag evidence or a scrollbar-gutter gesture, while excluding embedded editors and retaining geometry-only following.
* fix(app): recognize overlay chat scrollbars
* fix(app): scope scroll intent to chat viewport
* fix(app): rearm chat history at scroll start
* fix(app): track chat autoscroll intent
* fix(app): separate pointer scroll gestures
Use mobile action sheets and long-press file menus while preserving provider context across sheet portals. Keep sidebar and file-pane glyphs on shared visual rails, avoid scrollbar layout shifts, and hide keyboard affordances where shortcuts are unavailable.
Sidebar polish round.
Menus. `dropdown-menu` and `context-menu` were two copies of one engine that
differed only in how the trigger fires — identical from `computePosition` down —
and neither could nest. Both are now thin wrappers over
`components/ui/menu/`, which owns anchoring, a pure page-stack navigation model,
and one presentation fork: flyout on pointer, pushed pages in a sheet on compact.
All 31 call sites are unchanged. Items got a rounded inset fill, a visible
separator, and `selected` (a check) split from `active` (a fill).
Sidebar display preferences. The flat wall of four sections becomes one row per
decision with its value, and per-feature toggles over what a workspace row shows:
host, pull request, checks, scripts. Each is independent — a ticked toggle that
draws nothing is a lie. `display-preferences/` hides that grouping lives in
zustand while title source and row items are app settings.
Timestamps. "Last activity" now ticks itself. The label rounds to the minute and
never shows seconds; past a week it becomes a date. The timer lives in the leaf
`<Text>`, at most one per resolution tier shared by every row, and a tick only
re-renders when the string actually changed — so a memoized row that legitimately
never re-renders still stays current.
Colour. Full status tier regenerated at 90% of gamut max (was 85), same rule as
the muted tier, lightness and hue untouched. File icons are pulled toward neutral
in OKLab behind `getFileIconSvg` — material-icon-theme picks its palette for a
tree where the icon is the only colour on the row, and ours is not. New/deleted
files use the forge square-plus/square-minus glyphs at muted weight instead of
tinted pills. Project icons round a little more; uploaded icons never do.
The project status badge is a knockout again: it fills with the colour of the row
behind it, so the ring reads as a gap in the icon rather than a white halo.
File rows. The always-visible kebab is gone from the file explorer and diff tree
in favour of right-click, which also reclaims the reserved action slot.
* fix(app): stabilize native terminal keyboard input
Terminal focus requests blurred and remounted the hidden input even when the software keyboard was already visible, causing visible IME cycles and leaving clipboard commits coupled to stale native text. Keep focus requests idempotent while resetting only the native input buffer.
* test(app): remove misleading keyboard device scenario
Agent Device cannot durably assert software-keyboard continuity or commit a keyboard clipboard suggestion, so the scenario overstated coverage already owned by focused input-policy tests.
* fix(app): derive terminal keyboard focus from visibility
Use the keyboard-controller inset as the source of truth so a system-dismissed keyboard cannot leave focus activation suppressed. Avoid a duplicate focus request while explicitly showing the keyboard.
* fix(app): track terminal keyboard visibility across layouts
Keep keyboard visibility independent from compact-only terminal padding so tablet taps remain idempotent while the software keyboard is open.
The Cloud page pitched a hosted multi-user product and collected emails
through a signup form. The Hub is a self-hosted service in private beta
and the only way in is a Discord DM, so the signup server function and
its webhook are deleted rather than repointed. /cloud now 301s to /hub
from the worker entry, which already owns the site's permanent path
redirects, so old links and search results keep working.
The homepage also drops split panels, service proxy, voice, and keyboard
shortcuts. They had turned the page into a feature list; it should show
what Paseo enables at a high level.
Brand and provider icons move into shared modules so the Hub page, the
site header, and the landing page each use one copy instead of three.
* feat(hub): pass execution MCP servers to agents
Hub-owned execution capabilities need to reach provider sessions without becoming part of outward agent snapshots. Keep the configuration daemon-private so provider recovery continues to work.
* fix(hub): protect daemon MCP namespace
Hub execution MCP configuration must not replace the daemon-owned Paseo server. Validate only after owned-record lookup so idempotent replay remains stable while new launches fail before provider or durable state creation.
* fix(agent): reject unsupported MCP launches
* fix(agent): scope MCP support guard
* fix(agent): redact MCP metadata from wire payloads
* fix(agent): validate MCP support on restored sessions
* fix(session): rehydrate MCP config on resume
* fix(session): resume newest matching agent record
* fix(session): restore archive state after failed resume
* fix(app): preserve formatting when copying assistant selections
Browser selection serialized React Native Web layout nodes instead of the assistant message semantics. Recover semantic markup from renderer-owned metadata and reuse the rich clipboard generator used by Copy turn.
* fix(app): preserve assistant copy semantics
Carry source-only Markdown metadata through the selection copy boundary, unwrap generated code links, and cover ordered starts, hard breaks, and fence languages in Playwright. Refresh the Nix dependency hash for the added clipboard dependency.
* fix(app): preserve partial selection context
Wrap selected fragments with their shared semantic ancestor chain and enable GFM table and strikethrough conversion. Cover partial nested selections and GFM output through the browser clipboard.
* fix(app): preserve copied list and autolink text
Adjust partial ordered-list starts for omitted siblings, strip parser-generated anchors before Markdown reconstruction, and refresh the Nix dependency hash computed by CI.
* fix(app): normalize partial selection context
Normalize cloned ordered lists across block boundaries, degrade incomplete table fragments to selected text, and preserve table alignment provenance through Markdown reconstruction.
* fix(app): escape copied table delimiters
Rendered table cells no longer retain source escape characters. Escape literal pipes during DOM-to-Markdown conversion so copied tables preserve their column structure.
* fix(app): preserve authored link semantics
The rendered link boundary conflated parser-generated links with authored autolinks and discarded authored titles. Carry title semantics into the anchor and unwrap only links with explicit generated provenance.
* fix(app): preserve nested copy structure
Blank-line splitting separated loose nested lists before Markdown rendering, leaving no nesting for clipboard serialization to preserve. Derive protected separators from the Markdown block structure and keep copy metadata independent from monospace surface styling.
* fix(app): preserve selected file links
Assistant messages and rich clipboard HTML must apply the same link validation policy. A shared parser keeps file links semantic in both MIME representations while retaining markdown-it's unsafe-scheme rejection.
* fix(app): preserve partial table content
A selected body row wider than its retained header cannot be represented as GFM and is truncated when rich HTML is regenerated. Treat only those over-wide fragments, plus headerless fragments, as incomplete while preserving valid short rows.
* Improve mobile terminal typing and selection
* Address mobile terminal review feedback
* Remove dead native terminal input branch
* Replay bracketed paste in terminal preamble
* Stop per-key terminal resize claims
* Stabilize terminal resize browser test
* Use tracked terminal mode for native paste
* Restore terminal toolbar focus handling
* Adapt native terminal to active visibility state
* Stabilize native mobile terminal interactions
* Add native terminal fallback boundary
* test(app): expect explicit terminal resize reclaim
* fix(app): stabilize terminal buffer coordinates
* fix(app): restrict terminal resize ownership
* fix(terminal): gate restored input modes
* fix(app): recover stalled terminal WebViews
* chore(app): remove obsolete terminal harnesses
* fix(app): make terminal resize claims explicit
* test(app): stabilize native terminal device flows
* test(app): track absolute scrollback coordinates
* fix(app): isolate terminal WebView streams
* fix(terminal): arbitrate size ownership across clients
The daemon now distinguishes explicit ownership claims from passive geometry updates. This keeps the active client authoritative while allowing its split, viewport, and keyboard changes to resize the PTY without idle clients stealing it.
* test(server): make worker resize assertion portable
Query the worker's authoritative terminal snapshot instead of invoking a Unix-only shell command, so the resize regression exercises the same boundary on Windows and Unix.
* perf(server): bound Git process starts
Git refresh bursts could keep creating short-lived processes at the concurrency ceiling, causing sustained process churn on constrained hosts. Apply one daemon-global start-rate and concurrency policy to every Git command while preserving the legacy concurrency environment variable.
* build(nix): update npm dependency hash
* fix(server): hold Git slots until process exit
* fix(server): enforce Git limits across process lifetime
* fix(server): align Git metrics with process exit
* Fix Git limiter test deadlocks
* Tune default Git process rate
* fix(composer): keep background context out of loading state
Pull request link resolution can block a new workspace until its checkout target is known, but it is not submission work. Keep that safety gate while reserving the send-button spinner and drop lockout for actual submission or upload progress.
* test(composer): cover link resolution at browser boundary
Exercise the real new-workspace composer while pull request context resolves, including the submit presentation and file-drop path. Keep the two presentation booleans local to their only caller instead of exporting policy solely for an internal-state unit test.
* fix(composer): preserve background lookup ownership
Composer edits could release the resolving gate while a pull request lookup still owned the request, briefly allowing workspace creation from the wrong branch. Track logical candidates independently of unrelated draft changes and settle each reference independently so only invalid work releases early.
* fix(composer): preserve pasted PR selection order
* fix(composer): renew re-added PR lookups
* fix(composer): track pasted PR source order
* fix(composer): preserve resolved attachment order
* fix(composer): coordinate active lookup order
* fix(app): align turn footer actions
Keep running-turn actions in the same position as completed-turn actions and use the shared small icon size for both copy and fork controls.
* feat(app): improve sidebar workspace identity
Make dense sidebar workspace rows easier to scan and let each device control how connected hosts are identified.
* fix(app): keep host badges readable
Use a compact display menu and keep identity color on the host icon so translated controls fit and badge text retains standard contrast.
* fix(app): announce selected sidebar badge mode
* fix(app): retain icon-only host names for screen readers
* fix(app): name hoisted workspace rows
* fix(app): retain host identity in workspace labels
* fix(app): retain workspace status in row labels
* fix(app): defer host appearance until identity resolves
* fix(app): serialize host appearance writes
* fix(app): serialize host registry writes
* fix(app): bound sidebar host badge labels
* test(app): match workspace status labels
* fix(app): consolidate sidebar identity behavior
* test(app): assert status grouping behavior
* fix(app): keep sidebar row actions accessible
* fix(app): stabilize optimistic message layout
Native composers had no path from the device clipboard into image attachments. Route clipboard data URLs through the existing attachment persistence flow and expose the action only in the native attachment sheet.
* perf(app): retain inactive workspace screens
App-wide routes cleared the active workspace selection, which removed the retained workspace deck from the React tree and made returning rebuild every panel. Keep mounted deck entries inactive across those routes and suspend inactive panel subscriptions so state is preserved without background work.
* perf(app): suspend remaining inactive queries
Retained composers and commit diff panels still subscribed through query hooks that did not receive panel activity. Propagate the existing activity gate so app-wide routes leave those hidden surfaces dormant.
* refactor(app): separate panel activity from connectivity
A hidden retained panel is still connected to its host. Keep connectivity truthful, pause work inside query and stream owners, and preserve terminal emulator identity while its stream is detached.
* test(app): target active retained controls
* fix(app): retain compact and terminal route content
* fix(app): hide inactive workspace overlays
* fix(app): keep timeline pagination duplicate-free
Older daemons page projected history by canonical sequence ranges, so one projected block can appear in adjacent responses. Accept each block on the page containing its projection anchor while still advancing across empty projected pages.\n\nProjected siblings can share a provider message ID, so render identity also includes the timeline cursor instead of collapsing distinct blocks onto one layout row.
* fix(e2e): preserve isolated daemon launch contract
Keep the source-daemon spawn path recognizable to the repository launch-contract check while selecting the built supervisor only for published package fixtures.
* fix(app): preserve stable timeline item identity
* fix(acp): preserve permission choices
ACP question bridges encode answers as repeated permission options. Preserve each option through the UI and return the exact selected ID, while keeping auto-accept from choosing on the user's behalf.
* fix(acp): validate selected permission behavior
Reject inconsistent action IDs instead of allowing the action ID to override the response behavior. Move the misplaced browser spec so the current base CI contract can run.
* fix(acp): preserve invalid permission requests
Validate selected ACP actions before consuming the pending request so inconsistent client responses can be corrected and retried.
* test(acp): verify real permission questions
* fix(acp): reject empty permission action ids
* test(e2e): scope Kimi config to ACP spec
* fix(server): fail agents when provider process exits
The transport previously closed pending RPCs without terminating the active manager run, leaving the agent stuck in running state. Propagate unexpected process termination as a one-shot turn failure while suppressing it during intentional session close.
* fix(server): keep idle provider exits recoverable
A process death with no active foreground turn is not a failed turn and must not trigger turn-based lifecycle actions. Leave the session disconnected so the next prompt reconnects without publishing a false terminal event.
* fix(server): preserve provider exit recovery
Connection cleanup after a failed reconnect must not close the durable session boundary or discard AgentManager subscribers. Treat both foreground and autonomous root turns as active when propagating process death.
* test(server): make process exit harness portable
* fix(server): clear permissions after provider exit
* fix(server): preserve plan approval after provider exit
* fix(server): serialize provider reconnects
* fix(server): stop reconnects after session close
* fix(server): preserve provider subagent messages
The provider SDK defaults to forwarding only child tool activity, which leaves the nested timeline without the child response. Request the full nested transcript so provider subagent panes retain both sides of the conversation.
* fix(app): correct project status badge test imports
* fix(ci): skip unrelated required checks cleanly
Matrix jobs had to start no-op runners to preserve interpolated required-check names. Static named jobs let GitHub report genuine skipped conclusions while keeping those names stable.
* fix(ci): gate changes to install patching script
* fix(ci): route cross-package test contracts narrowly
* fix(ci): keep routed contracts merge-blocking
* fix(server): exclude nested test utilities from builds
* fix(ci): cover app-owned native and browser domains
* refactor(ci): align tests with runtime domains
Own browser specs, desktop specs, and Electron-only app code by stable directories so PR routing does not depend on filename conventions. Keep desktop integration coverage inside the required desktop check.
* fix(ci): keep routing and desktop suites isolated
The routing regression must run before dependency installation, and desktop Vitest must not collect Playwright-owned specs.
* fix(ci): isolate desktop e2e runtime
* perf(test): isolate Playwright workers
* perf(test): parallelize Playwright within specs
* fix(test): keep Playwright file scheduling
* fix(test): share dynamic Metro fixture
* fix(test): preserve own-host restart fixtures
* fix(ci): preserve routed contracts after rebase
* Stabilize Playwright wheel-scroll assertions
* feat(acp): auto-accept shared permission requests
Some ACP agents expose permission requests without a native mode that can bypass them. Keep the opt-in at the shared adapter boundary so every ACP implementation gets the same per-session behavior.
* test(acp): include the shared feature in provider expectations
* fix(acp): honor unattended auto-accept intent
* fix(agent): list client features without a model
Generated titles optimized for topical brevity and could omit the requested operation or the identifier that distinguished the task. Preserve the operation, target, and strongest anchor so sidebar titles remain actionable.
* feat(app): expand command center actions
Keep command center navigation stable by revealing only clipped rows without smooth centering. Prioritize a concise default list while making workspace, Git, pane, and creation actions searchable through the existing contribution and action-policy paths.
* test(app): search for query-only command action
Add project is intentionally absent from the unfiltered command center. Exercise the overlay flow through search before selecting it.
The outer test deadline could expire before the archive observer's own timeout after slow Windows setup. Leave enough room for daemon and Git setup so the operation-specific failure remains authoritative.
* Reapply: Stop completed turns from appearing stuck (#2484)
* Reapply: Keep older chat history and image previews stable (#2490)
* Lock timeline layout stability with user-driven specs
Two reds reproduce the reported jank:
- new agent, first prompt: the row paints at top=100 then jumps to 144 through
authoritative hydration, because the draft tab and the agent panel each render
their own AgentStreamView for the same logical row.
- long loaded timeline, submit: scrollHeight collapses 10793 -> 9111 mid-turn,
because splitOrderedTail reclassifies measured rows into the virtualized segment
where the virtualizer falls back to estimateStreamItemHeight.
Also restores centered timeline rows: the pagination anchor wrapper introduced in
#2490 was a plain block div, which broke alignSelf: center.
* Measure the turn footer blink from when the footer appears
Bounding the window at the prompt row conflated two things: the footer arriving a
frame after the row, and the footer leaving the layout and coming back. Bounded from
the footer's first frame, the blink still reproduces, so the footer-ownership race is
real rather than a sampling artifact.
* Rename the shadowed prompt row binding in the frame recorder
* fix(desktop): allow concurrent worktree dev instances
Desktop terminals can inherit another instance's profile path and debugging flags. Scope the profile to the current dev root and select an available debugging port so instances do not share global runtime state.
* fix(desktop): expose development providers in dev builds
Desktop-managed daemons inherited no Paseo runtime mode, so dev builds constructed the production provider registry. Mark unpackaged internal launches as development while continuing to force packaged launches to production.
* fix(timeline): stabilize optimistic turn layout
Keep initial history loading dormant until user intent and preserve active-turn layout across delayed transport updates. Add painted-frame coverage for optimistic timeline transitions.
* fix(timeline): preserve optimistic prompts through hydration
Canonical replacement relied on a transport submission record that can settle before a provider echoes the user row. Keep presentation continuity owned by the unreconciled row while destructive history replacement remains governed by active transactions.
* fix(timeline): make submitted prompts canonical
Submission settlement previously depended on status updates that could arrive after the optimistic row. Persist every accepted prompt in the canonical timeline and keep the footer active until RPC and canonical acknowledgement converge.
* fix(timeline): stop older history pages repeating entries
Backward pagination counted source sequence rows, so page boundaries inside a merged projected item could return that item repeatedly. Count projected entries and advance from their display anchors instead, with server and browser regressions covering the contract.
* fix(timeline): resume agents from the latest history
Use a bounded tail probe on resume so long background runs appear atomically instead of replaying every missed page. Preserve unchanged views, reconcile local/live rows, and keep skipped history reachable through backward pagination.
* fix(timeline): reconcile turn state after missed updates
Turn completion events can be missed while an agent timeline is unsubscribed, leaving footer activity stale even after the directory reports idle. Reconcile the single-owned turn lifecycle from authoritative resume snapshots while preserving newer live evidence.
* test(timeline): cover running state across refresh
Protect the hydration boundary where directory state must restore consistent tab, composer, and footer activity. Expose the running indicator semantically so the regression checks user-visible state instead of spinner implementation details.
* fix(timeline): keep history loading geometry stable
Reserve a fixed history-start gutter regardless of pagination state so showing or hiding the loader cannot shift timeline content.
* fix(timeline): keep turn activity coherent
Derive footer, tab, and composer activity from one lifecycle and submission projection. Idle directory transitions now request an authoritative resume so missed terminal events converge without treating directory delivery order as turn authority.
* fix(tests): align checks with runtime boundaries
* fix(desktop): stop dev app surviving its terminal
Electron was detached from the desktop runner, allowing it to outlive a stopped workspace terminal with broken output pipes and retain the single-instance lock. Keep Electron owned by the runner and preserve explicit process-group cleanup for detached child trees.
* fix(timeline): preserve activity through turn handoff
Correlate lifecycle events and snapshots with daemon-owned turn identity, and keep optimistic submissions active until their canonical row arrives. This prevents stale updates and RPC timing from exposing a false idle state.
* refactor(timeline): remove submission timing state
* fix(timeline): restore prompt identity on resume
* fix(timeline): exclude attachments from canonical prompt text
Structured provider prompts can include hidden history attachments before the user's text. Persist only plain prompt blocks so authoritative user messages match what the composer displayed while provider input remains unchanged.
* refactor(agent): remove unused pending-run message identity
* fix(desktop): let Chromium allocate the debugging port
* refactor(agent): remove impossible finalized-turn tracking
* fix(timeline): load history after keyboard scrolling
Treat observed upward displacement as user intent so keyboard and scrollbar navigation can wake dormant pagination.
* test(server): preserve accepted turn stream order
The manager-owned accepted turn boundary intentionally precedes coalesced output. Assert that lifecycle event explicitly while retaining the timeline-before-terminal ordering contract.
* test(app): scope sidebar fixtures by identity
Shared E2E daemon state may contain unrelated projects. Keep stale-route coverage tied to its named missing project and drag only the two rows seeded by the reorder spec.
* fix(timeline): gate prompt revisions by client capability
* fix(timeline): finalize canonical prompt delivery
Treat provider identity as a single stored-row transition so legacy clients receive one deliverable canonical prompt. Release identity-pending rows before later foreground output or the current terminal while preserving stale-turn isolation.
* fix(server): preserve legacy prompt pagination
* fix(timeline): keep assistant block identity stable
Assign canonical block IDs during live promotion so terminal finalization cannot remount image-bearing rows.
* fix(timeline): apply covered revisions during resume append
Run the exact canonical revision pass before overlapping resume pages append newer rows, while leaving cursor advancement to the incremental path.
* fix(server): preserve queued agent transitions
Project each queued live snapshot before awaiting stored metadata so later lifecycle state cannot overwrite an earlier transition.
* fix(server): snapshot queued agent updates
Capture each live agent payload before it enters the per-agent queue so shared nested state cannot change while an earlier storage read is pending.
* docs(app): date agent turn compatibility cleanup
* refactor(timeline): mint submitted prompt identity at acceptance
Canonical prompt rows now keep one immutable wire identity while provider identity remains internal for rewind translation. This removes the unreleased same-sequence revision and legacy projection contract.
* refactor(app): remove prompt revision reconciliation
Submitted prompt rows are immutable on the wire, so resume and live reducers no longer need to apply already-covered same-sequence updates.
* test(app): cover submitted prompt rewind roundtrip
* docs(timeline): describe immutable submitted prompt identity
Provider echoes now enrich daemon-internal identity only; wire rows keep their acceptance-time identity and rewind performs the provider translation.
* test(server): validate mock rewind targets
Reject unknown provider message IDs so app rewind coverage exercises wire-to-provider identity translation.
* fix(server): validate rewind before interrupting
* fix(server): omit rewind identity for daemon prompts
* test(app): guard rewind eligibility and rejection
* fix(server): preserve prompt identity across hydration
* refactor(timeline): remove stale revision remnants
* Revert "fix(server): preserve prompt identity across hydration"
This reverts commit 6adcf7e5b0bc8601761af59b461a746cb5e395fa.
* fix(timeline): settle missed submission acknowledgements
* fix(workspaces): keep restored merged workspaces active
Auto-archive previously reacted to every merged pull request snapshot, so restoring a workspace immediately archived it again. Persist the consumed change request with the archive transition so explicit recovery remains authoritative.
* test(e2e): clean partial archive scenarios
Track workspace resources as soon as creation succeeds so later setup failures archive the worktree and remove the registered project.
* test(workspaces): pin archive latch restoration
Cover the persisted archive marker across restore in CI and make the real browser assertion observe active workspace stability after refresh.
* fix(workspaces): latch restored legacy archives
Resolve the current merged change request while restoring records created before the archive marker existed, then persist it with the unarchive mutation.
* fix(workspaces): refresh latch on restore
When an archived workspace now points at a different merged change request, record that URL during restore so the restored workspace remains active.
* test(e2e): make archive latch setup fail loudly
Remove authentication-gated skipping and register each external fixture cleanup immediately after allocation so setup failures cannot leak resources.
* test(workspaces): include archive latch in reconciliation
Dictation auto-send bypassed the controlled composer update, so preserved draft surfaces could lock while showing an empty prompt. Publish the transcript before invoking the existing submit lifecycle so pending and failed submissions retain the user's spoken input.
* fix(app): exit focus mode for sidebar shortcuts
Focus mode kept sidebar actions hidden after their handlers ran. Exit only after a sidebar action is handled so the requested action still takes effect.
* fix(app): order focus exit before sidebar action
Leave focus mode before dispatching the sidebar command so its handler runs against the visible panel state.
* fix(app): preserve focus mode outside workspaces
Only exit active workspace focus mode after a sidebar shortcut is handled. Keep persisted focus state unchanged when the shortcut has no foreground workspace target.
The Windows runner could observe the marker after creation but before its contents were written. Wait for the expected value so the test synchronizes on the behavior it asserts.
* fix(app): show worktree names in workspace hover cards
Managed worktree paths exposed Paseo's internal directory layout instead of the useful worktree name. Carry the owned worktree root through the sidebar model while preserving the full workspace path as the copy target.
* fix(workspaces): source owned worktree names from daemon
The daemon owns worktree path semantics, so expose the managed worktree slug and let the sidebar model produce a display-ready label. This keeps path parsing out of the hover-card component while preserving the full path for copying.
* fix(app): preserve worktree labels in replica cache
* feat: make relay opt-in for device pairing
* fix(relay): keep pairing state authoritative
* fix(relay): refresh pairing state across transports
* fix(onboard): honor explicit relay opt-out
* fix(relay): surface transport startup failures
* fix(pairing): gate offers for older daemons
* test(cli): rely on real pairing transport coverage
* fix(cli): report live relay without a pid file
* fix(relay): synchronize config during daemon startup
* fix(relay): preserve mutability for ignored overrides
* fix(cli): use reachable daemon for pairing
* fix(app): redirect invalid pair device routes
* fix(pairing): support remote host device pairing
Keep Pair Device host-relative through the existing settings and daemon RPC flow. Route CLI output through an injected adapter so workflow tests do not mutate process globals.
* fix(pairing): validate pairing destinations
Expose the host-relative Pair Device flow on every supported app platform. Reject a reachable daemon whose identity does not match the requested Paseo home before reading or mutating its pairing state.
* fix(pairing): render QR codes across platforms
Generate SVG text without DOM or canvas and render it through the existing cross-platform SVG stack.
* fix(cli): pair from persisted relay state
Allow explicit relay pairing while the daemon is stopped when relay is already enabled in persisted configuration.
* fix(app): prevent text selection during Android swipes
Gesture interception did not cancel the native view where the touch began, leaving Android's pending text-selection action alive.
* fix(app): restore iOS sidebar close swipes
Fabric could fail to attach close handlers when their target lived inside retained but display-hidden panel content. Keep a registered native gesture host mounted outside the presentation wrapper so close swipes work over nested lists without activating rows.
* fix(app): recover sidebar drags after cancellation
Cancelled native drags could leave a lifted row and the shared outer list locked. Scope terminal cleanup to the owning gesture session and release the lock on cancellation or unmount.
* test(app): clean up partial drag fixtures
The bulk-history cases spawned about 80 Git processes, which could exhaust the per-test timeout on loaded Windows runners. Import each linear history in one Git process while preserving the real repository behavior under test.
* fix(workspaces): show isolation without checkout delay
* fix(workspaces): trust hydrated project capability
* fix(workspaces): resolve branch before creation
* fix(workspaces): honor invalidated checkout status
* fix(workspaces): preserve isolation while projects load
* fix(workspaces): keep provisional project routes usable
Route-derived projects do not know their worktree capability. Represent that uncertainty at the placement boundary so remembered worktree intent survives hydration without disabling the New Workspace screen indefinitely.
* fix(git): reconcile pull requests after branch changes
Treat persisted change-request metadata as a branch-bound hint and prefer current tracking configuration when it identifies the checked-out branch.
* fix(git): preserve enterprise fork identity
Use branch-bound metadata to supplement the owner when explicit tracking points at the same head on a self-hosted remote.
* fix(git): honor local branch upstreams
Treat Git remote dot with a merge ref as explicit current tracking so stale PR metadata cannot override a local upstream.
* fix(git): own change request branch bindings
Centralize persisted hint validation and rebinding so renames preserve change request identity while switches invalidate it, including normalized legacy Enterprise owners.
* fix(git): preserve PR identity through workspace renames
Resolve the backing worktree root during rename without adding a Git call, and retain fork owner identity from explicit same-name tracking configuration.
* fix(git): trust current enterprise fork identity
Explicit tracking now derives repository identity from the current remote path, so stale checkout metadata cannot override a repointed fork.
* fix(git): recognize enterprise repository aliases
GitHub repository identity is case-insensitive. Normalize equality checks so differently cased aliases cannot turn base tracking into a fork lookup.
* fix(git): reject ownerless legacy fork matches
Owner-qualified legacy hints only belong to their historical owner-prefixed branch names. A later ownerless branch reusing the same head must resolve from current Git state instead.
* fix(git): keep fork feature lookups local
A feature branch may track the upstream base branch for ancestry even when origin is a fork. Only treat that cross-repository base ref as PR identity when the local branch name identifies the tracked head.
* fix(app): preserve project selection across hosts
Rendered project identity and persisted equivalence were conflated after duplicate-project handling. Keep them separate so host changes resolve selectable equivalents deterministically without resetting legacy sidebar identity.
* fix(app): restore selected clone after host round trip
Keep manual origin and current destination placements separately so switching back restores the chosen checkout while archive gaps preserve the active host selection.
* fix(app): key status icons by project view
* fix(app): preserve origin during archive gaps