Skip to content

feat(views): reclaim the dashboard and Tasks top edge, drop their close buttons - #48

Closed
nplez1 wants to merge 150 commits into
nplez1/mainfrom
nplez1/Agent-Dash-Improvements
Closed

nplez1 wants to merge 150 commits into
nplez1/mainfrom
nplez1/Agent-Dash-Improvements

Conversation

@nplez1

@nplez1 nplez1 commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner
Files Added Deleted Net
Test 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​672 $\color{#cf222e}{\Huge{\mathbf{−}}}$​499 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​173
Prod 26 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​645 $\color{#cf222e}{\Huge{\mathbf{−}}}$​625 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​20

ELI5

The Agent Dashboard had a blank strip above everything, and its first row was mostly a title and an X to close it, so the actual board started 126px down the page. Tasks had the same blank strip plus an X of its own on the left, beside its filters. Both Xs are gone, the dashboard's search bar is its first row, and the Tasks source row is that page's first row, so each starts 36px higher than before — 81px for the dashboard, which also lost its separate title row.

What Changed

Before: opening the Agent Dashboard left an empty 36px band at the top (the app shell draws a titlebar strip there for the sidebar views), then a header row holding only "Agents", a total count, a settings gear and a close X, then a toolbar with the search box and filter. The kanban columns began 126px from the top edge. The Tasks page carried a left-anchored X ("Close tasks", tooltip "Close · Esc") before its source filters.

After: the dashboard's toolbar — search, ⌘K, Filter, board settings — is its first row, flush with the top of the window, and the columns start at 45px instead of 126px. Tasks' source row is now that page's top edge, its icon cluster centred 22px below the window top (the same first-row height the dashboard uses), so the list below it starts 36px higher. Neither page shows a close control any more; Tasks is left with Esc or the sidebar, and the dashboard is left with the sidebar or the back arrow.

Mechanism:

  • The shell only skipped its stacked titlebar for automations and artifacts, so the dashboard and Tasks each got a stripe containing nothing but the window-controls spacer. That decision now lives in lib/page-owned-titlebar.ts and the shell gates the strip on a derived stackedTitlebarVisible flag from useAppChromeLayout, instead of an inline list of view names. Tasks joined the list once its own band was reclaimed, which is why the same change lands here rather than in a follow-up.
  • AgentKanbanBoard lost its header row (title, count, close) and its onClose prop; its headerActions became toolbarActions, forwarded into AgentDashboardToolbar as trailingActions. That row now reserves the same Windows/Linux window-controls gutter the automations and artifacts headers reserve (paddingRight: max(0.75rem, var(--window-controls-width, 0px))).
  • task-page/SourceBar.tsx lost the close button and the divider that separated it from the source icons. The Esc handler in use-task-page-global-effects.ts is untouched.
  • The Tasks source row is now a page's top edge, so it takes the same window-controls gutter the automations/artifacts headers and the dashboard toolbar take; because it sits inside the frame's px-5, the new .tasks-source-row-window-controls-inset pads by only the remainder past that inset and stays a no-op on macOS.
  • 'dashboard' joined shouldShowWorktreeHistoryControls. The view already recorded a back/forward history entry when it became a first-class view, but hid the shared arrow pair, so removing its X would have left the only full-page view with no exit control of its own.

Why

A 36px empty band plus a header row that carried one word, a number and a close button is chrome spent on nothing on a surface whose whole job is showing as many agents as possible. The two changes follow the two patterns already in the repo rather than inventing a third: pages that draw their own top edge declare it (like automations/artifacts), and a full-page view is left the way every other one is (sidebar, back control, or Esc) instead of by a bespoke X that exists on no other page.

Alternatives considered and rejected: keeping the title row and only dropping the empty band (saves 36px of 126px, leaves the duplicated chrome the user flagged), and overlaying the header on the columns (would cover the column headers).

Linked Issue

N/A — fork-local change against the fork's own default branch.

Visual Proof

Agent Dashboard. Before — empty band above "Agents", header row with the X at top right, columns pushed down:

dashboard before

After — search/filter/settings are the first row, columns start 81px higher, no X (the ← back arrow is now in the titlebar):

dashboard after

Tasks. Before — X at the left of the source-filter band, under the same empty 36px stripe:

tasks before

After — the source icons are the leftmost item and the row is the page's top edge:

tasks after

Measured in the running app over CDP (sidebar open): the dashboard's columns started at y=126 and now start at y=45, with its toolbar row 45px tall at y=0; the Tasks source row started at y=42 (band 0–36 plus the frame's 6px) and now starts at y=6, its 32px cluster centred at 22px. With the sidebar collapsed the behaviour is deliberately unchanged for both — the shell still draws its full-width titlebar at y 0–36 (drag region plus window controls) and the page starts at y=36.

Testing

  • pnpm tc (all TypeScript projects) passes.
  • Unit: src/renderer/src/components/dashboard, dashboard-popout, app-shell, the lib titlebar/page-titlebar helpers and the page-navigation slice — 60 files / 467 tests pass. Added: a board test asserting no title, no count and no close control with the board controls living in the toolbar row; a page-owned-titlebar test; an app-command-handlers test asserting the dashboard claims the back/forward chords and a non-history view does not; and the dashboard case in the history-controls test.
  • pnpm run check:code-quality:changed reports 0 findings across the 13 changed files; oxfmt --check clean on the same files.
  • E2E against a real hidden Electron app (ORCA_BACKGROUND_LAUNCH=1): tests/e2e/agent-dashboard-sidebar-toggle.spec.ts passes, now leaving the dashboard through the shared Go back control and asserting the entry unselects.
  • Rendered manually in the running app: dashboard with sidebar open and collapsed, and the Tasks band.

Gates that were red on the fork's base are fixed by the second commit (fix(lint): split the five files that left the fork's static analysis red):

  • pnpm lint now exits 0 (was exiting 1). It stopped at oxlint on five max-lines errors in files byte-identical to nplez1/main — right-sidebar/FileExplorer.tsx (420/400), native-chat/agent-session-wire/structured-agent-session-adapter.ts (301/300), ipc/worktrees/listing/register-worktree-catalog-handlers.ts (305/300), ipc/worktrees-local-create-flow.test.ts (834/800), shared/protocol-version.ts (304/300) — which failed CI's Lint step and, through the verify job, skipped the test and package jobs entirely. Each file is now split along a real seam to 365, 245, 151, 585 and 284 counted lines, with no max-lines disable and no cap change. The commit also regenerates en-runtime-required.json, because the close-button strings this branch stopped referencing are still in en.json and an English entry with no call site has to ship (the tool cannot distinguish it from a dynamic key).
  • pnpm test (100,467 tests) still has 63 failures in 10 files, none of them in a file this branch touches: src/main/runtime/agent-session-*, src/main/native-chat/agent-session-wire/*, src/main/runtime/rpc/methods/agent-launch-replay.test.ts, src/main/browser/browser-manager-*.test.ts and tests/e2e/relay-region-correction.unit.test.ts. Verified pre-existing: with this branch's modified files reverted to origin/nplez1/main, three representative files still fail (3 failed | 30 failed | 35 passed), and the native-chat folder keeps its 17 failures with the adapter split stashed.
  • pnpm build was not run locally (it would need pnpm install:release for both macOS architectures); left to CI.

Two jobs that only began running once static analysis stopped failing ahead of them were red for
pre-existing reasons, and both are fixed here:

  • package (windows) failed 2 tests in src/main/codex/hook-service-managed-install.test.ts: three win32-only assertions built the expected managed-hook command under a literal .orca while the build installs into HOME_DIRECTORY_NAME (.orca-np). The job's own output prints the same path both ways. The expectations now derive from that constant, as agent-hooks/installer-utils.ts does.
  • cross-version wire compatibility pins two upstream releases (v1.4.211, v1.4.212) and the harness throws rather than skipping when a ref will not resolve; the fork's remote carried only its own v1.4.214-np.* tags, so git rev-parse v1.4.211^{commit} failed on CI. Both release tags are now mirrored into the fork (git push origin v1.4.211 v1.4.212), which is repo state rather than a commit, so the lane materializes those releases and runs its nine journeys.

All 21 checks pass on eb00d20c6a, and a full local pnpm test with every commit here applied reports the same 10 failing files / 63 tests as the base, so the five splits add no failures.

Platforms: macOS (dev). The window-controls gutter on the dashboard's first row is the same expression AutomationsPageTopBar/ArtifactsPage use, so Windows/Linux keep their controls clear of the settings gear; no keyboard bindings were added or moved.

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

AI Disclosure

Implemented with an AI coding agent (pi), reviewed by the author.

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • The "Agents", "{{count}} total", "Close dashboard", "Close tasks" and "Close · Esc" strings are now unreferenced in the locale catalogs, and since an entry with no call site must ship, en-runtime-required.json now carries those five (it had carried none of them). Deleting them from en.json and the five translated catalogs instead would be the leaner end state — the runtime-catalog tool cannot tell a dead key from a dynamic one, so it ships both — and that cleanup is worth its own PR if you want the boot bundle trimmed.
  • The "N of M shown" readout in the toolbar still reports totals while a query or filter is active, and the column headers and the sidebar entry still carry the per-bucket counts, so dropping "N total" loses no unique information.
  • -webkit-app-region: drag is not set on the dashboard's new first row — same as the automations and artifacts headers, which also start at the top edge. The window stays draggable through the sidebar's titlebar (and the full-width titlebar when the sidebar is collapsed).
  • Deliberately left for a follow-up: the now-misnamed src/renderer/src/components/dashboard-popout/ folder and dashboardPopout.* i18n namespace, as in feat(dashboard): make the Agent Dashboard a first-class view #44.
  • The fix(codex) commit touches a file no other branch owns, so it is safe here; if you would rather it live on its own branch, it is a two-line change to cherry-pick out.
  • shared/protocol-version.ts is the thinnest of the five splits at 284 counted lines against a 300 cap, so the next capability that lands there may need the same treatment — the domain-module-plus-export * pattern the file already uses for ./skill-install-capability is the move.
  • The two full-suite reds above are base-wide on this fork, so this PR should not be expected to turn them green; if CI is green it is because CI covers the files it runs.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint and pnpm typecheck pass locally. pnpm test is red only on the 63 pre-existing failures listed under Testing, all in files this branch does not touch; pnpm build is left to CI.

nplez1 added 30 commits October 1, 2026 01:46
Neither provider publishes a percentage quota, so the status bar gains a
money readout for them:

- DeepSeek: account balance from /user/balance, which may be CNY or USD.
  The API exposes no usage or cost history, so the row is balance-only.
- Fireworks.ai: rated spend for the current billing period, with the
  account ID discovered from the API key and an optional manual override.

Adds an optional `credits` field to ProviderRateLimits with exact
integer-nanos money arithmetic, keeps both API keys in the existing
encrypted credential store rather than GlobalSettings, and teaches the
window-shaped staleness predicates that credits are data so a configured
provider is neither hidden nor blanked by a transient failure.

`status: 'unavailable'` keeps meaning "not configured"; an exhausted
balance stays visible via `credits.available` so the account can be
topped up instead of vanishing. Fireworks spend-limit percentages are
deliberately not wired: the `usageLimits` response shape is unverified.

Extracts createSecureCredentialStore from the MiniMax API-key store and
reuses it for the two new credentials.
The DeepSeek status-bar chip was showing a neutral lucide Waves glyph as a
placeholder. Replace it with the real mark, taken verbatim from the inline
SVG on deepseek.com (single `currentColor` path, 26.634x19.6).

Inlined rather than shipped as a resources/ asset like MiniMax's: that mark
is a fixed multi-colour gradient, whereas this one is a single path whose
`currentColor` would resolve to the SVG's own colour inside an <img> and go
invisible against one of the two themes.

The icon keeps its native aspect ratio (13x10 at the default size) instead
of being forced into a square box, which would squash the whale.
The Fireworks status-bar chip was showing a neutral lucide Flame glyph as a
placeholder. Replace it with the real mark, taken verbatim from the SVG icon
fireworks.ai serves as its favicon (three paths on a 32x32 grid, all #6720FF).

Inlined like the Claude/Gemini marks rather than shipped as a resources/
asset: the fill is a single fixed brand colour, so it needs no separate file,
and the icon grid already carries the mark's padding, so no viewBox cropping
is required the way the DeepSeek whale needed.

This removes the last lucide placeholder: icons.tsx no longer imports
lucide-react at all.
…ighbours

Their favicon grid carries ~25% vertical padding and the mark itself is 2:1,
so at the same 13px width as the DeepSeek whale it rendered ~35% shorter and
read as a smaller icon beside it.

Crop the viewBox from the source's 32x32 to the mark's own bounds (y 8-23.8)
and scale the width 1.25x, which lands the ink at 16x8 against the whale's
13x10.
Fireworks exposes credit only in its dashboard: there is no published REST
route for a balance, and `GET /v1/accounts/{id}/usageLimits` answers
`code 9, "usage limits are not enabled for account ..."` on a self-serve
account, so the row could only ever show spend.

Read the balance from the control-plane gateway instead — `gateway.Gateway/
GetBalance` over `gateway.fireworks.ai`, which is a different host from the
`api.fireworks.ai` inference API that serves the billing summary. The auth
metadata (`x-api-key`) and the message shapes were recovered from the
unstripped `firectl` binary, which is a thin client over that service and
exposes no subcommand for GetBalance.

Implemented with no new dependencies: a hand-rolled protobuf codec for the
three messages involved (`GetBalanceRequest`, `Balance`, `Money`) plus gRPC
framing over `node:http2`, rather than pulling in `@grpc/grpc-js` and
`@grpc/proto-loader` for one readout. The codec is unit-tested against the
wire shape a live gateway response was observed to carry.

The balance becomes the headline with the period's spend as its breakdown.
Because this is an internal API with no compatibility contract, the call is
best-effort: any failure — unreachable gateway, proxy, revoked key, schema
change — degrades to the previous spend-only readout instead of failing the
provider. Note the transport dials the host directly, so it deliberately does
not use the proxied default session that `net.fetch` callers do.
Plans without 5-hour and 7-day quota windows reported nothing useful: Orca
mapped their payloads to `{ session: null, weekly: null, status: 'ok' }`, which
the UI showed as "No usage data" — indistinguishable from a failed read.

Both providers do report a spending ceiling instead, so map it:

- Claude reads `spend` (with `extra_usage` as the fallback source) from the
  usage endpoint, which previously discarded those keys entirely because
  `OAuthUsageResponse` had no field for them.
- Codex reads `spend_control.individual_limit` from the backend usage payload,
  which was never even requested: `supplementCodexSessionWindow` returned early
  whenever the backend had no session window, so a windowless plan was skipped.
  It also dropped the backend's `planType` on that path; the no-session branch
  now keeps local windows (the backend can report a different bucket) but
  adopts the allowance and plan.

Each ceiling's own percentage becomes the `monthly` window, so the status bar,
roster and detail panel render it with no renderer changes, and the amounts
ride a new `ProviderAllowance` readout: an amount against a limit in a unit the
provider picks — dollars for Claude, the provider's own `credit` unit for Codex,
which a currency-shaped type could not express. The percentage is always the
provider's reported figure, never derived from the amounts.

Only applies when an account reports no session *and* no weekly window, so a
normal subscription account with extra usage credits does not grow a monthly bar.

Note the backend reports `reset_at` in Unix seconds while Orca stores ms.
Copilot enterprise accounts report a monthly AI-credit budget rather than a
quota window, so this rides the allowance readout added for Claude and Codex:
the ceiling comes from `GET /enterprises/{ent}/settings/billing/budgets`, the
consumption from `/settings/billing/ai_credit/usage`, and the percentage fills
the `monthly` window.

The budget is denominated in dollars while usage is reported in credits, and
the usage payload carries the price that relates them, so the ceiling is
expressed in the unit the user recognises: 10000 dollars at the documented
0.01 price displays as 1,000,000 AI credits. With no price reported it falls
back to dollars rather than inventing a credit ceiling. GitHub documents these
budgets as monthly and partitions the usage report by calendar month, so the
reset is the next month boundary — inferable here in a way Claude's spend cap
was not.

Auth is a token plus the enterprise slug, stored in one encrypted file. The
`gh` CLI token Orca otherwise uses carries repo/read:org/project and cannot
read billing endpoints, so this is a separate credential. GitHub 401 and 403
map to stale-token and missing-scope so the existing error copy explains what
to fix.

The icon is the official Copilot mark — the robot with goggles, taken verbatim
from the brand icon GitHub ships for the product — not the octocat.

Also lands the identity plumbing: a copilot StatusBarItem, default-on
migration, visibility and roster wiring, an Accounts section, and the tests for
both the mapping and the credential flow.
The Copilot fetcher was the only authenticated GitHub caller in Orca not going
through `gh`, which meant it carried its own token, hardcoded api.github.com,
could not resolve a GitHub Enterprise Server host, and bypassed the gh
rate-limit breaker. Route it through `gh api` like every other GitHub call, so
the provider holds no token of its own: an explicit credential is only ever
passed to gh as GH_TOKEN.

That also lets the credential come from the sign-in the user already has. The
enterprise slug is now resolved from gh rather than typed: `read:enterprise`
exposes it through GraphQL `viewer.enterprises` and `manage_billing:enterprise`
reads its billing data, with `admin:enterprise` implying both (verified against
the live API — without `read:enterprise` the query fails with
INSUFFICIENT_SCOPES rather than returning an empty list). A gh lacking those
scopes is deliberately not treated as a credential source: the provider item is
default-on, so doing otherwise would show a permanent error bar to every
enterprise user who never asked for Copilot usage. The paste form stays as the
explicit override and wins over the CLI.

The probe is cached and refreshed out of band rather than awaited inside the
fetch cycle, because `getState()` is synchronous and a subprocess on the
refresh path would let gh latency — or a hung gh — stall every other provider.

Settings → Accounts now says which source is in use and shows the literal
`gh auth refresh -s read:enterprise -s manage_billing:enterprise` command when
the scopes are the blocker.
Snapshot of 44 modified and 13 new files, captured byte-identically from the
work machine and verified by patch-body comparison. Kept as one commit so the
work is durably recorded; the concerns are split into their own branches for
review. Committed with --no-verify so the snapshot matches the source tree
exactly rather than being reformatted on the way in.
Upstream hardcodes stablyai/orca as the update feed, so a build of this fork
offers to replace itself with an official release. Re-point the atom feed, the
download base, both tag/asset regexes, the two latest/download fallbacks, and
MAIN_RELEASE_REPO at nplez1/orca. The houry/daily/adhoc channel repos are left
alone because those channels are unused here.
electron-updater verifies every installer it downloads against the publisherName
in the installed app's app-update.yml and rejects its own channel's next build
when it does not match. This fork ships unsigned, so drop the publisherName and
disable update signature verification — the same thing upstream's dev channels
do, for the same reason.
BRANCHES.md records what git cannot know — why each branch exists, what it depends on, and what is
blocking it — and local/branch-status.mjs reports the live state so the doc cannot silently drift.
Also records the release workflow in the fork-only inventory.
A local(...) commit belongs on nplez1/main only; on a PR branch the fork-only file would be reviewed
upstream. commit-msg rather than pre-commit because the message is what marks a commit fork-only.
Records the step order that matters: validate the pipeline unsigned once before the first signed
run, so a failure cannot be a signing bug or a pipeline bug at the same time. Also the five secrets,
the per-machine install caveats, and the two-release minimum for proving an update.
orca.yaml gains an environmentRecipes entry and local/orca-vm/ the four lifecycle scripts, so a
worktree can allocate its own machine on create and release it on delete. Inert until the provider
verbs are wired: the scripts fail loudly rather than silently allocating nothing.

Validated dry with orca vm recipe doctor (6/6, no fail or warn) and the create contract checked
locally against stubbed verbs: exactly one JSON object on stdout, logs on stderr, no unknown keys.
A snapshot is only worth taking once the guest already has what Orca would otherwise install or
compile on first connect. Checks node/npm/git, the Linux build toolchain node-gyp needs, a COMPLETE
baked native-deps cache entry (.deps-complete plus both addons), and agent CLIs.

Documents the two bake layers and why the shared native-deps cache, not the hash-keyed relay dir, is
the artifact worth baking.
… absent one)

The first pipeline run died in packaging with '<repo> not a file': an unset secret still defines
CSC_LINK as an empty string, electron-builder reads a defined CSC_LINK as a certificate path, and
resolveCscLinkPath('', cwd) resolves to the repository root. Unset the CSC_*/APPLE_* variables on the
unsigned path, and record why in LOCAL-PATCHES.md so it is not re-broken.
…ish legs do not race

