Commit graph

1375 commits

Author SHA1 Message Date
hermes-seaeye[bot]
7c9d05267c
fmt(js): npm run fix on merge (#69939)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-23 07:08:53 +00:00
Brooklyn Nicholson
9859e1f7df fix(voice): speak the whole turn, not just its first bubble; idle-flush held narration
Two fixes for desktop hands-free voice:

- The live speech session bound to the first assistant bubble with text, so
  a tool-calling turn spoke only the opening narration and silently dropped
  every later interim AND the final answer. The conversation selector now
  aggregates all unspoken assistant bubbles in order (turn-scoped speech);
  auto-speak keeps its latest-reply-only behavior.

- The speak-stream WS producer blocked forever on the text queue, so a
  narration line with no trailing whitespace ("Let me check.") sat in the
  sentence chunker until end-of-turn — spoken long after the tool finished,
  with the UI stuck on "Preparing audio…". Mirror the CLI speaker's idle
  flush: sentence-terminated buffers flush after 0.5s of producer silence,
  anything else after ~2s; open <think> blocks are never flushed.
2026-07-23 01:53:37 -05:00
Brooklyn Nicholson
8cfc9e4e27 chore(desktop): dev-only credit-notice demo hotkey
Ctrl+Shift+C (and window.__creditsDemo()) steps the full credit-notice
lifecycle (usage 50->75->90, grant-spent, depleted/restored) through the real
gateway event fan-out via a new emitLocalGatewayEvent, so the toast/native/
billing-invalidation paths are testable without hitting real usage bands.
Installed only under import.meta.env.DEV, so it's tree-shaken from production.
2026-07-23 01:28:59 -05:00
Brooklyn Nicholson
664b131c30 fix(desktop): billing page always fetches fresh state (team-account desync)
The x-nous-credits-* headers are best-effort and can drift out of sync,
notably in team/org accounts where another member's spend moves the shared
balance without touching this client's headers. The billing endpoint is the
source of truth, so the page no longer trusts a cache: staleTime 0 +
refetchOnMount 'always' force a fresh fetch on every open and focus (still
polling 30s while mounted). The credits.* invalidation nudge still pulls a
crossing in immediately.
2026-07-23 01:28:59 -05:00
Brooklyn Nicholson
d010220588 fix(desktop): de-dupe credit toast icon, band-color the figure, split detail
Three fixes to how agent credit notices render as toasts:
- Strip the leading severity glyph (the toast already draws a kind icon, so the
  raw text doubled it). Native OS notifications keep the glyph (no icon there).
- Icon top-margin is now 0.42ch (font-relative) instead of a fixed rem.
- Band-color the $used figure (semibold) by $used/$cap: muted <75%,
  --ui-orange >=75%, --ui-red >=90% (depleted red, restored green), reusing the
  existing --ui-* usage palette. Icon shares the accent.
- Split a trailing '. detail' into a muted secondary line (title+description
  convention) instead of an inline middot.
Generic 'accentColor' + 'meta' slots on the notification; degrades gracefully
when a notice has no figure.
2026-07-23 01:28:59 -05:00
Brooklyn Nicholson
b8a2b9b93e feat(desktop): native OS credit alerts + billing-page nudge (#69808)
Round out the credit-notice handling from the previous commit with the two
optional pieces from the issue:

- Native OS notification for the urgent pair. `credits.depleted` /
  `credits.restored` also fire an Electron notification when Hermes is
  backgrounded, via a new `credits` NativeNotificationKind (the existing
  five didn't fit) with its own toggle in Settings → Notifications (the
  panel is data-driven off NATIVE_NOTIFICATION_KINDS, so the toggle and
  i18n are the only additions). The escalating usage line and grant-spent
  notice stay in-app toasts only. Dispatch is `global` (account-wide, not
  session-bound) and gated by the user's prefs + backgrounded check.
- Billing-page nudge. A `credits.*` crossing invalidates the
  `['billing','state']` query so Settings → Billing reflects the change
  immediately instead of waiting up to 30s for its poll.

`nativeNoticeInput()` is a pure mapping (urgent-key gate → native input),
unit-tested directly; the gateway-event branch does the localized-title
lookup and gated dispatch. i18n added for all four locales.
2026-07-23 01:25:58 -05:00
Brooklyn Nicholson
58e3d41582 fix(desktop): render agent credit notices as toasts (#69808)
The desktop renderer had no handler for the `notification.show` /
`notification.clear` WS events, so every credit-usage notice the backend
sends (`agent/credits_tracker.py` → `tui_gateway/server.py`) was silently
dropped. Credit warnings like "• Credits 50% used · $220.00 cap" never
appeared, even though the Ink TUI renders them in its status bar.

Add the two missing branches to the gateway-event dispatcher, delegating
to a small, pure-testable module:

- `store/agent-notices.ts` — `noticeToToast()` maps a notice to a toast
  (level → toast kind, sticky → durationMs 0, ttl → ttl_ms), and uses the
  notice `key` as the toast id. Re-emitting the same key REPLACES the
  toast, so the credits 50→75→90 line escalates in place instead of
  stacking, and a key-matched `notification.clear` maps straight to
  `dismissNotification(key)`.
- The notice `text` already carries its own glyph (• ⚠ ✕ ✓), so no toast
  icon is added.
- Notices are account-wide, so the toast shows regardless of which
  session is focused.

The Ink TUI (`ui-tui/src/app/turnController.ts`) is the reference for the
latest-wins / sticky-vs-ttl / key-matched-clear behavior.

Export `NotificationInput` so the mapping's return type can be named.
2026-07-23 01:25:58 -05:00
brooklyn!
0721d2ea80
Merge pull request #69884 from NousResearch/bb/salvage-55707-onboarding
fix(desktop): stop spurious onboarding for keyless custom providers (supersedes #55707, #45224)
2026-07-23 00:28:29 -05:00
brooklyn!
61e7db2820
Merge pull request #69887 from NousResearch/bb/tile-resume-profile
fix(desktop): route tiled session resumes to the owning profile
2026-07-23 00:28:01 -05:00
ethernet
26f1f6a76b
feat(desktop): improve tool call detail views (#69868)
* fix(desktop): improve fallback tool-call details

Show failed image-generation calls through the normal fallback row, remove duplicate normal-mode web-search JSON, and format Technical Mode payloads as readable JSON.

* feat(desktop): render terminal tool calls as transcripts

Show terminal commands with a prompt and exit status, then reveal ANSI-safe stdout and stderr in the expanded tool row.

* fix(desktop): reconcile tool calls by command

Match context-only tool starts with command-bearing completions when their IDs differ, preventing stale duplicate terminal rows. Show the web-search query above its result cards.
2026-07-23 01:27:38 -04:00
Brooklyn Nicholson
c3602f7f05 fix(desktop): route tiled session resumes to the owning profile
`resumeTile` — the cold-resume path for a session opened in a tile / split
pane — resumed with `{ session_id, cols }` and read messages with no profile,
so a tile opening a session from another profile let the gateway fall back to
the launch-profile DB and fork the conversation into the wrong profile: the
same cross-profile bleed the recovery resumes had (#67603), just a sibling
call path. Resolve the owning profile via the shared `resolveSessionProfile`
and carry it on both the transcript prefetch and the resume RPC.

Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>
2026-07-23 00:18:35 -05:00
Brooklyn Nicholson
58ffc10c17 fix(desktop): stop spurious provider onboarding from credential warnings
Narrow the setup-error matcher, match the server's empty-key contract,
and route gateway-event plus create/resume/branch through one
credential-warning policy. Preserve configured=true only for
non-authoritative transport fallback.

Co-authored-by: Yingliang Zhang <zhangyingliang@outlook.com>
Co-authored-by: Brandon R <kingdomwarrior23@gmail.com>
2026-07-23 00:18:21 -05:00
Gille
7cbdcf1ba6
fix(desktop): render remote markdown images in chat (#57944)
* fix(desktop): render remote markdown images in chat

* test(desktop): cover remote markdown image resolution
2026-07-23 00:16:20 -05:00
hermes-seaeye[bot]
ca16a8e07d
fmt(js): npm run fix on merge (#69879)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-23 05:14:26 +00:00
brooklyn!
e59dcf46f1
fix(desktop): end the #67603 model-switch dup, cross-profile session bleed, and [System:] bubble (#69861)
* fix(desktop): stop model-switch dup + route recovery resumes to the owning profile

Fixes two Desktop session-reconciliation symptoms from #67603.

Symptom 1 — duplicated user bubble after a model switch. The gateway
persists model-switch / personality notices as role=user `[System: …]`
rows (tui_gateway/server.py) so strict OpenAI-compatible providers don't
reject a non-leading system message (#48338). `preserveLocalPendingTurnMessages`
paired local optimistic rows with the stored transcript by user-role
ordinal, so a marker between two real user turns shifted every later
ordinal and the optimistic row was re-appended at the bottom. The single
trailing-marker case is already covered by the compression-era
`latestAuthoritativeUser` guard, but two switches around one turn (marker
before AND after the committed prompt) still duplicated it. Exclude
`[System:` bookkeeping markers from ordinal pairing on both sides.

Symptom 2 — a session appearing under two profiles. The main resume path
already resolves a session's owning profile via `resolveStoredSession`
(cache → active backend → cross-profile probe), but the recovery
`session.resume` calls (stale runtime id, session-not-found, wedged loop,
redirect) omitted `profile`, so the gateway fell back to the launch-profile
DB and forked the conversation into the wrong profile. Route every recovery
resume — and an uncached right-click branch — through the same resolver so
the profile is carried even for sessions outside the paginated sidebar
window (the cache-miss gap).

Tests: discriminating two-switch marker test (fails before, passes after);
cache-hit + cross-profile cache-miss coverage for the recovery resume and
for branching an uncached session.

Supersedes #68665 and #63590.
Closes #67603.

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>

* fix(desktop): scope the remembered session id per profile

A single global `hermes.desktop.lastSessionId` key remembered ONE session
across every profile, so a relaunch or cold start under profile B would try
to restore a session owned by profile A — reinforcing the impression that a
conversation had bled between profiles (#67603, second symptom).

Key the remembered id by the session's owning profile (resolved from the
session row's `profile`, falling back to the active gateway profile), read it
back for the active profile on restore, and clear an exhausted session under
its owner. The default profile keeps the original unsuffixed key so existing
installs' remembered session survives the upgrade.

Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>

* fix(gateway): hide [System:] bookkeeping markers from every transcript

Model-switch and personality notices are persisted as role=user `[System: …]`
rows so strict providers accept them mid-history, but they are model-facing
runtime metadata, not user turns. `_history_to_messages` — the single display
projection every client reads — passed them straight through, so on resume or
reload they rendered as a fake user bubble in the desktop, TUI, CLI, and web
transcripts.

Drop them in that projection. The raw marker stays in `session["history"]`
for the model, so nothing changes for inference; only the display loses a row
that never belonged to the user. This also removes the stored marker from the
payload the desktop reconciles against, killing the ordinal shift that
duplicated the optimistic prompt (#67603) at its source — the desktop-side
marker exclusion remains as a fallback for older backends.

Co-authored-by: Dolverin <Dolverin@users.noreply.github.com>

---------

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>
Co-authored-by: Dolverin <Dolverin@users.noreply.github.com>
2026-07-23 00:05:25 -05:00
brooklyn!
e1367b44bc
fix(desktop): let clarify choices and overlays keep their keys from type-to-focus (#69869) 2026-07-23 00:04:12 -05:00
Brooklyn Nicholson
143942d497 fix(desktop): scope the remembered session id per profile
A single global `hermes.desktop.lastSessionId` key remembered ONE session
across every profile, so a relaunch or cold start under profile B would try
to restore a session owned by profile A — reinforcing the impression that a
conversation had bled between profiles (#67603, second symptom).

Key the remembered id by the session's owning profile (resolved from the
session row's `profile`, falling back to the active gateway profile), read it
back for the active profile on restore, and clear an exhausted session under
its owner. The default profile keeps the original unsuffixed key so existing
installs' remembered session survives the upgrade.

Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>
2026-07-22 23:53:59 -05:00
brooklyn!
6096f73ce8
feat(desktop): add keyboard navigation to clarify choices (#69799)
Co-authored-by: Mapurite <272619650+mapu-og@users.noreply.github.com>
2026-07-23 04:48:32 +00:00
Brooklyn Nicholson
5fccc9aae9 fix(desktop): stop model-switch dup + route recovery resumes to the owning profile
Fixes two Desktop session-reconciliation symptoms from #67603.

Symptom 1 — duplicated user bubble after a model switch. The gateway
persists model-switch / personality notices as role=user `[System: …]`
rows (tui_gateway/server.py) so strict OpenAI-compatible providers don't
reject a non-leading system message (#48338). `preserveLocalPendingTurnMessages`
paired local optimistic rows with the stored transcript by user-role
ordinal, so a marker between two real user turns shifted every later
ordinal and the optimistic row was re-appended at the bottom. The single
trailing-marker case is already covered by the compression-era
`latestAuthoritativeUser` guard, but two switches around one turn (marker
before AND after the committed prompt) still duplicated it. Exclude
`[System:` bookkeeping markers from ordinal pairing on both sides.

Symptom 2 — a session appearing under two profiles. The main resume path
already resolves a session's owning profile via `resolveStoredSession`
(cache → active backend → cross-profile probe), but the recovery
`session.resume` calls (stale runtime id, session-not-found, wedged loop,
redirect) omitted `profile`, so the gateway fell back to the launch-profile
DB and forked the conversation into the wrong profile. Route every recovery
resume — and an uncached right-click branch — through the same resolver so
the profile is carried even for sessions outside the paginated sidebar
window (the cache-miss gap).

Tests: discriminating two-switch marker test (fails before, passes after);
cache-hit + cross-profile cache-miss coverage for the recovery resume and
for branching an uncached session.

Supersedes #68665 and #63590.
Closes #67603.

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>
2026-07-22 23:42:43 -05:00
hermes-seaeye[bot]
328e4f5a1e
fmt(js): npm run fix on merge (#69852)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-23 04:40:24 +00:00
brooklyn!
dc861964fc
fix(desktop): clarify options defensive rendering and layout fix (#69796)
- Replace undefined  CSS class with Tailwind v4's
   so long choice text wraps properly
  instead of overflowing the button container.
- Add  validation that strips non-string items,
  blanks, newlines, and text >200 chars — preventing garbage/JSON
  arrays from rendering as raw values.
- Add diagnostic  logging at both the gateway event
  handler and the tool-args parser when choices are dropped, so
  malformed payloads are no longer silent.
- Apply  in both the gateway event handler
  () and the inline tool-args parser
  () for consistent defense in depth.
- Add unit tests for  covering null, non-array,
  mixed-type, blank, multiline, and overlong inputs.

Closes #69122

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-23 04:32:33 +00:00
brooklyn!
46fb10203b
Merge pull request #69841 from NousResearch/bb/salvage-69428-expandable-overlay
fix(desktop): free code-block scrollbar & last-line selection from the toggle overlay
2026-07-22 23:29:34 -05:00
brooklyn!
d21165c2f0
fix(desktop): keep clarify answerable across reconnect/hydration + tool-progress off (#69795)
* fix(desktop): keep clarify lifecycle when tool progress is off

* fix(desktop): render clarify prompt from the request event

Re-authored onto the current use-message-stream/gateway-event.ts (the
original patched the pre-split use-message-stream.ts). When the tool.start
row that normally mounts the inline clarify UI is missed (stream reconnect
/ hydration race), upsert a stable pending clarify tool row from
clarify.request itself so the prompt stays answerable; a real
tool.start/complete with the same request id merges rather than duplicates.

Co-authored-by: 정수환 <centerid@naver.com>

* chore(contributors): map centerid@naver.com -> lidises

Attribution mapping for the salvaged #47544 commit.

* fix(desktop): correlate clarify rows by question so hydration can't duplicate

The hydrated row (from clarify.request's request_id) and the real tool.start
row (the model's tool_call_id) have different ids, so id-only matching appended
a second clarify card in the normal path (caught by the BLOCKING_CLARIFY e2e:
'question' resolved to 2 elements). Add 'question' to the tool match-value keys
so a clarify upsert merges into the existing pending clarify row regardless of
id (same request<->args correlation ClarifyToolPending already uses); when no
row exists yet (reconnect/hydration) it still creates one.

---------

Co-authored-by: 정수환 <centerid@naver.com>
2026-07-22 23:25:48 -05:00
Brooklyn Nicholson
936b407a72 fix(desktop): free code-block scrollbar & last-line selection from the toggle overlay
`ExpandableBlock` used a single full-width `absolute inset-x-0 bottom-0`
button that was BOTH the fade overflow cue and the expand/collapse control.
It sat on top of the whole 28px bottom strip, so it swallowed:

- the horizontal scrollbar of a wide code block (couldn't scroll sideways), and
- pointer/drag events on the block's last line (couldn't select/copy the tail).

Split the two responsibilities: the full-width fade stays as a pure
`pointer-events-none` cue, and the only clickable target is a compact toggle
pinned to the right edge (`pointer-events-auto`), clear of the draggable
scrollbar track. The inner container gains `overflow-x-auto` so wide code
gets a working scrollbar. Applies uniformly to code cards and the plain-text
fallback — no per-call prop needed.

Regression test asserts the pointer-events contract (fade `pointer-events-none`
+ full-width, toggle `pointer-events-auto` + right-pinned, not `inset-x-0`) and
that the toggle still flips `aria-expanded`.

Supersedes #69558, #69428
Fixes #69168

Co-authored-by: alelpoan <alelpoan@proton.me>
Co-authored-by: BerneYue <xiongyue_hnu@163.com>
2026-07-22 23:23:39 -05:00
ethernet
406d7a67f0
fix(desktop): split steer and queue keyboard shortcuts (#69797)
Keep Enter as the Cursor-style live-turn steering gesture and assign
Ctrl/Cmd+Enter to queue the current draft. Align keybind settings,
tooltips, translations, and the Electron queue-boundary coverage.
2026-07-23 00:13:58 -04:00
hermes-seaeye[bot]
2ebeede006
fmt(js): npm run fix on merge (#69823)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-23 04:09:14 +00:00
brooklyn!
88a9557385
Merge pull request #69812 from NousResearch/bb/desktop-tab-close-shift
fix(desktop): session-tab UX — ⌘W tab-shift, draft new-tab, unified status dot, stable lanes & optimistic delete
2026-07-22 23:01:17 -05:00
Brooklyn Nicholson
56b2b5a331 fix(desktop): mark session delete/archive in-flight so the row can't flash back
removeSession/archiveSession now pin their tombstone via beginSessionMutation
until the RPC settles (finally → endSessionMutation), keyed per id so
concurrent deletes across worktrees stay independent. This lets the
projects.tree prune hold the optimistic removal through the whole in-flight
window — no shared lock, no serialization.

Drops the now-redundant post-success re-filter band-aid in archiveSession:
the refresh honors tombstones generically, so there's nothing left to win.
2026-07-22 22:54:28 -05:00
Brooklyn Nicholson
4db2f78ec7 fix(desktop): honor optimistic session tombstones on list & tree refresh
A refresh racing an in-flight delete/archive resurrected the just-removed
row: refreshSessions repopulated $sessions straight from the backend page
ignoring tombstones, and the projects.tree prune dropped a tombstone the
moment its id left scoped_session_ids — so the grouped lane un-filtered it
too. The row only vanished on a later refresh once the RPC finally landed.

refreshSessions now filters tombstoned rows (and their lineage tip) before
merging. The prune keeps a tombstone while its mutation is still in flight
locally via $sessionMutationsInFlight, then hands it back to the normal
scoped-based prune once settled.
2026-07-22 22:54:25 -05:00
Brooklyn Nicholson
66747f154c fix(desktop): auto-expand a lane when starting a session in it, and stabilize sidebar collapse state
Clicking '+' on a collapsed worktree/branch lane (or repo) created a session in a folder the user couldn't see. It now force-expands the target node.

The root cause was that collapse state was stored as an XOR override of defaultOpen. defaultOpen flips for a worktree lane (collapsed while empty, open once it holds a session), so an explicit expand of an empty lane silently re-read as a 'collapse' the moment the lane gained its first row - collapsing the very lane you'd just opened to work in. Store the resolved open/collapsed boolean per node instead ($sidebarWorkspaceNodeOpen), which survives the default flip; one-time migration off the old set. The review file-tree shares this store and is updated to match.
2026-07-22 22:43:55 -05:00
Brooklyn Nicholson
87aaf87748 fix(desktop): render session status dot from one primitive (sidebar, tiles, main tab)
The sidebar row and the pane tabs each painted their own dot from different data: the sidebar read color from $sessionColorById[id] (map-only, no resolver fallback) layered with live state, while a tab painted a flat 'accent' color via sessionColorFor (map + fallback). A session older than the recents page missed the map, so the same session showed grey in the sidebar and its project color in the tab.

Add a single SessionStatusDot primitive keyed by the stored session id (the key every live-state atom already uses) that resolves color (override -> project, with fallback) and live state (working/needs-input/stalled/unread/background) itself. The sidebar row, session tiles, and the main workspace tab all render it, so a session's status/color can never disagree across surfaces. Tabs gain the full live status (pulse etc.), not just a static color. Collapses the now-orphaned generic 'accent' tab-dot path into the one primitive.
2026-07-22 22:43:51 -05:00
hermes-seaeye[bot]
7d96e602a9
fmt(js): npm run fix on merge (#69805)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-23 03:32:32 +00:00
ethernet
9f5e568812
fix(desktop): prevent stale worktree status (#69781)
Clear the coding rail while a session's workspace changes and discard late
Git status results for the previous cwd. This prevents Ctrl+Shift+B from
opening a worktree dialog against an old branch.
2026-07-22 23:24:17 -04:00
ethernet
dbf4f69b72
fix(desktop): queue prompts during context compaction (#69783) 2026-07-22 23:19:22 -04:00
Brooklyn Nicholson
067cb9e033 feat(desktop): shift next tab into main on ⌘W + always-shown new-session tab
- ⌘W on the main tab promotes the next stacked session tab into main
- '+' / ⌘T open a new 'New session' tab, unlisted until first message
  (no sidebar pollution); multiple new-session tabs allowed
- tile resolves its own row via by-id lookup once it has a message
2026-07-22 22:18:33 -05:00
brooklyn!
5c4d358a7e
Merge pull request #69750 from NousResearch/bb/desktop-fork-new-tab
fix(desktop): open branched chat in a new tab and switch to it
2026-07-22 22:17:17 -05:00
brooklyn!
6d76f5ca51
Merge pull request #69769 from NousResearch/bb/salvage-69720-clarify-late
feat(desktop): skipped clarify keeps its choices visible and answerable
2026-07-22 22:12:53 -05:00
Brooklyn Nicholson
f91d5b2d13 chore(desktop): dedupe session-color precedence, tighten tileStoredRow
- Extract resolveSessionColor(): the sessionColorFor fallback no longer
  re-implements the override -> project-color precedence that the
  $sessionColorById computed already owns.
- tileStoredRow: collapse the nested project-tree walk into a flatMap +
  find, matching the local style.
2026-07-22 22:11:49 -05:00
Brooklyn Nicholson
e80e036086 fix(desktop): use <Tip> instead of native title= on clarify skip buttons
The no-native-title guard (added on main after this PR's first CI run)
bans native title= on <button>. Wrap the shared ChoiceButton in the
themed <Tip>, which renders the child untouched when the label is falsy
so the live card is unaffected.
2026-07-22 21:56:01 -05:00
Brooklyn Nicholson
30f6fc81e2 fix(desktop): keep ever-active tab panes mounted so revisiting a tab doesn't layout-shift
A tab group rendered only the active pane's content, so every tab switch
unmounted and remounted the whole surface — revisiting a session tab
re-measured and re-scrolled the thread from scratch, visibly shifting
layout each time.

Panes that have been active in a zone now stay mounted in absolutely
positioned layers; the inactive ones hide via visibility (keeping their
layout box, so scroll positions and measurements survive) with
pointer-events disabled and aria-hidden. Mounting stays lazy — a pane
first mounts when first activated — so a boot-restored tab stack still
doesn't resume every session up front, and panes that leave the zone
(closed / moved) unmount as before.
2026-07-22 21:49:12 -05:00
Brooklyn Nicholson
bf21ba08e3 fix(desktop): resolve tab title and project color for sessions outside the recents page
Opening a session in a new tab left the tab titled 'Session' with no
project-color dot until new activity landed the row in the paginated
recents list. Two gaps:

1. tileTitle/tileAccent/tileDragPayload only looked the stored row up in
   $sessions (the recents page). Sessions opened from a project group are
   often older than that page, so the lookup missed entirely. Resolve
   through the project tree as a fallback (tileStoredRow) and re-sync pane
   titles/accents when $projectTree loads.

2. Even with the row present, liveSessionProjectId returned null for a
   session whose cwd sits outside its recorded git_repo_root (mid-session
   relocation / sibling worktree), because the cwd-under-root guard ran
   before the explicit-project folder match. The backend tree groups such
   rows under the project; the client now agrees — an explicit folder
   match is authoritative, only the auto-project (repo root) fallback
   still needs cwd-under-root confidence.

sessionColorFor also computes directly (overrides -> project color) when
the row isn't in the $sessionColorById map, which is keyed over $sessions
only.
2026-07-22 21:46:15 -05:00
ethernet
d63a1c4ccb
fix(desktop): separate workspace defaults from live cwd (#69765) 2026-07-23 02:44:26 +00:00
Brooklyn Nicholson
efa002dd5d refactor(desktop): share the clarify choice row and fix skip copy
Builds on the skipped-clarify card so it holds up beyond the timeout case:

- Extract a shared `ChoiceButton` (letter badge + label + row chrome) used
  by both the live pending card and the settled skip card. The two blocks
  had drifted into duplicated markup; now they can't diverge.
- Fix the hint copy. An empty `user_response` is emitted for BOTH a
  server-side timeout AND a manual Skip (tools/clarify_tool.py) — there is
  no field on the result that tells them apart — so asserting "This question
  timed out" was wrong half the time. Neutral wording ("This prompt is no
  longer waiting…") is correct for either, and the recover-your-answer path
  now also helps someone who mis-clicked Skip. Updated en/ja/zh/zh-hant.

No behavior change to the live prompt or the follow-up-message flow.

Co-authored-by: SHL0MS <SHL0MS@users.noreply.github.com>
2026-07-22 21:39:27 -05:00
SHL0MS
8b96fc57ed feat(desktop): skipped clarify keeps its choices visible and answerable
When a clarify prompt times out, the settled card collapsed to just
'Skipped' — the options were unrecoverable (the args carry them, the
renderer dropped them) and there was no way to answer late.

The skipped card now:
- renders the original choices, letter-badged like the live card
- clicking one drafts a quoted follow-up ('Re: "<question>" — my
  answer: <choice>') into the composer via the insert bus. Enter sends
  it; if the agent is mid-turn it queues like any other prompt.
- a hint line explains the question timed out and what picking does

No retroactive resolution of the expired request: the tool already
returned empty and the turn moved on — injecting into past context
would break prompt-cache and role-alternation invariants, and
clarify.respond on an expired id hard-errors (#56558). The follow-up
message path needs no backend change and works against old backends.

Answered clarifies and free-text (no-choice) skips are unchanged.

Interim UX for #44845 (durable ID-addressable clarify decisions).
2026-07-22 21:38:32 -05:00
Brooklyn Nicholson
35cdc63ca0 fix(desktop): use repo-forked codicon (git-fork has no glyph)
The bundled @vscode/codicons font has no `git-fork` glyph (only
`git-fork-private`), so the Branch menu item rendered blank. Use
`repo-forked`, the actual fork icon, to match the inline GitFork action.
2026-07-22 21:32:23 -05:00
Brooklyn Nicholson
a0a24ba215 fix(desktop): use fork icon for branch in session context menu
The row/tab context-menu 'Branch from here' item used the git-branch
codicon while the inline message action uses a GitFork icon. Switch the
menu item to the git-fork codicon so branching looks consistent across
surfaces.
2026-07-22 21:27:56 -05:00
Brooklyn Nicholson
6326b30c93 fix(desktop): open branched chat in a new tab and switch to it
Branching/forking a chat replaced the primary chat (setActiveSessionId +
setSelectedStoredSessionId + navigate). Instead, open the branch as its own
session tile tab in the center zone and reveal it, leaving the parent chat
exactly where it is — mirroring openNewSessionTile. All branch entry points
(message GitFork button, /branch and /fork, sidebar Branch, tile Branch) go
through forkBranch, so this covers every surface.
2026-07-22 21:13:06 -05:00
ethernet
8a21df18ac
fix(desktop): place steer messages before redirected replies (#69739)
* test(desktop): reproduce steer transcript placement

* fix(desktop): place steer messages before redirected replies
2026-07-22 22:04:57 -04:00
brooklyn!
2c1d585002
Merge pull request #69655 from NousResearch/bb/out-of-credits-ux
feat(billing): consistent out-of-credits UX across CLI, TUI, and desktop
2026-07-22 21:03:38 -05:00
brooklyn!
da3c506db5
Merge pull request #69691 from NousResearch/bb/desktop-billing-polish
Desktop billing polish + shared Progress primitive + settings skeletons
2026-07-22 21:03:34 -05:00