* fix(gitlab): guard against non-array API responses in MR/issue listing
fetchIssuesAsWorkItems and listMergeRequests parsed glab's JSON output
and called .map straight on it. When the GitLab API returns a JSON
object instead of an array (error body, unexpected shape) on a
successful exit, this crashed with a bare TypeError that got
misclassified as "Failed to load issues: JSON.parse(...).map is not
a function" instead of a useful message.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(gitlab): cover listIssues and keep payloads out of error classification
The guard missed listIssues in issues.ts — the RPC-backed issue list that
produces the reported "Failed to load issues: JSON.parse(...).map is not a
function". Hoist the guard into glab-api-response.ts so both files share it.
The thrown message is fed to classifyGlabError, which substring-matches it.
A response payload is content, not a diagnostic: an MR titled "fix network
timeout" classified as network_error and the canned copy replaced the payload
the user needed. Report a GitLab error envelope by its own message, and mark
an opaque body so classification is skipped.
* test(gitlab): make the list-guard tests fail on the regressions they name
Two assertions were vacuous under mutation. The envelope test used a "403
Forbidden" message whose keyword matches earlier in the classifier chain than
its sibling payload, so leaking the payload into classification still passed;
it now uses a 404 envelope beside a "403 forbidden" sibling. No call-site test
carried a classifier keyword, so deleting the marker-error branch entirely
failed only one unit test; the MR API path now uses a keyword-bearing body.
Also give the non-list branch the same "Failed to load issues" prefix as every
other list error, cover the `{ error }` envelope field, and pin the thrown type.
* test(gitlab): pin the reported-payload bound
Removing the 300-char slice survived the whole suite, and the banner's
break-words now depends on it. Name the limit and assert both branches
truncate, plus the envelope falling through a blank message to `error`.
* test(gitlab): pin message-over-error envelope precedence
Swapping the lookup order passed the whole suite. Anchor the bound regex too
so it cannot match an incidental ": " near the end of a message.
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(ai-vault): preserve agent metadata header on session row expansion
* chore(tests): remove redundant comment in AiVaultSessionRow test
* test(ai-vault): add happy-dom environment and window.api shim to AiVaultSessionRow tests
* test(ai-vault): assert the expanded session row via the rendered row
Replaces the parallel static-markup harness and second fixture with the
file's existing Testing Library row render, so both suites share one
session fixture and one prop list. Raw HTML substring matches are gone:
the identity assertions now run inside the metadata grid, because the
details-toggle button's aria-label repeats the agent name and made the
old check pass with the fix reverted.
Tags the grid with a data-testid like the row's other query anchors
rather than walking up from a text node, adds cleanup() — the suite has
no globals: true, so rows leaked across tests — and guards that the
worktree badge renders once when expanded. SessionWorktreeLine loses an
export the row no longer imports.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(repo-icon): keep a renamed fork's own owner avatar
Fork repos always took the upstream owner's avatar, so a renamed fork
showed its parent project's logo. Same-name forks (personal copies)
still prefer the upstream owner; renamed forks now keep their origin
owner across auto-detect, the startup backfill, and the settings
avatar refresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(repo-icon): re-read repo state before backfill avatar write
The startup backfill computed icon updates from a pre-loop snapshot, so
an icon chosen in settings while the upstream/origin probes were pending
could be clobbered. Re-read the repo after the probes and only migrate
an icon that is still the auto-detected GitHub avatar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(repo-icon): own the fork avatar rule in one shared selector
The renamed-fork rule was written out twice — once in the main-process
auto-detect and once in the renderer refresh — so the two copies could
drift. Move it next to `githubAvatarIcon` as `githubAvatarSlug`, which
collapses the renderer resolver to a single unbranched path.
Also stop swallowing a rejected origin probe: it cannot tell a renamed
fork from a same-name one, so degrading to the upstream owner would flip
a renamed fork's stored avatar back to the parent's. Letting it propagate
keeps the stored icon, matching how the non-fork path already behaved.
Adds coverage for the startup backfill, the third decision point the fix
claims, which had none.
* test(repo-icon): cover pending backfill icon change
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(file-explorer): open symlink files when stat fails
* fix(file-explorer): grant symlink targets path access on activation
Following a symlink out of the workspace was denied by the main-process
path allow-list, so both the stat and the file read failed. Activating the
row is explicit intent, so authorize the target the way terminal links and
Quick Open already do.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix: wrap unbroken auto-rename failure output
* test(sidebar): cover unbroken auto-rename failure containment
happy-dom does no intrinsic sizing, so assert the two declarations that
keep an unbroken token from widening DialogContent's grid column. The
test fails when either min-w-0 or overflow-wrap:anywhere is reverted.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* refactor(runtime): extract pure path-candidate, review-branch, and folder-workspace helpers from orca-runtime.ts
Mechanical move of three closed, pure module-scope clusters out of
orca-runtime.ts (37,608 -> 37,207 lines) into domain-named siblings:
- terminal-output-path-candidates.ts: PTY output path harvesting and the
recent-candidate history bound (3 entry points + 15 private callees).
- selected-review-branch.ts: forge-agnostic selected-review predicates and
lookup hints (GitHub/GitLab/Bitbucket/Azure DevOps/Gitea).
- runtime-folder-workspace.ts: folder-workspace id math and the repo+meta
-> Worktree projection.
Bodies are token-identical to their previous form; the only production
changes are the moves, the new import statements, and `export` keywords.
The no-control-regex suppression travels with the path-candidate scanning
that needs it. No max-lines suppression was added and the ratchet is
unchanged.
Adds characterization tests for the two clusters that had no direct
coverage; the path-candidate cluster keeps its existing tests, repointed
at the new module.
* fix flaky timer on CI
* Add daily macOS dev build release channel
Publish once-daily signed macOS builds from main at a dedicated cadence,
separate from hourly (too noisy) and release branches (too infrequent).
Builds are notarized and installable via the updater, but unvetted —
published to stablyai/orca-daily rather than the main repo to avoid
evicting stable/RC entries from the releases feed.
* fix lint
* fix commit
* Add third token mint to daily macOS build workflow
The upload step's 2x45m retry budget can outlive the one-hour token, so a third
is minted after it for verify and cleanup operations. Release notes are moved to
a file to ensure consistency between draft creation and publish. Daily channel
description updated with specific UTC release time.
* Strip liveness gate from AI Vault session delete
Delete now requires only path validation + user confirmation — no process
roster, no liveness check, no quiescence, no ownership ledger.
Co-authored-by: Orca <help@stably.ai>
* Remove obsolete AI Vault liveness delete reliability gate
Session delete no longer checks process liveness, so drop the
manifest entry that still referenced the deleted test files.
* minor fix
---------
Co-authored-by: Orca <help@stably.ai>
* fix(ui): align toggle switch handle symmetrically in on state
The switch handle (translate-x-4 / 16px) left a 6px gap on the right
in the on state while the off state had only a 2px gap on the left.
Accounting for border-box sizing (1px border each side reduces content
width to 34px), the correct on-position is translate-x-4.5 (18px) so
both sides have a 2px inset.
* docs: add JSDoc to exported functions in changed files for docstring coverage
* fix(ui): align toggle switch handle symmetrically in on state
The `h-5 w-9` switch track is `border-box` with a 1px border, leaving 34px
of content for the 14px `size-3.5` handle. `translate-x-0.5` insets the off
state by 2px, so the on state needs 34 - 14 - 2 = 18px. It used
`translate-x-4` (16px), leaving 4px on the right against 2px on the left.
Adds a boundary test so the offset cannot drift again across the 26
hand-rolled switch call sites, and drops the docstrings the original patch
added to satisfy a coverage bot (the surrounding files carry none).
Original patch by @sei0.
Co-authored-by: Orca <help@stably.ai>
* test(ui): widen the switch-handle alignment guard to every h-5 w-9 track
Scan the renderer with the readdirSync walk and TypeScript AST already used by
no-top-level-translate.test.ts instead of shelling out to `git grep`, and find
each knob inside its own track rather than by scanning source text forward.
Why: gating on `role="switch"` silently skipped ClaudeUsageLoadingState.tsx (a
loading skeleton, so a div with no role) and HiddenExperimentalGroup.tsx, so
regressing the loading-state handle back to translate-x-4 left the guard green.
The guard now also fails when the premise behind 34 - 14 - 2 = 18px stops
holding: a track that loses its 1px border or gains padding, a conditional whose
two branches no longer pair off/on, and a track whose knob moved into a child
component where the scan would otherwise just stop seeing it.
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>