Keep the shipped plugin runtime implementation documented internally while removing the public launch material until the feature is available to users. The companion add-back PR restores these pages and their drift check at launch.
* feat(workspaces): gate fork repository automation
Cross-repository change requests can execute contributor-controlled package hooks even when the setup command comes from the base repository. Persist the checkout provenance and require one explicit workspace-level setup action before repository automation runs.
* fix(workspaces): clarify blocked script errors
* fix(app): keep long toasts readable
* test(server): expect fork repository provenance
* test(server): make setup marker portable
* test(app): retry transient sidebar layout
* fix(app): restore diff copy actions on web
The canvas-backed diff owns selection without creating a DOM range, so browser context menus cannot expose copy actions themselves. Route keyboard and context-menu actions through the renderer's canonical source selection instead.
* fix(app): harden web diff copy actions
* fix(app): default split header selection to new pane
* fix(app): isolate nested diff context menus
* fix(app): select available split diff pane
* feat(plugins): phase 1 split runtime entries after green gate
The compiler now builds explicit client and server entries, so runtime boundaries are source-owned instead of registration-name filtering. Compiler and runtime tests prove the entry split, directory and suffix boundaries, client Node import errors, migration failure, and client-only operation without a subprocess.
* feat(plugins): phase 2 run client entries after green gate
Explicit client and server contexts replace the mixed plugin context. The app runner owns every client registration and its idempotent removal, including late contributions and composer pills; focused contribution tests prove the removal contract, and all five migrated examples reached running on the isolated worktree daemon.
* feat(cli): phase 3 scaffold runtime split after green gate
The generated project demonstrates the required shared RPC contract, server handler, client surface, and sidebar wiring. The scaffold test and an isolated init, typecheck, install, and running check prove the phase acceptance path.
* docs(plugins): phase 4 publish migration after green gate
Gate: current docs and the paseo-plugin skill describe only explicit client/server entries. The standalone migration guide maps every former registration, and migration-doc.test.ts proves every client add* method remains represented.
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Gate: compiler 13, runtime 24, typecheck, lint, and format passed. The dependency fixture proved node_modules/client was incorrectly treated as a plugin boundary.
* fixup! feat(plugins): phase 2 run client entries after green gate
Gate: client runtime and registry tests, typecheck, lint, format
* fixup! docs(plugins): phase 4 publish migration after green gate
Gate: migration docs test, paragraph audit, typecheck, lint, format
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Gate: compiler organization tests, typecheck, lint, targeted format
* fixup! feat(plugins): phase 2 run client entries after green gate
Gate: shared SDK tests, runtime tests, typecheck, lint, targeted format
* fixup! feat(cli): phase 3 scaffold runtime split after green gate
* fixup! docs(plugins): phase 4 publish migration after green gate
* fixup! feat(plugins): phase 1 split runtime entries after green gate
* docs(plugins): rewrite public plugin docs for runtime entries and mobile guardrails
* fixup! feat(cli): phase 3 scaffold runtime split after green gate
The scaffold now keeps DOM globals out of the program and declares only window.open inside client/web.ts. The scaffold test invokes tsc in a fresh process, proves the generated project passes, and proves a stray document access fails.
* fixup! docs(plugins): phase 4 publish migration after green gate
The internal guide and plugin skill now forbid both the DOM lib and triple-slash DOM references. They direct web adapters to declare only the globals used by client/web.ts.
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Removing the obsolete Babel parser changes the locked npm dependency graph. The macOS Nix desktop gate reported the new fixed-output hash, which this commit records.
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Reject relative imports that escape the plugin root while continuing to skip resolved node_modules internals. The compiler regression test proves the escaped import is rejected and the dependency-internal fixture remains accepted.
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Classify absolute imports and reject plugin-authored paths that escape into node_modules. Dependency internals remain exempt based on their importer path. Red-first compiler regressions cover both bypasses, and the existing dependency fixture remains green.
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Classify canonical esbuild resolution results so dependency-relative and symlink imports cannot escape client/server boundaries. The two compiler regressions failed before the fix and pass afterward.
* test(ci): synchronize flaky state transitions
Wait for repository watcher registration before emitting buffered ref events, let sidebar order polling retry unlaid-out rows, and wait for the inactive browser parking state before screenshot capture. These changes directly address the three observed CI failures; the affected server, Playwright, and desktop browser tests pass locally.
* test(ci): synchronize Mermaid completion layout assertion
The Playwright streaming acceptance test exposed a completion remount between visibility and layout sampling. Check the existing completion promise around the measurement so that transition is not reported as diagram loss.
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Resolve bare package imports before boundary classification so symlinked dependency entries cannot expose server modules to the client bundle. The focused regression proves the bypass and the compiler file passes 20/20.
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Allow canonical paths only within a matching linked package root while preserving runtime and containment checks for imports that leave it. The linked-dependency regression fails before the fix and compiler tests pass 21/21 afterward.
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Reject matching package manifests that contain the plugin or live inside it, so linked-dependency roots cannot exempt plugin or workspace files. The ancestor-manifest regression fails before the fix and compiler tests pass 22/22 afterward.
* fixup! feat(plugins): phase 1 split runtime entries after green gate
Validate plugin-local lexical boundaries before canonical linked-root exemptions and require remembered-root imports to originate inside that root. The linked-root bypass regression fails before the fix and compiler tests pass 23/23 afterward.
* feat(app): add fullscreen viewer for Mermaid diagrams on web
Add a fullscreen action to the diagram toolbar that opens the diagram in
a modal ZoomableViewport with free zoom/pan, closable via the close
button, Escape, or clicking outside the diagram. The sandboxed iframe
runtime moves into a shared MermaidIframeRuntime so the inline host and
the fullscreen viewer drive the same renderer.
Fixes uncovered while verifying the feature end to end:
- The runtime measured the rendered SVG with getBoundingClientRect(),
which depends on the iframe size the host derives from that very
measurement. Each streaming re-render shrank the reported size by the
diagram box padding, ratcheting diagrams down to a few pixels. Measure
the viewBox instead, which is the diagram's own size, and let the SVG
fill whatever frame the host provides.
- ZoomableViewport's root is flex: 1, so as a flex item its 0% basis
overrode the inline diagram box height and the box collapsed, scaling
the diagram tiny and clipping the toolbar unclickable. Size the box
from its height and keep a minimum tall enough for the toolbar.
- react-native-web's Modal focus trap focuses the first descendant that
takes focus, which was the diagram iframe; every keystroke then landed
in the sandboxed frame and Escape never reached the window listener.
Mark the presentational iframe inert and focus the viewer's content
layer on mount.
Closes#4106
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(app): exercise Mermaid fullscreen interactions
Cover zoom, pan, outside-click dismissal, and streaming layout through the real browser path. Remove the mocked DOM test that duplicated those behaviors without exercising the viewport or iframe runtime.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
The homepage download CTA was chosen by a client-only hook that started at
"mac-silicon" and corrected itself in an effect, so the server-rendered HTML
said "Download for Mac" for every visitor and only became right after
hydration. On a phone that window is long enough to photograph. Worse, the
detection had no mobile branch at all: Android user agents also say "Linux",
so once hydration did land, a Pixel was offered an AppImage.
Resolve the platform from the request user agent during SSR instead, so the
first paint is already correct. Mobile visitors get the App Store or Play
Store as the primary action and a link to /download for the desktop build,
rather than a .dmg they cannot open.
The markup now varies by user agent, so HTML and server-function responses
carry `Cache-Control: private, no-store` and `Vary: User-Agent`. An absent
cache-control is not no-store — a shared cache may assign heuristic freshness,
and turning on Workers Cache would store a headerless 200 for two hours under
a key that ignores the user agent, letting one Android visitor pin the Play
Store button for every Mac visitor after them.
Also tighten the mobile layout the wrong CTA was sitting in: the primary
action gets its own row so the icon buttons stop orphaning one of themselves,
provider cards drop to a two-column grid, and the hardcoded mobile line break
in the phone showcase gives way to a balanced measure.
* feat(app): make import session a first-class entry point
* feat(server): improve import session discovery resilience
Search up to 500 persisted candidates per provider and isolate listing failures behind eight-second deadlines so healthy results remain usable. Preserve native persisted title precedence while keeping discovery read-only.
Parsing now continues through each transcript so native titles and the true last prompt are retained; the old early exit made lastPromptPreview equal firstPromptPreview. A cheap record prefilter avoids JSON.parse for unrelated lines during the full scan.
* feat(app): searchable, grouped import session sheet with per-provider errors
The sheet now carries a debounced search field gated on the host's
importSessionSearch feature, grows its per-provider page on demand
(15 -> 45 -> 90 -> 200) instead of capping at the 15 most recent, and groups
unscoped rows by folder under the project name the app knows for that
directory. Worktrees of one project would otherwise all head up as the same
bare name, so a directory below its project root carries the path under that
root beside the name.
A workspace-scoped sheet says so and offers "Show all sessions". Importing a
row from another directory once that is on drops the workspace from the
request, which the daemon would otherwise reject, and routes the user to the
agent's own workspace rather than opening a dead tab in the current one. That
decision lives in resolveImportTarget: a scoped listing is trusted wholesale
because the daemon already matched it realpath-aware, which the client cannot.
A provider that fails now gets its own error row with a Retry that refetches
only that provider, sourced from both a failed request and the daemon's
providerErrors. React Query no longer retries these, so the header spinner
settles once every provider has answered or given up (#2512).
* test(e2e): import session flow with fixture Claude sessions
* fix(app): shorten import sheet scope copy so it fits at phone width
At 390px the scoped subtitle rendered "Sessions in this wor..." — the label,
its action, and the sheet's refresh and close buttons do not share one line at
phone width. The label already sits under the sheet title "Import session", so
"Sessions in this" was repeating context the header had: it becomes "This
workspace" and the action becomes "Show all". The unscoped label still names
the host, which nothing else on the sheet does.
* refactor(app): flat recency-ordered import list with per-row folder label
Grouping by folder buried recency: a busy directory's block of fifteen pushed
a two-minute-old session in a quiet directory below the fold, and the axis a
user is scanning on is when they last touched the session, not where it lives.
The unscoped sheet is now one list in the order aggregateSessionEntries already
produces, and each row carries its folder as a muted third line.
resolveDirectoryLabel stays — it is what keeps two worktrees of one project
apart — and formatDirectoryLabel renders its two parts as the single ellipsised
line a row has room for. A scoped sheet lists one directory, so its rows carry
no folder line at all.
* fix(app): close import sheet before refreshing sessions
Provider-list invalidation waited for every active query before the sheet could close. Mark the disabled picker queries stale after close and navigation so reopening refreshes without delaying the imported agent.
* fix(server): reuse workspaces for session imports
Host-wide imports should join the oldest active workspace for their directory, or restore its archived workspace, instead of minting duplicates. Rollback remains limited to workspace records created by the failed import.
* test(e2e): express the import session journey through a helper DSL
* test(e2e): align sidebar journeys with import entry point
* feat(providers): add Gajae Code to the ACP provider catalog
Gajae Code (gjc) ships a conformance-tested ACP agent (`gjc acp`), so it can be
a one-click catalog entry instead of a hand-written custom provider in
config.json. Command and env match what the agent's own installer writes.
* feat(providers): add Gajae Code icon
---------
Co-authored-by: Yeachan-Heo <yeachan.heo@gmail.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* feat(website): publish Hub pricing options
Present self-hosted and hosted Hub choices together while reading the hosted offer from Hub's public catalog.
* fix(website): require a valid hosted offer
* fix(website): preserve Hub page on catalog failure
Join and truncate prompt-derived titles for the visual tooltip, add agent ID and live human activity metadata on a second row, and keep activity subscriptions local to the open tooltip. Preserve the full normalized title as the accessible tab name and cover the behavior through Playwright.
Drive the streaming assertion from the actual turn lifecycle so iframe replacement at completion is not mistaken for diagram loss. Preserve the cached timeline range during reload reconciliation so a projected message spanning the cache cursor replaces its partial prefix instead of being appended to it.
A repository-controlled core.fsmonitor command can execute when the daemon refreshes Git state. Opening or reconciling a malicious checkout therefore allows code execution as the daemon user. Force core.fsmonitor=false on daemon-owned Git invocations before repository config is considered.
Plugins can transform live timeline source items at render time, append durable plugin rows from daemon handlers, and register client-only slash commands in the composer.
Related to #3940 and #3952.
* fix(app): keep Changes comparison and diff in sync
Own the working comparison at the workspace checkout so the Changes tree and adjacent working diff share it. Immutable commit diffs remain scoped to their commit SHA.
* docs(app): align working diff ownership notes
Non-AppImage packages install the Electron executable at a stable path, but invoking it through the CLI symlink cannot restart the daemon. Link the bundled CLI shim for those targets while preserving AppImage links.\n\nFixes #4184
* feat(app): customize top-level sidebar items
Let users reorder and hide built-in and plugin sidebar rows from Appearance settings while keeping the preference device-local. Add Search as a first-class row and standardize section explanations as header info tooltips.
* fix(app): preserve consecutive sidebar preference edits
Derive each sidebar change from the latest cached settings and retain unavailable plugin slots while editing the visible list. Keep E2E interaction selectors tied to accessible control names.
* test(app): isolate provider settings unit boundary
Keep the legacy provider-section test focused on provider behavior now that its shared section shell can render the settings info control.
Create tag-triggered GitHub releases as drafts and publish them only after the desktop updater manifests are complete, stamped, and uploaded. Preserve beta prerelease semantics and safe retry behavior.\n\nCloses #4183
* fix(app): activate mobile panels after motion settles
Gesture previews were publishing React presentation state and activating retained content before the native motion finished. Keep transient motion on the UI thread and publish the active panel only from the accepted animation completion.
* perf(app): re-render only changed history rows
Every coalesced stream tick re-rendered all mounted history rows because the inverted FlatList recreates each cell with a new index and ref. Keep layout item identity for unchanged rows and memoize the row on it, and resolve tool-call groups through a stable event so the render callbacks stop churning.
* perf(app): count replica row bytes once
Budget accounting measured every stored row of a touched host on each persist through the Buffer polyfill. Cache the UTF-8 length per immutable row and count without materialising bytes.
* fix(app): settle mobile panels on position
Activity now follows the UI-thread position reaching the canonical anchor instead of an animation completion callback. A successful gesture keeps its finishing motion and the matching command adopts it; settlement and command acceptance may arrive in either order.
* fix(app): keep settled panel props across JS stalls
Turn off Reanimated's FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS. Its settled-props collector drops entries older than two seconds before React receives them, so a JS stall across a settle left the overlay display and backdrop opacity reverted on the next commit while the store held the new panel.
* fix(app): reconcile hidden keyboard end on iOS
Only a hidden end event resets the keyboard shift. A late visible end no longer resurrects a keyboard that already dismissed.
* fix(app): focus the replacement input through autoFocus after clearing
Clearing swaps the native input for a fresh instance. Focusing the doomed instance asked Android for the keyboard and then tore the focused view down, which cancelled the show; a JS focus command sent alongside the remount reached a view that was not attached yet and the IME ignored it. Focus and blur requested while the replacement is pending now decide the replacement's autoFocus, which native applies once the view is attached.
* fix(app): revise history rows in every viewport
The history row memo boundary bails out until its item identity changes, but only the native viewport cloned rows flagged by historyRowRevision. On web and desktop a history host whose tool-call group kept updating from the live head rendered stale. Move the revision into useRevisedHistoryRows and run both viewports' history through it. Regression test renders the web strategy with a memoized row and asserts only the revised host re-renders.
* perf(app): keep large diffs responsive
Render file headers on the canvas and bound expensive text shaping to the active viewport while preserving sticky, hover, and horizontal-scroll behavior. Let native scroll cancellation arbitrate header presses so finger scrolling cannot expand files.
* test(app): assert canvas diff header accessibility
* test(app): assert canvas diff header semantics
System-injected prompts have canonical turns but no visible user row, so completed timing was discarded despite an exact end timestamp. Preserve completion metadata with an explicitly unknown duration so the footer can show the truthful timestamp.
* feat(claude): add Fable 5.1 model
Expose the new 1M-context model through the curated Claude catalog and normalize its dated and context-suffixed runtime IDs back to one canonical entry.
* fix(claude): group Fable models in catalog
* chore(release): cut 0.7.1
* fix(nix): refresh 0.7.1 dependency hash
* test(cli): follow package version in plugin scaffold
* test(cli): keep Claude catalog checks extensible
* fix(ci): eliminate recent cross-platform flakes
Synchronize tests on owned state instead of elapsed time or transient identities. Preserve file rename collision safety when platform file IDs are unavailable.
* fix(ci): close follow-up synchronization gaps
Keep fetches in flight through buffered-event classification, scope mock agents to navigation tests, and wait for live workspace hydration before opening agent routes.
* fix(ci): synchronize browser lifecycle boundaries
* fix(app): cap oversized assistant messages
A single assistant message can expand into enough rendered Markdown nodes to crash the renderer. Limit the Markdown input while preserving the complete timeline value and report its full byte size.
* fix(app): bound oversized stream processing
Cap the render input before paced reveal and defer full-message byte counting until completion. Reuse the existing grapheme boundary policy so capped text never ends in a partial glyph.
* fix(app): preserve capped text without segmentation
Hermes lacks Intl.Segmenter, while the shared reveal boundary intentionally returns zero without it. Keep that contract and fall back locally to a surrogate-safe 32K prefix.
The shared image lightbox handled Escape after the global shortcut dispatcher had already routed the same key to agent interruption. Register the viewer with the established web overlay owner so dismissal takes first refusal while ordinary Escape interruption remains intact.
* feat(plugins): support prepared Git installations
Let Git plugins declare explicit preparation commands without making package installation the default. Keep installation transactional, make plugin trust explicit, and use one canonical source reference for monorepos.
* fix(plugins): parse Windows source references
* fix(server): keep daemon alive when JSONL RPC stdin hits EPIPE
A dead Claude/OMP child currently turns stdin.write() EPIPE into
uncaughtException and kills the daemon. Catch the write, listen for
stdin errors, and close that transport instead.
Closes#4047
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test(server): reproduce JSONL RPC stdin EPIPE
* test(server): gate native EPIPE fixture on POSIX
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(android): keep timeline still when tapping text
Selectable Android text takes touch focus. React Native then scrolls focused children using untransformed coordinates, which moves inverted timelines even though the user did not scroll.\n\nSkip that auto-scroll only for touch-focused, selectable, non-editable text in inverted vertical scroll views, preserving normal focus navigation and editable inputs.
* test(e2e): isolate PR pane fixtures from bots
Track each foreground run start as pending, started, or failed so a previous turn error cannot reject a newly accepted prompt. Includes a regression for resuming after a failed turn.
Keep mobile interface chrome at 15px while defaulting readable content to 16px. Migrate existing mobile 15px content preferences once while preserving later explicit choices.
* feat(hub): make daemon authority explicit
Connecting a daemon establishes presence without granting execution. Hub authority now uses semantic session permissions, with workflow execution granted explicitly and inspectable through the normal session protocol.
Attention frames remain part of the existing agent and terminal subscriptions, keeping session output solicited.
* test(server): match sentence-case tool labels