Hosted Hub replaces its automatic trial with a real Free plan, so the
pricing section becomes three columns: Self-hosted, Free, and Pro. Both
hosted columns render entirely from `GET /api/billing/plans` — name,
price, the `included` figures, and the catalog's feature prose — because
those figures are derived from the entitlement template the instance
enforces, so nothing on this page can drift from the limit a customer
actually gets.
The parser requires both slugs and the new `included` object and throws
on anything else. The page already degrades to "temporarily unavailable"
when the loader throws, so a strict boundary costs a fallback we have
rather than a wrong number we would ship. This is also why the change
must not merge before the Hub side deploys: against today's API the
section renders that fallback.
Public docs follow the two Hub changes: Free replaces the trial and
points at /hub for the figures Stripe metadata now owns; sign-in lands on
Home with its four-step checklist; `hub.execute` gets the grant command
it needs; and trigger authoring documents that the agent is validated
against the target daemon at save, which needs that daemon connected and
accepts Claude, Codex, and OpenCode only.
* fix(opencode): reconnect v2 sessions after helper exits
A helper exit aborted the session lifetime, so every later prompt failed even though OpenCode can serve the same native session from another helper. Keep the session open, fail the active turn, and acquire and configure the current helper on the next OpenCode operation.
* fix(opencode): forget exited v2 helper generation
A dead shared helper could remain selected as the current generation. Clear that selection at exit so the first lazy reconnect acquires a live helper.
* feat(auth): authenticate daemon clients through hello
Give local CLI and desktop clients a per-run credential while remote clients send the host password inside the protocol. Keep relay passwordless admission for this mobile release so existing saved hosts keep working until the follow-up enforcement release.
Move password recovery for saved hosts into host settings and keep pairing credentials in the add flow. Include isolated web QA evidence for both flows.
* fix(auth): preserve Hub bootstrap and local credential expectations
Hub-owned sockets already carry admission; keep their hello handling synchronous through session bootstrap while resolving credentials asynchronously for ordinary sockets. Keep the resolver out of lightweight configuration imports.
Update CLI expectations for same-home credentials, let a rejected connection yield to another usable connection, and clear pairing secrets when add-flow targets change.
* fix(pairing): retain passwords across edits to the same target
Track the last valid pairing host while a URI is edited. This preserves an entered password for whitespace and partial edits but clears it when a different host becomes the target.
* test(auth): align local credential checks across platforms
Expect the CLI to authenticate through the credential in its selected home, and verify POSIX file modes only where Node exposes them.
* feat(app): move host password into a settings row with a modal
The host page rendered an always-visible password form above Name on every
host. Replace it with a Password row under a Security section below
Appearance; tapping it opens a modal with Save and, when a password is saved,
Clear. The connection-error callout keeps its guidance and gains a Set
password button that opens the same modal. Drop the unused
pairing.hostPassword body/reopen/saveError strings.
* chore: remove committed QA screenshots
QA evidence stays out of the repository; the PR body describes it in words.
* feat(app): keep the host password out of the host page
The saved password is connection data, entered only when a host is added. A
Password setting on the host page reads as the daemon's own password, so the
row, modal, and callout button are gone. A rejected host shows the daemon's
reason with guidance to remove the host and add it again with the password.
useHostMutations no longer exposes setHostPassword; relay imports still set it
inside the runtime.
* feat(app): show host connection errors in an error alert
HostConnectionError renders the existing Alert with the connection error as
its title. Auth rejections add the re-add guidance as the description; other
errors show the title only. The Overview and Connections pages share it.
* style(app): soften alert borders to half-opacity accent
* feat(app): size alerts on the button scale
Alert takes a size prop (xs/sm/md/lg, default md) whose geometry lives next to
the button's in control-geometry: one font size for title and description, the
button icon size centered on the first text line, button padding and radius,
and a one-line alert as tall as the button of the same size. Consumers pick
sm among settings rows, cards, panels, and modals, and md when the alert is
the whole page or sheet state.
* style(app): roomier, rounder alerts with a tight title block
Alert sm padding returns to the original 12/16 and every size is at least
borderRadius.xl. Title and description sit a small gap apart with no line
height override. The icon slot carries a zero-width text strut in the alert's
font so the icon centers on the first line for titled, title-only, and
description-only alerts.
* refactor(app): align the alert icon with a centered lead row
Drop the zero-width strut. The icon and the alert's first text (title, else
description) share one row centered on each other; the description and actions
sit below, indented by the icon slot width plus gap so they start at the lead
text's left edge.
* refactor(app): drop the unused Alert icon override
* refactor(server): give Pi extension handling a registry
* feat(server): map Pi task extensions to Paseo task lists
* feat(server): map Pi subagent extensions to the Subagents track
* refactor(server): hydrate Pi child sessions asynchronously through the extension host
Child session JSONL reads blocked the daemon during live events and replay. The extension host now prepares immediate upserts and bounded async timeline hydration; replay awaits it, and closed sessions discard it. Preserve claimed notification text so completion details remain visible in the parent conversation.
* feat(server): present Pi question extensions as one question form
Keep the extension questionnaire in one Paseo permission while Pi walks its RPC dialogs. The host defers an early dialog until the form is answered, then replies to each dialog using the collected choices.
* fix(server): satisfy Pi extension start hook contract
The server build checks the session interface more strictly than the fast typecheck. Return the optional permission explicitly so the adapter satisfies the published extension contract.
* docs: document Pi extension adapters
* chore(server): format Pi subagent captures
These real RPC fixtures were added before the formatter ran over the captured JSON. Normalize whitespace so repository format checks pass without changing the captured data.
* test(server): resolve Pi child fixtures as local paths on Windows
File URL pathnames retain a leading slash before a Windows drive letter. Convert fixture URLs to filesystem paths and escape those paths when inserting them into captured JSON, so child timeline assertions exercise the real file on every CI platform.
* refactor(server): remove Oh My Pi handling from the Pi provider
* fix(server): keep Pi extension adapter failures out of core session handling
* fix(server): bound Pi child-session hydration during history replay
* fix(opencode): support v2 while preserving v1 provider behavior
* fix(opencode): target v2.0.7, keeping v1 support
OpenCode 2.0.4 removed the /api/health endpoint and the plugin
await-activation endpoint, and moved form reply/cancel under the session
namespace. The v2 adapter was pinned to the 2.0.3 SDK, so any 2.0.4+
binary failed at helper-server startup with an opaque HTTP 404.
- Pin @opencode/client to 2.0.7 and require v2 >= 2.0.4 at runtime
selection, with a clear error for older binaries.
- Use server.info for helper-server readiness.
- Move form reply/cancel/list to session.form and send the permission
decision field instead of reply.
- Replace plugin.awaitActivation with a plugin.list readiness wait. A cold
location registers its config-derived commands, skills, and providers
asynchronously; an empty inventory means it is still warming.
- Send session.command.name, which replaced command, and drop the removed
SkillInfo.slash field.
- Update the v2 test harness, unit tests, dependency lockfile, CI matrix,
and provider docs to 2.0.7.
* fix(opencode): target 2.0.10 and address v2 review findings
* test(opencode): colocate v2 suites with their modules
Greptile's review flagged that the v2 tests lived in top-level
opencode-agent.*.test.ts files and imported the readiness helper, v2
client, harness, and API types from a distance. Move each v2 suite beside
the module it describes and leave the top-level files to the v1 public
path:
- opencode/v2/agent.test.ts: session lifecycle, structured output,
resume, reconciliation, interrupt/replacement, helper isolation.
- opencode/v2/readiness.test.ts: location readiness deadline behavior.
- opencode/v2/permissions.test.ts: question and approval routing.
- opencode/v2/configuration.test.ts: permissionRules, feature flags, and
helper-isolation decisions, exercised directly through the module.
- opencode/v2/mapping.test.ts: model and mode normalization.
- opencode/runtime-client.test.ts: runtime version selection.
The v1 recovery, permission-action, and catalog-deadline suites stay in
place. Test counts are preserved, with deeper per-module coverage for
configuration.
* test(opencode): split v2 local E2E by behavior and add harness helpers
The re-review flagged the v2 scenarios in opencode-bridge.local.e2e.test.ts
for inline event filtering, permission-kind branching, and polling in test
bodies. Move stream, permission, and subagent coordination behind helpers
in opencode/test-utils/v2-local-e2e-helpers.ts, and split the session
scenarios into opencode-v2.local.e2e.test.ts so each file covers one
behavior:
- opencode-bridge.local.e2e.test.ts: process sharing, shell.env scoping,
the native tool bridge, and runtime version selection.
- opencode-v2.local.e2e.test.ts: session lifecycle, structured output,
streaming, approvals, MCP preapproval, stop/replacement, rewind and
subagents, commands, and skills/attachments.
Each helper waits on or answers the provider directly, so the tests state
domain intent and assert the collected result instead of polling or
branching inline. The CI-selected "versioned runtime" case stays in the
bridge file, and the live approvals and stop/replacement scenarios were
re-run green through the new helpers.
* fix(ci): preserve required server checks across OpenCode versions
Run v1 and v2 integration coverage sequentially so branch protection keeps its existing static check names.
* fix(agents): load startup history before registering sessions
A failed history stream while adding a runtime notice must close the unregistered session instead of leaving an initializing agent behind.
* fix(agents): limit startup history before buffering it
Apply the existing timeline content limits to each incoming history event so raw cumulative tool output is not retained until registration completes.
* fix(opencode): resolve Windows executables from platform packages
Recent v1 npm releases place opencode.exe in the architecture dependency rather than the CLI bin directory. Resolve that dependency from the CLI package while retaining support for the older layout.
* refactor(opencode): name public runtime option types
* test(opencode): let the spec own both runtime versions
Install each version in an isolated temporary directory and select it through provider configuration. Restore the CI workflow unchanged so version coverage is owned by the spec rather than workflow orchestration.
* test(opencode): launch native Windows binaries in isolated specs
Avoid npm command wrappers retaining locked executable files after helper shutdown during temporary installation cleanup.
* fix(opencode): preserve the legacy path when adding v2 support
Fall back to the existing client when version probing is inconclusive. Load v2 code and its bridge plugin only after positive v2 selection, keep v1 sessions undecorated, and restore original v1 discovery errors.
* fix(opencode): retry inconclusive version selection
A failed or unrecognized version probe previously cached the legacy fallback, pinning an upgraded binary to the v1 API until client recreation. Retry detection on the next operation while keeping the legacy path available.
* fix(opencode): release legacy runtime after inconclusive probe
Retryable version detection can clear the selected client after falling back to v1. Track whether the legacy client was selected so shutdown still releases its server without touching an unused v1 manager during a v2-only run.
* test(opencode): assert full runtime notice history
* fix(opencode): restore notice and retry behavior after rebase
---------
Co-authored-by: Jonathan karras <6307688+karrots@users.noreply.github.com>
Co-authored-by: Dhruv Singal <dhruvsingalabc@gmail.com>
* fix(app): keep restored workspaces during directory refresh
Capture workspace maps and cursors from one transaction baseline so cache hydration cannot pair an empty map with a current cursor. Reject legacy directory checkpoints to recover caches already persisted with missing workspaces.
* test(app): clarify workspace cache recovery flow
* test(server): wait for repository observation before asserting ref refresh
The first checkout facts read can begin before the metadata watcher registers. The test sent an event through an optional watcher lookup at that point and silently dropped it, so a slow setup left only the initial snapshot. Hold the first read to reproduce the ordering and wait for observation setup and the initial refresh before emitting the ref event.
* test(server): isolate ref refresh from background fetch
An asynchronous background fetch could also schedule a structural refresh or populate the known-ref set. Give this test an empty fetch result and wait for that fetch to complete so the second facts read proves the injected watcher event caused it.
* test(server): require the registered repository watcher in the assertion
* fix(server): let Hub title created workspaces
Agent creation can provision a workspace under hub.execute, but a separate title request required workspace.manage. Permit the same execution authority for title changes and their responses, and lock the Hub request contract with socket and permission tests.
* test(server): keep Hub title repro free of conditional flow
* fix(server): back off degraded git polling on an unchanged workspace
When the recursive watcher declines a repository (over 250,000 entries on
macOS/Windows, or any watcher setup failure), WorkspaceGit falls back to
polling. That fallback rescheduled itself every 5 seconds for as long as the
workspace existed, with no client or agent involved. On a repository large
enough to defeat the watcher each tick is an expensive `git status`, so the
daemon held about a core indefinitely.
Double the gap after every tick whose workspace snapshot came back identical,
up to a 60-second ceiling, and reset to the 5-second base on the first change.
Both degraded loops — working tree and repository metadata — share the rule.
Explicit reads still force a refresh, so the ceiling only bounds how long an
edit made outside Paseo stays undiscovered.
Fixes#5102
* test(server): cover repository-metadata degraded backoff, document failed-tick semantics
* test(server): drive the recovery-refresh assertion off the fake clock, not waitFor
* test(server): declare metadata poll counter as a function
Observe provider response rejection before transport acceptance completes, while preserving the original promise and errors returned to callers. Cover early failure, send rejection, closure, cancellation, and successful retries through the public provider interface.
Closes#4781.
* feat(website): present company sponsorship as support, not ad space
The homepage showed four empty "Sponsor / $500 a month" boxes under a
"New" badge, right below a note about staying independent. They read as
ad inventory for sale. The price now lives only on /sponsor, next to the
checkout. The homepage shows a Sponsors row only once a company has
sponsored, and the READMEs link to /sponsor instead of straight to
Stripe checkout.
* feat(website): drop subtitles from the sponsor options
Each option carried a line like "From $5 a month" or "with an invoice" that tried to set it apart, but the differences are not real: GitHub Sponsors invoices too and any channel takes small amounts. Showing the names lets people pick the platform they already use.
Refresh Git exclusions when ignored directories appear after subscription and bound Linux and native classification work. Preserve tracked-file notifications, ignore-update barriers, and watcher recovery.
Fixes#4558. Supersedes #4808 by @Marcus172; this change incorporates six commits originally authored by Marek Rom and adds verified follow-up fixes. The independent session-context leak is fixed by #5208. Combined isolated daemon verification passed.
Co-authored-by: Marek Rom <marek.marcus.rom@gmail.com>
* feat(website): rework sponsorship with homepage sponsor spots
The founder note now states how the project is funded (sponsorship and
Paseo Hub) instead of describing the product, and the same note and
backing links render on the homepage and /sponsor from one module so the
two cannot drift.
Companies can sponsor one of four homepage logo spots at $500 a month
through Stripe rather than GitHub Sponsors, because they need invoices
and a vendor relationship. The spots render as placeholders until sold;
approving a sponsor is an entry in src/data/sponsors.ts. The checkout
URL is a placeholder until the Stripe Payment Link exists.
* feat(website): point sponsor spot checkout at the Stripe payment link
* docs(readme): add a sponsors section
* docs(readme): add the sponsors section to the translated READMEs
* docs(website): document the sponsor approval and Stripe cap workflow
The daemon dumps a heap snapshot next to the server package when it nears
its heap limit. Those files are multi-gigabyte debugging artifacts we want
to keep on disk, not stage.
feat(claude): add Opus 5.5 with its model capabilities
Combine the catalog and runtime identity work from #5191 with the
per-model effort default from #5197. Claude Code 2.1.280 introduces
Opus 5.5 with 1M context, medium effort, and always-on thinking.
Resolve prefixed minor model releases before their major version so
Opus 5.5 retains its identity and capabilities after a turn.
Co-authored-by: Leonardo G. Urci <leonardoguimaraesurci@gmail.com>
Co-authored-by: Sebastian Galindo <seb@jexhq.com>
Co-authored-by: Rodrigo <rodrigo.parisusao25201@gmail.com>
When the client reconnects with a live sync cursor, the daemon answers
with only the agents that changed, and the replica rebuilds the merged
directory from what it already holds. That rebuild went through
projectAgentDirectoryEntry, which returned null for any agent without a
projectPlacement, so those members never reached the merged map,
commitSnapshot treated them as absent, and removeFromDirectory deleted
them from the store.
An agent hydrated from its timeline or restored from the replica cache
has no placement: neither normalizeAgentSnapshot nor deserializeAgent
sets one. Every other path in the same flow derives a placement from the
working directory instead of dropping the agent, so the projection does
the same now via resolveProjectPlacement, and the function is total.
The conversations were always intact on the daemon; only the client lost
them, which is why a full restart brought them back until the next
reconnect drained the sidebar again.
Co-authored-by: Bag Raul Cristian <17720135+bagutzu@users.noreply.github.com>
Collapses the 0.9.0-beta.1 and 0.9.0-beta.2 entries into one stable entry
covering v0.8.0..83f9fba20. Adds #4413; the other post-beta.2 fixes correct
regressions introduced inside this same range.
* fix(app): keep the draft-create handoff until the new chat is current
Creating an agent from the draft composer on web flashed the "Updating
messages" panel toast for 40-200ms. That toast means "this chat may be out
of date, we are catching up" — a chat this client just created cannot be.
The timeline reducer released the create handoff at the first synchronized
page, but the visibility catch-up flag only clears once catch-up settles,
and a fresh chat always has a parked follow-up "after" page because live
rows arrive during the tail fetch. Between the two releases the state
machine saw a hydrated chat with no handoff and a pending catch-up, which
resolves to the resume toast. #4863 turned that branch from silent to
status, which is what made the window visible.
Both signals now flip in one synchronous call: createViewedTimelineSync
gains an onTimelineCurrent port, called from setVisibilityCatchUpReady —
the single place the sync declares a chat current, already covering the
parked follow-up and the fetchLatestTail overflow path. The owner
implements it by clearing the handoff. The optimistic "running" override
therefore lasts one extra round trip.
The state machine and sync-status rules are unchanged; they were correct.
* fix(app): release the create handoff when a chat leaves timeline demand
Closing a new chat's tab before catch-up settled left its sent attempt in
useCreateFlowStore forever. Reopening it in the same session then supplied
optimistic-create continuity for an authoritative chat: the panel showed it
as running and suppressed "Updating messages" while the real catch-up ran —
the state this fix exists to protect. An offline or slow host widens that
window from milliseconds to minutes.
Leaving the demanded set ends the owner's obligation to a chat exactly as
reaching current does, so onTimelineCurrent becomes onCatchUpEnded and fires
on both. It reports after the membership commit, so the sync never describes
itself mid-transition. Disconnect and backgrounding preserve demand and
therefore preserve the handoff.
* fix(app): count chat Find matches across the whole chat
Chat Find searched the whole chat but its counter read "1 of N in
message", where N was the occurrences inside the selected message. The
per-message split is an implementation detail of how the client verifies
and highlights a hit; a reader counting matches is counting the scope
they searched. The counter now reads "3 of 7" for the position across
every message that holds a hit, and Next/Previous walk that sequence,
crossing message boundaries and wrapping at the ends. Per-message
occurrence stays inside the model as navigation state.
The total needs a per-message count for messages that are not on screen,
so the daemon now reports one. It counts with the same matcher the
browser uses on rendered text — query words joined by any whitespace,
case-insensitive, never across a Markdown block boundary — which also
stops it from reporting locations the client would reject. The client
replaces the estimate with the rendered count as each message is
revealed, so the total is exact for everything that has been on screen.
`count` is optional on the wire and tagged COMPAT(timelineSearchCount):
a host that omits it is worth one match per located message until a
reveal corrects it.
* fix(server): stop chat search reporting matches the browser cannot show
Falling back to the whole Markdown source when the per-block scan found
nothing reported a match whenever the query words straddled a block
boundary — "one target" across "target one\n\ntarget two" — and on
syntax the reader never sees. The client always rejected those
locations, so before the whole-chat counter the cost was a wasted round
trip. Now that the total sums the host's per-message counts, a rejected
location inflates the number the reader sees until navigation reaches
that message and drops it. The block scan alone is the whole rule.
The model's Location derives from the wire type again, with count
narrowed to required: the search boundary fills in the COMPAT default,
so the interior never re-checks it.
* fix(app): find text in assistant messages that streamed in the open chat
Chat find failed with the generic "check the host connection" text
whenever the oldest hit sat in a response that streamed while the chat
was open and had more than one Markdown block. Presentation rendered
such a message as one row per block with ids `<messageId>:block:n`,
while hydrated history stayed one row with the raw message id, and every
message-addressed DOM lookup (find reveal, scroll-to-message, history
reveal, the render-cap expansion) only knew the raw id. The reveal step
polled for a row that never existed and gave up after five seconds.
The defect was the dual rendering, so this removes it: every assistant
message is a block group however it arrived, single-paragraph messages
included, so a row id is never a message id. Rows carry
`data-message-id`, `getStreamItemMessageId` is the one way from a row to
its message, and find, scroll-to-message, history reveal and selection
copy address messages through it. Chat find force-mounts every row of
the selected message under virtualization so counts span the whole
message, and pins the chosen occurrence to its row while the scroll
settles. Link reference definitions fold into their neighbouring block
so history never paints an empty row. Find failures now say what went
wrong (connection, history changed, could not show the match) instead
of blaming the connection for everything.
* fix(app): let the Markdown parser decide which blocks are link definitions
A hand-rolled CommonMark regex rejected destinations markdown-it accepts,
such as escaped spaces, so those definitions still became their own empty
row and the neighbouring paragraph lost its link. The block splitter now
parses the block with the same parser: no tokens plus registered
references means definitions only. The two new chat find journeys move
their mechanics into named helpers so the test bodies read as intent.
Control+F moves the caret forward on macOS, so chat Find and file pane Find
were stealing a keystroke the text editor owns. Chat Find listens on
`document` in the capture phase, so the composer received an already-cancelled
Control+F and the caret never moved.
Each Find surface carried its own copy of the shortcut predicate and only the
terminal's was platform-aware. There is now one predicate, `isFindShortcut` in
`@/pane-find`, that takes the platform as a parameter: Command without Control
on macOS, Control without Command elsewhere, no Shift or Alt. Chat Find, file
pane Find, the Find widget's own query box, terminal Find, and the terminal
emulator runtime all call it, and `terminal-find-shortcut.ts` is gone.
App surfaces source the platform from `findShortcutPlatform()`, which wraps the
app's existing `getShortcutOs()`. The predicate stays free of React Native
imports because the terminal emulator runtime is bundled on its own into the
terminal WebView; its default platform and `getShortcutOs()`'s browser branch
now share one user-agent read in `@/utils/mac-user-agent`.
Closes#5097
* fix(app): subscribe only the chats this session opened
Open-chat membership was sourced from the persisted workspace layout, which is
restored from disk at launch and carries a tab for every chat the user has ever
opened on the host. Every member got an authoritative timeline catch-up at
session start, and each of those fetches resumed its agent on the daemon: one
provider session per historical tab, every launch.
Two things followed from each resume. Registration stamped `updatedAt` over the
restored timestamp, and the resumed agent started with no attention because the
resume path never read the stored flag. Both were persisted, so restarting Paseo
rewrote every workspace's last-used time in the sidebar and cleared Ready to
review on chats the user never looked at. The timestamp loss was permanent:
workspace `statusEnteredAt` is re-derived from `updatedAt` on every daemon start.
Keep the open set on the session instead. A chat joins it the first time the user
opens it and stays until its tab closes or the session ends, so hiding, switching
workspaces, evicting a retained view, backgrounding and reconnect all preserve the
subscription, with no recent-agent limit. Layout keeps its release role and loses
its source role.
Harden the producer as well, so a resume from any caller stays inert: registration
adopts restored timestamps rather than stamping now, and resume carries the stored
attention through.
* fix(agents): keep a resumed agent's updatedAt from moving backwards
Dropping the registration stamp exposed a skew the stamp had been erasing.
`extractTimestamps` read `lastActivityAt ?? updatedAt`, but the two diverge:
renaming, labelling, restoring from archive, marking unread, and clearing
attention all move `updatedAt` on an unloaded agent without touching
`lastActivityAt`. Eight of 854 agent records on one dev machine carry that skew,
up to eight days of it.
A resume then handed clients a timestamp older than one they already held, and
`acceptAgentDirectoryUpdate` drops any state update that goes backwards. Marking
a chat unread without opening it, then opening it, left the unread dot stuck in
History, the agent list, Command Center and the tab: the daemon cleared attention
and the client refused the update.
Read both fields and take the newest, which is already the rule
`resolveStoredAgentPayloadUpdatedAt` applies to the list payload. Move that
function beside the other stored-record readers as `resolveStoredAgentUpdatedAt`
so one rule covers both paths.
Also replace the `!options?.updatedAt` inference in `registerSession` with an
explicit `restoring` flag. The inference was correct for all four call sites, but
it made a second meaning out of a field, and the next caller to seed a timestamp
would have opted out of activity stamping silently.
* test(app): assert a reloaded layout subscribes only the chat it restores into
No e2e reloaded the page with a populated workspace layout and asserted which
agents the daemon acknowledged, so the regression this branch fixes had no
end-to-end guard. Opening four chats across two workspaces leaves a tab per chat
in persisted layout; after a reload only the restored chat may be subscribed,
because subscribing resumes that agent on the daemon.
Verified it fails when session-context's layout callback is pointed back at
replaceVisibleAgentIds: the three restored tabs nobody opened join the
acknowledged set.
uploads.github.com returned HTTP 500 five times during the v0.9.0-beta.1
release, each costing a ~50 minute rebuild. The upload step passed every
artifact to one gh invocation, so a single failed asset discarded the rest
and failed the job.
Upload each asset separately with bounded retries, shared by the macOS,
Linux and Windows jobs.
Metro's jest workers OOM transforming the generated ws-outbound.aot.js
validator, the last module in the web bundle. Two consecutive v0.9.0-beta.1
macOS x64 builds died at 5081/5082 modules on that file.
Set max-old-space-size per runner: 8192 on the 14 GB Intel runner, 4096 on
the 7 GB arm64 runner.
* fix(app): restore character-paced streaming reveal
Restore the v0.8 character reveal model and remove the word-level fade implementation so streamed assistant and reasoning text remains opaque, grapheme-safe, and immediately completes when a turn ends.
* fix(android): preserve inverted timeline selection
React Native's prebuilt Android library does not consume source patches under node_modules. Register the compiled scroll manager before the core package so inverted child-reveal calculations leave focus and text selection in place while normal and explicit scrolling remain unchanged.
Desktop and CLI helpers imported daemon bootstrap through the server root, while supervisor configuration retained the full wire schema tree. Expose existing capabilities through narrow package entrypoints and share configuration schemas without loading message definitions. Guard the dependency trees without tree-shaking so unused exports cannot hide the runtime cost.
* fix(app): open chat find from the focused composer
Listen during capture so the composer text input cannot swallow the find shortcut before it reaches chat search.
* test(app): verify draft retention on first chat find
* feat(plugins): unify source installation and simplify plugin settings
* feat(plugins): install npm sources and review CLI updates
Treat install selectors as one-time choices. Keep source identity separate from the installed revision, and resolve ordinary updates from the current upstream default. npm only acquires directories; plugin loading remains independent of npm.
* fix(plugins): preserve CLI diagnostics and exercise reviewed updates
Move new source capability coverage to real browser journeys. Update the requirements regression to exercise preview and apply, and preserve the existing duplicate-selector diagnostic across positional option scopes.
* test(plugins): remove unused pending helper
* test(plugins): pack registry fixtures with the Windows command runner
* test(plugins): assert failed client contribution setup
* docs(plugins): consolidate guides and document npm publishing
* docs(plugins): introduce publishing choices before npm instructions
* test(plugins): own browser journey cleanup in a fixture
* feat(plugins): prepare scaffolded packages for publishing
* fix(website): render phone mockups correctly in Safari
Replace trigonometric CSS scaling with measured numeric ratios after Safari placed phone content outside its clipped frames. Scale desktop mockup corners with the frame so they remain proportional on mobile.
* test(website): expose the website browser test suite
* test(website): run mockup regressions in CI
* fix(app): dock the New workspace composer like a chat on phones
Only the composer is bounded by the viewport; the setup fields sit above it in a
bottom-anchored stack that moves up as the draft grows. The keyboard reservation
is retained on every screen, so the composer keeps one height across keyboard
transitions. Removes the per-screen reserve policy.
* fix(app): share composer docking across mobile hosts
Extract chat's retained capacity and platform keyboard motion so chat,
workspace drafts, and New workspace share one sizing and dismissal boundary.
Keep the native drivers and settled portal reconciliation unchanged.
* fix(app): preserve overlays in centered composer docks
Honor the optional overlay child in both layouts so changing dock placement does not silently discard part of its content.
* test(app): resolve keyboard controller through its web entry
The shared keyboard entry point makes stream tests load the provider. Keep its real ESM dependencies inside the Vitest resolver so CommonJS does not bypass the React Native web alias and parse native Flow sources.
* fix(app): keep native chat scrolling responsive
Place keyboard dismissal behind scrollable content so an idle Android Fabric JS responder cannot intercept native drags. Keep decorative form padding transparent to iOS hit testing and exercise idle native scrolling after submission from every composer host.
* fix(plugins): preserve nested provider subagent links
The plugin adapter routed descendants to their managed root but discarded their direct parent when publishing descriptors. Preserve runtime-to-public child identities and forward optional spawning tool IDs.
* test(plugins): separate opening and live subagent scenarios
* test(server): retry terminal fixture directory cleanup
Match the daemon harness cleanup policy so transient Windows EBUSY locks do not fail terminal ownership tests after their assertions complete.
Show a spinner attachment while a selected file is read and uploaded, yield between 128 KiB chunks so the UI stays responsive, validate every file in a batch before sending any of them, and leave one SelectedFile shape across the picker, drag-and-drop, and the upload path.
* fix(history): preserve chronology and clarify search matches
Keep workspace names primary and highlight each searched field so results explain why they matched. Filter before cursor pagination and retain date buckets.
Constrain shared subsequence matching to each whitespace-delimited word while preserving fuzzy abbreviations in Command Center and other consumers.
* test(history): reuse response helper in pagination regression
* test(history): express browser journey through named helpers
* fix(app): start menu entering animation only once the surface is placed
Reanimated's web entering path snapshots the element rect at animationend and
writes top/left back when it cleans up the keyframe 750ms later. When the
150ms animation ends before anchor and content measurement resolve, that
snapshot is the off-screen measuring position and the open menu jumps back to
-9999. Remount the surface when its position resolves so the animation starts
from the final position.
* fix(app): stabilize native composer sizing after edits
* test(app): verify workspace composer space after keyboard dismissal
* test(app): start the hold-to-delete regression mid-draft
From the end of the draft the pre-fix input also returns to baseline. Placing the cursor
eight lines into the draft before the first hold-delete leaves the old build at the
capped height with the placeholder visible, and the fixed build at one line.
* fix(server): preserve comparison base when restoring worktrees
Restore archived worktrees from the workspace's saved comparison base instead of using the feature branch as its own base. Preserve the branch HEAD and allow restore when an exact comparison ref no longer exists.
* test(app): isolate draft preference checks from menu animation
* fix(voice): speed up replies and hide prompt wrappers
Keep voice prompt cleanup in the client presentation path so provider history and live messages render consistently without rewriting canonical input. Correct native TTS scheduling and threading, and reconnect interruption to speech detection and session cancellation.
* test(voice): clarify playback and timeline regression journeys
* test(voice): centralize native worker cleanup and repair fixture path
* test(app): exercise workspace eviction without reloading the app
* test(app): preserve streamed output during workspace eviction
Hold the turn until the workspace is evicted instead of dropping all stream events during navigation. Slow CI runners can finish the entire response before navigation ends; waiting for producer completion before release makes that ordering deterministic.
* test(app): stabilize subscriptions during workspace eviction
Opening another chat replaces the timeline subscription, making buffered frames from earlier generations unroutable. Preopen all chats before holding the stream, finish production before eviction, and assert both early and final response content with no subscription replacement.
* fix(app): preserve composer capacity when the keyboard closes
Releasing the keyboard reservation enlarged an unchanged capped draft. Retain the last editing reservation while still recalculating capacity for viewport changes and subsequent keyboard openings.
* perf(app): isolate composer editing and resize updates
Draft and cursor subscriptions rerendered the chat and full composer on every native edit. Give editing consumers a live text source, preserve attachment identity on text updates, and read viewport settling state without a render.
Read live input text for web resize measurement so removing per-keystroke props does not collapse a draft when its width changes.
* fix(app): restore current draft when the composer remounts
The last replacement command does not include subsequent edits. Seed remounted editors from the live draft snapshot so switching launch modes restores the saved text without subscribing the composer to every edit.
* fix(app): synchronize composer resolution state before paint
Avoid an extra passive-effect delay when forwarding forge resolution state from the isolated binding. Exercise live-text width measurement through the actual composer E2E journey instead of a hook-only test component.
* fix(app): keep reconnect toast transitions continuous
Preserve the toast surface when its content changes, and apply initial appearance before mounting screens so settings hydration cannot restart a visible toast.
* test(app): use supported appearance QA coverage
* fix(client): preserve creation on older hosts
Creation keys must not disable workspace and agent creation when the host lacks request receipts. Keep capability selection inside the client and degrade to the original RPCs with the initial prompt intact.
* test(creation): own legacy daemon cleanup in fixtures
* fix(client): preserve original prompted creation on legacy hosts
Legacy receipt support does not preserve create-and-prompt semantics when split into a later send. Reuse the original creation RPC so naming, creation context and startup receive the same input as before.
* feat(app): add pane Find to file source
* fix(app): float the pane Find widget and tighten its chrome
Find pushed the first lines of the file down, ran 480px wide with 32px
ghost buttons, and let the replacement input start left of the query
input. Float the CodeMirror top panel out of flow so the widget overlays
the top right of the pane, move the disclosure toggle into a gutter that
spans both rows so the two fields share a leading rail, put the match
count inside the query field, and drop the icon controls to the shared
pane-toolbar chrome the file bar above already uses.
* fix(app): keep the active match visible and drop the Find button
The floating widget covered the active match whenever the match sat under
it and the document was too short to scroll clear. CodeMirror's own panel
scroll margin cannot reveal a first line, so the file integration now owns
placement: the widget renders as an overlay beside the editor, the model
measures it against the active match, and flips it to the opposite corner
only when the current corner collides. Hysteresis keeps navigation from
bouncing the widget between corners.
Also removes the standalone Find button shown while Find is closed, per
user direction. Cmd/Ctrl+F is the entry point.
* fix(app): stop the Find placeholder from hiding Go to line
Hiding `.cm-panels` suppressed every CodeMirror panel in the file editor,
including the Go to line dialog that Mod-Alt-g opens through the same
search keymap. Hide only the inert Find placeholder panel instead, by its
own class, and leave the shared panel container alone.
* feat(app): find text throughout chat history
Discover candidate transcript locations on the daemon, then reuse prompt navigation to load history and verify rendered occurrences on the client. Keep search state inside its own modules without changing timeline or task presentation contracts.
* fix(app): show actionable chat search failures
Keep transport error details in the search model while showing localized retry guidance. Use named client request options for timeline search.
* feat(app): add pane Find to file source
* fix(app): float the pane Find widget and tighten its chrome
Find pushed the first lines of the file down, ran 480px wide with 32px
ghost buttons, and let the replacement input start left of the query
input. Float the CodeMirror top panel out of flow so the widget overlays
the top right of the pane, move the disclosure toggle into a gutter that
spans both rows so the two fields share a leading rail, put the match
count inside the query field, and drop the icon controls to the shared
pane-toolbar chrome the file bar above already uses.
* fix(app): keep the active match visible and drop the Find button
The floating widget covered the active match whenever the match sat under
it and the document was too short to scroll clear. CodeMirror's own panel
scroll margin cannot reveal a first line, so the file integration now owns
placement: the widget renders as an overlay beside the editor, the model
measures it against the active match, and flips it to the opposite corner
only when the current corner collides. Hysteresis keeps navigation from
bouncing the widget between corners.
Also removes the standalone Find button shown while Find is closed, per
user direction. Cmd/Ctrl+F is the entry point.
* fix(app): stop the Find placeholder from hiding Go to line
Hiding `.cm-panels` suppressed every CodeMirror panel in the file editor,
including the Go to line dialog that Mod-Alt-g opens through the same
search keymap. Hide only the inert Find placeholder panel instead, by its
own class, and leave the shared panel container alone.
* feat(app): find text in terminal scrollback
* test(app): account for terminal write completion before barriers
* fix(terminal): only Cmd+F opens Find on macOS; Ctrl+F reaches the shell
* test(app): exercise terminal find shortcuts against real ptys
* test(app): wait for visible vim lines before terminal page down
* test(app): account for vim page overlap in terminal shortcut journey
* feat(app): add pane Find to file source
* fix(app): float the pane Find widget and tighten its chrome
Find pushed the first lines of the file down, ran 480px wide with 32px
ghost buttons, and let the replacement input start left of the query
input. Float the CodeMirror top panel out of flow so the widget overlays
the top right of the pane, move the disclosure toggle into a gutter that
spans both rows so the two fields share a leading rail, put the match
count inside the query field, and drop the icon controls to the shared
pane-toolbar chrome the file bar above already uses.
* fix(app): keep the active match visible and drop the Find button
The floating widget covered the active match whenever the match sat under
it and the document was too short to scroll clear. CodeMirror's own panel
scroll margin cannot reveal a first line, so the file integration now owns
placement: the widget renders as an overlay beside the editor, the model
measures it against the active match, and flips it to the opposite corner
only when the current corner collides. Hysteresis keeps navigation from
bouncing the widget between corners.
Also removes the standalone Find button shown while Find is closed, per
user direction. Cmd/Ctrl+F is the entry point.
* fix(app): stop the Find placeholder from hiding Go to line
Hiding `.cm-panels` suppressed every CodeMirror panel in the file editor,
including the Go to line dialog that Mod-Alt-g opens through the same
search keymap. Hide only the inert Find placeholder panel instead, by its
own class, and leave the shared panel container alone.
* chore(ci): audit packaging costs and repeated test execution
Read-only audit: Linux packaging and smokes consume 12-14 minutes; all three CLI shards repeat the 294-test source suite and build the server twice. Preserve the 17 required contexts, move packaging to main, run CLI source tests once, and consolidate repeated E2E setup into journeys. Historical logs and the complete audit were reported outside the repository.
* ci: move packaging to main and remove repeated CLI suites
PR runners were rebuilding distribution artifacts and executing the entire CLI source suite on all three E2E shards. Keep packaging coverage after merge, let the local E2E runner own its dependency build, and execute source tests once in an existing required check. Root typecheck already validates plugin examples.
* test(ci): consolidate journeys and remove repeated startup work
Run user journeys with shared setup, retain one Hub CLI contract, and weight the existing Playwright shards using observed costs. Remove implementation-only and ad-hoc measurement checks. Await root-watch closure and publish creation failure commands atomically so teardown and failure injection have completion boundaries.
* test(hub): remove incidental observation and speculative shutdown changes
Hub control RPCs need a connected client without an agent observation. Preserve the failed-create subscription contract and remove product shutdown changes whose Windows cleanup cause was not reproduced.
* fix(client): clear the scheduled retry when a connect attempt opens a transport
An explicit connect() during reconnect backoff opened a new transport but
left the scheduled retry armed. When it fired on the healthy connection it
disposed the transport and reconnected, dropping requests in flight. This
is why owned-subscriptions-old-daemon could still fail after #4858 added
the explicit reconnect. attemptConnect now owns the rule ensureConnected
already applied; the new unit test fails on the old client.
* test(e2e): keep the completed-block, font-readiness, and live-page guards inside journeys
- agent-stream-ui: the auto-scroll journey anchors on the completed intro
block by content and requires its Markdown root to stay the same mounted
node while later blocks stream (#1989 guard, reshaped off the tail block).
- changes-pane: the code-typography journey holds font loads and requires
no diff geometry before they resolve, then applies the font change.
- agent-timeline-pagination: the live-output page load runs against a
thirty-minute mock stream and gates the release on the running turn, so
the status assertion no longer races a ten-second turn.
* test(desktop): install the Electron binary once before the unit suite
Electron 44 downloads its binary on the first Node require("electron")
instead of at npm ci. Vitest workers importing electron-log/main raced
that download and extraction (File exists on dist/electron, then
"Electron failed to install correctly" in a sibling worker) on both
Ubuntu and Windows desktop-tests jobs. Run the package's idempotent
install-electron in one process before vitest forks.
* ci(desktop-packages): pin actions and smoke packaging-path pull requests
Pin checkout, setup-node, and upload-artifact to full commit SHAs. Keep
packaging off ordinary PR CI, but run the Linux package smoke on pull
requests that touch packages/desktop, nix, or the workflow so a packaging
or sandbox regression cannot land on main unchecked. The desktop release
workflow already builds and smokes its own artifacts on all platforms.
The mock provider splits "**Bold" into two frames, "**Bo" and "ld". The
helper forwarded both back-to-back and asserted the strong span reads
"Bold", which only holds when the browser commits both frames to the
store in one reducer flush and the message first mounts with "**Bold".
When a frame boundary lands between the two tasks the message mounts
with "**Bo" and word pacing holds "ld" until the whitespace after it
arrives, so the span reads "Bo" until the gate releases. That is the
designed behaviour of the reveal, and the batching is up to the
browser's task scheduling.
Stop after "**Bold text" instead: the space after "Bold" ends the word,
so every batching reveals "Bold" and never more than "text". Forcing the
frames into separate commits reproduced the CI failure before the change
and passes after it.
* fix(app): keep open chats current and show resume progress
Keep open-chat delivery independent of mounted views, freeze hidden chat rendering, and catch up visible chats before hidden ones. Distinguish host reconnection from timeline freshness so cached messages do not silently appear current after resume.
* fix(app): prevent sync status from overlapping action errors
Share the existing panel toast slot so action errors remain readable during reconnect and catch-up. Verify the interaction and draft retention in the real browser, preserve the tested freeze transition, and refresh the Nix dependency hash for react-freeze.
* test(hub): expose complete creation and message retry failures
Partial response assertions hid the creation error and allowed duplicate responses with incomplete agent data. Check complete success envelopes, the shared agent snapshot, and null message errors so future failures print the actual error payload.
* fix(app): dismiss retained chat menus before freezing
An async fork navigates before its closing state commits, so freezing the source chat could leave its portal backdrop blocking the destination. Remove the shared menu surface in the inactive commit before suspension.
Keep regression assertions aligned with open-chat subscriptions, per-agent incremental recovery, plugin contribution lifetimes, and rendered image completion.
* test(app): expect the reloaded plugin to contribute its pill again
A full reload discards the pressed pill with the page, then evaluates the plugin again and its agents snapshot re-adds the pill. The old count-zero assertion only passed on main because the chat was not ready yet when the URL settled; open chats now subscribe from session start, so the pill is rendered by then.
* fix(app): suspend hidden chats on web only
react-freeze is Suspense with a never-resolving thenable. Suspending a committed subtree hides its host nodes, tears down layout effects, and detaches refs, which changes Fabric ownership of the retained chat. Split the boundary by platform: the web file keeps the two-commit freeze, the native file keeps the chat mounted and live, and the retained-panel signal remains the only thing that stops background work. Restore the mobile-panels rule the branch had rewritten.
* fix(creation): never read a receipt while replacing it
Admission reads the creation receipt while the runner replaces it atomically. On Windows a rename over a file this process still has open for reading fails with EPERM, and that error marked an otherwise completed creation as failed (#4865). Queue reads and writes per receipt so one owner never overlaps them, and serialize the record when the write is queued because the runner mutates it afterwards. No retry on EPERM: the conflict is removed rather than tolerated.
Linux cannot reproduce the rename failure, so the Windows server-tests job (creation/index.test.ts and hub/execution-session.websocket.test.ts) is the verification for this change.
* feat(app): reveal streaming text as one continuous fade front
Streaming assistant text is released by word and every character fades in
over the same 150 ms envelope from a start time owned by the message. Start
times never decrease in reading order, so a line paints as a single front
moving left to right, however the text is chunked. Per-word envelopes with an
internal stagger let the next word's first letter overtake the previous word's
last letter, which read as holes and flashing words at the real frame rate;
100 ms frame strips hid it.
Native renderers receive the fade ranges as a prop on a host view wrapping the
root Text, so ranges and text land in the same Fabric mount transaction. The
earlier binding sent ranges through a separate async module call keyed by view
tag and had to reconcile them against the view's text; on a fast phone the UI
thread lags JS by several commits, mounts and calls interleave, and every
reconciliation heuristic guessed wrong. Native code no longer infers newness
or creates start times.
The mock load-test provider now separates cycles with a blank line so a cycle
heading is not appended to the previous paragraph.
* fix(ci): refresh streaming dependencies and isolate E2E setup
* test(app): distinguish fade cleanup from Markdown replacement
* test(app): detect restarted fades during Markdown updates
* docs(app): keep streaming guidance focused on constraints and QA
* test(app): control animation time in Android fade assertions
Fixed wall-clock sleeps let emulator scheduling select the sampled animation frame. Inject the animator time source and drive paint at exact timestamps within one UI-thread operation; production keeps the system clock. Verify the mid-fade sample and the exact cleanup boundary without sleeps, polling, or retries.
* fix(test): compile the desktop renderer before lifecycle checks
The lifecycle fixture treated Metro /status as app readiness. Its first
Settings click therefore spent its 90-second UI budget compiling the cold
bundle. An isolated cold-cache run restricted to one CPU reproduced the
click timeout before the app runtime had loaded.
Reuse the browser suite's existing bundle warmup before launching Electron,
and preserve Metro output in the lifecycle artifacts. The UI timeout,
build-request timeout, lifecycle assertions and product behavior are unchanged.
CI did not preserve Metro output, so its precise startup timing is not
recoverable from the failed run. The retained log makes subsequent startup
failures diagnosable without relying only on an empty renderer text dump.
Validation: three complete lifecycle E2E runs under private Xvfb displays;
existing Metro readiness tests; app typecheck; targeted lint; root format.
* test(file-observer): await burst delivery through native recovery
The deletion phase required every event within 10 seconds, but native
coalescing recovery uses a 30-second safety audit followed by the existing
reconciliation debounce. An idle diagnostic excludes that delayed audit
and therefore cannot act as a completion barrier.
Await the required delivered events instead, preserving every deletion
assertion and the existing overall test watchdog. A real-filesystem test
with controlled native notifications reproduces 95 outstanding paths at
the old deadline and verifies completion when the safety audit delivers
them. No production observer behavior changes.
* test(file-observer): verify burst recovery through public events
The controlled-clock regression constructed a private native backend,
bypassing the interface used by consumers. Consolidate recovery coverage
into the public observer integration test instead: drive real writes,
deletes and directory renames, then await every required public event,
including the 450 surviving files at their renamed paths.
No public capability was added: subscribe callbacks already express
completion. No production behavior or test timeout changes.
* test(app): drive Android fade tests through the native host
The instrumentation suite constructed TailFadeInAnimator directly, advanced
its private clock, and counted FadeInSpan instances. It now mounts the
WordFadeHost that React mounts, feeds it the ranges prop the bridge sends,
and asserts rendered pixels: hidden before a start time, a monotone front
inside and across words, settled frames equal to the baseline, and no frame
callbacks left after settling. Span counts and internal timing state are no
longer asserted.
Choreographer cannot be advanced deterministically from a test and the fade
epoch is the message's wall-clock start time, so the host takes a
WordFadeFrameClock (time plus frame callbacks) as its smallest public seam;
production keeps the Choreographer-backed default.
Fixture text is sized in pixels so every sample stays on one line at any
density; at 420 dpi the 32sp fixture wrapped and the column helper summed
glyphs from the first line.
* fix(app): prevent and recover workspace layout crash loops
* fix(desktop): capture resized browser tabs without hanging
A resident guest can paint fresh pixels after resize while capturePage's
native surface-copy request remains pending. The same failed guest returns
a fresh frame through frame subscription, so waiting for resize or another
animation frame does not correct the failing copy request.
Capture one subscribed frame within the existing five-second budget and
release the subscription on completion or cancellation. Preserve temporary
frame production and its original throttling policy. Remove viewport copy
retries and E2E screenshot polling; assert the resized image dimensions.
Refs #4866. Three full real-Electron browser journeys passed under Xvfb,
including physical clicks and fresh screenshots while the window is hidden.
* test(app): choose Agent before exercising the runtime catalog
The newly merged catalog journey assumed an empty workspace opened an agent draft tab. This branch opens the New launcher, so create the agent draft tab explicitly before checking its composer and model choices.
* fix: settle closed browser captures and validate persisted layout fixtures
Destroying a guest during frame subscription left its screenshot pending until the capture deadline and blocked queued captures. Reject at destruction, remove the listener on every exit, and return browser_tab_closed. The regression proves immediate settlement and queue release.
Reuse the unchanged persisted layout schema from a UI-independent module so the recovery fixture validates storage before editing it.
* test(app): encapsulate Explorer layout setup and cleanup
Keep persisted layout schemas and corrupted-state construction inside the seedCorruptedWorkspaceLayout helper. Use the existing withWorkspace fixture for cleanup in all four Explorer journeys so test bodies follow a straight setup, action and assertion flow.
* test(app): target the selected workspace in layout recovery
The corruption helper selected the first persisted entry, so a multi-workspace journey could leave the intended workspace untouched. Resolve its exact persistence key from the requested workspace ID and fail setup when absent. The failing-first journey now preserves an earlier workspace with an agent draft tab while recovering the selected workspace.
* feat(app): give compact Changes an overview and a straight rail
Three fixes to the Explorer's Changes tab on phones, where there is no
tree rail and the diff is the only view of what changed.
No placeholder stat while the sidebar slides in. `useCheckoutDiffQuery`
reported `isLoading` only when the query was enabled and connected, so an
inactive retained panel settled on `files: []`. The comparison toolbar
summed that into "+0 -0" and the body rendered "No changes" for a diff
nobody had fetched yet, both replaced a moment later by real numbers. The
result now derives from the payload alone: no payload means loading,
whatever the query is doing. Callers already handle loading correctly, so
nothing downstream needed a special case.
Jump to file. A floating action button over the diff opens a sheet with
the same changed-files tree the desktop rail renders; tapping a file
closes the sheet and scrolls the diff there. `ChangedFilesTree` moved out
of `diff-pane.tsx` into its own module so both surfaces own one tree, and
its list is now the sheet-aware one so it scrolls with the sheet gesture
on native. `DiffDocument` takes a `contentInsetBottom` so the button
never traps the last line.
Rails on the compact header. The tab labels sat at 20dp and the close X
at 16dp while every toolbar row below them used 12dp leading and 8dp
trailing. The close button was also a hand-rolled Pressable rather than a
ToolbarButton. `paneContentToolbarTrailingPadding` now takes whether the
row's last control is a bare glyph or a framed one: a glyph's hitbox
overhangs its ink so the row pads by less than the rail, but the compact
git-actions trigger paints its own frame and was landing flush against
the screen edge. The commits header chevron moved to the depth-0 tree
rail. Desktop geometry is unchanged.
* fix(app): preserve Explorer touch targets and reset file overview
* fix(app): preserve diff selections across repaints
Header typography refreshes and height resizes changed the repaint callback
and inadvertently cleared active source selections. Keep selection updates
stable while retaining explicit source and selection-layout invalidation.
* test(app): express diff copy regression as user actions
* test(app): express file overview flows as user actions
* fix(providers): keep duplicate catalogs from breaking agent drafts
Normalize model identities before publication and when materializing older
catalogs, preserving the first definition and configured overrides. Keep
explicit default variants separate from the base model selection.
* fix(providers): preserve saved variant identities across resumes
Persist the selection encoding so legacy names remain literal. Refresh provider persistence when a selection changes, before another prompt is sent.
* test(app): let fixtures own catalog journey cleanup
* fix(providers): preserve the existing default variant contract
Keep one Default catalog choice and let the provider resolve an omitted variant. Remove the encoding and persistence machinery; other variant IDs retain their literal meaning.
* test(creation): expose failed completion after conflicting requests
Creation returns a failed snapshot instead of rejecting. Assert the result before its side effects so platform failures report the underlying error.
* test(app): wait for the seed client after daemon restart
The browser and seed client reconnect independently. Wait for the seed connection before issuing post-restart RPCs instead of assuming browser readiness covers both clients.
* test(app): isolate visible prompts and checkout diff fixtures
Inactive tabs retain message DOM, so prompt assertions must inspect visible rows. Ignore the local fixture remote before observation rather than deleting it after the daemon has started tracking the checkout.
* fix(file-observer): preserve final deletions when batching events
A duplicate creation notification followed by deletion was collapsed into an update, hiding final absence from consumers that distinguish deletes. Keep final deletes while retaining update semantics for atomic replacement.
* fix(desktop): wait for Metro HTTP readiness before launching Electron
Expo briefly listens while probing port availability. A successful TCP connection can therefore launch Electron before Metro starts and leave it exiting on connection refusal. Require the configured renderer origin to report the running packager status.
* test(ci): consolidate fixture and readiness corrections
Await the connection and UI states each journey owns, isolate Git fixture contents before observation, and hold real Markdown frames at asserted checkpoints. Keep creation completion diagnostics explicit without claiming the original Windows failure is fixed.
* fix(desktop): wait for Metro HTTP readiness before launching Electron
Expo briefly listens while probing port availability. A successful TCP connection can therefore launch Electron before Metro starts and leave it exiting on connection refusal. Require the configured renderer origin to report the running packager status.
* test(ci): consolidate fixture and readiness corrections
Await the connection and UI states each journey owns, isolate Git fixture contents before observation, and hold real Markdown frames at asserted checkpoints. Keep creation completion diagnostics explicit without claiming the original Windows failure is fixed.
* test(desktop): describe Metro readiness through HTTP states
Keep protocol mechanics in the fixture and assert readiness only after a successful packager response. Cover non-success status and unrelated HTTP content without inline synchronization in the test body.
Restore provider/model selection in Android tablet modal windows by giving both native modal hosts their own gesture-handler root. Preserve phone sheet and web portal behavior.
Include native regression scripts, test dependency resolution, and verified CI test corrections. Generated media and logs are excluded. Refs #4475 and #4377; consolidates #4721 and #4771.
Co-authored-by: Mike P. Kuhl <mkuhl@softmachine.at>
Co-authored-by: Cameron Chin <cjcrjc@users.noreply.github.com>
* fix(agents): prevent OOM from cumulative tool history
Repeated progress updates retained complete historical copies of growing tool output. Retain projected items on ingestion and serve them for every history fetch, preserving source sequence coverage for catch-up. Keep intermediate payloads only in streaming; confine compatibility degradation to child transcripts and overwritten fork checkpoints.
* test(client): expect projected subagent capability in hello
* fix(app): report unexpected child history failures
Keep connection failures recoverable while forwarding unexpected fetch and reconciliation errors to diagnostics. Name observer arguments to keep server and agent identities explicit.
* test(agents): assert projected history after rewind
* docs(protocol): tag projected child compatibility gates
* fix(app): keep growing composers below the header
Native keyboard translation left the dock at full height, so intrinsic composer growth could extend behind the header. Reserve the keyboard destination at motion boundaries and bound the composer to its stationary viewport while preserving native input scrolling and animation.
* docs: reuse existing test suites and automation
Keep plan proposals at their original timeline position through approval, rejection, and cancellation. Make plan cards collapsible while retaining readable history after follow-up prompts and daemon restart.
* fix(subscriptions): preserve older-host workflows and explicit timeline recovery
Keep established app and SDK workflows available on older daemons without emulating independent directory filters. Restore live timeline membership without choosing the consumer's history recovery policy. Preserve daemon filesystem paths separately from legacy workspace IDs.
* fix(client): keep legacy workspace replies scoped to the requested page
* fix(app): keep incomplete Markdown formatted while streaming
Treat unfinished formatting at the growing assistant-message tail as provisional so delimiters and link destinations do not flash while tokens arrive. Completed messages retain ordinary Markdown parsing.
* test(app): express streaming Markdown checks as user actions
* fix(agents): restore archived Codex workspaces without losing focus
History selection must survive without cached agent records, and reading an archived Codex thread must not acquire an interactive writer. Keep workspace restore explicit, serialize native lifecycle transitions, and leave transcript lifetime with its owner.
* test(app): express Codex restore specs as user journeys
* test(app): finish restore fixture cleanup after teardown errors
* test(app): align restore coverage and await fixture readiness
Workspace restore leaves agents archived until explicit Unarchive. Keep that distinction covered for multiple agents. Compile the recovery fixture before its interaction test starts, and wait for sidebar agent hydration before asserting tab presence.
* fix(app): let touch workspace titles use the available width
Omit trailing stats when the permanent touch menu replaces them. Keep concealed stats in layout only for temporary desktop shortcut hints, preserving hover and shortcut geometry.
* test(app): express sidebar width checks as user journeys
* fix(desktop): preserve Linux sandboxing wherever the host allows it
Chromium checks its Linux sandbox before main.js, while package installation probes run as root and miss AppArmor restrictions on desktop users. Choose the launch flags before exec and always install the SUID alternative for native packages. Test published artifacts as an ordinary user with namespaces both denied and allowed; never repair permissions in the smoke harness.
* fix(desktop): retain the Linux release build baseline
* fix(ci): allow canceled desktop smoke uploads to stop
* test(desktop): verify the installed RPM sandbox under AppArmor
* fix(ci): resolve packaged desktop dependencies before installation
* chore(desktop): keep QA evidence out of the repository
* fix(daemon): keep lifecycle operations on the selected instance
Restart reconstructed launch inputs and address fallback could redirect a home-selected shutdown to another daemon. Let supervisors publish bound endpoints, retain their worker restart contract, and share captured local lifecycle ownership between CLI and Desktop.
* test(daemon): verify bounded desktop readiness and cancellable artifacts
* test(cli): verify reload target hints and durable offline pairing
* test(daemon): align lifecycle fixtures with ownership and targeting
* fix(daemon): preserve reachability when status details time out
Provider discovery can outlast a status request after authentication succeeds. Keep local connection facts separate from unavailable details, while explicit remote queries retain their error outcome.
Migrate CLI and packaged Desktop fixtures to persistent managed configuration and explicit targets. Isolate generic Git observation from forge discovery and reuse disposable CLI homes on Windows.
* test(daemon): observe restart through the worker status RPC
The bounded CLI status observation may omit worker details while an authenticated connection remains healthy. Exercise the app-style restart through its full RPC and retain the replacement observation, with a real slow provider probe covering the distinction.
* fix(app): reload to the project picker after a render error
Keep the recovery boundary above the router so Reload starts a new navigation tree at the picker instead of remounting the failed workspace. Preserve saved layouts and normal startup restoration.
* test(app): exercise reload through fixture routes
Give each observation a server-issued ID owned by its physical connection. Preserve snapshot-only reads and combined list-and-subscribe, source-bound replies, lifecycle cleanup and legacy wire compatibility.
* perf(app): optimize wrapped measurements and checkpoint native layout experiment
Compute full grapheme geometry only for final wrap fragments. Preserve shaped-text search behavior while bounding additive wrap probes. Keep the Android background layout experiment isolated from the normal diff renderer; native renderer integration and iOS bindings remain unfinished.
* perf(server): reduce cold diff generation work
Keep active diff reads owned across invalidation and wait for the initial watcher inventory before cold generation. Batch patch and blob reads while preserving per-file size isolation, bounded output, and daemon responsiveness. Avoid constructing fallback tokens when full-file highlighting already covers both sides.
* test(server): exercise oversized highlighted diff rejection
The single-line fixture now bypasses highlighting under the long-line guard, so it no longer reaches the structured payload limit. Use short dense lines to keep testing oversized rejection and continued socket usability.
* fix(server): queue fresh diffs for forced reads
A forced request following a repository mutation must not reuse an older active build. Invalidate that result and coalesce waiting callers onto one subsequent build. Give the cache a named object input and completed-value type.
* test(server): share deferred diff loader setup
* test(server): name completed diff cache setup
* refactor(server): use aliases for new diff imports
* fix(server): keep shipped diff imports resolvable by Node
The TypeScript build preserves path aliases without a runtime resolver. Restore relative imports for shipped code and declarations, and correct the server import guideline that prompted the regression.
* fix(desktop): throttle hidden browsers between screenshots
Keep Chromium background throttling enabled outside serialized captures. Temporarily enable frame production and wait for fresh paint so hidden-window screenshots remain usable without continuously animating idle guests.
* fix(desktop): share screenshot deadline across paint and capture
* test(desktop): wait for browser fixture readiness before sampling
* test(desktop): exit Electron after screenshot verification
CI completed every screenshot assertion but remained alive after app.quit. Exit the standalone test explicitly and keep its deadline active until process shutdown.
* test(desktop): clean browser profile after Electron exits
Explicit Electron exit skips quit handlers, and removing the profile before shutdown lets Chromium recreate it. The launcher owns the temporary profile and removes it after the child exits.
* test(desktop): reuse browser E2E for hidden screenshot regression
Exercise the running app and browser MCP tools within the existing browser journey. Remove the separate Electron fixtures, npm script, and CI step.
* fix(desktop): restore Linux launcher identity
Electron now derives Linux window identity from the scoped package name before app startup. Pin the existing Paseo.desktop identity in packaged metadata and check the built archive in the packaged-app smoke test.
* test(desktop): declare packaged smoke archive dependency
* fix(desktop): update Nix hash for smoke dependency
The prefix allowlist hid Fast for GPT-6 Astra and exposed it for models outside documented Codex Fast support. Use explicit model IDs and avoid claiming a universal usage multiplier.