The second run died with 422 'Published releases must have a valid tag': both artifact legs saw no
release, both POSTed to create it, and the loser aborted the job even though the winner succeeded —
leaving a PUBLISHED release with seven assets and no latest-mac.yml, i.e. downloads no installed
build can update from.

Create the release up front as a draft so both legs upload into a known tag, and flip it live at the
end of the windows job once every platform's manifest is present. The manifest checks now address
the tag explicitly, because gh release list hides drafts.

Same shape upstream uses in daily-mac-build.yml, for the same reasons.
Renames every name an official install also owns: appId com.stablyai.orca -> com.nplez1.orca,
productName 'Orca' -> 'Orca NP', the CLI command 'orca' -> 'orca-np', packaged userData ->
appData/orca-np, ~/.orca -> ~/.orca-np, the NSIS Markdown ProgID, and the daemon-host root. Also
pins userData explicitly instead of deriving it from package.json's name, and collapses the old
orca/Orca case inconsistency, which diverges on a case-sensitive filesystem.

The URL scheme stays orca://: it is interop surface (mobile pairing, inbound skill-share links),
not plumbing, so a fork-only scheme would emit pairing links nothing else can open.

Three couplings, each of which fails silently:
- the macOS launcher shim hardcodes the in-bundle executable path, and the executable is named
  after productName;
