Commit Graph
169 Commits
Author SHA1 Message Date
OrcaWinandOrcaWin 318bf64769 test: remove low-value catalog and shared checks (#41474)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-12 01:13:49 -08:00
Jinjing e881c7e76c Highlight featured and newly added plugins (#41460)
* 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
2026-11-12 00:40:31 -08:00
Kelvin Amoaba 68d2314615 fix(plugins): only auto-close short brackets when a syntax omits autoClosingPairs (#41451)
Monaco falls back to brackets for auto-closing, so a plugin bracket up to 128
characters was typed into the file as you type. The shared schema now fills in
autoClosingPairs with the brackets that pass the 8-character typed-pair rule.
2026-11-12 08:27:20 +00:00
Kelvin Amoaba 1ed47daf1f fix(workspace-cleanup): show merged and unchanged workspaces instead of flagging landed commits as unpushed (#41408)
* feat(workspace-cleanup): mark merged and unchanged workspaces from git evidence

Squash-merged branches whose remote was deleted were flagged as unpushed
and needed a forced delete. The scan now reads what HEAD holds beyond the
workspace's base (ancestor, Git's squash proof, or a cached merged PR) and
reports merged/unchanged reasons; landed commits no longer count as at risk.
A gone upstream no longer hides unpushed commits. Renames idle-clean to
inactive and drops the dead tier, selectedByDefault and volatile-local-context.

* fix(workspace-cleanup): read HEAD once, skip a reused branch as its own base

Merge evidence now compares the cached PR and the base against the HEAD it
reads, not the listing's possibly stale one; a workspace opened on an
existing branch no longer counts that branch as its base; the proof
wrapper settles the WSL route before building its caches; a visit keeps
merged/unchanged reasons; landed rows stop sorting as unpushed.

* fix(workspace-cleanup): qualify base refs and trust the branch proof only for HEAD

Base refs resolve through the worktree base-ref qualifier so a tag cannot
shadow them and only the branch's own family is skipped, including the
repo default; SHA-256 pinned commits are not bases; the squash proof runs
only while the branch ref is the HEAD just read; expanded details stop
counting landed commits as unpushed.

* test(workspace-cleanup): build the repo fixture without a cast

* fix(workspace-cleanup): never take a tag as a workspace's base
2026-11-12 08:14:14 +00:00
OrcaWinandOrcaWin 4dcf689fb2 test: remove low-value mobile and host policy suites (#41436)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-11 23:25:07 -08:00
Kelvin Amoaba e5a92090cf fix(plugins): allow syntax comment markers longer than 8 characters (#41411)
Liquid's {% comment %} / {% endcomment %} were dropped by hosts and refused by
validate:plugin. Comment markers are only inserted by Toggle Comment, so they
share the 128-character printable one-line rule with brackets; typed pairs keep 8.
2026-11-12 06:30:01 +00:00
Neil 46b225e473 chore(links): point website, docs and download links at orca.dev (#41376)
The site now lives at orca.dev; the old onorca.dev and www.onorca.dev hosts
redirect there. Linking to orca.dev directly skips that redirect.

Only page links change. Service hosts, sign-in and relay endpoints stay as they are.
2026-11-11 21:50:37 -08:00
OrcaWinandOrcaWin 9a04aa89ef test: remove 56 low-value presentation, CLI, and reload tests (#41389)
* test: remove presentation and command-selection inventories

* test: remove CLI and display projection inventories

* test: remove browser reload projection suite

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-11 21:08:44 -08:00
Brennan Benson 545b493529 feat(settings): move Remove project to a confirmed row at the bottom of Project Settings (#41020)
* feat(settings): move Remove project to a confirmed row at the bottom of Project Settings

* chore(i18n): sync protected copy keys for the remove-project dialog

* fix(settings): use primitive variants in the remove-project disclosure and dialog

* fix(settings): keep remove-project row off the per-write store path

The terminal count now lives in a dialog-only component, and the VM-recipe check is
memoized on worktree/runtime identity. Checkouts are grouped by host id, SSH labels
resolve through the setup's runtime owner, and a runtime missing from the catalog
shows as Unknown host instead of its raw id.

* fix(settings): select only the removed repos' worktree lists

Selecting the whole worktreesByRepo map re-rendered the remove-project row on every
other repo's worktree write. Also drop the orphaned terminalCount catalog keys.

* fix(settings): count remove-project terminals once on open; hide raw SSH ids

The dialog's terminal warning reads the count when Remove… is clicked instead of
subscribing, so no store write walks tabs. An SSH host with no label now shows as
Unknown host like an unlisted runtime, since generated target ids mean nothing to users.

* fix(settings): re-check terminal count before removing a project

A terminal opened from another client while the confirm dialog is up would close
without warning. Confirm now re-reads the count and, if it grew, updates the warning
and waits for a second confirm.

* test(settings): build remove-project fixture setups without a type assertion

* test(settings): cover the remove-project confirm holding for new terminals

* test(settings): drop the copy assertion from the remove-project confirm test
2026-11-11 21:06:36 -08:00
Neil 16552ad983 refactor(plugins): organize panel and worker modules by concrete owners (#41277)
* refactor(plugins): group panel and worker modules by owner

* chore(tests): prune retired plugin supervision timeout allowance

* refactor(plugins): consolidate duplicate owner imports
2026-11-11 15:23:33 -08:00
Brennan BensonandRenato Pires ed31974c2d fix: warn before closing a window with a busy native chat (#40953)
* fix: warn before closing a window with a busy chat

Co-authored-by: Renato Pires <contact@raydev.ca>

* test: cover missing chat status and child-only observations

* fix: synchronize the shared close-warning translation keys

---------

Co-authored-by: Renato Pires <contact@raydev.ca>
2026-11-11 13:30:07 -08:00
OrcaWinandOrcaWin 3c7d1e1675 test: remove 263 low-value cases across 20 suites (#41144)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-11 04:05:46 -08:00
Jinwoo Hong 818055fcbb feat(orchestration): answer workers' permission prompts from the coordinator (#40756)
* feat(orchestration): deny a worker's permission prompt, and say when mail is held

A chat worker that stops at a permission prompt now escalates with the prompt
itself as structured data (approval id, state, worker, summary, allowed
actions). The coordinator can deny it with a reason through
`orchestration deny`; the worker carries on and gets the reason as mail.
Only a person can allow a prompt: the answer method decides "person" from
the transport, never from params. The stop report keys one escalation per
approval and sends one closing line when it is answered, and no longer tells
the coordinator that `orchestration send` answers a prompt.

`orchestration send` now warns when the delivery gate would hold the mail
right now (an open prompt, a blocked status, typed text), and says delivery
state is unknown for a worker on a paired server.

* fix(orchestration): only the desktop app counts as a person for worker approvals

A paired device can carry a remote CLI, so it is treated as an agent: it can deny with a reason but never allow.

* fix(orchestration): approval answers and held receipts follow the real gates

- A same-option answer counts as Orca's only when Orca recorded it; a retry returns the same
  receipt with no second reason mail.
- An unreadable chat is not a closed prompt: the answer refuses, and the closer keeps watching.
- A closing line tells a reason only when it went to the worker as mail.
- The terminal held receipt prints the delivery gate's own refusal; the chat one prints what the
  pointer's admission and queue gate would do (not attached, refused, or behind an open prompt).

* fix(orchestration): read the held receipt only after the mail is stored

An await before the insert let a worker settle meanwhile, so the mail could land on a settled
Dispatch after its follow-up scan. The closer also removes only the approval map it read.

* fix(orchestration): tell the coordinator not to block on a worker's permission prompt

The approval escalation now says the worker is waiting for permission, tells a chat
coordinator the user has been shown Allow/Deny (a terminal coordinator: a person must
answer it in Orca or the worker), asks it to carry on with other work and not message
the worker until the answer arrives, and offers deny only as the way out when the
prompt is holding up the task. The guide and CLI docs say the same.

* fix(orchestration): a paired coordinator without an Orca session id mails a deny reason from its Run

* feat(orchestration): answer a Claude terminal worker's permission prompt

A worker pane's PermissionRequest hook now waits for Orca's answer, so the coordinator can deny
it with a reason (the worker continues) and a person can allow it. One record per waiting hook,
keyed by Claude's prompt and tool call; it is answerable only while the hook waits and the dialog
is on screen, and anything else gets `{}` as before.

* test: annotate the fake screen instead of casting

* test: wait on the hang-up instead of sleeping

* fix(orchestration): claim a terminal answer only from what Claude shows

An answer is written only while a caught-up screen still shows the dialog
in the same process run, and claimed only once Claude shows it applied: the
hook's deny line for a deny, no other close for an allow. A Yes, a No or the
2:00 auto-block that wins the race is refused and named from the screen.

A hook that hangs up is not an answer: with the dialog still up the
coordinator is told a person must answer it in the worker's terminal.

A PermissionRequest entry the user made async stays async: it prints {}
first and never waits for a decision.

* fix(orchestration): name a terminal prompt's close from its tool_result

The screen could show an older prompt's close, so who answered is now read
from the tool_result Claude's transcript records for that tool call: Orca's
own deny text credits Orca, the user's No and the 2:00 safety-check block
name themselves, and a run names an allow. Hook events cannot say this: a
deny or a No fires no PostToolUse. The screen only says whether the dialog
is still up before a write, and a PostToolUse seen mid-answer is kept.

* fix(orchestration): read a prompt's tool_result without blocking main

The transcript read is async, reads only its tail, and skips any path that
is not a regular file, so a FIFO or device cannot stall the main process.
A closed prompt still awaiting its result gets one last read when the lead's
turn stops, so a result written late still names the close.

* feat(native-chat): answer workers' approvals from the coordinator chat

A worker's open permission prompt appears as a card at the end of its coordinator's chat, drawn
from the escalation payload as soon as it is stored. The person allows or denies it there; the
card's buttons answer the worker's own prompt over the desktop app's IPC, so only a person can
allow. Once answered, the card turns into a receipt saying who answered.

* fix(orchestration): read a terminal prompt's countdown when it is first escalated

* refactor(orchestration): give a terminal prompt's hold state its own module

* fix(native-chat): let a worker's approval card answer, and drop it once it lapses

The window's runtime calls never carried the orchestration contract
version, so the card's Allow and Deny were refused as an obsolete client.
The renderer ships with its main process, so desktop IPC states the current
contract. An open card whose agent's own countdown has passed, with no
closing message (the app restarted while it was open), is no longer shown.

* test: say why the desktop fence test's runtime stub is enough

* fix(orchestration): a chat worker's approval asks to allow its tool, like a terminal worker's

* fix(native-chat): pause the approval countdown while the window is hidden

* chore(i18n): protect the approval card's worker line copy

* fix(orchestration): close a terminal prompt's card as soon as Orca can't answer it

When the dialog leaves without an answer through Orca, the closing line is
published at once, so the coordinator's card becomes a receipt; a tool
result that arrives later adds a second line naming how it closed, without
waking the coordinator. On startup, open terminal prompts left by an earlier
run are closed the same way, since their holds lived in memory. A Claude
terminal card's reason is the warning Claude prints under the command.

* fix(orchestration): keep the dialog-warning patterns ASCII for the orcad bundle

* fix(native-chat): say when a deny reason reached the worker as a separate message

A coordinator's deny reason for a chat worker whose prompt takes no message is sent
as mail. The receipt showed it the same as a reason that went with the denial, so it
now labels it 'Reason, sent as a message'.

* chore(native-chat): follow main's moved window-activity and agent-session modules

* fix(orchestration): only Orca's own reports draw approval cards, and every card closes

- Cards and the startup sweep read approvals only from rows whose id is the stop report's own
  stable id for that approval, through one shared parser; a worker's send cannot draw a card.
- A chat worker's card closes as soon as its own chat answers it (a journal watch replaces the
  turn-edge check), when an answer finds the prompt already gone, and after a restart.
- A terminal coordinator is told nobody was shown the prompt and to tell the user; the
  terminal-only close asks the same and goes out as an escalation.
- A chat coordinator's line names only the answers its card offers; a prompt with none is a
  plain prompt with no card.
- A person's Deny on a chat worker mails no extra reason.
- A record a quit or budget end closed stays closed.
- One closed-approval builder; `waitingOn` becomes `awaitsReply`.

* fix(orchestration): a failing read cannot hang an answer, fail a stored send, or fail the database open

* refactor(orchestration): add the held-send line in send-delivery-hold, keeping send-methods under the size cap

* test(orchestration): read the worker's mail with a schema, not a cast

* fix(orchestration): an open approval stays visible and closes even behind many closed ones or a missing database
2026-11-11 04:36:35 -05:00
Kelvin AmoabaandKooooojun 801db2bb4c fix(native-chat): show Claude/Codex startup dialogs as a chat card and lock Send while one is up (#41083)
* fix(native-chat): answer Claude/Codex folder-trust dialog from a chat card

* fix(native-chat): show a chat card for Claude/Codex startup menus

* fix(native-chat): keep startup-menu cards holding Send; drop hand-typed identify retries

Startup-menu cards now hold the composer like the folder-trust card, so collapsing one can't free a Send whose Return would pick the highlighted choice.

The hand-typed agent probe retries and agentless hold are left out; Orca-launched panes already know their agent, and hand-typed native Claude identification predates this fix.

* fix(native-chat): read the whole screen for a tall startup dialog's heading

* fix(native-chat): drop the unreachable cwd prompt from startup-menu reasons

* fix(native-chat): show startup-menu warning text and give its choices no default

A startup-menu card now carries the dialog's body (Claude's bypass, API key, MCP and import warnings) under its heading, and its choices render as equals on desktop and mobile so Orca never nudges a consent choice. Long mobile option labels wrap inside their button.

---------

Co-authored-by: Kooooojun <edisonhuang1122@gmail.com>
2026-11-11 08:37:51 +00:00
Kelvin Amoaba 8afd012d69 feat(browser): move extensions out of Experimental into Settings › Browser, on by default (#40939)
* feat(browser): move extensions out of Experimental into Settings › Browser, on by default

The old experimentalBrowserExtensions key is retired on load so profiles that saved the old false default start with extensions on.

* test(startup): mock extension startup in the ready-phase identity test

Extensions now default on, so ready-phase startup installs extension support, which this browser-identity fixture's Electron and store fakes don't cover.
2026-11-11 04:25:48 +00:00
Neil 08bb458da4 Remove the per-repo agent folder trust prompt (#40934)
The first-launch 'Trust <repo> for agents?' modal blocked agent launches
to ask a question that offered no real protection: undecided repos, and
every unattended launch, already pre-trusted. Drop the dialog, the launch
hold and the skipAgentWorkspaceTrust plumbing. Repos default to
pre-trust; the per-repo setting remains as an opt-out.
2026-11-11 00:34:24 +00:00
Kelvin Amoaba d5759e67a2 fix(browser): keep unsent page notes across restarts and ask before closing a tab that has them (#40851)
* fix(browser): keep unsent page notes across restarts and ask before closing a tab that has them

Unsent browser notes lived only in the renderer store and hydration reset them, so a reload, restart or tab close lost them. Each saved browser page now carries its unsent notes, hydration restores them, and every close path (tab strip, keyboard, CLI, phone) asks before discarding them. A page script's window.close() can't discard them.

* fix(browser): keep tabs with unsent notes open on bulk close, like pinned tabs
2026-11-10 20:48:45 +00:00
Neil 9ef5e48f09 Plugins: add editor.readTypography and editor.typography.changed (#40736)
* 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.
2026-11-10 03:38:47 -08:00
Brennan Benson 55db56d989 Make Settings easier to scan and organize General (#40249)
* Simplify Settings chrome and organize General into focused views

* Preserve search and skill actions across Settings views

* Correct Settings validation leftovers

* Keep skill conflict details visible in compact Settings rows

* Refresh Settings search and protected copy catalogs

* Use Settings design tokens and button variants

* Keep Settings test setup and translation catalogs current

* Keep Settings search metadata independent of display labels

* Build repository search groups only for the selected pane

* Keep pending skill installs and server menu labels accurate

* Use the supported role locator in the skill regression

* Keep absent skill installation actions on their supported path

* Restore the Settings sidebar to its existing appearance

* Restore shared navigation title keys in pane headings

* Preserve the Settings shell entry animation

* Drop unused Voice labels after the main sync

* Preserve main Voice translations and the moved skill module import
2026-11-09 16:12:36 -08:00
OrcaWinandOrcaWin fc379afda7 test: remove low-value locale, automation and settings cases (#40437)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-09 09:49:58 -08:00
Neil f358eb6a7d Plugins: add workspace.rename and project names in workspace.list (#40197)
* 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.
2026-11-09 00:48:33 -08:00
Jinjing 7c1483278b Reduce excessive tooltips and clarify action details (#39960)
* wip

* rm execessive tooltip

* Clarify deletion options and improve resource popover layout

- Show the scope of persistent deletion choices directly in the menu
- Keep resource details accessible in short windows
- Style disabled dropdown actions consistently

* Clarify detached commits and deletion guidance

* Preserve translated tooltip guidance
2026-11-08 21:53:12 -08:00
OrcaWinandOrcaWin c0d3d2e0db test: remove 13 low-value suites and redundant cases (#40138)
* Remove low-value file, runtime and chat test cases

* test: remove low-value shared contract and timing suites

* test: remove leaking browser IPC mock suite

* test: isolate legacy relay bridge transport fixture

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-08 21:51:31 -08:00
Jinjing b28931e680 Allow preserving child workspaces when deleting a parent (#39795)
* Allow preserving child workspaces on deletion

* Fix detach workflow CI ratchets

* Keep preservation choice safe when eligibility changes
2026-11-08 15:20:00 -08:00
NeilandCopilot fc8dcd65eb Simplify delete workspace dialog (#39801)
* feat(sidebar): simplify delete workspace dialog

Replace the "Don't ask again" checkbox with an "Always delete" option in a
split-button dropdown on the Delete button, matching the force-delete toasts.
Shorten the description and change-loss copy so the dialog reads lighter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(sidebar): keep delete change-loss warning protected and shown when status is unknown

Default Delete forces removal, so restore the permanent-loss notice while a
target's Git status is still checking or unavailable. Mark the dirty-change
hint as protected copy so language packs cannot replace the loss warning.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-08 02:29:48 -08:00
OrcaWinandOrcaWin 5c5c7ca235 test: remove 283 low-value tests (#39533)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-07 08:11:30 -08:00
OrcaWinandOrcaWin 5f226987f1 test: remove 279 low-value tests (#39527)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-07 07:58:19 -08:00
Neil c0e8e4e6eb docs(plugins): theme background video ships in 1.4.254 (#39478)
#39450 merged after the 1.4.253 cut, so the videoPath since-version and the
theme reference name 1.4.254.
2026-11-07 03:49:08 -08:00
Neil b0dd73e7aa feat(plugins): play a looping video behind app themes (#39450)
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.
2026-11-07 03:32:14 -08:00
0173de9b52 fix(plugins): don't count time asleep as worker idle time (#28299)
* 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>
2026-11-07 03:19:46 -08:00
Neil 35975c0580 feat(themes): theme tokens for chat text styling and a readable chat selection (#39432)
* feat(themes): public plugin theme tokens for chat text and selection

Themes can now set chat bold text, muted text, inline code, code blocks and
the chat selection. Omitted tokens paint exactly what chat painted before,
except the selection, which now keeps selected text 4.5:1 by default.

* docs(themes): note the bubble and code-block checks for chat text tokens

* fix(themes): never lower the chat selection under its visible floor
2026-11-07 03:02:23 -08:00
Neil 068d9ad3fd fix(plugins): keep a workspace-scoped panel edit made just before a workspace switch (#39413)
* fix(plugins): keep a workspace-scoped panel edit made just before a workspace switch

The guest's persist queue holds atoms, not values, and the renderer stored each
ui.persist under whichever workspace was active when it arrived. An edit made
less than 0.4 s before a switch was flushed after the new workspace's hydrate,
so workspace B's value was written under B and A's edit was lost.

The host now names each ui.hydrate; the guest flushes pending writes before
applying the next hydrate and tags ui.persist with the hydration its values came
from, and the renderer stores the write under that hydration's workspace.
(STA-13316)

* test(plugins): cover workspace-persist hydration routing edge cases and add switch e2e

Adds unit coverage for A->B->A routing, legacy (no hydration) persists, the eight-hydration window, plugin-scope entries, and guest flush/tagging; adds an Electron e2e that types in workspace A, switches inside the persist debounce, and checks both workspaces after switching back and after the panel's guest is recreated. Regenerates orca-testing.mjs after rebasing on main.
2026-11-07 02:50:30 -08:00
Neil 51f3c956ed feat(plugins): let plugins ship web fonts for the terminal, editor and interface (#39416)
* 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.
2026-11-07 02:08:35 -08:00
OrcaWinandOrcaWin 95fadd01ba test: remove 321 low-value main and shared tests (#39429)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-07 01:37:39 -08:00
Neil 2aaaa1c6d3 fix(plugins): deny when a fail-closed hook returns a malformed denial (#39393)
A terminal.send guard with onError: deny that returned an invalid result (e.g. a
deny reason over 500 chars) was logged and skipped, letting the action through.
Treat a malformed result from a fail-closed participant as a failure. (STA-13532)
2026-11-07 01:13:52 -08:00
Neil 8229ecc6ae fix(hooks): remember approved setup scripts and let automations re-approve skipped setup (#39354)
- Keep a bounded history of approved setup hashes per repo; creates send it and the host
  runs setup when the created branch matches any approved content.
- Approving a created branch's different setup now saves it to that history.
- Automation runs record skipped setup content; the run page and list offer
  "Review and approve setup", and saving re-confirms setup after a skipped run.
2026-11-07 00:36:42 -08:00
Neil 090945007b Ask once per repo before pre-trusting agent folders (#38946)
* Ask once per repo before pre-trusting agent folders

* Route the trust prompt's repo lookup by the launch host; use dialog primitives as designed

* Bound the first-launch trust hold and honour the answer for the launch

* Move agent folder trust settings into their own settings group
2026-11-06 17:02:46 -08:00
Neil 490a5c05b8 fix(hooks): run a created worktree's setup once its own content is approved (#37978)
* fix(hooks): run a created worktree's setup once its own content is approved

Adds worktrees:runApprovedSetup (IPC) and worktree.runApprovedSetup (RPC,
capability worktree.run-approved-setup.v1). The host re-hashes the worktree's
setup and default-tab commands, runs them only on a match, and otherwise
reports the new content. After a create mismatch the host opens only the
command-less default tabs; approving in the trust prompt starts setup and the
command tabs. Clients whose host lacks the capability keep setup skipped.

* fix(hooks): review a created branch's different setup from the workspace, not instead of it

The trust prompt now says the workspace was created and its branch's setup
differs; approving runs it for that workspace without replacing the primary
checkout's approval (which made the next create prompt twice). Declining shows
a toast whose Review setup action reopens the prompt. Repo-wide trust returns
the content's hash so the host can run it.

* fix(worktrees): import approved setup from its path after the RPC methods move
2026-11-06 15:37:32 -08:00
NeilandClaude d5d92649ac plugins: show the publisher when two plugins or themes share a name (#39205)
Co-authored-by: Claude <noreply@anthropic.com>
2026-11-06 15:15:26 -08:00
Neil b9a4b5fd2f fix(plugins): re-review any plugin whose catalog moves it to another repository (#36643)
* 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
2026-11-06 14:41:29 -08:00
Neil 1824515698 fix(plugins): warn, not fail, when a language pack translates protected security text (#39187)
Orca already drops protected strings when it loads a language pack and keeps
its own English text for trust, permission and destructive dialogs.
validate:plugin and orca plugin validate still rejected the whole pack, so the
community directory refused otherwise-good packs. They now report how many
protected strings are ignored as a warning; every other pack rule stays strict.
2026-11-06 14:35:22 -08:00
Jinjing c21f3c5173 Redesign plugin details with publisher discovery (#39181)
* 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
2026-11-06 14:34:25 -08:00
Neil be3b87f3ac cli: add orca plugin install, update, uninstall and catalog list/add/rm (#36647)
* cli: add orca plugin install, update, uninstall and catalog list/add/rm

* plugins: escape the invisible-character regex so the orcad bundle stays ASCII

* plugins: split approval re-run helpers and plugin RPC binding to stay under max-lines
2026-11-06 14:20:27 -08:00
Neil 26741256dc fix(sidebar): keep the selected card's host pill outline visible in dim themes (#39054)
* fix(sidebar): keep the selected card's host pill outline visible in dim themes

#38841 tinted the host pill outline 40% of the sidebar text into the
sidebar. A theme whose sidebar text is only 4.5:1 leaves that outline at
about 1.6:1 against the selected wash, which matches the pill's fill, so
the pill loses its shape.

When an app theme is painted, its colors are now concrete, so compute
once per theme the smallest mix (from 40%) that gives the outline 2:1
against the selected wash, the same floor focus rings use, and paint it
as --worktree-host-pill-outline-mix. main.css falls back to 40%, so
Orca's built-in themes and any theme bright enough keep today's look.
The tinted sidebar resets it, like the row wash. The wash's CSS fallback
joins the fallback table so the check (and the token reference) read
what main.css paints.

Refs #38641

* fix(sidebar): floor the host pill outline in the tinted left sidebar too

The tinted sidebar paints the app's text on its tint mixed into the app
background, so the theme's measured outline mix doesn't apply there and
it fell back to 40%: a dim theme (#919191 on #2a2a2a) at the default 8%
tint left the outline at 1.6:1 on the selected wash. Measure the tinted
colors once whenever the app theme or tint settings change, paint the
result as a root variable, and have the tinted sidebar read it. Share the
measurement (hostPillOutlineMixOver) with the theme path, and make its
comment name the actual invariant instead of the pill label's contrast.

Refs #38641
2026-11-06 06:06:50 -08:00
Brennan Benson 45a468894d Show proposed files and collapsible details in Codex approvals (#37960)
Show Codex file-change proposals in approval cards and keep verbose operation details collapsed by default on desktop and phone, with reason, access and answer choices visible.

Preserve approval identity and answer behavior, keyboard focus, and scroll position when details expand or collapse.
2026-11-05 20:32:50 -08:00
Kelvin Amoaba 4fbb1ccd97 feat(native-chat): color chat links with the theme's link color (#38676)
Chat links used the body text color, so a link only showed as a faint
underline and a theme's textLink.foreground had no effect. They now
paint --chat-link: the theme's link color, else the built-in note blue
for the chat's own scheme, mixed toward the chat foreground like the
other chat hues. Code and bold link labels follow the link color.

When the chat paints a terminal palette the app theme doesn't match,
the app theme's link color is cleared, as its bubble already is. The
theme contrast check now measures the chat link on the theme's user
bubble and drops only the bubble when it fails.
2026-11-05 21:57:27 +00:00
NeilandMatthew Wilkinson f660d8c47d feat(plugins): add permissioned desktop appearance publication (#38524)
* feat(plugins): add permissioned desktop appearance publication

* docs(plugins): add one-line API comments for appearance provider

* chore(plugins): regenerate declarative testing kit for appearance API

---------

Co-authored-by: Matthew Wilkinson <wilkinson.m@outlook.com>
2026-11-05 11:31:23 +00:00
Neil 6bb2e13c35 refactor(tooling): organize remaining scripts by domain owners (#38407)
* refactor(tooling): group remaining scripts by concrete domain owners

* fix(tooling): preserve relocated launcher resource roots
2026-11-05 00:29:16 -08:00
Jingyue Wu 51cca09d7b Show each workspace's project in the cleanup dialog, undoable Ignore, calmer delete confirmation (#38281)
Rows show their project, two-line layout with fixed time/size columns and hover actions; Ignore can be undone per row; delete confirmation reads 'Delete N workspaces?'. Fixes Ignore/Stop ignoring being reverted by streamed scan frames or scan settlement, and the git-unknown flash on open/close.

Co-authored-by: Jingyue-Wu <75918217+Jingyue-Wu@users.noreply.github.com>
2026-11-04 22:14:30 -05:00
Kelvin Amoaba d8fb85f7bc feat(browser): save and fill passwords in Orca's browser (#33994)
* feat(browser): save and fill passwords in Orca's browser (experimental)

Off by default under Settings > Experimental > Saved passwords.

* fix(browser): secure saved passwords and fix login lifecycles

* fix(browser): keep the guest preload self-contained so saved passwords load

The shared uuid import split into a chunk the sandboxed preload can't require, so the
whole preload failed on every page. Element tokens now come from crypto.getRandomValues.

* fix(browser): fill a saved login on mouse press so clicking a row works

Pressing Orca's UI blurs the page field, which closed the list before the click landed.

* feat(browser): manage saved passwords from a Passwords section in Settings › Browser

Replaces the Manage passwords dialog with an inline section: save and Touch ID
switches, import menu, a searchable list grouped by site with last-used times and
hover actions in a capped scroll box, delete confirmations, and an Add/Edit dialog.

* feat(browser): turn saved passwords on for everyone instead of behind Experimental

Drops the experimental flag, its Experimental toggle and the badge; the Passwords section in
Settings › Browser now shows on desktop for the local host without opting in. With no toggle,
the guest disable message and the apply-to-open-pages step had nothing left to trigger them.

* fix(browser): refuse protected password actions when Touch ID is set up but unavailable, and keep fills on the matched page

Touch ID lockout or a closed lid made canPromptTouchID() false, which counted as a Mac without
Touch ID, so show, copy and protected fill went through unchecked. Enrollment from bioutil now
tells the two apart. A fill also kept no hold on the page across the Touch ID prompt, so a page
that navigated meanwhile got the password; it now goes only to the frame and origin it was matched for.

* fix(browser): keep a browser profile when its saved logins can't be deleted

Profile deletion removed the profile first and only logged a failed vault delete, so the
encrypted vault could stay on disk with nothing left to retry it. The vault now goes first,
and the profile stays (deletion reports failure) when it can't.
2026-11-05 02:08:08 +00:00