Commit Graph

8038 Commits

Author SHA1 Message Date
Maciej Lisiewski
0006a5e229 ci: fix PLW1509, PLW1510, PLW2101 linter errors 2026-10-10 15:20:13 +02:00
Teknium
a7b2ba02e3 fix(desktop): channel selector only where it can save, shows the choice at once, and never calls a custom branch main
Review findings on #135949 (Greptile, @unsupportedpastels):

- An older runtime (v0.21.6) answers checks but rejects --set-channel, so the
  selector failed on every click. source_check now marks its reply
  `channelSelectable`; Desktop shows the row only when the install says so.
- The confirmed choice painted only after the forced check returned. The row
  now shows it at once, and a failure re-reads the install instead of guessing.
- A checkout following a custom branch rendered as "Every commit" (main). The
  row now appears only for stable, or for main itself.
2026-10-10 02:33:22 -07:00
Teknium
354fe11d7e test(desktop): the source-channel handoff fixture answers stable from releases/latest
Since #135847, stable is the latest published GitHub release
(`releases/latest`), not the R2 release-candidates pointer. The fixture still
served only the pointer, so the check reported "No published stable release"
and the test went red on main (the Python-only PR skipped the Desktop lane).
2026-10-10 02:33:22 -07:00
Teknium
9287719a65 feat(desktop): About ▸ Updates picks stable releases or every commit for source installs 2026-10-10 02:33:22 -07:00
Teknium
24051d9084 feat(desktop): drop the "This could run on your computer" strip above the composer
The local-setup card appeared above the input after the first finished turn
on any machine whose catalog recommends a local model, and stayed across
relaunches until dismissed. It read as a nag on every new session.