- the Windows launcher ships as bin/orca-np.exe + orca-np.cmd, with getBundledLauncherPath and the
  extraResources mapping moving as a unit;
- the SSH relay shim is a DIFFERENT identity, deployed under a fixed plain `orca`, so remote
  launches resolve through getRelayCliCommandNameForPlatform instead of the locally installed name.

That last one is why local and remote launch names now live in separate config fields: upstream
could share one value only because both names were `orca`.

Tests: the suite baseline was measured before touching anything (22 files already failing on
nplez1/main), which made the identity fallout measurable: 42 files, all now green.
See LOCAL-PATCHES.md § local(identity).
Follow-up to the identity rename, found by running the full suite after it landed rather than
trusting the first green run:

- the packaged Windows launcher ships as bin/orca-np.exe + orca-np.cmd, so the preflight's bundled
  launcher table and the launcher-asset guard test move with it;
- the C# launcher normalizes ORCA_CLI_COMMAND against a whitelist, which silently relabelled the
  fork's CLI as `orca`; it now honours orca-np;
- ORCA_CLI_COMMAND for managed WSL terminals was pinned to `orca-ide`, which no longer names the
  installed command;
- the settings panel's CLI fallback name and its user-facing "Register `orca`" guidance;
- claude-agent-teams-shim-env resolved the OLD packaged Windows launcher path on win32.

