Commit graph

937 commits

Author SHA1 Message Date
Brooklyn Nicholson
d14bf23fa3 chore(desktop): build config — keep tsc emit out of src, gitignore artifacts 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
aff205dcc2 chore(desktop): i18n strings for tabs, zones, and session menus 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
369d0eeeff refactor(desktop): retire desktop-controller for the contribution shell; views as contributions 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
10fbade64b feat(desktop): electron — openDir IPC + ⌘W menu bridge (tabs, not windows) 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
7f74b324c3 feat(desktop): store + lib — layout/preview/session atoms, escape-layers, keybind helpers 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
0f922002eb feat(desktop): contribution controller, surfaces, and wiring 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
0f398f8e9c feat(desktop): focused-session-aware titlebar + statusbar 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
2afbe77763 feat(desktop): session hooks — open-in-tile, per-session actions, resilient resume 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
860a3f67bb feat(desktop): chat view — drop overlays, composer scoping, tile integration 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
eae1d7d147 feat(desktop): ⌘W close-tab, ⌘⇧T reopen, ⌘T new tab, ⌘1-9 + ⌃Tab tab switching 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
ac4f596ca2 feat(desktop): pointer session drag/drop + row/tab menus with close others/right/all 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
e6fea77d14 feat(desktop): multi-session tiles — per-profile state, tile pane, pane mirror 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
f1379bd6c2 feat(desktop): routes, nav, and command palette as contributions 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
aae35c5ee4 feat(desktop): shared UI — per-session prompt overlays, gateway overlays, tab primitives 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
63a9bde77b feat(desktop): layout-tree renderer — splits, zones, pointer drag-session, tab strip 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
c388daa665 feat(desktop): layout-tree model + store + workspace geometry 2026-07-13 17:57:54 -04:00
Brooklyn Nicholson
7ed7170960 feat(desktop): plugin manager, runtime loader, and plugins settings 2026-07-13 17:57:53 -04:00
Brooklyn Nicholson
aefb36299a feat(desktop): contribution registry — namespaced areas, keybinds, palette 2026-07-13 17:57:53 -04:00
Brooklyn Nicholson
99ff67eb03 feat(desktop): plugin SDK surface — rest door, socket, react-query, UI kit 2026-07-13 17:57:53 -04:00
Brooklyn Nicholson
39230d1738 refactor(desktop): funnel zoom apply+notify so restore can't desync
alelpoan's fix (emit hermes:zoom:changed after restore) is correct, but the
bug's root is duplication: setAndPersistZoomLevel and restorePersistedZoomLevel
each independently did setZoomLevel + send, and restore forgot the send.

Collapse both (and the lifecycle re-assert) into a single applyZoomLevel()
helper in zoom.ts that always applies-then-notifies — the regression can't
recur by omitting a send. Replace the source-grep test (which broke on main:
the sibling source-assertion pet test it copied was refactored to a behavioral
one, dropping the fs/path imports it relied on) with behavioral coverage of the
funnel, matching zoom.ts's "unit-testable without booting a BrowserWindow"
convention.

Co-authored-by: alelpoan <alelpoan@proton.me>
2026-07-13 17:39:03 -04:00
alelpoan
9f7a3cb1f6 test(desktop): cover restorePersistedZoomLevel renderer notification 2026-07-13 17:32:17 -04:00
alelpoan
a1a4c8ce1f fix(desktop): sync UI Scale setting after zoom restore on window load 2026-07-13 17:32:17 -04:00
ethernet
5d691374c3 fix(desktop): recognize little-endian Mach-O magic in native binary classifier
classifyNativeBinary only checked big-endian Mach-O/Fat magic bytes
(feedfacf, feedface, cafebabe). Real Darwin .node files from node-pty
prebuilds are stored little-endian on disk (cffaedfe = MH_CIGAM_64),
so every Darwin prebuild classified as null, and validateStagedBinaries
threw a platform mismatch on macOS — breaking npm run check for every
macOS contributor. CI didn't catch it because runners are Linux (ELF
path was correct) and tests only planted big-endian fake headers.

