* perf(persistence): saves name the profile domains they changed
UI changes, window moves, SSH terminal exits, phone tab switches and
repo/folder/SSH/lineage edits no longer re-serialize and hash the whole
profile on the main thread. Each save names its domains (or 'whole' where
an operation can't know them), and one per-domain saved form serves both
selective and whole saves, writing dependent domains together.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): keep folder re-tiering and plugin context on named saves
Folder workspace unread writes re-tier sort stamps again, and plugins
hear an active folder's rename, now that those saves name their domain
instead of the whole profile. The folder-note test drains the load's
whole save so it checks the named save.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(persistence): typed folder workspace fixture for the re-tier case
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(persistence): build the serializer from typed state in the parity test
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): read unprojected domains without a type assertion
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(persistence): stall and fail the selective writer UI saves now use
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* perf(persistence): every mutation saves only the domains it changed
Removing a project, renaming a worktree's path, re-adopting an SSH
target, importing a catalog, deleting a group or folder workspace,
retrying a failed terminal close and remapping session panes now name
their domains instead of rewriting the whole profile. Only startup load
and maintenance resume rewrite every domain, through a separately named
entry point a lint rule keeps out of mutation code. A named
automationRuns save refreshes a pending run delta so it can't write an
older run list.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): a named automation-run save supersedes older run lists
A catalog import replaces the run list while an earlier run write can
still be in flight; if that write fails, its restored pending list must
not win over the import. Domain lists name the local session through
the shared constant so the layout-writer ratchet stays exact.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): keep the whole-row fallback for replaced run lists
A run list replaced outside the run-delta path (catalog import, SSH
re-adoption) saves as a whole row again, so histories that can't use
the per-run storage still save. A failed in-flight run write restores
its pending list only while that list is still current; otherwise it
marks the run domain for a whole-row save.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(persistence): save run lists per-run rows can't hold as one row
A run delta whose list can't be stored as per-run rows (a legacy
history with duplicate run ids) now saves as the ordinary whole-row
replacement instead of failing the transaction, so a catalog import
or run edit on such a profile still commits.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(orcad): fit orcad inside a `ulimit -v` address-space limit
On hosts that cap virtual memory (ulimit -v 8 GiB on an HPC node), the
managed orcad crash-looped with "Failed to reserve virtual memory for
CodeRange" whenever it started a worker thread.
Two reservations used up the limit:
- undici's WASM HTTP parser, loaded by the first fetch, reserves a
trap-handler guard region (4-9 GiB). The pinned-Node orcad launch now
passes --disable-wasm-trap-handler --wasm-max-mem-pages=1024.
- each Worker isolate reserves V8's default CodeRange (512 MB on x64,
~600 MB of address space per worker). Main-process workers now go
through spawnWorkerThread(), which caps the code range at 64 MB, and
a lint rule keeps new sites on it.
Fixes#37538. Thanks to lin-tianyu for the report and logs.
* fix(orcad): keep the runtime selector one command; enforce spawnWorkerThread with ast-grep
Refs #37538
* test(orcad): run the address-space regression on Node
Refs #37538
* docs(orcad): note the WASM memory cap at the pinned-Node flags
Refs #37538
* refactor(mobile): keep phone list facts beside the desktop's worktree row
The phone wrote its own list facts (lineage depth, host labels, list keys
and so on) onto the worktree.ps row object it got from the desktop, so a
desktop field with one of those names would be read as the phone's own.
List items are now { row, view }: the row is the desktop's, typed from the
shared RuntimeWorktreePsSummary contract, and view holds what the phone
works out. Host labels are a lookup beside the rows instead of fields on
them. A type test fails if the desktop row gains a view field's name or a
type the phone row would not accept, and a contract test feeds rows from
the desktop's own worktree.ps builder through the phone list.
* fix(mobile): type desktop replies from shared contracts and reject unchecked rows (#37923)
* wip: contract guards
* fix(mobile): type desktop replies from the shared contracts and ban unchecked z.custom
Session tabs, the createSupport reply and native-chat frames were typed by
hand on the phone, so a desktop change could leave the phone compiling
against a stale shape. They are now Pick/Partial of shared types; the
createSupport reply and native-chat transcript frames gain shared types the
desktop uses too, which also gives the phone the 'chat-program' reason it
was missing. Type tests fail when a desktop tab gains a phone-only name or
a field type the phone would not accept.
Catalog rows were admitted with z.custom<Worktree>() and no check. They now
need a string worktreeId, and an ast-grep rule rejects a z.custom with no
check anywhere in mobile/src.
* test(mobile): the desktop contract check also catches a null the phone would not accept
* fix(i18n): format renderer dates and numbers in the UI language
About 80 renderer sites formatted with the OS locale: Intl constructors
given undefined (or nothing) and argless toLocale*String calls, while
nearby text followed the UI language.
- toLocale*String calls pass getIntlLocale().
- Module-level Intl formatters and collators use new ui-intl-formatters
helpers, cached per options object and rebuilt on language switches;
per-call constructors pass getIntlLocale().
- ReleaseBuildPicker's memoized labels re-run on language changes.
- An ast-grep rule (renderer-intl-ui-locale) rejects undefined/omitted
locales in src/renderer; machine-facing code passes a fixed locale.
* fix(i18n): collators check their own locale support; re-sort memoized lists on language changes; widen the Intl guard
* fix(i18n): name the formatter options type
* fix(i18n): reuse the CSV viewer's existing language subscription
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* test: load real modules before timed waits, and ratchet in-test vi.importActual
Two tests paid a cold real-module load inside their timed wait:
- the empty-remote wake test's 1s vi.waitFor raced the post-create refresh's
lazy session-tabs import; it now loads those modules in beforeAll;
- the runtime label-only create tests called vi.importActual inside each test
(19-23s of the 30s budget on the first case); it now runs in beforeAll.
test-no-in-test-import-actual flags vi.importActual outside vi.mock/doMock/
hoisted factories, beforeAll and module scope; the 50 existing findings are
grandfathered in the flaky-test ratchet baseline.
* test: flag vi.importActual in a doMock factory registered inside a test
* test: flag vi.importActual in any vi.doMock factory; its load runs on the next import
* perf(lint): keep JS plugins out of default oxlint and adopt ast-grep
Any jsPlugin makes Oxlint hand every file's AST to Node, so the seven custom
plugins cost the default `oxlint` run several times its native time.
- Move app-store-performance, quadratic-buffer-concat, renderer-scrollbar-style
and ime-enter-guard (scope- or dataflow-aware) to config/oxlint-js-plugins.json,
run by `pnpm lint:js-plugins`, CI, lint-staged and the changed-lines gate.
- Port sort-comparator-performance, mobile-pairing and native-chat-design-tokens
to ast-grep rules with rule tests, and delete the plugins.
- Add @ast-grep/cli, config/sgconfig.yml, `pnpm lint:ast-grep` / `test:ast-grep`,
a CI step, and an ast-grep pass in check-changed-code-quality.
- Add flaky-test rules (wall-clock reads, real-time waits, polling loops,
unquarantined skips, unexplained timeout bumps), baselined by a count ratchet.
* Enforce flaky-test ratchet on changed files; refresh baseline
* docs(agents): keep custom-rule details out of AGENTS.md
* Resolve ast-grep from the repo so gate scans of other roots work