Commit Graph
6651 Commits
Author SHA1 Message Date
Brennan Benson 20c2deda29 fix(native-chat): queue stale idle TUI handoffs 2026-08-12 18:42:39 -07:00
Brennan Benson 4446a4681a fix(native-chat): wait for structured TUI process proof 2026-08-12 18:41:13 -07:00
Brennan Benson 6f30927a6c fix(i18n): catalog structured session recovery control 2026-08-12 18:40:21 -07:00
Brennan Benson c23139423a fix(native-chat): gate restart recovery publications 2026-08-12 18:39:52 -07:00
Brennan Benson a03beee134 fix(native-chat): revalidate restored and retried owners 2026-08-12 18:39:16 -07:00
Brennan Benson 35fb039187 fix(native-chat): validate pinned launch environment 2026-08-12 18:38:23 -07:00
Brennan Benson e2b3600e3b fix(native-chat): close handoff admission races 2026-08-12 18:37:56 -07:00
Brennan Benson 62b26c58b9 fix(native-chat): serialize structured handoff recovery 2026-08-12 18:37:10 -07:00
Brennan Benson 2d3b082ab2 fix(native-chat): harden shared handoff recovery 2026-08-12 18:36:05 -07:00
Brennan Benson 53a3a9442b fix(native-chat): preserve handoff launch authority 2026-08-12 18:32:07 -07:00
Brennan Benson 8b2601b2f2 fix mobile structured first-send hydration race 2026-08-12 18:26:18 -07:00
Brennan Benson d53823acc7 test(native-chat): give the host-oracle matrix test an explicit timeout 2026-08-12 17:40:41 -07:00
Brennan Benson 5fc6f7a1cc test(native-chat): derive refusal retry state from host 2026-08-12 17:35:57 -07:00
Brennan Benson 2355a20b29 fix(native-chat): rotate refused send operations 2026-08-12 17:21:10 -07:00
Brennan Benson d82d807180 fix(native-chat): rotate settled handoff operations 2026-08-12 17:04:49 -07:00
Brennan Benson 7f4a1d13f7 fix(native-chat): replay pending option mutations 2026-08-12 16:54:16 -07:00
Brennan Benson 973dd58aba fix(native-chat): preserve options across structured handoffs 2026-08-12 16:40:09 -07:00
Brennan Benson 2c8b009101 fix(native-chat): hydrate resumed structured options 2026-08-12 14:16:00 -07:00
Brennan Benson 5971fcb437 fix(native-chat): persist structured session options 2026-08-12 13:58:48 -07:00
Brennan Benson 1cf45caa24 fix(native-chat): route structured Codex options directly 2026-08-12 12:43:24 -07:00
Brennan Benson 7b3144c037 Merge remote-tracking branch 'origin/brennanb2025/native-chat-toggle' into brennanb2025/codex-structured-mobile-write 2026-08-11 22:57:35 -07:00
Brennan Benson 6456b4cb5a fix(native-chat): recover TUI transcripts read-only 2026-08-11 22:25:32 -07:00
Brennan Benson c8a67dcf3b fix(native-chat): retain cold TUI proof authority 2026-08-11 22:17:56 -07:00
Brennan Benson eac2520f42 fix(native-chat): recover claimed TUI owners 2026-08-11 22:10:24 -07:00
Brennan Benson ff35abf7a8 fix(native-chat): recover TUI transcript gaps 2026-08-11 21:56:01 -07:00
Brennan Benson ed78cfd598 fix(native-chat): keep TUI transcripts readable 2026-08-11 21:37:39 -07:00
Brennan Benson 7316ac3926 fix(native-chat): complete mobile reverse after TUI exit 2026-08-11 20:35:12 -07:00
Brennan Benson c18e81c8d2 fix(native-chat): clear sticky handoff recovery 2026-08-11 19:46:33 -07:00
Brennan Benson c0f6e427d3 fix(native-chat): make reverse handoff transactional 2026-08-11 15:01:52 -07:00
Brennan Benson 438d256ef5 Merge remote-tracking branch 'origin/main' into brennanb2025/codex-structured-mobile-write
# Conflicts:
#	mobile/app/h/[hostId]/session/[worktreeId].tsx
#	mobile/src/session/MobileMarkdownReader.tsx
#	src/renderer/src/components/native-chat/NativeChatView.tsx
#	src/renderer/src/components/native-chat/native-chat-view-types.ts
#	src/renderer/src/components/tab-bar/TabBar.tsx
#	src/renderer/src/runtime/web-session-tabs-sync.ts
2026-08-11 13:16:52 -07:00
Jinwoo Hong 92f928cf89 feat(terminal): add copy action to link popover (#13857) 2026-08-11 12:58:07 -07:00
Jinjing 97ebfd9b81 fix(cmd-j): improve keyboard selection visibility in light mode (#13855)
Use color-mix to blend selection highlight instead of flat accent, making the keyboard cursor visible on light popover surfaces in both light and dark modes. Fix race condition in selection handling where deferred query lag could break arrow navigation by always accepting cmdk's internal cursor updates.
2026-08-11 12:31:30 -07:00
Jinwoo Hong 6acd69c516 fix(terminal): prevent link action clicks reaching child (#13797) 2026-08-11 12:27:54 -07:00
OrcaWin e790266546 fix(windows): show first window before shell PATH hydration (#13799) 2026-08-11 12:14:36 -07:00
Jinwoo Hong 536d17dca3 fix(artifacts): recover committed cloud mutations (#13796) 2026-08-11 12:13:16 -07:00
OrcaWin c96ded8dfd fix(startup): restore Windows PATH before shell changes (#13792) 2026-08-11 11:50:21 -07:00
Jinjing ef56ca8b81 fix(feedback): pre-include Orca version and OS in errors and feedback (#13851)
* fix(feedback): pre-include Orca version and OS in errors and feedback

Make terminal errors and Send Feedback carry easy-to-copy client
environment details so bug reports include build and platform context
without a follow-up ask.

* fix(feedback): satisfy exhaustive-deps in environment prefill hook

Destructure hook params so useEffect/useLayoutEffect dependency lists
are complete and the changed-code quality gate passes.

* fix(preload): stop importing node:os in sandboxed preload

Sandboxed Electron preloads cannot require node:os. That import crashed
the whole preload script, leaving window.api undefined and taking down
App chrome (dock badge, preflight). Keep platform.get on process APIs
only, and guard best-effort badge/preflight callers when api is missing.

* fix(feedback): count text typed below the env footer

Strip only the prefilled Orca/OS/Shell block for Send validation so
users who click past the footer and type can still submit.
2026-08-11 11:49:44 -07:00
Brennan Benson 34635aed0f fix: align structured TUI return readiness 2026-08-11 06:45:21 -07:00
Brennan Benson 9d8857c20a fix: complete structured chat round trips 2026-08-11 06:12:59 -07:00
Brennan Benson 6bc01085d0 fix: bind structured TUI before rollout proof 2026-08-11 05:20:06 -07:00
Brennan Benson 1ab28b5fc0 fix: harden structured session handoffs 2026-08-11 04:24:54 -07:00
OrcaWin a0a654c3a9 fix(renderer): reject malformed cursor and language-pack inputs (#13451)
## What this changes

Two renderer inputs were trusted because their TypeScript types said they were valid. Both are now validated at the boundary that owns them.

**Terminal cursor style.** `normalizeTerminalCursorStyleDefault` preserved any non-null migration-stamped value without checking it against the actual enum, so a runtime value outside `bar | block | underline` survived into `terminal.options.cursorStyle`. It now enum-checks and falls back to `block`. Applied on both read paths (desktop `Store` load, web `getStoredSettings`) and both write paths (`Store.updateSettings`, web `settings.set`), so an unsupported value cannot reach persistence, the `settings:changed` publication, or xterm.

**Plugin language packs.** The renderer stored the `listLanguagePacks()` IPC result without a runtime check, so a non-array response was assigned to state and later crashed its first array consumer on `.find`. Ingress now accepts only an array, drops members failing `isPluginLanguagePackRegistration`, keeps valid siblings in their original order, and logs which failure mode occurred. The registration guard requires `resourceLanguage` to equal `pluginLanguageResourceId(id)`, which keeps a pack with a missing or inconsistent identity out of i18next — that shape reproduces as `TypeError: Cannot read properties of undefined (reading 'includes')` with the guard removed.

Catalog validation is shape-only on this path (`validatePluginLanguagePackCatalogShape`), so revalidating an already-parsed catalog does not allocate a second copy of it. `isCatalogObject` also now requires a plain-object prototype, and the walk rejects repeated or cyclic object references.

## Root cause: partially known

Worth stating plainly, because the fix is defensive rather than causal:

- The non-array pack container and the out-of-enum cursor value are both reproduced directly by tests.
- Two additional field stacks are *consistent with* an invalid `resourceLanguage` and are blocked by this guard, but the reports do not prove that malformed registrations were their source.
- No production writer in current code or history emits an out-of-enum cursor style. The Ghostty config importer (`src/main/ghostty/mapper.ts`) already rejects unsupported `cursor-style` values. The original writer is unidentified.

Every in-tree producer supplies valid data, so these guards are no-ops on the current happy path. They are boundary hardening against a mutation we have not located, not a repair of a known writer.

## Trade-offs

- Malformed registrations are skipped with one warning rather than surfaced in the UI. Valid siblings keep their identity and order. Note the main-process registry already reports per-plugin parse errors, so this path only catches corruption after main has validated.
- Renderer ingress walks each catalog once per lazy load or `contentPacksChanged`. It does not run on render or terminal-output paths, but a maximum-size burst still costs tens of milliseconds synchronously. Pack count and plugin-ID length remain uncapped.
- An unsupported cursor value now renders and persists as `block`, so anyone relying on an undocumented third-party value loses it.
- Loading settings whose cursor style is absent or invalid now marks state dirty and rewrites once, matching the existing `terminalRightClickToPasteDefaultedForPlatform` pattern above it.

## Compatibility

`listLanguagePacks` is local `ipcMain`/`ipcRenderer` only and is not implemented in the web build, so nothing here crosses the remote wire — no RPC parameter, publication schema, stream frame, opcode, or capability changed. Validation is receiver-side at existing boundaries. Nothing is platform-, shell-, PTY-, native-module-, SSH-, WSL-, or worktree-dependent, and the web build normalizes on both read and write. Malformed plugin data is rejected before i18next sees it; no new permission, network path, executable input, or persistence schema was added.

## Verification

Locally on the final head: the 5 touched test files pass (591 tests), plus node and web typecheck, oxlint, and oxfmt. All GitHub required checks pass, including Windows packaging, static analysis, Git and wire compatibility, shell contracts, and all 32 Node 24/26 shards. The path-gated E2E job is skipped after its detector passed.

Scope note: an earlier revision generalized this hardening to `Project.sourceRepoIds` and profile transfer without field evidence. That scope was removed; the diff is cursor and plugin paths only.
2026-08-11 03:18:55 -07:00
Brennan Benson 827e4a1c6c feat: toggle structured sessions between chat and TUI 2026-08-11 03:01:11 -07:00
Neil 3713dd7376 perf(git): reuse pinned OIDs for SSH file diffs (#13586)
Forward the renderer's already-pinned {mergeBase, headOid} to the SSH relay so a single-file branch diff reads the two blobs directly instead of rediscovering live HEAD. Six sequential git processes become two concurrent reads, and a branch move mid-review no longer changes which revision is displayed.

Equivalence with the legacy route is proven against real Git across 14 change types; wire compatibility is proven over a real SSH socket against relay bundles built from main and from the pre-merge-base.
2026-08-11 02:18:29 -07:00
Neil 383ae50a35 fix(utf8): stop reading a code point past the end of a sliced string (#13782)
Once V8 optimizes the calling function, `String.prototype.codePointAt` on a
sliced string pairs a trailing high surrogate with the code unit that follows
the SLICE inside its parent, returning a code point the string does not
contain. Every UTF-8 byte scan built on `codePointAt` therefore reported one
byte too many for a prefix slice cut mid-pair, but only after tier-up, which
is what made terminal-stream-byte-length.test.ts fail intermittently on the
same commit.

Read the units explicitly with `charCodeAt`, which stays bounds-correct in
every tier, via a shared `readUtf8CodePointAt`.
2026-08-11 02:04:10 -07:00
NeilandOrca c8ee48701b perf(renderer): keep catalog array identity and path-status cache on no-op refetches (#13770)
* perf(renderer): keep catalog array identity and path-status cache on no-op refetches

mergeByIdentity always allocated a fresh array, so project-group and folder-workspace
refetches replaced state by reference even when nothing changed, refiring a forced
folderWorkspace.getPathStatus sweep per row on every repos:changed.

Unchanged merges now return the previous array, and the paired
folderWorkspacePathStatuses resets are gated on the same check so a no-op refetch
neither clears the cache nor needs to refill it. Real catalog changes clear and
refire exactly as before.

Co-authored-by: Orca <help@stably.ai>

* refactor(renderer): make the catalog arrays readonly

The identity guard can now return the caller's array, so a future in-place push or
sort on state.projectGroups / state.folderWorkspaces would alias store state. Typing
the merge helpers and the slice fields readonly removes both `as T[]` casts the guard
introduced and adds none, matching what #13744 did for state.repos.

Partial test fixtures cast the element rather than the array, so no `as unknown as`
laundering is needed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:50:37 -07:00
NeilandOrca e8904cf7d2 perf(renderer): skip runtime status map churn on unchanged re-probes (#13771)
setRuntimeEnvironmentStatus always cloned runtimeStatusByEnvironmentId, so a re-probe
returning an identical status still invalidated every subscriber. It now returns the
state unchanged when the computed entry matches the stored one.

checkedAt is excluded from the comparison: it is Date.now() per probe and no consumer
reads it, so including it would make the guard dead code. Generation advance and the
disconnect toasts still run on every call.

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:48:17 -07:00
NeilandOrca 0fe04e2c91 perf(sqlite): cache prepared statements in SyncDatabase (#13769)
prepare() recompiled every statement, so orchestration reads re-parsed the same SQL
on the main thread. Adds a bounded LRU keyed by SQL, cleared on close() and before
schema-changing exec().

Wildcard selects are excluded: node:sqlite builds the first post-schema-change row
from stale column names, so a reused SELECT * can silently drop a freshly added
column. PRAGMAs stay uncached.

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:48:14 -07:00
Neil 0aa7d0b5c1 refactor(renderer): unify the window park-visibility primitive (#13778)
The stream-gating hook (#13608) and the subscription parking installer
(#13621) each reimplemented `isWindowVisible() || isDocumentVisibilityProvenStale()`
plus its visibilitychange + stale-recovery subscription, and each carried
its own 500ms park delay that agreed only by coincidence.

Extract both into `lib/window-park-visibility.ts` and leave the two
consumers as thin wrappers: `useWindowStreamVisible` feeds the snapshot
and subscribe into `useSyncExternalStore`, the parking installer calls
them imperatively. The subscribe now always uses the defensive
`typeof document` guard the parking side already had, and registers
stale recovery before the visibilitychange listener so the stale latch
is cleared before the callback reads it.

Both park delays now derive from one `WINDOW_HIDE_PARK_GRACE_MS`: they
debounce the same signal against the same user behaviour (a quick
app-switch round trip). The parking side's 8x reveal-side backoff stays
where it is - that models its own resume cost, not the grace window.

Renderer-authoritative gating (#13622 native chat, #13634 editor panels)
is deliberately left alone; it keys off Zustand routing state, not OS
visibility.

Also switch the active-runtime ref writes in web-session-tabs-sync to
`useLayoutEffect` so no resume event can land between paint and the
passive flush, and correct the stale comment above them - the install
effect reads neither ref (both readers are gated on `isVisibilityResume`).
2026-08-11 01:43:47 -07:00
NeilandOrca 12f58f6bee fix(editor): keep an invalidated diff visible until its reload lands (#13777)
Mark invalidated diff content stale instead of dropping it, mirroring the file-content treatment from #13634. The diff read-generation fence (diffReadGenerationRef) already rejects a superseded read's write-back, so retaining the bytes and swapping them on arrival is safe and keeps every RPC saving -- it just stops the loading placeholder flashing on each reveal.

The git-status reload effect now treats a stale entry like a missing one so the lazy-load effect stays the single fetcher for it.

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:43:44 -07:00