The offer keeps its quiet homes: the "Run locally" row at the top of the
model menu (until setup completes) and the handoff tour's model-pill step.
The card's state machine (armed/shown/dismissed, the message.complete hook,
the menu-open request) existed only for the card, so it goes with it; the
store keeps the eligibility read both remaining surfaces use.
2026-10-09 14:01:55 -07:00
ethernet
040dbb9053 Merge pull request #135348 from NousResearch/harder-linter-fixes-part2
Harder linter fixes part 2
2026-10-09 13:08:10 -04:00
GrgaPalcic
2af85c331a fix(desktop): make mermaid SVG well-formed via the HTML parser, not a void-tag regex
The void-tag pass from the previous commit fixes `<br>`, but mermaid's
label HTML carries more than void tags: a non-breaking space (`&nbsp;`,
mermaid's `#nbsp;`, or a literal U+00A0 in the source) is serialised as
the `&nbsp;` entity, which XML does not define. Such a label still
decodes to a broken image.

mermaid.render() returns the HTML serialisation of the diagram, so parse
it with the HTML parser it was written for and re-serialise it with
XMLSerializer. That fixes the whole class at once. Against mermaid
11.16 in Chromium, across flowchart, class, state, ER, mindmap, block,
kanban, sequence, journey, gantt, pie, timeline, gitGraph, quadrant and
xychart output, the round-trip yields the same element tree and image
size as the regex wherever the regex output is valid XML, and also loads
the non-breaking-space cases the regex leaves broken.

Drop a label's plain `xmlns` attribute before serialising: the
serializer declares the namespace itself, and a spec-conformant one
(jsdom) writes both, a duplicate attribute.

The tests' fixtures put `<br>` directly in SVG <text>, which mermaid
never emits; replace them with the real 11.16 label shape (XHTML in a
<foreignObject>) and assert the label keeps its line break and its
non-breaking space.

Refs #133089, #134031
2026-10-09 11:57:18 -05:00
flyer
766ab6dd2d fix(desktop): render Mermaid labels that contain <br/>
`mermaid.render()` returns SVG aimed at inline HTML, and its sanitizer
gives back a `<br/>` written in a label as an open `<br>`. That string is
not well-formed XML, so the strict `image/svg+xml` parse in
`normalizeSvgSize` yields the parsererror document instead of an `<svg>`,
takes the `el.tagName !== 'svg'` early return, and hands the *unnormalized*
string back. The diagram `<img>` then gets it as a `data:` URI, which Blink
decodes as strict XML and rejects for the same reason — so the message shows
a broken image whose alt text reads "Mermaid diagram", with nothing thrown
and nothing written to any log.

Close HTML void tags before the markup reaches either XML parse. The pass is
generic rather than a `<br>` special case, because the same class of input
reaches `<hr>`, `<img>`, `<input>`, …, and it is idempotent: already-closed
tags and non-void tags (including names that merely start with a void tag,
such as `<break>`) pass through untouched.

Fixes #133089

Verified by replaying 32 real ```mermaid fences from a session database
through the app's own renderer chunks in Electron 40.10.2 / Chromium 144:
24/32 fail before this change — every failure has `<br/>` in a label, with a
perfect 1:1 correlation — and 31/31 renderable diagrams succeed after it. The
32nd is an invalid Mermaid source that correctly keeps falling back to its
source block.
2026-10-09 11:57:18 -05:00
Maciej Lisiewski
9eab688b42 ci: ruff UP017 fixes 2026-10-09 17:34:36 +02:00
Jeffrey Quesnelle
e0550c97bb Merge pull request #135438 from NousResearch/fix/local-recs-weak-hardware
fix(local-models): stop recommending the 27B on hardware that can't run it well
2026-10-09 11:19:21 -04:00
Teknium
ecf9655b5a refactor(desktop): move plugin-contract type re-exports out of the SDK barrel
Keeps apps/desktop/src/sdk/index.ts under its code-health line cap after the
ctx.pet type exports.
2026-10-09 02:04:56 -07:00
Teknium
ecb66f60dd feat(desktop): plugin SDK pet bubble (ctx.pet.say)
Plugins can now put a short plain-text line in the core pet's speech bubble
instead of locating the pet canvas in the app DOM and floating their own
overlay over it (pet-wallet, #135178).

- ctx.pet.say(text, { id?, tone?, ttlMs? }) -> disposer; ctx.pet.clear(id?);
  ctx.pet.visible (ReadableAtom<boolean>)
- host-owned rendering in PetBubble, in-window (plugin lines only) and in the
  pop-out overlay (lines ride the existing pet-state IPC push)
- plain text, 120-char cap, control/bidi chars stripped, plugin-name label,
  TTL 6 s default (1-30 s), 3 live lines and 10 says/10 s per plugin
- core error / waiting states keep the bubble; nothing shows without a pet
- every line a plugin has up is cleared on unload/disable/reload
- docs: API section, migration-table row for pet-wallet, SDK exports
2026-10-09 02:04:56 -07:00
emozilla
f560c14e73 fix(local-models): recommend nothing when no model reaches the speed floor
When no resident model cleared 20 tok/s, the resolver still recommended the
fastest one (fastest-resident). A "32 GB" machine without a discrete GPU reads
31.5 GB, where only the 27B fits, at a predicted 13 tok/s, so CPU-only and
integrated-GPU machines were steered to a slow dense model. Those machines now
get no recommendation, and every fitting model stays one click away in Browse.
The no-recommendation copy now says the model must run at full speed, the user
guide states the rule, and the unused fastest-resident tooltip string is gone
from every locale.
2026-10-09 00:44:28 -04:00
emozilla
02118fc2de fix(desktop): offer local setup only for the recommended model
The composer's "This could run on your computer" card took the recommended
catalog row, else the first row marked `fits`. A row fits when any build runs,
including one spilled to system RAM, so a GTX 1080 with 16 GB of RAM was
offered the 27B, which the resolver never recommends there. The card now names
the recommended model, and a machine without one gets no card.
2026-10-09 00:19:54 -04:00
Teknium
eb699a158c fix(pm): MSIX plugin installs build on a writable copy of the bundled Python (#135236)
Enabling a plugin with Python dependencies, and `hermes pm install`, failed
in the packaged Windows app: uv lock/sync/pip check exit 101 with "Unable to
create process using ...\venv\Scripts\python.exe".

uv queries a venv by running its Scripts\python.exe. That redirector lives
outside the package and CreateProcess's the interpreter named by
pyvenv.cfg `home`. 6a031bf1b3 moved generations onto the packaged
interpreter, so `home` names a file under WindowsApps that a process without
package identity may not start (exit 101 is the redirector's
RC_CREATE_PROCESS). Restore the verified writable copy of the pinned Python
in the user store as the build interpreter for sealed Windows payloads.

venv_command (also from 6a031bf1b3) stays: Hermes still enters those venvs
on the packaged interpreter, so its own children keep package access to the
bundled git, rg and PM worker.
2026-10-08 20:20:55 -07:00
ethernet
94b1b6d422 refactor(desktop): drive FadeScroll's edge fade with scroll-driven CSS
FadeScroll tracked clipped edges in React state, with a scroll handler and a
resize observer, then rebuilt an inline mask on every change. Move that to a
`scroll-fade-y` utility: the mask's stops are scaled by two registered
<number> properties that the scroller's own scroll position animates
(animation-timeline: scroll(self y)), so an edge only fades while content is
clipped behind it and a list that fits is never dimmed. No listener, no state;
a browser without scroll-driven animations shows no fade.

From each edge inward the mask is `pad` of fully hidden content, then `fade`
of gradient, both optional props. The onboarding apps list sets pad to 1.5rem
so the gradient starts at the chips instead of spending itself on the empty
padding at the box edge. edgeMask/scrollEdges stay for the profile switcher.
2026-10-08 22:10:44 -04:00
ethernet
9809922e04 feat(desktop): fade top/bottom of the onboarding connectors list
The connector/plugin chip picker is height-capped, so the last rows were
clipped with no hint there was more. Wrap the grid in the existing
FadeScroll, which already fades whichever edge has content clipped behind
it and leaves a list that fits untouched.
2026-10-08 22:09:38 -04:00
Teknium
9d05e7ff92 test(desktop): model settings tests clear their toast timers before jsdom tears down
The main-model apply test raises a success toast, which arms a 5 s
auto-dismiss timer. Nothing cleared it, so when it fired after the file's
jsdom window was gone, vitest reported "window is not defined" from
dismissNotification as an unhandled error and failed JS & TS checks with
every test green. afterEach now calls clearNotifications().
2026-10-08 15:20:58 -07:00
kshitijk4poor
d2cbfcf7db test(desktop): the React Compiler cache is its own module with focused tests
The cache lived inline in vite.config.ts, and the renderer builder tests use a bare fixture
config, so nothing exercised it: a broken warm path would still pass every test.

Move it to scripts/compiler-cache.mjs (behaviour unchanged; toolchain files and cache root are
now parameters, and an existing closeBundle hook is chained instead of replaced) and test hit
vs fresh output, toolchain/NODE_ENV generations, corrupt entries, pruning, incremental (watch)
rebuilds and the dev-server bypass. A cold and warm production build of the real app stay
byte-identical. Reported by Greptile on #135092.
2026-10-08 15:20:58 -07:00
kshitijk4poor
1f6d8b6ba8 fix(desktop): a node-pty layout without its macOS spawn-helper is an incomplete native tree
nativeTreeComplete decides whether a later build may skip restaging, but never looked at
node-pty. A partially extracted package with pty.node and no spawn-helper was recorded
complete; repairing the installed package without a lockfile change then left the staged
copy broken, and every later build shipped a terminal that fails on its first spawn.

Check the layout node-pty itself loads (build/Release, build/Debug, then prebuilds, as in
lib/utils.js) for a binding, and on macOS for the spawn-helper beside it (lib/unixTerminal.js).
Reported by Greptile on #135092.
2026-10-08 15:20:58 -07:00
kshitijk4poor
27e0f189ac fix(desktop): the React Compiler cache serves production builds only, keyed by NODE_ENV
The compiler emits HMR cache-reset code when NODE_ENV is development, and the cache key ignored it, so a dev-server session could seed entries a later production build reused. A dev server also wrote one entry per save with no build to prune them. The cache now wraps only 'vite build', NODE_ENV joins the toolchain key, and entries drop the map's sourcesContent (the module text the key already hashes; restored on a hit): 22 MB -> 15 MB. Verified: warm == cold byte-identical without sourcemaps; with --sourcemap two cold builds already differ in one map's __VITE_ASSET__ placeholder order, so that diff is vite's, not the cache's.
2026-10-08 15:20:58 -07:00
kshitijk4poor
3fc37481eb refactor(desktop): one receipt reader, one output list, no native double copy on renderer reuse
rendererCurrent and productCurrent share hostReceipt() for the schema/product/host checks. The stamped outputs come from bundleElectronMain's own electronOutputs list, so a renamed or added bundle can never be copied stale from the previous dist. The reuse copy skips node_modules/ and native/, which copyNativeTree recopies from nativeDeps right after, and a --typecheck build never reuses a renderer that skipped tsc.
2026-10-08 15:20:58 -07:00
kshitijk4poor
136a4282bb fix(desktop): an incomplete native tree restages on every build
Skipping stage-native-deps on an admitted receipt kept a tree staged without a piece forever:
get-windows (or its win32 x64 binding, which only the native installer inside staging can fetch),
the macOS get-windows helper, or the host HUD helper (no X11 toolchain on Linux). The receipt
identity cannot see the host fix that would complete it.

The receipt now records `degraded` from the staged tree itself (nativeTreeComplete: every piece
this host can produce for the target is present), and build.mjs reuses only a complete one
(nativeInputsComplete). Gaps no host fix closes — win32-arm64 has no get-windows prebuild, a
cross-target pack never builds the helpers — do not count. Judging the tree rather than each
builder's return value covers soft-fail paths that still return a path (the win32 binding).
2026-10-08 15:20:58 -07:00
kshitijk4poor
97260490e1 perf(desktop): skip restaging native inputs whose receipt still matches 2026-10-08 15:20:58 -07:00
kshitijk4poor
6232135eca perf(desktop): cache the React Compiler pass by content, so an update recompiles only the files it changed 2026-10-08 15:20:58 -07:00
kshitijk4poor
1d41d9f610 fix(desktop): native helpers build byte-identical, so the prepared native input stays comparable 2026-10-08 15:20:58 -07:00
ethernet
aac3440bac fix(desktop): build-bundle cleans under the lock, restores the shell, never signs
Review findings on the local build scripts:

- Cleanup ran before desktop.py took the checkout lock, so a second run deleted
  the outputs of a build that was still going. desktop.py gains --clean, which
  removes this checkout's outputs after build_lock is held. Both scripts pass it
  instead of deleting files themselves.
- build-bundle.ps1 left RELEASE_CLAIM_* and HERMES_PAYLOAD_* in the caller's
  session. It now saves those variables and restores them in the finally block.
- The scripts said "unsigned" but inherited signing credentials. The ps1 removes
  AZURE_* for the build. The sh removes CSC_* and APPLE_* and sets
  CSC_IDENTITY_AUTO_DISCOVERY=false, the switch the desktop rebuild command uses.
  The wording now says what is ignored.
- The Store bundle step needs the claim timestamp, and the script deletes the
  claim. The ps1 prints it as HERMES_RELEASE_EPOCH, and BUILDING.md documents it.
2026-10-08 18:02:26 -04:00
ethernet
57949d6630 feat(desktop): build-bundle scripts for local test bundles
scripts/build-bundle.sh (macOS, Linux) and scripts/build-bundle.ps1 (Windows)
build the checked-out commit through scripts/bundles/desktop.py --commit. That
path needs no tag and no push.

Both scripts refuse a dirty tree before any work starts. Both delete the
previous build output first. The staged Windows payload is read-only, so a
plain delete in a reused checkout fails with WinError 5.

build-bundle.ps1 -Store builds the Microsoft Store MSIX. The driver admits
Store packaging only for a stable tag. The script makes a local claim tag
rc.<N>-vX.Y.Z for the next patch after the newest published stable tag. It
counts local and remote attempt refs and starts at 900, so it cannot collide
with a real attempt. A finally block deletes the tag. The script never pushes it.
2026-10-08 18:02:26 -04:00
Maciej Lisiewski
d76cce4f11 ci: apply all automatic linter fixes available 2026-10-08 17:22:54 +02:00
teknium1
8f463316fd feat(plugins): one name resolver for every verb; one switch turns a unified package on
- hermes_cli: _find_plugin_entry is the single resolver (key / manifest name / unique leaf /
  catalog name via the installer-owned record). enable/disable/show/capabilities/update/remove,
  the dashboard and plugins.manage settings all go through it, so the catalog name you
  installed with keeps working (17 catalog entries install under another manifest name).
  _catalog_install_on_disk reuses the same catalog lookup.
- Desktop hub: join agent rows to desktop halves by package folder (what Electron stamps),
  not manifest name, so a self-cloned package is one row instead of two.
- Desktop: turning a unified package on (install dialog 'Enable after install' or the Agent
  switch) also turns its opt-in desktop half on, unless the user switched it off. Off stays
  per half. Capability consent is untouched.
- docs: plugin-catalog page.
2026-10-08 04:12:02 -07:00
teknium1
dacb06ea93 feat(plugins): show on_session_finalize return messages to the user
A plugin's on_session_finalize hook can now return a str or {"message": str}
and Hermes shows it at the session boundary without starting a turn or
rewriting the model reply:

- CLI /new: printed before the new session; CLI quit: printed above the
  exit summary; one-shot -q/-z: printed to stderr (stdout stays the answer)
- TUI: session.close returns the messages; /new shows them as system lines
- Gateway /new and shutdown: sent to the owning chat via the adapter, while
  adapters are still connected

on_session_end stays observer-only: it fires after every turn, not at a
session boundary. Gateway finalize dispatch moves to
gateway/run_shutdown_session_end.py.
2026-10-08 04:11:24 -07:00
teknium1
cc1500a071 fix(desktop): a linked plugin checkout drives its desktop half; doctor flags a stale copy
The Desktop loads a unified package's desktop half from the copy in
desktop-plugins/<name>/, not from the package. Editing the source shipped
nothing and nothing said so:

- a package symlinked into plugins/ (the in-place dev layout the Python
  loader already follows) was never copied at all — Dirent.isDirectory()
  is false for a symlink;
- an installed package edited in place stayed stale until Rescan/restart.

Electron now follows symlinked package dirs, marks their copy linked, and
re-syncs it on any byte difference. The renderer watches a linked copy's
source plugin.js and, on save, runs the reconcile and hot-reloads. Catalog
and git installs keep the mtime rule. `hermes plugins doctor` warns when
the copy differs from the source and says how to refresh it.
2026-10-08 04:07:30 -07:00
Teknium
0240fa4a84 fix(release): read the Hermes version line in the Nix check and accept rc.N attempt refs
Two stable-release identity checks broke on the first pipeline cuts of v0.21.6:

- nix.yml "Verify release package runtime identity" took the LAST token of the
  whole `hermes --version` output. That output ends with the OpenAI SDK line, so
  the check compared 0.21.6 against 2.24.0 and failed every release. Parse the
  first line ("Hermes Agent v<version> ...") instead.
- msix-shared.mjs releaseEpoch() still required the retired `vX.Y.Z-rc` claim
  name. Attempt refs are `rc.<N>-vX.Y.Z` (versioning.parse_attempt_ref), so the
  Windows universal bundle threw "Stable claim tag differs from its payload tag".
  Accept exactly `rc.<N>-<tag>`; a different version (rc.3-v0.27.10 for v0.27.1)
  still refuses.
2026-10-08 01:58:48 -07:00
Teknium
167e9fdb84 fix(desktop): never idle-reap a local profile backend (#134942)
* fix(desktop): never idle-reap a local profile backend

Support f502bc6f: with a remote primary, a "This device" profile's local
`hermes serve` child was SIGTERM'd by the pool idle reaper after 60 min with
nothing streamed (the #105239 pinned-tier TTL), leaving "This device ·
Backend offline" while the user still had the chat open.

A local backend runs its profile's cron jobs and bot chats with nobody
watching, so idleness is not a reason to kill it. The idle reaper now only
reclaims connection descriptors (remote routes: no child, no slot). Local
children are still bounded by the maxBackends slot cap and foreground
reclaim (activeTurn vetoed), unchanged. Removes POOL_PINNED_IDLE_MS.

The Backend Idle Timeout setting description now says it applies to remote
connections only.

E2E no-idle-reap.spec.ts: a pooled local profile backend survives two reaper
ticks past both idle windows. Fails on main (backend reaped), passes here.

* chore(desktop): drop the dead pinned-tier idle policy (retireIdle, lastStreamedAt)

Greptile P2 on #134942: with local children no longer idle-reaped, retireIdle
has no production caller and lastStreamedAt no reader. Removes both, the stamp
write in touchPoolBackend, and the pinned-tier test. The shared retirement
safeguards stay covered through evictTo (identity, admission permit, activeTurn
veto).
2026-10-08 00:54:37 -07:00
ethernet
05d5d7e537 fix(desktop): tag every line of a multi-line waiter script error
PowerShell errors span several lines (message, location, details). The
script tagged only the first physical line with the attempt, so the
parent's attempt filter kept that line and dropped the rest. Write-WaiterLog
now tags each non-empty line.
2026-10-08 00:41:22 -04:00
ethernet
ddde33122d fix(desktop): make the relaunch waiter's script log per-attempt, UTF-8 and bounded
Review follow-up to #134911, which merged before these landed.

- The script log is shared by every attempt, so a failure report could
  quote another attempt's lines. Each line now carries the stage dir
  name and waiterLogTail keeps only the current attempt's.
- Add-Content writes the Windows code page by default while the reader
  decodes UTF-8, which mangles localized errors and non-ASCII paths. The
  script now appends explicit UTF-8 without a BOM.
- waiterLogTail read the whole append-only file before slicing. It now
  reads at most the last 16KB, and the script restarts the log once it
  passes 64KB so it cannot grow without bound.
2026-10-08 00:33:33 -04:00
ethernet
bdb8f92f3b fix(desktop): log why the MSIX relaunch waiter failed to start
startRelaunchWaiter returned undefined for four different failures
(staging dir, script copy, PowerShell spawn, missed handshake), and the
detached script runs with stdio ignored. The Store updater then showed
only "Could not register automatic relaunch" and desktop.log had no
reason, so a user report could not be told apart.

Each failure path now writes its cause to desktop.log (error text,
paths, exit code or signal, elapsed ms). The script appends its own
steps and exit reason to %TEMP%\hermes-relaunch-waiter.log, which
survives the stage dir it deletes on exit, and a failed handshake
includes that file's tail in desktop.log.

The two waiter call sites shared the same options block; they now go
through one startUpdateRelaunchWaiter so the log hook cannot be
forgotten at one of them.
2026-10-08 00:22:46 -04:00
Brooklyn Nicholson
08165d5893 feat(desktop): the dimmed composer comes back after 5s without scrolling 2026-10-07 21:08:42 -05:00
Brooklyn Nicholson
436a6e130e fix(desktop): the scrolled-up composer comes back on hover or focus anywhere in the dock 2026-10-07 21:08:42 -05:00
hermes-seaeye[bot]
e675225af2 fmt(js): npm run fix on merge (#134819)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-07 23:35:01 +00:00
ethernet
bc2e4d3773 Merge pull request #134209 from NousResearch/sid/onboarding
feat(onboarding): first-run setup chat
2026-10-07 19:17:15 -04:00
Austin Pickett
6869d009c9 fix(desktop): the link-title window cannot become visible
On Windows Desktop the hidden tier-2 link-title BrowserWindow has still
painted a blank top-level frame over the chat during browser-tool turns
(#64867). Encode the invisibility contract in the launch options —
off-screen bounds, skipTaskbar, focusable: false — and belt-and-suspenders
it with setOpacity(0) + hide() after creation, best-effort like the audio
mute.

Salvaged from #66523 (thanks @stantheman0128).
2026-10-07 17:13:33 -04:00
Austin Pickett
649d6c0391 test(desktop): fold the MEDIA pdf card case into the existing media suite 2026-10-07 17:05:12 -04:00
Austin Pickett
dbbc8d7947 test(desktop): pin the preview height clamp's viewport growth and ceiling 2026-10-07 17:05:12 -04:00
Austin Pickett
7b9ce36a21 fix(desktop): widen the inline image preview envelope so stacked image sets stay viewable 2026-10-07 17:05:12 -04:00
Austin Pickett
39a01d069d fix(model-picker): keep custom provider rows and the current model honest across switches
Three defects in one class, backend + renderer:

- _lap_bare_custom_row only built the bare `model.provider: custom` row
  while the session was ON it; switching away made it vanish and the
  unconfigured-canonical fallback substituted a 0-model placeholder
  (#59702). Recover the endpoint from the config.yaml `model:` block when
  the caller's current-provider slice points elsewhere; is_current is then
  true only when that slice points back at the URL, and the live probe
  stays gated on refresh / probe-current.

- The Desktop Settings model page kept the previous provider's model
  selected after a provider switch (withActive() painted it as still
  selectable, and Apply would pin the new provider to the old model),
  and a named custom_providers entry the user just switched to showed 0
  models because the normal non-refresh options load probes only the
  CURRENT custom provider (#59063). A user-driven switch now clears the
  model and fires one refresh-scoped options fetch for an empty
  configured endpoint.

- groupModels derived model families solely from the provider catalog;
  an OpenRouter current model the catalog omits left the picker with no
  active-model row (#57534). Prepend the current model when the catalog
  lacks it.

Tests: red on base for the bare-custom regression, the settings switch,
and the missing-current-model row; green with the fix.
2026-10-07 17:04:44 -04:00
hermes-seaeye[bot]
3090ef7731 fmt(js): npm run fix on merge (#134737)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-07 20:26:07 +00:00
Austin Pickett
3adc457de5 Merge remote-tracking branch 'origin/main' into sid/onboarding
Conflicts, all in apps/desktop/src/i18n:
- de/en/es/fr/zh.ts, types.ts: import blocks only. Main added the
  *_projects section split, this branch the *_onboarding one; kept both.
- en_shared_metrics, zh_shared_metrics, ar_common, zh-hant (usage-stats
  consent copy): kept this branch's rewritten copy. Main's only change on
  its side since the merge base was a prettier reflow of
  collectedReliability (2943ee6f19), which the branch's rewrite replaces;
  types_shared_metrics on HEAD already matches the branch's keys.

Follow-through: main's Undo-hide toast (sidebar.projects.hiddenFromSidebar /
undoHide) arrived without ar or zh-hant strings, which this branch's
overlay-completeness test rejects. Added both to ar_chrome / zh-hant_chrome
and regenerated locales/_keys.desktop.json.
2026-10-07 16:16:23 -04:00
Austin Pickett
354724fb08 style(desktop): sort the projects-sibling imports per perfectionist 2026-10-07 16:08:56 -04:00
Austin Pickett
bf8cf34c50 refactor(desktop): move sidebar projects copy into per-locale siblings for #73091
The undo-hide toast strings pushed 8 locale catalog files past their
main-pinned FILE_LINES caps (they may only go down). Offset the growth
the AGENTS.md way: extract the existing sidebar.projects block from each
over-cap file (en/de/es/fr/ja/ru/zh/types) into a topical sibling
<lang>_projects.ts following the <lang>_boot / <lang>_model_menu
convention. No key changes; locales/_keys.desktop.json verified unchanged.
No behaviour change — pure code move.
2026-10-07 16:08:56 -04:00