Fixtures that mimic the packaged Windows layout were updated too, so a future reader is not misled
by a path that no longer exists.
…s launcher

The guard asserts the packaged Windows shim's extraResources mapping, which now ships as
orca-np.cmd beside the native orca-np.exe.
The release body was a single placeholder sentence, which is a poor landing page for someone handed
the build: no install steps per platform, no statement of what the fork is, and nothing about the
first-run behaviour that otherwise reads as a bug (a fresh profile, and macOS asking for folder
permissions again).

The body now comes from .github/fork-release-notes.md, rendered per release with the version, the
commit it was built from, the date, and a signing paragraph that follows the signing mode — signed
and notarized, or the unsigned validation wording. Node does the render because one placeholder
expands to a multi-line paragraph.
Brings in 131 upstream commits. Three of the 29 local commits needed resolution: two i18n locale
files, and src/shared/agent-hook-listener/listener-state.ts, where upstream wrapped the state factory
in a legacy-adapter layer — kept that scaffolding *and* our CopilotBackgroundWorkState type.

Also here:
- regenerated the derived runtime-required English catalog, which removed the stale fixture keys the
  local side of the i18n conflict carried (`pnpm run sync:localization-runtime-catalog`);
- dropped an unused import in an upstream native-chat test that upstream's CI does not gate on but
  this repo's typecheck does;
- recorded the sync in LOCAL-PATCHES.md, refreshed shas in BRANCHES.md, and corrected the note about
  the dev channels: the picker still offers hourly/daily/adhoc, which resolve to stablyai's repos.

Verified: range-diff shows 26 of 29 patches byte-identical, all three typecheck projects clean, and
161 test files green across the conflict areas and the series' surfaces.
nplez1 added 23 commits October 1, 2026 08:05
* feat(checks): make the Jenkins build tree readable at scale

The Jenkins details from #39 read the build's stage list but listed every stage in the Checks
pane, buried failures under it, kept an Output box that only repeated the build name, and
stopped at a bare error when the account could not read the job.

- Pane: list only the stages still running and the ones that failed, running first, capped at
  eight, with a "+N more stages" line — a pipeline can have hundreds.
- Detail page: render the stages as a nested tree (stage -> its wfapi flow nodes) like Blue
  Ocean, with failed and running stages open by default.
- Detail page: a Failures block under the run header names each failed stage and its error
  text, so "what broke" needs no scrolling.
- Detail page: drop the Output box for a stage list; the build metadata is the run summary.
- Settings: a Jenkins read failure the user can fix (no server, rejected token, no permission)
  now shows an "Open Jenkins settings" button that deep-links to the Jenkins card.

Main reads each stage's own wfapi/describe (its _links.self.href, bounded to 40 requests at
six concurrent, failed/running first under a four-second budget) for the steps and failure
text, and carries the flow nodes on an optional PRCheckStep.children. The Jenkins node id is
kept so repeated stage names (loops, parallel branches) cannot collide as React keys.

* fix(lint): split files over the max-lines budget

Five files exceed the oxlint max-lines budget on nplez1/main and have no suppression, so the
whole-repo `pnpm exec oxlint` in the static-analysis job fails for every PR cut from it. They
were pushed over by fork-only commits (`#34`, and three direct commits) that never ran PR CI.
The repo's rule is to split rather than suppress, so this extracts cohesive pieces:

- filesystem-watcher.test.ts: the local/WSL lifecycle tests move to
  filesystem-watcher-local-lifecycle.test.ts.
- filesystem-watcher-local-subscription.ts: scheduleLocalWatcherTeardown moves to
  filesystem-watcher-local-teardown.ts.
- ssh-target-cleanup.ts: the per-tab session helpers move to ssh-target-tab-sessions.ts.
- ai-vault-session-refresh.ts: the session-ids key moves to ai-vault-session-ids.ts.
- accounts-search.ts: the Grok and Cursor search entries move to
  accounts-search-extra-providers.ts and are re-exported.

No behavior change; the affected suites pass.
…#41)

* feat(tasks): cache the Jira board and remember its view state

Returning to the Jira board always showed a loading screen, even right after
leaving it, and the board forgot every choice made on it. Three causes, all in
the board view's own state:

- The overview and the sprint/backlog pages were refetched from scratch on every
  mount, because the Tasks view unmounts when you switch to a workspace.
- The sprint/backlog tab, the Me/All filter, and the filter query lived only in
  component state, so nothing survived that unmount.
- The provider filter chrome is hidden while the board is shown, which left the
  board with no filter box at all.

Fixes:

- Keep the last board snapshot in a module cache keyed by site/board (30 minute
  TTL, three boards) and seed the hooks from it synchronously, so a cached board
  paints on the first render. The refetch then runs silently behind it, reading
  back the pages that were already loaded so a paged board cannot mix fresh page
  one with stale later pages. Refresh still shows the spinner, and switching
  sprint drops the cached page rather than pairing the new sprint with the old
  one's issues.
- Persist the board/list mode, sprint/backlog tab, Me/All filter, and filter text
  per device in local storage, mirroring the Linear issue-view preferences. They
  stay out of taskResumeState: that schema is strict behind a field-level catch,
  so a host that predates a key rejects the whole payload and drops the GitHub
  and Jira resume state with it.
- Give the board its own filter box, narrowing the loaded issues client-side by
  key, summary, assignee, status, type, or label.
- Open on the viewer's own work (Me) rather than the whole board filter. When the
  board's site has no resolvable viewer account, show everything instead of an
  empty board behind a disabled button.
- Drop the Team filter and its Team field / Team value settings; the board filter
  is now Me/All.

The Team field was the only consumer of the custom-field listing, so
jira:listCustomFields, jira.listCustomFields, the preload and runtime clients, and
the main-process query all go with it, and the generated RPC params catalog is
regenerated.

* feat(tasks): rank Jira priority by severity and wash blockers

A Jira issue's priority was plain text in the same muted grey as every other
field, so a blocker read exactly like a trivial task in a dense list and was
easy to skim past on a board.

- Give each priority a severity chip: a coloured bubble plus the tier name, so
  the hue is never the only signal.
- Blocker and Critical share the red chip; Major is orange. Everything below
  (medium, normal, low, minor, lowest, trivial, and any custom scheme) stays
  muted, because a renamed priority scheme must not be mislabelled.
- A blocker additionally washes its whole task surface — row or board card — and
  keeps that wash on hover and while selected, since a chip alone does not stand
  out enough on a board.

