Commit Graph

4045 Commits

Author SHA1 Message Date
Teknium 269e5bde33 fix(desktop,i18n): polish Russian catalog and register ru in locale tests/docs
- Translate leftover English 'toolset(s)' strings in ru.ts
- Cover ru aliases/config value in languages.test.ts (parity with ar)
- List Russian among desktop UI languages in website/docs/user-guide/desktop.md
2026-09-01 08:31:03 -07:00
FASTCHIP a922dad9d8 feat(desktop): add Russian (ru) locale
Full desktop UI translation (ru.ts, 3076 string/function leaves,
mirrors en.ts 1:1) plus locale registration in types, catalog and
language list with aliases (ru, ru-ru, ru_ru, ru-by).

Russian plurals (1 / 2-4 / 5+, 11-14 exception) via RU_PLURAL/RU_NOUN
helpers; count accepts number | string to match en.ts signatures.

Verified against current main: structural validation 3076/3076 with
placeholder parity, typecheck (renderer/electron/e2e) clean,
i18n vitest 28/28, prettier + eslint clean.
2026-09-01 08:31:03 -07:00
Teknium da96a9c204 fix(desktop): never lose HERMES_BACKEND_READY emitted before the port wait attaches
The spawn-time output tail (#93608) puts child stdout into flowing mode at
spawn. Both backend spawn paths in main.ts then await claimBackendChild
(whose Windows Get-Process probe cold-starts in 2-8s) and boot-progress IPC
BEFORE waitForDashboardPortAnnouncement attaches its stdout listener. Node
streams never replay consumed chunks to late listeners, so a READY sentinel
printed during that window was lost forever — the wait hit its 90s timeout
and a healthy backend was killed (deterministic on Windows, racy on
macOS/Linux; still firing on v0.21.0 incl. concurrent multi-profile boots).

Fix (belt and suspenders, both spawn paths — primary and profile pool):
- create the port-announcement promise immediately after spawn, before any
  await
- new bufferedOutput option on waitForDashboardPortAnnouncement: after
  attaching its own listener, waitForDashboardPort scans the output tail's
  already-buffered text for the sentinel, making listener-attach ordering
  irrelevant regardless of call-site shape

The readyFile path was already ordering-safe (it polls a file, not the
stream). Approach follows stale PR #60986 by @ParaWheeler, rebased onto the
output-tail/readyFile plumbing added since.

Fixes #60323
2026-09-01 08:30:21 -07:00
Teknium a1c25d393a feat(desktop): built-in optional-skills catalog in Capabilities → Skills with one-click install
The Skills tab now lists the entire official optional-skills catalog
(optional-skills/ shipped with the repo) below the installed skills.
Each catalog row has an Install button that routes through the standard
hub action pipeline; once the install finishes the row flips into the
installed list with the normal enabled/disabled toggle.

- backend: GET /api/skills/hub/official — OptionalSkillSource.list_local()
  scan (no network) + per-profile installed flags from the hub lock
- desktop: catalog section in SkillsView with search/scope integration,
  install-state spinners off $hubActions, and an OfficialSkillDetail pane
  (hub preview: frontmatter + full SKILL.md + Install)
- CapRow gains an optional action slot (button instead of the Switch)
- electron: route the new endpoint with the skills family (primary backend)
- i18n: officialCatalog/officialPill keys across en/ja/zh/zh-hant
2026-09-01 01:39:59 -07:00
hermes-seaeye[bot] 9366ba3afb fmt(js): npm run fix on merge (#100128)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 08:34:56 +00:00
Sergey0515 f85edd3d94 fix(desktop): stop plugin-launched gateways before the Windows release gate (#70337)
releaseBackendLock tree-kills only the Desktop's own backends
(backendConnectionState + backendPool). A messaging gateway launched by
the gateway-launcher desktop plugin via /api/gateway/start lives outside
those structures; on Windows its launcher (venv\Scripts\python.exe)
keeps the venv mandatory-locked, so the 15s release gate aborts the
hand-off BEFORE the venv-blocker scan — the scanner's pausable-gateway
exemption and the CLI updater's pause machinery never get their chance.

Delegate to `hermes gateway stop --all` (launcher + worker discovery
across every profile; gateway.pid records only the uv WORKER and
taskkill /T from the worker never reaches its parent). Per review, adds
the drain-semantics counterpart: every applyUpdates abort path
(lock-held, venv-blocked, probe-failed, updater-spawn-failed) restores
gateways via `gateway start --all`, so a failed update no longer
strands every profile's gateway stopped. Pure DI'd module + tests.

Salvaged from PR #76057 (issue #70337; overlap credit: #70477 by
@JonthanaHanh targeted the same symptom earlier via ZIP-dir preservation).
2026-09-01 01:30:09 -07:00
xy952666680 6b18224df5 fix(desktop): reap detached hindsight venv daemon before update handoff (Windows)
The pre-handoff teardown tree-kills only the backends the Desktop owns
(backendConnectionState + backendPool). The memory plugin's hindsight
daemon is spawned DETACHED off venv\Scripts\pythonw.exe, so it survives
the teardown, keeps venv files mapped, and either dead-ends the
venv-blocker scan with no in-app remedy or (pre-#74805 shim-only gate)
raced the updater into a half-updated venv.

Add a narrowly-scoped reap: kill only processes whose exe lives under
venv\Scripts (ordinal case-insensitive prefix — no PowerShell -like
wildcard hazards) AND whose cmdline references hindsight_api.main.
External holders (user terminals, unrelated scripts) are never killed —
scanVenvBlockers still reports them and the hand-off aborts, per existing
design. Selection logic is a pure DI'd module with unit tests.

Salvaged from PR #75477 (scoped per review: the narrow daemon kill; the
PR's generic every-exe kill was rejected as over-broad, its updateInFlight
half was superseded by #75778/#73822, and its generic lock-probe half by
the #74805 release gate + #99724 scanner classification).
2026-09-01 01:30:09 -07:00
liuhao1024 46ac31e84c fix(desktop): sanitized deferral evidence for ledgered manual serve blockers
The Desktop venv-blocker scan (since #99724) defers ledger-verified
serve/dashboard holders to the CLI updater's stop+relaunch rungs, but the
scan output only carried an opaque deferred_backends count — nothing
explained WHICH holders the deferral consumed or why they vanished from
processes.

Add sanitized decision evidence (#98350): deferred_backend_evidence lists
structured ledger identity only (pid, purpose, recorded port) — never the
command line, which can carry tokens or private endpoints. Adds a desktop
parser contract fixture proving the consumer tolerates the diagnostics
while keeping blocked/processes authoritative.

Salvaged from PR #98350; the exemption half of that PR was independently
consolidated on main via #99724 (_is_updater_owned_backend).
2026-09-01 01:30:09 -07:00
hermes-seaeye[bot] 04224b2f82 fmt(js): npm run fix on merge (#100096)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 07:41:45 +00:00
Teknium b0d2148b43 test(desktop): isolate the consent dialog in preview-pane tests
The pane now mounts RealProfileConsentDialog, whose config hook needs a
QueryClientProvider the pane tests don't set up. Mock it out like the
pane's other collaborators — the dialog has its own test file.
2026-09-01 00:36:47 -07:00
Teknium ada7213cca feat(desktop): first-open consent prompt for real-profile browsing
Opening a Browser pane (docked tab or ?win=browser popout) while
browser.use_real_profile is off now offers to turn it on: a one-time
dialog explaining the cookie/login snapshot, with 'Use my profile'
(writes the config key through the same PUT /api/config + shared query
cache the Capabilities toggle reads, so the existing toggle flips on
the spot), 'Not now' (mutes for this app run), and 'Don't show again'
(persists the opt-out across launches).

A claim atom ensures only one mounted Browser pane renders the prompt,
so split zones never stack duplicate dialogs. Prompt copy added to all
five locale catalogs.
2026-09-01 00:36:47 -07:00
Teknium 383f827493 fix(desktop): satisfy import-sort and ref-mirror lint rules in comment mode
Auto-fix perfectionist import/export ordering across the new
preview-annotate files, and extract the conversation-switch annotate
reset into a useCallback so the effect body carries no .current writes
(no-restricted-syntax ref-mirror rule).
2026-09-01 00:30:56 -07:00
Adolanium 10f2a20966 feat(desktop): add comment mode to the in-app browser
Click or drag on the preview page to pin a numbered comment. Saving a pin only adds it to the stack. Add comments drops the crops and a short prompt into the composer and never sends the turn.

Capture takes the visible page then crops in bitmap space, because Electron's rect crop on guest webviews is empty on Windows and shifted on high-DPI.
2026-09-01 00:30:56 -07:00
Brooklyn Nicholson 21b2095d00 fix(desktop): type the config.set mock so display-toggles tests typecheck 2026-08-31 21:11:11 -05:00
Brooklyn Nicholson c42daab67b fix(desktop): mirror the Appearance switches on connect, not just on change
A gateway the app has never spoken to holds no answer at all, and for a
switch that defaults on, silence reads as consent to exactly the thing
the user turned off. Mirroring on connect as well as on change closes
that, and the connect push is narrow on purpose: only settings the user
has actually touched are re-sent, so a fresh install can't broadcast its
defaults over a config.yaml somebody hand-edited on the server.
2026-08-31 21:11:11 -05:00
hermes-seaeye[bot] de590cc3cb fmt(js): npm run fix on merge (#99917)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 02:09:50 +00:00
Brooklyn Nicholson 0b247df94f fix(desktop): dock the sidebar rails down to 640px
The rails left the grid for the hover-reveal overlay at 768px, which cost
the docked sidebar on any half-screen split (1512 -> 756, 1440 -> 720).

Derive the collapse point from the layout instead: a rail costs 237px
(SIDEBAR_DEFAULT_WIDTH) and the chat beside it wants roughly the 420px a
popped-out session window enforces on itself. Express it as a dock floor
rather than a collapse ceiling so an exactly-640px window still docks.
2026-08-31 21:08:57 -05:00
Brooklyn Nicholson b0bcb3640c fix(desktop): drop redundant onReorderSessions check after sessionsDraggable 2026-08-31 21:03:25 -05:00
Brooklyn Nicholson 9052ce76c4 feat(desktop): collapse Yesterday / Last week groups in the sessions sidebar
Date and status labels were separators only. Clicking one now hides the
sessions under it, same as project and profile rows, and Collapse all
covers those buckets too.
2026-08-31 21:03:25 -05:00
Gille e07172319d fix(desktop): make /stop interrupt active turns
Preserve the existing background-process cleanup after interrupting the targeted Desktop session. This salvages the narrow /stop behavior from the broader, conflicting PR #45030 on the current slash-command architecture.

Co-authored-by: AlvaroBiano <alvarobiano@users.noreply.github.com>
2026-08-31 20:50:10 -05:00
xxxigm bcecd675f7 fix(desktop): unwrap Models-page code-skew 503 and recycle the owned backend (#97046)
Show a Restart backend action that kills the SSH serve before the local child
so reconnect cannot reuse a stale lockfile, instead of dumping raw IPC JSON.
2026-08-31 20:43:36 -05:00
Brooklyn Nicholson 42c415820c test(desktop): assert regenerate rejection restores the full history
Drive the real reloadFromMessage catch, not a copied merge object.
Same coverage on the session-tile sibling.

Co-authored-by: ygd58 <buraysandro9@gmail.com>
2026-08-31 20:43:06 -05:00
Brooklyn Nicholson f615f3796d fix(desktop): restore the transcript when regenerate is rejected
reloadFromMessage hid/truncated messages for the optimistic UI, then on
a failed submit (provider error, compressed-away turn) reset only the
busy flags. restoreToMessage already rolled messages back; do the same
on the primary chat and the session-tile path.

Co-authored-by: ygd58 <buraysandro9@gmail.com>
2026-08-31 20:43:06 -05:00
Brooklyn Nicholson 6eddeca6ef fix(desktop): stop hidden composers from stealing the caret
Keep-alive tabs remount their composer on transcript/status backstops
and were calling focus() while the user typed in the front tab. Gate
autofocus on pane visibility, and refuse to steal the caret from
another visible composer. A hidden tab that still holds DOM focus
does not block the pane the user just switched to.

Co-authored-by: Dan Bennett <dan@danbennett.me>
Co-authored-by: mor44-AI <andrefmontemor@gmail.com>
Co-authored-by: d4rk pr10r <darkpriorlabs@gmail.com>
2026-08-31 20:41:08 -05:00
Brooklyn Nicholson 60ba6024b5 fix(desktop): isolate keep-alive panes from the visible transcript
Hidden session panes published stick-to-bottom into window-global atoms
and every mounted list subscribed to the same jump broadcast, so a buried
tab yanked the reader and flashed the composer. Only the visible pane
may publish, scroll requests are keyed by session, and a run start or
same-session refresh leaves a scrolled-up reader where they were.

Co-authored-by: gamewocao <gamewocao@users.noreply.github.com>
Co-authored-by: mor44-AI <andrefmontemor@gmail.com>
Co-authored-by: d4rk pr10r <darkpriorlabs@gmail.com>
Co-authored-by: Jackal991 <lawrence@hydra-flow.co.uk>
2026-08-31 20:41:08 -05:00
Brooklyn Nicholson d72ce5e434 fix(desktop): rebind the visible chat after a reaped runtime
session.reclaimed now heals through markRuntimeGone before dropping
cache. A 4001 on the visible session's dispatcher request asks for a
durable resume. prompt.submit uses the window dispatcher so the turn
lease survives the ACK, and a primary route no longer registers a
phantom turn lease.

Co-authored-by: Lester Liang <153183032+lesterlxt@users.noreply.github.com>
Co-authored-by: wz-heng <68931789+wz-heng@users.noreply.github.com>
2026-08-31 20:40:31 -05:00
Brooklyn Nicholson 36bea50139 fix(desktop): latch approval and goal polls off a dead runtime
process.list already stopped hammering a reaped id. approval.pending and
goal status did not, and session.info heartbeats republished an equivalent
state object so every tab rerendered. Share the gone-latch from
runtime-gone and keep heartbeat identity when nothing changed.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
2026-08-31 20:40:31 -05:00
chelsealong cbe007a559 fix(desktop): stop status-stack remounts from re-arming a dead-runtime poll storm (#98434)
A boot-restored chat can stay bound to a dead runtime id and remount its
composer status stack repeatedly with no genuine rebind ever occurring.
The stack's mount effect cleared the gone-polling latch on every mount, so
each remount re-armed process.list + slash.exec('goal status') against
the same phantom id forever, churning the composer every ~5s.

Real rebinds already reset the latch at the runtime-mint seams
(use-gateway-boot.ts, store/gateway.ts). Drop the redundant per-mount
reset so the latch actually holds across a remount.
2026-08-31 20:40:31 -05:00
Brooklyn Nicholson 308454b372 fix(desktop): route model catalogs and picker reseeds through the focused owner
requestModelOptions now sends the owner profile on the RPC, forced reseeds call getGlobalModelInfo(profile), and picker/cache keys include the registry connection so a tile cannot fall back through the ambient socket.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
2026-08-31 20:40:09 -05:00
Brooklyn Nicholson f08666d42a fix(desktop): persist composer model/provider per remote connection and profile
Sticky composer keys were global, so a provider picked on one remote profile could ride into session.create on another. Persistence now follows an explicit (connectionId, profile) owner published before the active-profile reseed; unresolved legacy owners fail closed.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
2026-08-31 20:40:09 -05:00
Brooklyn Nicholson 0e7eebc266 fix(desktop): scope remote model catalog and primary-label REST to the focused profile
A shared dashboard's launch HERMES_HOME is not the selected profile. model.options now runs under @_profile_scoped, and global-remote REST keeps ?profile= even for the primary label.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
2026-08-31 20:40:09 -05:00
Brooklyn Nicholson 8b28bdceb5 fix(desktop): stop a stale composer model pinning every new chat
Settings → Model while a chat is open flips the composer source to
'default' but leaves the live session's model painted. Sending that
value on session.create pinned every new chat and skipped model.default.

Only a manual composer pick is a per-session override.

Co-authored-by: Tharanee <tharanee@tharanee.net>
2026-08-31 20:39:06 -05:00
Gille f98f5e74e0 fix(desktop): preserve terminal startup output 2026-08-31 19:43:31 -05:00
hermes-seaeye[bot] 66b844c967 fmt(js): npm run fix on merge (#99871)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 00:41:26 +00:00
Brooklyn Nicholson e600507a8f fix(desktop): drop the stray paren that broke the /btw event test typecheck 2026-08-31 19:36:03 -05:00
Brooklyn Nicholson 38e4de48d8 test(desktop): cover /btw prompt.btw dispatch and btw.complete
Pin the RPC route (not slash.exec), bare-/btw usage, older-gateway
fallback, and originating-session answer rendering.

Co-authored-by: SsSs <w-kwan@hotmail.com>
Co-authored-by: kokhlo <konstantin.khlopkov93@gmail.com>
Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
2026-08-31 19:36:03 -05:00
Brooklyn Nicholson 5994b833b0 fix(desktop): route /btw through prompt.btw so the answer reaches the chat
Desktop sent /btw through the slash worker, which printed the answer after
process_command returned, so only the acknowledgement ever showed. Use the
TUI's prompt.btw RPC and persist btw.complete on the originating session.

Co-authored-by: SsSs <w-kwan@hotmail.com>
Co-authored-by: kokhlo <konstantin.khlopkov93@gmail.com>
Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
2026-08-31 19:36:03 -05:00
Brooklyn Nicholson a2907a8bcd fix(desktop): skip cold process probes for dead backend-ownership PIDs
Windows Get-Process and macOS ps exit 1 on a missing PID, so reapOrphans
kept stale records and the next launch paid another 2-8s spawn each.
Throw ESRCH from the existing isPidAlive helper before any shell-out.

Closes #92875

Co-authored-by: Jackal991 <139240222+Jackal991@users.noreply.github.com>
Co-authored-by: jonotonfoto <126111813+jonotonfoto@users.noreply.github.com>
Co-authored-by: foras910521-lab <268267187+foras910521-lab@users.noreply.github.com>
2026-08-31 19:32:14 -05:00
hermes-seaeye[bot] e22f8a7fbd fmt(js): npm run fix on merge (#99862)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 00:28:40 +00:00
Gille 9dffcc431a fix(desktop): keep project previews visible on drill-in 2026-08-31 19:21:16 -05:00
briandevans cebe2168ab fix(desktop): resolve the clarify card by zone, not document order
visibleClarifyCard() bottomed out in queryVisible(), which only drops
[data-pane-hidden] and then returns the first remaining DOM match. That is
enough to tell a foreground tab from a background one, but a split layout
has two chat surfaces on screen at once: both cards survive the hidden-pane
filter, so the winner is decided by document order. The earlier zone owns
Enter and 1..N permanently and the other visible card can never receive its
own shortcut — worse than the mount-order behaviour it replaced, because
mount order at least changed as panes came and went.

Break the tie on the ladder the app already has instead of inventing a
second notion of which surface is "the" one: tree/store's tabTargetGroup
walks hovered zone, then focused zone, and every tab verb (Cmd+1..9,
Ctrl+Tab, the Cmd+W family) targets through it; composerTargetInHoveredZone
(#74447) mirrors it for the model hotkey. visibleClarifyCard now does the
same, matching each visible card's closest [data-tree-group] against those
rungs. The single-card path short-circuits before any store read, so the
common case costs nothing extra.

The final fallback stays document order rather than null on purpose. When
neither rung names a zone holding a card — pointer off every zone, nothing
interacted with yet — returning null would leave Enter doing nothing at all,
a strictly worse regression than answering the first visible card.

Regression coverage pins both halves: the resolver unit tests assert the
active zone selects either card (not just the later one), that hover
overrides focus, that a hovered zone with no card falls through to the
focused one, and that neither rung resolving still yields a card; the
ClarifyTool integration tests render two visible cards in two zones and
assert exactly one clarify.respond fires, carrying whichever zone is
focused. All four order-sensitive assertions fail against the previous
resolver.
2026-08-31 19:20:32 -05:00
briandevans a3662bb32c fix(desktop): answer the visible clarify card, not the first-mounted one
A pending clarify card binds Enter / 1-9 / A-Z / arrows on `window`, and
inactive tabs stay MOUNTED, so every parked clarify keeps a live listener.
Nothing in the handler asked whether the card was on screen: the first
listener registered won, called preventDefault(), and the rest bailed on
defaultPrevented. Registration order is mount order, not visibility.

With two chats waiting on a question, answering the one in front of you
sent `clarify.respond` for a background session's request instead —
silently answering a question the user never saw and resuming that turn.

The invariant is already stated one file over, in composer-focus-keys.ts:
"a clarify card waiting in a background thread must not take the
foreground composer's letter keys". `clarifyCardOwnsKey` honours it via
queryVisible(); the card's own listener did not. That asymmetry is the
bug — the key-ownership resolver and the key handler disagreed about
which card is live.

Export that lookup as `visibleClarifyCard()` and have both sides use it,
so they cannot drift apart again. The card bails unless it IS the visible
card, which also leaves the keystroke unprevented for the composer when
the only pending card is hidden.

Scoped by visible-card identity rather than `usePaneVisible()`: split
zones each render their own active pane, so two cards can be visible at
once and a per-pane visible flag would not disambiguate them.
2026-08-31 19:20:32 -05:00
686f6c61 37fc92a3d6 fix(desktop): keep SSH serve teardown across re-entrant quit
Window X calls app.quit(); backendShutdown.finally() calls it again.
teardownSshConnection deletes the map entry before SSH exec kill, so
the second before-quit saw an empty map and exited while disconnect
was still running. Latch the in-flight teardown so the re-entrant quit
still waits.
2026-08-31 13:59:51 -07:00
Teknium 2e9a39d28e fix(desktop): heal v1 SSH gateway routes into the v2 connections registry
reconcileRegistryDrift only healed remote/cloud v1 routes. A v1 global
mode:'ssh' route (host, no url) written by Settings after the one-shot
migration had no registry identity: resolvedConnectionId returned null,
primary stayed 'local', and every launch re-homed the window onto a
fresh local backend. Because the heal skipped SSH entirely, the two
config files re-drifted after every update relaunch instead of
converging once.

Normalize the v1 SSH descriptor into a v2 kind:'ssh' entry (via the
same validated normalizeConnectionInput path the editor uses) and align
primary/lastUsed, with the same narrow-heal rules as remote: already-
registered targets and deliberate primary picks are left alone, and
unusable hosts never touch the registry.

Diagnosis credit: mgallmur-glitch (root cause) and jakewvincent
(re-drift after update relaunch) on #93888.
2026-08-31 12:17:31 -07:00
Teknium 6e41ab3460 fix(desktop): bounded auto-restart for no-mux SSH tunnel flaps instead of instant connection death (#96266)
A no-mux tunnel is a single persistent `ssh -N -L` child. On main, ANY
death of that child after readiness immediately set tunnel.alive=false,
which poisons SshConnection.isAlive() forever; upstream lifecycle probes
then treat the whole SSH connection as dead, tear down the scope, and
SIGTERM a perfectly healthy backend (~10s after HERMES_BACKEND_READY in
the #96266 logs: '[ssh] connection closed (no-mux tunnels killed)' ->
'Ignoring stale Hermes backend exit (SIGTERM)' -> 90s port-announcement
timeout, with retry/repair looping the same failure).

Now a post-readiness child death is a tunnel FLAP: the child is
restarted with a bounded budget (5 attempts, 1s delay by default,
injectable for tests) and only an exhausted budget marks the tunnel —
and thus the connection — unhealthy. Deliberate teardown (cancelForward
/ close) sets tunnel.stopping, cancels any pending restart timer, and
never restarts. Pre-readiness deaths keep failing fast with classified
stderr (auth/bind errors unchanged).

Fixes the kill chain of #96266.
2026-08-31 12:16:48 -07:00
andyst-dev 4d3e1e4d13 fix(desktop): prevent venv scan timeout on busy Windows hosts 2026-08-31 12:00:33 -07:00
Oliver Mee 61534aac45 fix(desktop): keep primary SSH session resumes remote
Untagged session rows come from the ambient primary backend. Do not synthesize a local owner for them, and clear stale explicit hints before issuing an id-only resume.
2026-08-31 11:58:54 -07:00
Jack Lau 7a1fca6676 fix(desktop): resolve the e2e Electron binary per platform and layout
findElectron() probed exactly one path, and got three things wrong at
once for anyone not on a hoisted POSIX install:

* It looked only under the REPO ROOT. This is an npm workspaces repo and
  npm hoists a dependency only when nothing conflicts, so `electron`
  installing into apps/desktop/node_modules is an ordinary outcome, not a
  broken tree.
* It joined a bare `electron`. On Windows the dist file is
  `electron.exe`, so the probe could never match there.
* Its PATH fallback spawned `which`, which is not a command on Windows,
  so the fallback failed for a reason unrelated to whether electron is on
  PATH.

The three combine into a misleading error: the suite refuses to start
with 'Run "npm install" from the repo root' on a tree that has electron
installed. Reproduced on Windows 11 against this repo, where
apps/desktop/node_modules/electron/dist/electron.exe exists and the old
body throws that message; the reporter on #88036 hit the same thing on
Linux and had to hand-symlink the package before the suite would run.

Resolution now asks the installed `electron` package for its own path
first (its main export IS the absolute executable, resolved from
path.txt and honouring ELECTRON_OVERRIDE_DIST_PATH), then falls back to
explicit dist probes for each root, then to PATH with the platform's
lookup command. The error message lists what was searched.

The rules live in e2e/electron-binary.ts so they can be unit-tested
without importing the Playwright runner, with the platform passed in
rather than read from process.platform: reading it would leave every
Windows rule untested on the Linux CI runner.

Wiring: the vitest `electron` project picks up e2e/**/*.unit.test.ts and
Playwright ignores the same pattern, so helper unit tests run in exactly
one runner and the specs are untouched.

Verified: 5 unit tests pass; mutation-checked one rule at a time
(hardcoding the binary name fails 2, reversing the probe order fails 1,
hardcoding `which` fails 1). tsc -p . and tsc -p tsconfig.e2e.json
clean.

This is the environment blocker called out in #88036, not its rendering
bug, so it is deliberately a subset.

Refs #88036
2026-08-31 11:56:42 -07:00
joaomarcos c631b483f1 fix(desktop): satisfy import ordering 2026-08-31 11:53:43 -07:00
joaomarcos 1940c23a4f fix(desktop): retain session remount polling reset 2026-08-31 11:53:43 -07:00