The test reads the catalog at ref master but created it with plain git init, so
it failed wherever init.defaultBranch is main (e.g. Xcode's git on macOS).
* feat(plugins): Featured and Newly added rows in the plugin catalog
Featured shows the most installed plugin per category; Newly added lists plugins
by the catalog's new entry-level addedAt (hidden when no catalog dates entries),
with a New badge for 24h and an Added this week filter. Snapshots move to a v3
cache key so older builds never read addedAt.
* feat(plugins): derive Newly added dates from release dates, as the website does
Drops the catalog-level addedAt field (schema, docs, v3 snapshot key). Main now
estimates a listing date for each non-official plugin from its oldest dated
release, and the Newly added row, New badge and This week filter read it through
shared helpers that mirror orca-site's pluginNew.ts: 30-day window, newest first,
dates more than an hour in the future ignored, "just now / N hours ago / N days ago".
* feat(plugins): show nine Newly added plugins, drop the counts release dates inflate
Listing dates come from each community plugin's oldest kept release, and
catalogs keep only recent releases, so almost every plugin looks new: 181
of 182 fall in the 30-day window and 142 count as this week. Remove what
that inflates rather than flag it: the "N this week" count and its See all,
the This week filter, and the 24h New badge.
The Newly added row now offers the website's nine in the catalog's normal
wrapping grid, the same columns and card widths as the catalog groups.
* fix(plugins): pick Featured winners by every kind a plugin belongs to
Featured used only a plugin's first filter kind, while the category filters
count every kind, so category order could change winners or drop a kind's only
candidate. Each plugin, walked by installs with a key tie-break, now fills one
unfilled kind it belongs to (the one with the fewest candidates left, then by
name). Also note why Featured is a capped one-row teaser and pin the cap.
* fix(plugins): hide Featured when Newly added is empty
Without Newly added, Featured sat directly on the Official catalog, whose
leading cards are the same popular plugins in the same columns, so back-to-back
rows repeated and read as a render bug. Featured now shows only alongside
Newly added. Leaving featured plugins out of their catalog instead would move a
card into the catalog the moment it's installed and skew the catalog's N of M.
Adds a PluginManager test that no plugin card shows twice in the empty state,
and page-level fixtures (390, 600, 1440, empty 1440) for screenshots.
* fix(plugins): cap Newly added at two full rows of the current columns
At phone width nine stacked cards pushed the catalogs about 3.5 screens
down. The grid now shows two full rows of its container-query column
count (8 at four columns, 4 at two, 2 at one), hides a partial last row,
and the pick renders at most eight. Hidden plugins stay listed in their
catalog group, so there's no See all.
* Correct plugin catalog fixture evidence
* Make plugin highlight tests match observable behavior
* chore(endpoints): call orca.dev for feedback, plugin lists and update notes
The site moved to orca.dev. Feedback, connection diagnostics, plugin install
stats, the plugin kill list, update nudges and the changelog feed now use
orca.dev, as do the diagnostics token URL baked into builds and the release
download mirror (downloads.orca.dev). The old hosts serve the same paths,
so released builds keep working.
Skill share links on share.orca.dev are accepted, and the mobile scanner
recognizes the orca.dev Android download code. The old hosts stay accepted.
* chore(ci): leave the release JavaScript benchmark workflow unchanged
Editing it triggers the benchmark, whose parity check currently fails on
arm64 because max-quality brotli output for the mobile page differs by
architecture. The benchmark only needs some diagnostics URL, and the old
host still serves it.
Dev-folder plugins are hashed at discovery with DEV_FOLDER_SKIPPED_DIRECTORIES
(e.g. .orca/), but the worker activation integrity check re-hashed the whole
tree. Once Orca wrote typings into .orca/types, the activation hash no longer
matched the reviewed hash and the dev worker refused to start. Pass the dev
flag through to the integrity check so both hashes skip the same directories.
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* perf(persistence): saves name the profile domains they changed
UI changes, window moves, SSH terminal exits, phone tab switches and
repo/folder/SSH/lineage edits no longer re-serialize and hash the whole
profile on the main thread. Each save names its domains (or 'whole' where
an operation can't know them), and one per-domain saved form serves both
selective and whole saves, writing dependent domains together.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): keep folder re-tiering and plugin context on named saves
Folder workspace unread writes re-tier sort stamps again, and plugins
hear an active folder's rename, now that those saves name their domain
instead of the whole profile. The folder-note test drains the load's
whole save so it checks the named save.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(persistence): typed folder workspace fixture for the re-tier case
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(persistence): build the serializer from typed state in the parity test
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): read unprojected domains without a type assertion
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(persistence): stall and fail the selective writer UI saves now use
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* perf(persistence): every mutation saves only the domains it changed
Removing a project, renaming a worktree's path, re-adopting an SSH
target, importing a catalog, deleting a group or folder workspace,
retrying a failed terminal close and remapping session panes now name
their domains instead of rewriting the whole profile. Only startup load
and maintenance resume rewrite every domain, through a separately named
entry point a lint rule keeps out of mutation code. A named
automationRuns save refreshes a pending run delta so it can't write an
older run list.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): a named automation-run save supersedes older run lists
A catalog import replaces the run list while an earlier run write can
still be in flight; if that write fails, its restored pending list must
not win over the import. Domain lists name the local session through
the shared constant so the layout-writer ratchet stays exact.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): keep the whole-row fallback for replaced run lists
A run list replaced outside the run-delta path (catalog import, SSH
re-adoption) saves as a whole row again, so histories that can't use
the per-run storage still save. A failed in-flight run write restores
its pending list only while that list is still current; otherwise it
marks the run domain for a whole-row save.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): save run lists per-run rows can't hold as one row
A run delta whose list can't be stored as per-run rows (a legacy
history with duplicate run ids) now saves as the ordinary whole-row
replacement instead of failing the transaction, so a catalog import
or run edit on such a profile still commits.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(plugins): add editor.readTypography and editor.typography.changed
Plugins that render code next to Orca's editor had to open the private
profile database and reimplement Orca's font rules to match it. Host API 1.6
adds editor.readTypography (no capability; worker, panel and editor frames):
the font family and CSS stack, size with editor zoom, weight, line height in
px, ligatures, word wrap and minimap that the file editor resolves now.
editor.typography.changed carries the same values after a settings write or
an editor zoom changes them; it is the first event that needs no capability.
The pure resolution (editor typography, editor zoom, monospace font stack)
moves to src/shared so main and the renderer use one implementation.
* fix(plugins): keep editor typography resolution from throwing in main
A hand-edited profile with a non-string font family made the shared resolver
throw, which the main-process watcher would surface at startup and on every
settings write. Font values are now type-checked, the watcher catches and
logs, and it reads live settings so a write without listeners cannot leave the
event payload stale. Over-long font families are cut to the schema bound
instead of failing every read. Regenerates the testing runtime.
The watcher deduped on the public payload (branch, displayName, terminals),
so moving focus between two workspaces that project to the same payload was
dropped. Dedupe on the focused worktree id as well; the emitted payload still
omits that id because it can embed a provider path.
Fixes#40502
Co-authored-by: Neil <neil@stably.ai>
* feat(plugins): add workspace.rename and project names in workspace.list
Plugins that name workspaces had to read Orca's private runtime token and call
worktree.set over the runtime socket, bypassing capability consent. workspace.rename
(workspace:write, worker only, host API 1.5) does the same update as a sidebar
rename. workspace.list rows gain the project name so plugins can prefix names
without reading the runtime's repo list.
* fix(plugins): harden workspace.rename against twin folders and hidden characters
Folder renames require a unique folder id, so twins on two hosts are not both renamed,
and fail when no folder has the id. Names refuse C1, bidi and zero-width characters,
which could make a label read as another workspace's name.
* fix(plugins): workspace.rename matches a sidebar rename for folders and reports kept names
A folder rename also cancels a pending first-agent-message rename, so the automatic
title cannot replace the plugin's name. A rename Orca does not apply (during a WSL
move) is a conflict instead of a silent success. workspace.list's project is optional,
so mocks written for host API 1.4 still validate. More invisible characters are refused.
* refactor: organize browser guest lifecycle owners
* test: resolve cross-version client owners and pre-ticket baseline
* test: record the released terminal registry path
* perf(rpc): stop the dispatcher importing every RPC method by default
Callers pass the method list. A test that builds a dispatcher no longer loads
the full method registry, cutting the dispatcher's import graph from ~4k to
~550 modules.
* chore(lint): keep the RPC dispatcher and plugin RPC off the method registry
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
App themes may set background.videoPath: a muted, looping MP4 or WebM (H.264
recommended, VP9 or AV1; one video track, no audio; <= 60 s, 8 MB, 2.3 MP)
played over the required static background.path. Main checks it from container
headers without decoding and serves it on the orca-plugin-image slot
theme-<i>-video; images stay static-only. The renderer reveals it on its first
frame, keeps the still on any failure or a decoded size past the limits, plays
it only while main reports the window focused, shown and not minimized and no
dialog is open, and omits it under prefers-reduced-motion.
Animated WebP was measured and declined: software-decoded every frame, it cost
11-23 points of CPU vs 3-10 for the same loop as video.
Implements #38363 proposed by @dohaidang.
* fix(plugins): don't count time asleep as worker idle time
The idle reaper measured idleness with the wall clock, so after a sleep longer than the reap window its first sweep reaped every worker before their own timers could fire. A sweep gap well past the 60s interval now starts a fresh idle window.
* fix(plugins): count a sleep before the first idle sweep as asleep, not idle
Start the reaper's sleep-detection clock when the first worker starts, so the
first sweep after waking can see the gap even if no sweep ran before the lid
closed.
* fix(plugins): keep worker manager under max-lines and restore real timers after sleep tests
Move the idle-candidate filter into PluginWorkerIdleClock so the rebased manager
stays within the 300-line lint cap, and reset fake timers after the idle-reap
tests so the sleep-before-first-sweep case does not leak them.
---------
Co-authored-by: petersindex <287557875+petersindex@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(plugins): resolve the active workspace context on remote hosts
* fix(plugins): scope the remote workspace context to its host
* docs(plugins): document the remote context resolver
* fix(plugins): keep remote context ids raw and never fall back for local rows
The stored-row fallback returned host-qualified ids (ssh:host|repo::path), which no
terminal selector or agent-status row matches, so agents.list stayed empty for the
remote workspace. It also answered for a removed local worktree, turning a stale
focus that used to read null into a context for a workspace that no longer exists.
* fix(plugins): resolve a focused paired-server workspace from the desktop's server rows
A paired orcad server's workspaces live in that server's store, so neither the local
catalog nor the desktop's stored rows can name one and workspace.readContext,
workspace.changed and agents.list saw no context. The runtime now asks the relay host
catalog, which answers from the worktree.ps rows the desktop already caches. Only a
miss on a reachable server fetches, bounded at 1.5 s and at most once per 10 s per
missing workspace; an unreachable or hung server answers null.
* fix(plugins): keep a stale local focus stamp resolving as before, and cover #38653 end to end
A focus stamped 'local' whose repo row now routes elsewhere resolved through the
unscoped selector before #38653; the host-scoped lookup turned it into null. A
local stamp now retries unscoped, so local and unstamped focus answers exactly
what main did. Remote stamps keep the scoped lookup, so a paired-server focus
never borrows a local twin's context.
Tests: real-git parity of local, folder and SSH contexts against the pre-change
selector answer for every session shape the renderer writes; panel polling
against a hung or failing server (one fetch, one bounded wait per 10 s window);
an e2e that pairs a desktop with a headless Orca server, clicks a local and a
server row, reads workspace.readContext from a plugin panel, and keeps polling
fast after the server goes away.
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(plugins): isolate development watcher retries by root
* test(plugins): keep coverage for late binding checks after housekeeping disposal
The per-root rewrite replaced the old housekeeping unit tests; restore the
disposal/coalescing case so a slow check that resolves after dispose cannot
restart a watcher or refresh, and per-root checks do not stack.
* fix(plugins): skip the full plugin rediscovery while a dev folder retry keeps failing
A maintenance retry that subscribes already refreshes itself; one that keeps failing (an
unreachable WSL share) rediscovered every plugin each tick. Only a changed root binding needs
discovery now.
Also covers what the per-root rewrite must keep: adding/removing a folder leaves the others'
watchers and frame revisions alone and releases its subscriptions, a rejecting registration
retries once per tick, Linux sibling folders keep separate parent watches, and an Electron e2e
where deleting/recreating/removing one dev folder never reloads another folder's open panel.
---------
Co-authored-by: be0hhh <182023099+be0hhh@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(plugins): stop a late replacing command from repeating the built-in action
The renderer ran Orca's own action after 2 s but the still-running replacing
command could then call commands.run for the same action, doing it twice.
Main now owns the 2 s cutoff: it rejects at the deadline (the renderer falls
back on that answer) and from then refuses the late handler's commands.run of
the replaced action; a handler that already ran the action is not followed by a
fallback. The deadline now starts when the renderer asked, not after plugins
load. The renderer's own timer is only a safety net. (STA-13530)
* test(plugins): cover replacement races and verify the sidebar toggles once end to end
A handler's commands.run now counts for the oldest in-flight press only, so a second
concurrent press whose handler fails still gets Orca's fallback. Adds unit cases for a
throwing/crashed handler, two presses racing, and a press after a late handler returns,
plus an Electron e2e spec driving fast, late (2.1 s), throwing and no-op replacements of
sidebar.left.toggle.
* fix(plugins): stop terminal inventories for agent status events nobody hears
Each agent.status.changed event for plugins lists the workspace's terminals,
which refreshes every PTY provider and host. Interest was only checked when an
event was admitted, so after the last panel closed the queued backlog kept
running full inventories with no one to deliver to.
Recheck interest before each lookup and drop the backlog once nobody listens,
and let events queued behind a slow lookup share one listing per workspace
while keeping arrival order. (STA-13319)
* fix(plugins): keep the agent status queue moving past a throwing delivery
A synchronous throw from one emitEvent dropped the rest of its batch and left
later queued events waiting for the next arrival. Catch per delivery, and cover
a failed listing, a 1000-event storm, a panel reopening mid-lookup, and several
panels on the real bridge; the panel e2e now checks a burst arrives in order.
(STA-13319)
* feat(plugins): let plugins contribute web fonts to the font pickers
Plugins declare font families under contributes.fonts (WOFF/WOFF2 only).
Install checks each file's signature against its extension and caps file
size, family count and plugin total; main never parses a font. Files are
served over the plugin image scheme only while the plugin is enabled,
typed and nosniff. The renderer registers each family under a name that
carries the plugin key, so a plugin font cannot shadow an installed or
built-in font, and it applies only once the user picks it. Disabling or
uninstalling removes the faces; uninstalling also resets font settings
that named them.
Refs #38607
* fix(plugins): harden plugin fonts and re-measure editors when they load
- Refuse WOFF/WOFF2-wrapped font collections by checking the flavor bytes.
- Check dev folders' font files at discovery, not only at install.
- Re-check family, weight and style from the plugin list in the renderer.
- Track loaded families (case-insensitively) from document font loads, so
the code editor re-measures and terminals, previews, the SSH terminal and
the dashboard popout re-apply only when their own font changes.
- Register plugin fonts in the dashboard popout window.
- Give dev-folder font URLs their change count so edits reload.
- Keep monospace fallbacks unless the chosen family is exactly that font.
* fix(terminal): keep every fallback only for plugin font families
System font choices keep their existing fallback list unchanged.
* fix(plugins): leave the dashboard popout's plugin list alone; add the font row string
Registering fonts there started the plugin auto-update watcher in a window with no toaster, which could consume update notices. Adds the new contribution-row string to the English catalog and the fonts list to the RPC test manifest.
* fix(plugins): drop a plugin's approval when its catalog moves it to another repository
* plugins: revokePluginConsent refuses when the plugin service is not running
* cli: orca plugin install/update ask again when a plugin moves to another repository, for every plugin kind
* Redesign plugin detail pages with publisher discovery
- Show publisher provenance, related plugins, and clearer source links
- Preserve installed release attribution and README links across catalog changes
- Support navigation between plugin pages while preserving review queues
* Fix publisher link button styling
* Format resolved installer test
* Remove unrelated scratch files
* perf(plugins): read catalog images in batched Git calls
Each catalog refresh ran one ls-tree and one cat-file per image or
manifest read. Batch every file an extraction round asks for into one
ls-tree listing and one cat-file --batch read, then rerun extraction
over the loaded files until nothing is missing.
* fix(plugins): bound speculative catalog blob prefetch
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* fix(orcad): fit orcad inside a `ulimit -v` address-space limit
On hosts that cap virtual memory (ulimit -v 8 GiB on an HPC node), the
managed orcad crash-looped with "Failed to reserve virtual memory for
CodeRange" whenever it started a worker thread.
Two reservations used up the limit:
- undici's WASM HTTP parser, loaded by the first fetch, reserves a
trap-handler guard region (4-9 GiB). The pinned-Node orcad launch now
passes --disable-wasm-trap-handler --wasm-max-mem-pages=1024.
- each Worker isolate reserves V8's default CodeRange (512 MB on x64,
~600 MB of address space per worker). Main-process workers now go
through spawnWorkerThread(), which caps the code range at 64 MB, and
a lint rule keeps new sites on it.
Fixes#37538. Thanks to lin-tianyu for the report and logs.
* fix(orcad): keep the runtime selector one command; enforce spawnWorkerThread with ast-grep
Refs #37538
* test(orcad): run the address-space regression on Node
Refs #37538
* docs(orcad): note the WASM memory cap at the pinned-Node flags
Refs #37538