The tiers are matched by name against the standard Jira vocabularies
(blocker/highest, critical, major/high). Two new tokens, --severity-blocker and
--severity-major, carry the ramp in both themes; Major no longer borrows
--status-warning, which stays free for its own needs-attention role.
Settings -> AI Provider Accounts now has a master switch at the top of each
provider section. Turning a provider off stops Orca both from polling its
usage/quota and from reading its sign-in out of software installed on this
computer (cursor-agent Keychain, ~/.codex/auth.json, gh CLI, the Grok, Gemini
and OpenCode auth files, the macOS Claude Keychain), hides its status-bar
meter, and drops any cached snapshot. Providers stay on by default; the
switched-off set persists in GlobalSettings.disabledUsageProviders.

The rate-limit service takes an isUsageProviderDisabled probe read from
settings, so the gate covers the full cycle, the codex/claude/grok-only
cycles, the inactive-account probes, the on-disk credential-presence flags and
the codex reset-credit redemption. The pane's cursor/grok status endpoints and
the startup gh probe are gated the same way, and RuntimeAccountController
returns an empty Claude/Codex roster so a disabled provider's system-default
identity is never read.
xterm carries 0x07 as an EXECUTABLE inside APC/DCS/PM/SOS, so a BEL that
terminates one of those payloads never rings the bell. Tracking only OSC
announced Pi's `ESC _ pi:c BEL` prompt repaint as a phantom "Attention
requested" notification on every repaint.

Deliberate divergence: xterm keeps those four states open until ST, but Pi
terminates its APC with BEL and never sends ST, so xterm-strict semantics
would pin the string open and swallow every later real bell.
Every fix here is a surface upstream added or rewrote after this fork's base, so no
conflict marker showed it. `pnpm tc` and the touched suites are what found them:

- The replayed `local(identity)` patch tore `getWindowsPowerShellLifecycleCommand`
  apart: the merge kept upstream's `const innerCommand =` opener and the base's
  closing paren, so the function had no `return`. `innerCommand` was unused; upstream's
  `return (...)` shape is restored with only the rename kept.
- `use-worktree-activity-status`'s effect lost the comma after `hasRetainedFailed`
  (upstream inserted that field where the fork appends `setupRunning`).
- Upstream's new hook providers (qoder, codebuddy, dsh) are a compile error in the
  fork's `DESCENDANT_PROVIDERS` Record, which enumerates `AgentHookSource` by design,
  so descendant support is decided together with each new provider. They report no
  children, like their peers.
- The new dsh hook service hardcoded `.orca` for its remote script and managed-hooks
  path where claude/devin/droid/gemini/copilot/cursor use `HOME_DIRECTORY_NAME`, so an
  SSH install would have written into an official install's directory.
- `providers.zcode !== undefined && ...` in the status bar's pending chain is not the
  predicate the other providers get: zcode is optional, so an absent slice must not
  read as pending and hold the setup CTA forever.
- Upstream's new CLI-identity suites pin upstream's `orca`/`orca-dev`/`orca.exe`. The
  fork ships `orca-np`/`orca-np-dev`/`orca-np.exe`, and the login-shell harness stubs
  the app's launcher by that name — its bare-name arm models the fork's own command.
- The ZCode fetcher joined the shared rate-limit harness, so the DeepSeek and
  Fireworks suites need its mock the way the Cursor suite already had it.
- The Jenkins panel suite's `@/i18n/i18n` mock predates the localized catalog reading
  `i18n` off that module.
- The derived English runtime catalog is regenerated
  (`pnpm run sync:localization-runtime-catalog`), which is what the two `--ours`
  resolutions taken during the replay left behind.
- The mobile page's Phase C sweep is re-measured, not bumped: this head cuts the swept
  tree into 73 chunks against the 69 the table recorded, past the four the fence gives
  a refactor, so `MOBILE_WEB_APP_BUNDLE_SCRIPT_SWEEP` was re-derived by building
  `routes.slice(0, n)` for every n. Its worst marginal route now costs ten, which moves
  in the route count at which the derived asset ceiling passes the shell's 256 (32 to
  30). The page still builds inside both budgets.
129 published commits replayed from 976844fac3: 107 byte-identical, 22 adapted, none
dropped. Sixteen stopped on a conflict across 37 files — every one an additive union, a
re-seat onto upstream's refactor, or an upstream deletion of a file the fork had
patched. BRANCHES.md re-measures the base drift at 975 / 1069 commits behind.
The e2e workflow's two sides each named a report. Upstream's step sets
`PLAYWRIGHT_JSON_OUTPUT_FILE: ci-shards/results.json` for its new failure summary; this
fork's verdict step read `ci-shards/report.json`, which its own `PLAYWRIGHT_JSON_OUTPUT_NAME`
wrote. Measured on Playwright 1.63.0, `JSON_OUTPUT_FILE` wins when both are set and
`JSON_OUTPUT_NAME` is then ignored — so the union of the two sides left the fork's verdict
reading a file nothing writes, and the fork's scheduled lane would have failed its
known-failure ratchet on a missing report.

One report now serves both readers: the step keeps upstream's env var and upstream's own
`--reporter=list,json`, and the verdict reads `ci-shards/results.json`. That also drops the
`if repository != stablyai/orca` shell guard, which existed only to add the fork's reporter
argument — upstream's lane's command is now byte-identical to upstream's, and the report the
verdict reads is the one the failure summary reads.

