Commit Graph
19 Commits
Author SHA1 Message Date
Neil 2dfaa676d8 chore: update oxlint and oxfmt (#17150) 2026-08-29 14:13:35 -07:00
NeilandOrca 2100fb2553 fix(runtime): cap remote git.diff and file previews at the transport budget (#14160)
* fix(runtime): cap remote git.diff and file previews at the transport budget

A remote or mobile user who opens the diff of a large image loses their whole
WebSocket, not just that request: the E2EE channel closes with 1013 when a reply
exceeds the 4 MiB outbound envelope. Two producers can exceed it unaided.

git.diff/branchDiff/commitDiff cap text with MAX_RENDERED_DIFF_COMBINED_CHARACTERS
(6M chars) -- a *renderer* budget that sits above the transport limit -- and return
base64 for previewable binaries bounded only by MAX_GIT_SHOW_BYTES, so a 10 MiB PNG
changed in place is ~26.7 MiB in one envelope. files.readPreview inlines base64 up
to 10 MiB, and mobile calls it for every image tab.

Both now measure against a budget derived from the outbound limit. The check sits in
orca-runtime-git.ts, downstream of the dedupe and of both the SSH-provider and local
branches, so a payload forwarded verbatim by an old relay is covered by the same code
and src/relay needs no change. Local and in-process callers pass no budget and keep
full fidelity.

Measuring raw bytes would not work, which is the whole reason this needs a module.
JSON escaping turns one control byte into six (\u00XX), and binary-buffer.ts sniffs
only for NUL in the first 8 KiB -- so a NUL-free file of 0x01-0x1f bytes is classified
as *text*, would pass a raw-byte cap, and would then blow the envelope. The budget is
escape-aware, with a three-branch fast path that keeps normal diffs at two native
byteLength calls and scans only the ambiguous band.

The SSH branch of readFileExplorerPreview had the same raw-vs-escaped gap: its stat
gate sizes base64 binaries, but text crossed unbounded. It now honours the same
decoded-text limit the local branch already enforced.

No wire change: GitDiffResult is untouched -- no third kind, no new field. Old clients
see an error for one request instead of a dropped connection. diff_too_large joins the
structured passthrough codes and lands on an existing error arm in both mobile
consumers and the desktop remote path; file_too_large was already handled on both.

Instruments the 1013 close, which nothing measured before, so the incidence this cap
is meant to drive to zero is finally observable. `emitter` separates a producer size
bug from a wedged link.

Known regression: remote image previews between ~3.096 and ~3.146 MB now return
file_too_large. They only intermittently worked before -- above ~3.0 MB they killed
the socket -- so this trades intermittent connection loss for a consistent error.

Test: 10281 passed in src/main/runtime + src/shared + src/main/git; mobile 3427
passed. Each of the six budget-enforcement sites is independently mutation-killed.
Escaping fixtures cover newline-dense, control-char, CJK, lone-surrogate and base64
content against native JSON.stringify. tsc clean for node, web and cli; oxlint clean.

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

* fix(runtime): harden remote reply transport budgets

* test(runtime): cover desktop remote preview budgets

* test(runtime): close telemetry review gaps

* chore(shared): repoint budget imports after the shared/types barrel removal

Upstream #14447 dropped the shared/types barrel; GitDiffResult now lives in
git-diff-compare-types and GlobalSettings in global-settings-types.

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

* fix(ssh): surface an over-cap preview read as file_too_large

The stream reader aborts an over-cap read with StreamProtocolError, whose numeric
code falls through mapRuntimeError to a generic runtime_error carrying the raw
"Reported totalSize N exceeds client cap M" string. Neither preview client
recognizes that: runtime-file-client.ts and mobile-file-preview-response.ts both
key on file_too_large. It also made the two file_too_large guards directly below
the read unreachable on the streaming path.

Gives the cap its own error type so the caller can translate it, keeping the
bandwidth saving the cap exists for. A genuine protocol fault still propagates
unmasked.

Found by the readiness review. Mutation-verified: removing the translation fails
exactly the new test.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-14 00:24:57 -07:00
Brennan Benson 0ed6db77cf fix(mobile): open agent-cited external chat files (#14166)
* fix(mobile): open agent-cited external chat files

* fix(mobile): keep cited external files read-only

* refactor(mobile): derive cited-file mode from provenance

* fix(mobile): accept sentence-final cited paths

* fix(mobile): preserve cited SSH grant scope

* refactor(file-links): share location suffix parsing
2026-08-13 01:12:19 -07:00
Neil 991a3fe963 chore(lint): update oxlint to 1.77 and enable no-op cleanup rules (#13901)
Enable eleven oxlint rules that simplify code without changing behavior, and fix
every existing violation. Each candidate was gated on measured cost rather than
assumption, so rules that regressed runtime performance or type checking were
dropped instead of suppressed.

typescript/no-redundant-type-constituents is the largest addition: 113 sites, no
autofix. Dead constituents are deleted. Where the redundant literal existed to
document intent (`string | 'all'`), it is preserved as `(string & {})`, which
keeps the autocomplete hint the original code was reaching for instead of
flattening it away. The rule also caught a broken import —
remote-shared-control-retirement-probe.ts pulled RuntimeStatus from
src/shared/types, which does not export it, so the type silently degraded to
`any`; no tsconfig covers that file, so tsc never saw it.

oxlint stays at 1.77.0 rather than 1.78.0 because .npmrc sets
minimum-release-age=4320 and 1.78.0 is younger than that window.

Rules evaluated and rejected, with what disqualified each:
- prefer-string-raw: String.raw is a runtime call, not a literal (184x slower)
- prefer-string-replace-all: 26% slower
- text-encoding-identifier-case: ~5% slower, reproducible
- prefer-spread: [...str] is 110% slower than split('') and differs on surrogates
- no-implicit-coercion: `!!x` narrows types and `Boolean(x)` does not (22 tsc errors)
- prefer-arrow-callback: arrows are not constructible, breaking `new` on mocks
- object-shorthand: rewrites source text asserted by a tracked reliability gate
- switch-case-braces: pushes ten files past max-lines, which cannot be suppressed
- no-useless-switch-case: drops `case undefined:` that switch-exhaustiveness-check needs
- arrow-body-style: 115 violations have no fix, and it breaks max-lines
- newline-after-import: false-positives on the leading-semicolon ASI idiom

electron-vite-output-contract asserted on the literal
Object.prototype.hasOwnProperty.call text; retarget it to Object.hasOwn, which
rejects inherited keys identically.
2026-08-11 18:19:43 -07:00
Neil c56ff58dd7 test(mobile): centralize renderer setup (#13408) 2026-08-09 18:31:00 -07:00
Brennan Benson 6df8997c3c fix(file-explorer): sort numbered file names naturally across every listing surface (#11576)
* fix(file-explorer): sort numbered file names naturally

The File Explorer compared names with bare localeCompare, so numbered
files listed 100, 200 before 99. Hoist the numeric collator Source
Control file rows already use (#10850) into src/shared and apply it to
the local and runtime directory listings, the name-filtered view, and
Source Control directory nodes, which were inconsistent with the file
rows one line below (#11426).

* fix(file-explorer): natural sort on SSH funnels, relay, and pickers

Adversarial-review round 1 rework:
- Both readDir funnels short-circuited to the SSH filesystem provider
  before the patched sort, so SSH workspaces kept lexicographic order;
  re-sort locally after the provider returns (the remote relay may be an
  older build), and fix the relay's own comparator for relay-native
  consumers.
- sortDirEntries (shared, unit-tested) owns the directories-first +
  natural-order listing contract used by every funnel.
- compareFileNames breaks numeric-collation ties ('2' vs '02') by code
  units so sibling order stays total instead of readdir order, and pins
  the collator locale to 'en' so every host produces one order.
- The SSH folder browser and runtime server dir picker now match the
  Explorer they browse into.
- Ordering pinned by tests at the relay, source-control tree, and shared
  helper.

* fix(mobile): natural sort in the mobile file explorer

Mobile re-sorted host readDir results with bare localeCompare, undoing
the host funnel's natural order (round-2 review). Reuse the shared
comparator and pin the order in the mobile suite.

* fix(file-explorer): natural sort at the renderer choke point and remaining ties

Round-3 review: the remote-runtime RPC and paired-web routes return the
host's order verbatim, so re-sort in readFileExplorerDirectory where
every desktop route converges; pin the SSH funnel with a handler-level
test; and route Source Control path compares through compareFileNames so
numeric-collation ties share one total order with the Explorer.

* docs(file-name-sort): state the real perf baseline in the hoist comment

* refactor(source-control): drop the dead collator export; pin the test oracle locale

* fix(file-listings): cover remaining natural-sort surfaces
2026-08-05 17:11:28 -07:00
Jinwoo HongandOrcaWin 07671d4a06 fix(mobile): recover unreliable relay connections (#10709)
* fix(mobile): recover unreliable relay connections

* test(mobile): use valid raster preview fixtures

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-26 00:48:06 -07:00
NeilandOrca aab112933e Revert "fix(memory): bound OOM-prone accumulators (#10179)" (#10255)
Co-authored-by: Orca <help@stably.ai>
2026-07-23 18:35:31 -07:00
Neil 8f40ddf328 fix(memory): bound OOM-prone accumulators (#10179) 2026-07-23 06:22:56 -07:00
Jinjing 23fc1ea59a fix(mobile): bind markdown creation to file owner (#10083) 2026-07-22 22:14:30 -07:00
JinjingandOrca a03a3dd51b Render png on mobile (#9087)
* Add mobile image-diff previews via shared data-URI builder

- Extracts a `buildImageDataUri` helper (src/shared/image-data-uri.ts) shared by
  the desktop ImageViewer and mobile, so both trim whitespace-wrapped base64 and
  skip non-previewable mimes (e.g. application/pdf) the same way.
- Adds mobile-diff-image-preview.ts to render binary git.diff results (add/modify/
  delete) as images instead of falling back to "Binary preview unavailable".
- Extracts resolveMobileFileTabDoc to consolidate the session file-tab loading
  logic (diff/image/html/text) out of the route file for testability.

* Fix stale binary image fallback for empty modified diffs and relay reads

- mobileDiffImageDataUri now distinguishes a true deletion (modified
  side absent) from a modify whose binary bytes arrived empty
  (relay/size-cap cases), returning null instead of the stale
  pre-change image
- readWorkingDiffFile passes the file path to bufferToBlob so relay
  working-tree reads can detect previewable image extensions instead
  of always reporting empty binary content
- add mobile-file-tab-doc.test.ts covering diff/image/binary/text
  resolution paths

* Regenerate skill bundle manifest for 1.4.144-rc.2

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

* fix(review): trim comments to AGENTS.md's one/two-line why-only rule

Comments in mobile-diff-image-preview.ts and mobile-file-tab-doc.ts ran
3-6 lines and narrated mechanism instead of stating only the non-obvious
reason, per AGENTS.md's "Code Comments: Document the Why, Briefly" rule.

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

* Distinguish read failures from true deletions in binary diff results

- Working-tree stat/readFile errors and relay reads previously collapsed
  onto the same empty-content signal as a genuine deletion, letting
  previewers fall back to stale original bytes on a failed read.
- Add modifiedDeleted/missing flags through status.ts, git-handler-ops,
  and git-working-file-read so only proven deletions trigger the
  original-bytes fallback; failed reads now return null.
- Tighten buildImageDataUri to accept only image/* mimes instead of
  special-casing application/pdf.

* fix(relay): expect missing:false on index blob maxBuffer overflow

readBlobAtIndex now returns a missing flag so staged deletions are
distinct from size-capped binary reads; update the overflow test.

* Allow opening deleted files to show pre-delete text or image diffs

Deleted files can now be opened to view their pre-delete content via
git.diff (including images via modifiedDeleted). Only unresolved conflicts
remain unopenable. Centralizes the canOpen rule in canOpenMobileGitStatusEntry()
to keep opener guards consistent across the mobile source control UI.

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-16 18:21:08 -07:00
Neil fb15d647c6 refactor: remove state-only React effects (#8437) 2026-07-12 17:11:08 -07:00
Brennan BensonandOrca 9a3bf1293c Mobile file tree follow-ups: reconnect no-blank + old-desktop files.list fallback (#7599)
Co-authored-by: Orca <help@stably.ai>
2026-07-06 17:13:21 -07:00
Brennan BensonandOrca 99ae2dc90f Show full workspace file tree on mobile (#7289)
Co-authored-by: Orca <help@stably.ai>
2026-07-06 15:41:51 -07:00
Neil ce687221d3 lint(unicorn): enable prefer-number-properties, prefer-array-find, prefer-array-index-of (#7516)
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.

prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
  argument, Number.isNaN does not. Verified every call site already passes a
  number (Number.parseInt results, number-typed fields, Date.getTime()), so the
  conversion is behavior-preserving today and guards against a future non-numeric
  argument silently coercing.

prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
  Drops the intermediate array and short-circuits.

prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).

Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
2026-07-05 23:56:37 -07:00
Brennan BensonandOrca c06507ee30 Open terminal file links on mobile (#7134)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 18:17:47 -07:00
Brennan BensonandOrca 55c0c74951 Fix mobile file previews (#6315)
Co-authored-by: Orca <help@stably.ai>
2026-06-26 12:10:58 -07:00
c9bd61376f feat(mobile): combine PR sidebar and checks parity (#5641)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-06-17 22:52:12 -07:00
Jinwoo HongandOrca adf5ccbc92 feat(mobile): view image files from the Files tree (#5472)
Co-authored-by: Orca <help@stably.ai>
2026-06-15 22:30:05 -07:00