Add recognition for all six Mach-O/Fat byte orderings:
- MH_CIGAM (cefaedfe) — LE 32-bit
- MH_CIGAM_64 (cffaedfe) — LE 64-bit [the one real prebuilds use]
- FAT_CIGAM (bebafeca) — LE universal

Update makeFakeNode to write LE CIGAM_64 bytes for the darwin fixture
(matching real on-disk format) and add regression tests for all new
magic forms.
2026-07-13 17:22:17 -04:00
ethernet
7a44a8fdec fix(desktop): prevent staging wrong-platform node-pty binary for cross targets
stageNodePty received electron-builder's { platform, arch } but unconditionally
copied host build/Release, staging a host binary (e.g. macOS Mach-O) for a
foreign target (e.g. linux-arm64). The fallback rebuild also didn't pass the
target arch to electron-rebuild.

Now:
- build/Release is only staged when target platform+arch match the host
- cross-platform targets with no matching prebuild fail closed
- same-platform different-arch rebuild passes --arch to electron-rebuild
- post-staging validation reads .node magic bytes (ELF/Mach-O/PE) and rejects
  any binary whose platform doesn't match the target

Adds stageNodePtyInto() (testable core) and classifyNativeBinary() (pure),
plus 11 regression tests covering the cross-target scenarios.
2026-07-13 17:22:17 -04:00
ethernet
47d56b802a test(desktop): run scripts/ tests in vitest 2026-07-13 17:22:17 -04:00
ethernet
a6857faf48 test(desktop): fix remaining act() warnings in gateway-connecting-overlay
Move setGatewayState + rerender calls inside act() blocks and make the
synchronous soft-switch test async so all state updates are wrapped.
Eliminates the last 4 act() warnings (44 → 0).
2026-07-13 17:22:17 -04:00
ethernet
b034fa7026 test(desktop): convert zoom source-regex test to behavior test
Extracted zoomWiringForWindowKind() + ZOOM_WINDOW_CONFIG into zoom.ts so
the pet-overlay-
opts-out / chat-windows-keep-zoom contract is tested via the pure
config,
not by reading source. Callers in main.ts now use
zoomWiringForWindowKind()
instead of inline { zoom: false } / default { zoom: true }.
2026-07-13 17:22:17 -04:00
ethernet
727025a2f3 test(desktop): replace windows-child-process.test.ts regex with real tests 2026-07-13 17:22:17 -04:00
ethernet
c5794c505e change(lint): don't ignore config files in eslint conf 2026-07-13 17:22:17 -04:00
ethernet
955c5b73c5 test(desktop): move node tests to vitest as well 2026-07-13 17:22:17 -04:00
ethernet
c008f41bb9 fix(desktop): stage-native-deps falls back to electron-rebuild when no native binary exists
When neither a prebuild nor a compiled build/Release/*.node is found for
the target platform-arch, stage-native-deps.mjs now runs
electron-rebuild -f -w node-pty to compile one from source before
re-copying build/Release into the staged dist.

This makes the staging script self-sufficient — it always produces a
working native binary dir regardless of whether npm ci --ignore-scripts
skipped postinstall or whether node-pty publishes prebuilds for the
target (e.g. linux-x64 has no prebuild).
2026-07-13 17:22:17 -04:00
ethernet
ef61436967 test(desktop): fix React act() warnings across all desktop test files
Add vitest.setup.ts with IS_REACT_ACT_ENVIRONMENT=true + auto-cleanup,
and wrap render()/fireEvent() calls in act() across 8 test files:

- provider-config-panel.test.tsx: wrap renderPanel + fireEvent in act
- providers-settings.test.tsx: wrap renderProvidersSettings + fireEvent
in act
- use-prompt-actions/index.test.tsx: add actRender helper, wrap all 38
render
  calls, wrap Harness handle methods (submitText/cancelRun/steerPrompt/
  restoreToMessage) in act at the onReady callback level
- attachments.test.tsx: make renderWithI18n async + wrap in act
- skills/index.test.tsx: make renderSkills async + wrap fireEvent in act
- messaging/index.test.tsx: make renderMessaging async + wrap fireEvent
in act
- gateway-connecting-overlay.test.tsx: wrap all render/rerender in act
- preview-pane.test.tsx: wrap render calls in act, make tests async

Reduces act warnings from 44 to 4 (remaining are fake-timer + pre-render
store mutation edge cases). All 146 test files / 1180 tests still pass.
2026-07-13 17:22:17 -04:00
ethernet
999d63b517 test(desktop): fix a handful of broken tests 2026-07-13 17:22:17 -04:00
ethernet
56bec611e5 cleanup(desktop): note that ts imports don't need extension in one comment 2026-07-13 17:22:17 -04:00
ethernet
2762555038 cleanup(desktop): remove 'use strict' in ts & mjs
it's implied by ts and mjs files already
2026-07-13 17:22:17 -04:00
ethernet
6800ec9d66 change(ci/desktop): move desktop app build into check job 2026-07-13 17:22:17 -04:00
ethernet
7c98c65163 cleanup(desktop): lint&fmt all 2026-07-13 17:22:17 -04:00
ethernet
0fac4cd8e9 test(desktop): fix session preview registry tests fails
clearing the registry sets localstorage values, so we must clear it
afterwards
2026-07-13 17:22:17 -04:00
ethernet
6016997a72 test(desktop): stub CSS global in .test.tsx files 2026-07-13 17:22:17 -04:00
ethernet
92025df393 test(desktop): fix attachment list test not querying the selector correctly 2026-07-13 17:22:17 -04:00
ethernet
66c097ab78 test(desktop): warn when using document in tsx tests
this is almost always a mistake
2026-07-13 17:22:17 -04:00
ethernet
f382ff84f7 change(desktop): add vitest config for the desktop app 2026-07-13 17:22:17 -04:00
ethernet
3c408684ea test(desktop): rename panes test to describe its behavior
when the desktop app was first build, this was intentionally meant to
not save, but later it was added as a feature, and the test never
updated.
2026-07-13 17:22:17 -04:00
ethernet
47c47d6a03 test(desktop): handful of broken tests changed to match intended behavior 2026-07-13 17:22:17 -04:00
ethernet
bffc098d09 test(desktop): fix relative import in oauth-net-request.test.ts, drop dead source-regex test
oauth-session-request.test.ts regexed main.ts source text (extracting the
fetchJsonViaOauthSession function body and matching regexes against it) to
check Electron net.request doesn't set the forbidden Content-Length header
and does call request.write(body).

That behavior is already covered for real by oauth-net-request.test.ts,
which imports the actual serializeJsonBody/setJsonRequestHeaders helpers
from oauth-net-request.ts and asserts on a mock request object's setHeader
calls -- it only needed its relative import corrected to include the .ts
extension (Node's ESM loader doesn't resolve extensionless relative
specifiers). Removed the now-fully-superseded oauth-session-request.test.ts
and its dangling package.json wiring.
2026-07-13 17:22:17 -04:00
ethernet
1b8f1504b3 test(desktop): extract profile-delete routing decision for real unit tests
profile-delete-respawn.test.ts regexed main.ts source text to check that
prepareProfileDeleteRequest returns the torn-down profile name, and that
the hermes:api ipcMain handler captures that return value and routes to
the primary backend instead of respawning a pool backend for the just-
deleted profile.

Extract the pure decision logic into profile-delete-routing.ts (no
Electron import):
- profileNameFromDeleteRequest(request): parses a DELETE
  /api/profiles/<name> path, moved verbatim (already pure).
- decideProfileDeleteAction(profile, deps): the branch decision (noop /
  teardown-primary / teardown-pool) and the profile name to return,
  parameterized over isDefaultProfile/isValidProfileName/primaryProfileKey.
- resolveRouteProfile(tornDownProfile, profile): the
   routing ternary from the hermes:api
  handler.

prepareProfileDeleteRequest in main.ts now calls decideProfileDeleteAction
for the decision and only performs the async side effects (teardown +
writeActiveDesktopProfile) the decision calls for.

profile-delete-routing.test.ts replaces profile-delete-respawn.test.ts
(which was never wired into test:desktop:platforms), importing the pure
functions directly and asserting real return values across every branch
-- no readFileSync, no regex-on-source. Wired the new test file into
test:desktop:platforms in package.json.
2026-07-13 17:22:17 -04:00
ethernet
ec2cb3ab47 test(desktop): extract Windows hermes-resolution helpers for real unit tests
windows-hermes-resolution.test.ts regexed main.ts source text to check
three Windows resolution bugs that caused desktop reinstall loops:
1. findOnPath()'s PATHEXT extension order (must try real extensions before
   the empty one, or an extensionless Git-Bash hermes shim shadows
   hermes.cmd/.exe).
2. handOffWindowsBootstrapRecovery()'s --update vs --repair choice (must
   gate on any real-install signal, not just the hermes.exe shim).
3. unwrapWindowsVenvHermesCommand()'s probe-before-trust behavior (must
   canImportHermesCli() before returning a venv python, or a broken venv
   gets re-selected forever).

Extract all three into pure, dependency-injected functions in
windows-hermes-path.ts (no Electron import): buildPathExtCandidates(),
chooseUpdaterArgs(), resolveVenvHermesCommand(). main.ts's
findOnPath/handOffWindowsBootstrapRecovery/unwrapWindowsVenvHermesCommand
now call these with their existing helpers (fileExists, canImportHermesCli,
getVenvPython, etc.) passed through as deps.

windows-hermes-path.test.ts replaces windows-hermes-resolution.test.ts,
importing the pure functions directly and asserting real return values
with fake/injected dependencies (fake venvs, fake probes) -- no readFileSync,
no regex-on-source.
2026-07-13 17:22:17 -04:00
ethernet
4527943e91 test(desktop): extract hiddenWindowsChildOptions + stopBackendChild for real unit tests
windows-child-process.test.ts regexed main.ts and bootstrap-runner.ts
source text to check that spawn/execFileSync call sites wrapped their
options with hiddenWindowsChildOptions(), and that backend teardown
chose the right kill strategy.

Extract both into dependency-free sibling modules:
- windows-child-options.ts: hiddenWindowsChildOptions(options,
isWindows)
  now takes isWindows as an injectable param (defaults to the real
  platform check). main.ts and bootstrap-runner.ts both import the same
  implementation instead of each defining their own copy.
- backend-child.ts: stopBackendChild(child, deps) with
forceKillProcessTree
  and isWindows injected, so the SIGTERM-vs-tree-kill branching is
directly
  testable with a fake child + a spy.

windows-child-options.test.ts replaces windows-child-process.test.ts,
calling the real functions with fake spawn/execFileSync-shaped objects
and asserting on the actual returned options / kill call.
2026-07-13 17:22:17 -04:00
ethernet
7b3f3047ab feat(ci): run JS tests in CI, add npm run check in ws root 2026-07-13 17:22:17 -04:00
Brooklyn Nicholson
da52ffea14 fix(desktop): pin unscoped streams + clear view sync on switch
Live deltas from session A were attaching to session B after New Session
when events arrived without session_id — fallback used the newly focused
activeSessionId (#47709).

Pin unscoped stream events to the session that received message.start
(#48281). Also reset RAF-pending view staging on new/resume/create so a
stale background flush cannot repaint over the switched chat (#47743).

Co-authored-by: Ray <rayjun0412@gmail.com>
Co-authored-by: zapabob <1920071390@campus.ouj.ac.jp>
2026-07-13 16:10:33 -04:00