The verdict contract suite pins the interaction instead of the strings: the env var's value,
the reporter argument's presence, the absence of a second output name, and both readers
naming the same file. The unit-shard step is unchanged: its report arguments stay inside the
guard, because upstream added no reporter of its own there.
Workflow run 36563775496 published `v1.4.214-np.15` from c8fa8fe, signed and
notarized, with both update manifests (latest-mac.yml, latest.yml) present.
…ng (#43)

* fix(agent-status): keep restored spinner titles from reading as working

Before: a cold restore handed checkpoint.json's lastTitle — written on
checkpoint ticks, so it can capture a mid-turn spinner frame (real values
on this machine: "⠴ π - orca", "⠧ π - Jenkins-Details",
"⠧ π - Remove-Team") — into the PTY record and title tracker via
seedTerminalRestoreTail -> applySeededAgentStatus. The (re)attach
snapshot in getTerminalSideEffectSnapshot replayed that title to every
surface, and because normalizeTerminalTitle canonicalizes braille frames
but never strips them, the restored title classified as working through
detectAgentStatusFromTitle. The sidebar worktree row, the worktree card,
and the tab bar all flipped to working together and only self-corrected
via the 30s cold-park delays and the 60s watchdog heal.

After: the title tracker records provenance — a seeded title is restored
state this process never observed on the wire, and the first live frame
(including synthetic hook frames) confirms it. The replay snapshot stamps
such facts restoredUnconfirmed (mirroring the agent-status hydration
stamp), and the renderer fact handler — the single choke point every
reader's titles flow through — strips working indicators from an
unconfirmed fact that asserts working, feeding readers a title that can
never claim working until a live frame re-asserts it. Both title fields
are cleared so the completion coordinator cannot arm on the restored raw
title and later mint a false task-complete. Permission claims ("π !")
keep signalling, the OMP static marker is rewritten through its protocol
table ("π :" -> "π >"), and observed titles — park/unpark and
worktree-switch replays — pass through untouched. The treatment is host
agnostic: local-daemon and SSH PTYs share the tracker and fact handler,
and remote clients run the same handler.

Route: clearing only the record-title fallback (where normalizedTitle is
null) would not have fixed the bug. The restore seed populates the
tracker, so the working title is served from the non-null tracker branch
(a runtime probe returned "⠋ π - Jenkins-Details" with a tracker entry
present). Since normalizedTitle being null does not identify a cold
restore, the fix takes the provenance route: explicit restoredUnconfirmed
state plus reader-side treatment, never a blanket clear on replay.

Accepted, as agreed: an agent genuinely mid-turn across a relaunch keeps
its indicator quiet until the next live frame re-asserts it — the same
treatment restored hook rows already receive (restoredUnconfirmed state
is never fresh). The same applies to the residual same-run reclaim whose
seed-only title asserts working (e.g. an OMP static working marker that
never repaints): quiet until its next title change. A stale working title
on a PTY that keeps producing output is still healed by main's 3s
stale-title timer exactly as before.

The checkpoint wire format is unchanged; the fact channel only gains an
optional field.

* fix(lint): split the six files the sync left over the max-lines cap

Before: `pnpm exec oxlint` fails with six max-lines errors, so static
analysis is red on nplez1/main and on every branch cut from it. The
upstream sync re-seated this fork's rate-limits, status-bar, sidebar and
identity patches onto upstream files that had grown in the meantime, and
the combined results crossed the 300/400-line caps.

After: the linter is clean again. Per repo rules there is no max-lines
disable and no cap bump anywhere — each over-cap file yields one cohesive
unit to a domain-named sibling module, and the behavior is unchanged:

- service-full-cycle-preparation.ts: the copilot stored-token-vs-gh
  credential selection moves to service-copilot-cycle-credentials.ts;
  the three repeated settled-result promise adapters move (as
  trackSettledProviderResult) onto service-sibling-provider-result.ts
- service-full-cycle-application.ts: the nine settled-result to
  error-snapshot fallback blocks collapse into providerResultSnapshot in
  service-provider-result-snapshot.ts, shapes preserved exactly,
  including opencode-go's explicit monthly slot
- tooltip.tsx: the rate-limit window unit (window derivation, usage
  urgency colors, per-window bar) moves to
  provider-rate-limit-window-sections.tsx
- use-status-bar-controller.ts: the usage visibility projection moves to
  status-bar-usage-projection.ts
- tui-agent-config.ts: the per-agent authoring table moves to
  tui-agent-config-table.ts
- worktree-activation.ts: the sidebar reveal group moves to
  worktree-activation-sidebar-reveal.ts

Pure refactor: no behavior changes, no test edits.
The floating workspace titlebar doubles as its move target, but once enough
tabs fill the strip every press lands on a sortable tab — a no-drag target —
so there is nowhere left to grab and the panel cannot be moved.

Reserve a fixed-width handle at the far left of the titlebar, outside the tab
strip, so it stays pressable no matter how many tabs are open. It relies on
pointer events bubbling to the existing titlebar drag handlers, stays a plain
div because `button` is a no-drag selector, and draws the same right border as
the inter-tab dividers so the strip reads as one set of separators.

Covered by unit tests for the handle's drag-target classification and a hidden
renderer E2E that opens the panel with more tabs than fit, drags the handle,
and asserts the panel moves.
Upstream's c85f487 moved the agent skills to internal delivery: it deleted
.agents/skills/**, gitignored the directory, and now symlinks the private
copies in via a $ORCA_INTERNAL_DEV_SETUP setup hook. That variable is set only
by stablyai tooling and no-ops for this fork, so no worktree here ever gets
those skills.

Re-track the three that are useful outside the org, restored from c85f487^:
electron (CDP/playwright-cli, plus this repo's ORCA_BACKGROUND_LAUNCH=1 launch
rule), react-useeffect, and typescript. The auto-pr-merge / auto-review-fix /
auto-submit trio is intentionally left out; it encodes stablyai's merge policy.

Force-added with `git add -f` so .agents/skills/ stays ignored and the internal
hook can still populate machine-local symlinks there.

See LOCAL-PATCHES.md, "local(skills)".
* feat(dashboard): make the Agent Dashboard a first-class view

The Agent Dashboard was a sidebar companion sheet (or a separate pop-out
window) with its own toggle state, so it behaved nothing like the other
sidebar entries: no back/forward history entry, and a selected state read
from a boolean instead of the active view.

Make it a top-level view like Tasks and Automations: switching to it sets
activeView, records a back/forward history entry, and highlights the
sidebar button via aria-current="page". Drop the pop-out window and the
in-window drawer entirely.

- add 'dashboard' to TopLevelView, UiViewHistory, the RPC view enum, and
  the persisted-view whitelist (startup hydration falls back to terminal
  when the experiment is off)
- add previousViewBeforeDashboard plus open/closeAgentDashboardPage,
  mirroring the automations page (recordViewVisit + rewind on close)
- register 'dashboard' in the shared back/forward history entries and
  replay
- render a new AgentDashboardPage in the workspace content shell, reusing
  AgentKanbanBoard
- sidebar entry selects on activeView === 'dashboard'; the shortcut
  toggles the view (still inert in Settings)
- remove the dashboard pop-out BrowserWindow, its IPC, the preload
  dashboard API, the pop-out renderer entry, the snapshot bridge, and the
  drawer/host, updating main, preload, and renderer wiring

* fix(lint): split the six files the sync left over the max-lines cap

Before: `pnpm exec oxlint` fails with six max-lines errors, so static
analysis is red on nplez1/main and on every branch cut from it. The
upstream sync re-seated this fork's rate-limits, status-bar, sidebar and
identity patches onto upstream files that had grown in the meantime, and
the combined results crossed the 300/400-line caps.

After: the linter is clean again. Per repo rules there is no max-lines
disable and no cap bump anywhere — each over-cap file yields one cohesive
unit to a domain-named sibling module, and the behavior is unchanged:

- service-full-cycle-preparation.ts: the copilot stored-token-vs-gh
  credential selection moves to service-copilot-cycle-credentials.ts;
  the three repeated settled-result promise adapters move (as
  trackSettledProviderResult) onto service-sibling-provider-result.ts
- service-full-cycle-application.ts: the nine settled-result to
  error-snapshot fallback blocks collapse into providerResultSnapshot in
  service-provider-result-snapshot.ts, shapes preserved exactly,
  including opencode-go's explicit monthly slot
- tooltip.tsx: the rate-limit window unit (window derivation, usage
  urgency colors, per-window bar) moves to
  provider-rate-limit-window-sections.tsx
- use-status-bar-controller.ts: the usage visibility projection moves to
  status-bar-usage-projection.ts
- tui-agent-config.ts: the per-agent authoring table moves to
  tui-agent-config-table.ts
- worktree-activation.ts: the sidebar reveal group moves to
  worktree-activation-sidebar-reveal.ts

Pure refactor: no behavior changes, no test edits.

* fix(test): drop the removed pop-out entry from renderer entry lists

Removing src/renderer/src/popout.tsx left three stale references to it:

- the renderer node-builtin boundary test walked it as an entry and threw
  ENOENT instead of building the graph
- the react commit-cascade install-order test asserted the shim is its
  first import
- config/knip.json listed it as an entrypoint

Walk only the remaining renderer entries.
…bar (#45)

* feat(issue-pane): show the workspace's linked issue in the right sidebar

Add an Issue tab to the right-side panel that appears only while the active
workspace is linked to a GitHub, Linear, or Jira issue. The pane renders the
issue as compact vertical property rows — status, assignee, priority, labels —
with an inline-editable title, a description, and an "Open in <provider>"
button, so it fits the ~280px sidebar instead of reusing the Task view's wide
controls.

The assignee field is a typeahead: an empty query lists a short roster with the
current user pinned first, and typing searches the wider directory for Jira or
filters the roster for Linear and GitHub.

Also extracts shared pieces so the Task view and the pane stay in sync:
gh-edit-section-pills, jira-issue-title-labels-editor, and two Jira hydration
hooks (useJiraIssueWorkspaceDetail, useJiraIssueComments, useJiraIssueSideData).

Known limitation: adding 'issue' to STATIC_RIGHT_SIDEBAR_TABS means a newer
paired client sending that tab to an older host has the small ui.set patch
rejected, so the tab preference does not persist across that skew. Follows the
existing static-tab precedent; a host-capability gate would close it fully.

* feat(issue-pane): show and post comments from the Issue pane

Add a compact comment thread and a composer to each provider pane, so an
issue's conversation can be read and replied to without leaving the sidebar.

- Shared IssueCommentThread: avatar, author, relative time, markdown body.
- Jira reuses the Task view's JiraIssueCommentComposer, Linear reuses
  LinearIssueCommentFooter, GitHub reuses GHCommentComposer.
- Each posts through the existing mutation path and appends optimistically, so
  a reply appears immediately and the Task view stays in sync.

* fix(issue-pane): stop mutating the search ref during render

React Doctor flags a render-time ref write in IssueAssigneeCombobox
("Ref mutated during render"): React may replay or discard render work, so
the write can leak from UI that never commits. Move the latest-callback ref
sync into an effect. Behavior is unchanged — useRef already seeds the value.
`reviewDecision` reports that a review is required; it does not say whether the
requirement gates the person looking at the pull request. A ruleset bypass actor, or
an administrator waiving classic branch protection, therefore saw "Approval required"
with the merge refused, while GitHub's own merge box let them merge.

Orca now asks GitHub the viewer-scoped question (`PullRequest.viewerCanMergeAsAdmin`),
only for a pull request whose review gate is unmet, and offers the merge when GitHub
says yes. Nothing bypasses silently: the surface must confirm, and the host only adds
`gh pr merge --admin` when the flag was asked for *and* GitHub confirms the privilege.
The bypass is withheld whenever a merge queue is required or its status is unknown,
because `--admin` also skips the queue, and while GitHub reports the merge box blocked
- which can mean required checks are failing, something the confirmation never covers.

The rule lives in one shared module so the host and both presentations cannot drift.
Every fix here is a surface upstream added or rewrote after this fork's base,
so no conflict marker showed it — `pnpm tc` is what found them:

- `deleteBranchOfRemovedWorktree` survived the resolution as an inline body in
  `removeWorktree`, so `finishUnregisteredWorktreeRemoval` lost its definition.
  Restored as the helper upstream keeps, with the fork's remote-branch cleanup
  inside the per-repo serialized section.
- The fork's `deleteRemoteBranch` never reached upstream's extracted
  `finishLocalWorktreeRemoval` or the runtime finish args, so the option was
  dropped between the IPC entry point and `git worktree remove`. Threaded
  through both, and recorded as `false` on the interrupted-removal path, whose
  persisted record carries no remote-branch intent — deleting a branch the user
  never asked for is worse than leaving it.
- `remove-registered-local-worktree` sat over its max-lines budget once the
  option was threaded, so the orphaned-worktree recovery moved to
  `orphaned-local-worktree-cleanup.ts` rather than growing a suppression.
- `AccountsPane` kept the `SecretAtRestProtection` import its states moved out
  of when the fork extracted the credential sections.
- The derived English runtime catalog is regenerated
  (`pnpm run sync:localization-runtime-catalog`), which is what the two `--ours`
  resolutions taken during the replay left behind.
Upstream added a real Antigravity fetcher to the shared rate-limit harness in
this sync's range, so `resetRateLimitProviderMocks` now resets it. The fork's
DeepSeek and Fireworks suites mock every other provider the harness resets but
not this one, so all 15 of their cases died on
`vi.mocked(fetchAntigravityRateLimits).mockResolvedValue is not a function`.
Same shape the ZCode fetcher arrived in last sync — the shared harness grows a
provider and each suite's mock list has to follow.

`claude/hook-service.test.ts` also still expected `~/.orca/agent-hooks` in one
case upstream added while the fork ships `~/.orca-np`; every other expectation
in that file had already been re-pointed.
Found by the full suite, all of them shapes where upstream's new test or new
field met a fork surface no conflict marker connected:

- **`tui-agent-config-table.ts` lost the fork's Claude trust presets.** `#34`
  extracted the table into a new module, so git saw a pure file add — but
  upstream's `21d4ae9448` added `preflightTrust` to `claude` and
  `claude-agent-teams` in the same range, and the extracted copy was taken from
  the fork's pre-sync table. The trust preflight was silently off for both
  launch modes; `agent-workspace-trust-spawn`'s preset census and four
  spawn/relay suites caught it.
- **Two upstream tests hardcoded `~/.orca`** (`windows-hook-files` — a new file
  this sync — and `hook-process-capture`), while the fork's managed hooks write
  `~/.orca-np`. Both now derive from `HOME_DIRECTORY_NAME`, so the next rename
  cannot leave them behind.
- **`FileExplorerFilesTreePane` read `nameFilterFiles` eagerly** where upstream
  short-circuits behind `hasNameFilter`, so the pane threw in upstream's
  partial fixture; and `hasError` used `!!treeError`, which reads an empty-string
  error as "no error" — upstream's own case says an empty message is a message.
- **One upstream test mock** (`structured-agent-session-accepted-send-capability`)
  predates the fork's path-search capability module, which subscribes on import.
- **`UNHIDDEN_SPAWNER_PIN` 60 → 59**, taken from the failing test's own message.
  Two upstream files moved off direct spawning in this range.
…nd Tasks close buttons

The Agent Dashboard sat under an empty 36px titlebar and then spent two more rows on
chrome of its own: a header carrying only a title, a count and a close button, and a
toolbar carrying search and filters. The board's own columns therefore started 126px
down the page. Tasks had a second, redundant exit as well — a left-anchored X sitting
beside its source filters.

Before: a blank band at the top of the dashboard, an X in the dashboard header, and an X
on the left of the Tasks band. After: the dashboard's toolbar is its first row — search,
filter and board settings — with the columns starting 81px higher, and both pages are
left the way every other top-level view is.

- the shell only suppressed its stacked titlebar for automations/artifacts, so the
  dashboard got a stripe holding nothing but the window-controls spacer; move that
  "page owns its top edge" decision into lib/page-owned-titlebar.ts and gate the strip on
  a derived stackedTitlebarVisible flag instead of an inline view comparison
- delete AgentKanbanBoard's header row (title, total, close) and forward its trailing
  controls into AgentDashboardToolbar, which carries the same window-controls gutter the
  automations and artifacts headers reserve on their first row
- delete the Tasks close button and its divider; Esc and the sidebar still close Tasks
- add 'dashboard' to shouldShowWorktreeHistoryControls: the view already recorded a
  history entry, but hid the shared back/forward pair, so removing its X would have left
  it with no exit control of its own
`pnpm exec oxlint` fails on the fork's line with five max-lines errors — 301, 304, 305
(cap 300), 420 (cap 400) and 834 (cap 800) — so CI's Lint step fails and the verify job
fails with it, which in turn skips the test and package jobs for every PR. None of the
five is touched by the dashboard change on this branch; each is byte-identical to
nplez1/main, and four of them were left over the cap by earlier fork PRs.

Split each along a real seam. No max-lines disable and no cap change, per the repo rule,
and nothing here changes behavior:

- shared/agent-session-capability.ts: the agent-session capability IDs move out of
  protocol-version.ts and are re-exported from it, the pattern ./skill-install-capability
  already follows (304 -> 284)
- ipc/worktrees/listing/list-repo-worktrees.ts: the scan pipeline that worktrees:listAll
  and worktrees:list carried verbatim in both handlers — around 55 duplicated lines — now
  lives in one helper that both call (305 -> 151)
- native-chat/agent-session-wire/structured-agent-session-adapter-errors.ts: the adapter's
  refusal vocabulary and pre-spawn guard, re-exported so the claude, codex and runtime
  callers keep importing them from the adapter (301 -> 245)
- right-sidebar/FileExplorerQuerySection.tsx: the explorer's query band — view switch,
  both cross-faded query rows, the truncation notice, the Contents filters (420 -> 365)
- ipc/worktrees-local-setup-launch.test.ts: the four setup/startup-terminal tests move out
  of worktrees-local-create-flow.test.ts, which keeps its own copy of the shared mock
  prologue as every other suite in that folder does (834 -> 585; 20 tests before and after)

Also regenerates en-runtime-required.json. The close-button strings this branch stopped
referencing are still in en.json, and an English entry with no call site has to ship —
a dynamic key resolves from the catalog, so the tool cannot tell it apart from a live one.
`pnpm run sync:localization-runtime-catalog` adds those five entries.

Verified: `pnpm lint` exits 0 (it previously stopped at oxlint), `pnpm tc` exits 0, and the
suites around every split pass — src/shared 10,466 tests, src/main/ipc + src/main/claude +
src/main/codex 8,934, right-sidebar 2,248. The native-chat folder keeps the 17 failures it
has on nplez1/main (reproduced with this branch's files reverted).
…rectory

The Windows packaging job runs src/main/codex/hook-service-managed-install.test.ts, whose
three win32-only assertions build the expected managed command under a literal '.orca'
while the build installs into HOME_DIRECTORY_NAME ('.orca-np', the fork's rename). The
job's own failure prints both strings: 'The Expected' at .orca/agent-hooks/codex-hook.cmd
against 'Received' at .orca-np/agent-hooks/codex-hook.cmd, so the two failing tests are
comparing the same path two ways.

Derive every expectation here from HOME_DIRECTORY_NAME instead, as the production path
(agent-hooks/installer-utils.ts) already does, so the name cannot drift again. Two of
these four lines are on the win32 branch a macOS run skips, which is why this sat unseen:
the Windows job only started running once static analysis stopped failing ahead of it.
Tasks sat under the same empty 36px titlebar stripe the dashboard did: the shell draws
one above every stacked-sidebar view, and Tasks renders nothing in it, so its source-icon
row started 42px down the window with a blank band above it.

Before: a blank band above the Tasks source row, whose center sat 58px down the page.
After: that row is the page's top edge — its icon cluster's center lands 22px below the
window top, the same first-row height the dashboard now uses — and everything below moves
up with it, so the list card starts 36px higher.

- add 'tasks' to lib/page-owned-titlebar.ts, the predicate the shell already consults for
  automations, artifacts and the dashboard
- the source row is now the top edge, so it needs the window-controls gutter the
  automations/artifacts headers and the dashboard toolbar carry. It sits inside the
  frame's px-5, so .tasks-source-row-window-controls-inset pads by only the remainder past
  that inset and stays a no-op on macOS, where the var is 0px
- Frame.tsx keeps pt-1.5; its comment claimed the cluster aligned with the sidebar's first
  row, which is no longer the intent now that Tasks owns the top edge

With the sidebar collapsed nothing changes: the shell still draws its full-width titlebar
(36px, drag region and window controls included) and the page starts below it.
@nplez1 nplez1 changed the title feat(dashboard): reclaim the dashboard top edge, drop the dashboard and Tasks close buttons feat(views): reclaim the dashboard and Tasks top edge, drop their close buttons Oct 2, 2026
… launch (#50)

The floating workspace launched its default agent from a boxed 24px outline button in
its window controls (border, secondary fill, shadow), next to maximize and minimize,
while its tab strip kept a plain "+". The main window's tab bar has one control for the
same job: a 28px split button whose primary launches the default agent and whose chevron
(or a hold) opens the create menu. Side by side, the same action looked like two different
controls.

Before: [tab][tab] + … boxed [agent][⤢][−]. After: [tab][tab] [agent][⌄] … [⤢][−], the
same split control the main tab bar renders, with the window controls back to being window
buttons.

- TabBarNewTabButton now names where its agent comes from instead of a boolean that meant
  "hide it": `detected` (the configured default, only where the host detects it), `configured`
  (the settings default alone), and `none` (plain "+"). The floating panel asks for
  `configured`, because its synthetic worktree has no detected agents and its launch path
  resolves a genuinely missing agent itself.
- FloatingTerminalWindowControls loses its agent button and the launch path behind it; the
  split control's launch is the shared `launchAgentFromNewTabEntry`, the same one the create
  menu's agent entries already used from the floating tab strip. That path reports
  `launchSource: 'tab_bar_quick_launch'` rather than the deleted button's `'shortcut'`, which
  is what it is now — a tab-bar quick launch.
- The launch-caller fixture's floating profile follows the call site: same synthetic worktree
  id (no repo behind it), now through the tab bar's launch with the group it resolves to.
- The three guarantees the deleted button's tests pinned (launch through the shared funnel
  rather than the tab store, focus the launched terminal on menu close, report a launch the
  planner refused) move to use-tab-bar-create-menu-controller.test.ts, the code that now owns
  them. The fourth, "the floating workspace stays off the structured route", is a shared
  resolver invariant that structured-native-chat-launch-route.test.ts already pins.
- Also regenerates en-runtime-required.json: the two strings the deleted button referenced are
  still in en.json, and an English entry with no call site has to ship — a dynamic key resolves
  from the catalog, so the tool cannot tell it apart from a live one.

Verified: `pnpm tc` clean, full `pnpm lint` clean (including the localization verifiers),
changed-code gate clean, and the tab-bar, floating-terminal and launch-caller suites pass
(89 files, 1001 tests). Rendered in the dev app with an agent configured: the strip shows the
split control and the window controls show only maximize and minimize.
@nplez1 nplez1 closed this Oct 9, 2026
nplez1 pushed a commit that referenced this pull request Oct 19, 2026
…iew off the deprecated owner resolver (stablyai#30562)

* refactor(renderer): fold browser creation, file actions and doc-preview callers off the deprecated owner resolver

The creation-action policy and browser creation (#3, #48) fold together, so the provider the
policy offers is the one creation uses. Browser panes, page lifecycle and conversion, tab
duplication, the explorer tree pane, doc-preview actions and editor path moves (#21, #43, #45-#47,
#49, #50) read runtimeEnvironmentIdWithLegacyFallbackUnlessTwin. File-explorer writes on a folder
(#51) take the owner from the rows alone, never from the focused server. Deprecated importers
16 -> 6.

* refactor(renderer): fold file preview off the deprecated owner resolver; Open in Orca Browser refuses a twin

A project two hosts publish reads as no server for the doc-preview plan, but the file:// path now
refuses it with its own message instead of opening this machine's copy (#7). Deprecated importers
6 -> 5.

* fix(renderer): a twin's file preview and open-externally refuse instead of opening this machine's copy

Codex P2 on the F7 folds: a folder twin read as no server, so the preview plan built a local
file:// tab and open-externally handed the path to this machine's OS. Both now check the owner
for ambiguity first. Also fix two test fixtures that broke the renderer typecheck.

* refactor(renderer): move the file:// open target out of file-preview.ts

file-preview.ts crossed its line limit after main grew it; the file:// target, its type and its two
refusal messages move to workspace-file-browser-open-target.ts, which they never depended on
file-preview for.

* test: mock the replacement resolver in the quick-action availability suite (Codex P2)
@nplez1
nplez1 deleted the nplez1/Agent-Dash-Improvements branch October 27, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant