Commit Graph

282 Commits

Author SHA1 Message Date
brooklyn! c856f36459 perf(desktop): kill the layout-thrash cascade on session switch (#66033)
Follow-up to #65890 (router transitions off) and #65898 (structural
compare + first-paint budget): profiling the switch path on real 1000+-
message sessions with a new CDP harness showed the remaining freeze is
NOT markdown rendering — it's a forced-reflow cascade from mount-time
layout reads interleaved with style writes across the transcript's
layout effects, plus the first-paint budget cut landing too late to
stop the full-budget commit.

Measured on the two largest local sessions (996 and 1363 messages),
main-thread longtask totals per switch: warm 2450ms -> 557ms and
1158ms -> 194ms; first paint 1690ms -> 444ms. Harness:
scripts/profile-session-switch.mjs (same CDP family as
profile-real-stream.mjs).

- use-resize-observer: drop the synchronous initial callback and ride
  the observer's spec-guaranteed first delivery instead (same frame,
  after layout, before paint). The sync call ran while the commit's
  layout was dirty, so every size read in a callback forced a full
  reflow — with one instance per user bubble (measureClamp read
  scrollHeight, then WROTE --human-msg-full, re-dirtying layout for the
  next bubble), the switch commit thrashed for over a second. Inside RO
  timing the same reads are free. Composer metrics (2x
  getBoundingClientRect + documentElement style writes) rides the same
  fix.
- Same class, same fix at the remaining call sites profiling surfaced:
  ExpandableBlock and TerminalOutput (dozens per tool-heavy transcript)
  now measure/pin via RO initial delivery; the tool-window and
  thinking-preview pins drop their sync pin() call; the thread
  timeline's initial active-tick compute joins its existing
  scroll-time rAF batching so back-to-back transcript updates coalesce.
- thread/list: cut the render budget in the RENDER phase (state-from-
  props adjustment) instead of the post-commit layout effect. The
  effect-time cut was too late — on a warm switch React first built and
  committed the full 300-part tree, then re-rendered at 60, then bumped
  back to 300, so the expensive commit still happened (and on a cold
  switch the bump rAF usually fired while the transcript was still
  empty, so the prefetched messages rendered at full budget anyway).
  The render-phase cut restarts the component before any child renders;
  a second trigger handles the cold path where messages land later
  under the same sessionKey.
- thread/list: backfill 60 -> 300 inside startTransition so the older
  turns' markdown+shiki render is interruptible background work instead
  of a synchronous freeze one frame after the switch paints. Functional
  Math.max so an urgent "Show earlier" click can't be rebased back down.
- composer focus: skip the rAF/timeout focus retries when the element
  is already focused — focus() runs the full focusing steps (forcing
  layout) even on the active element, ~585ms per switch on a large
  dirty DOM.
- Replace the tautological render-budget test (it re-declared the
  constants locally and asserted 60 < 300) with behavior tests of the
  now-exported buildGroups + firstVisibleGroupIndex.

Verification: apps/desktop `npx tsc --noEmit` clean; full
`npx vitest run` 210 files / 1763 passed; manual CDP check confirms the
deferred backfill commits the full transcript, stays pinned to bottom,
and "Show earlier" still pages.
2026-07-16 22:46:08 -04:00
ethernet f08b1f3445 feat(desktop): button tooltip keybind hints + keybinds settings tab + unified worktree dialog (#65204)
* feat(desktop): add useKeybindHint hook and TipKeybindLabel

Add a shared hook that reads the current keybind combo for an action
id from the $bindings store (rebindable) or KEYBIND_READONLY (fixed),
returning a formatted string or null when unbound.

Add TipKeybindLabel — a convenience component that auto-reads both
its label (from i18n) and keybind combo from the action registry.
Pass only actionId for the common case; pass text to override when
the tooltip is context-dependent.

* fix(desktop): replace native title= on buttons with themed Tip

Migrate all <button>/<Button> elements using the native HTML title=
attribute to the instant, themed <Tip> component. Native tooltips are
unstyled, delayed (~500ms OS default), and visually inconsistent with
the app's instant themed tooltips.

Also adds <Tip> wrappers to icon-only buttons that were missing
tooltips entirely (dialog close, search clear, overlay close,
master-detail pane controls, keybind panel rebind/reset buttons).

Adds an enforcement test (no-native-title.test.ts) that scans all
.tsx files for <button>/<Button> with title= and fails if any are
found. Updates DESIGN.md with the icon-only button tooltip rule and
keybind hint guidance.

* feat(desktop): wire keybind hints into button tooltips

Add actionId to TitlebarTool, StatusbarItem, and SidebarNavItem so
their tooltips show the current keybind combo via TipKeybindLabel.
Fix the hardcoded NEW_SESSION_KBD in the sidebar to read from
$bindings so it stays live on rebind.

Wired surfaces:
- Titlebar: sidebar toggle, flip panes, keybinds, settings
- Statusbar: terminal toggle (view.showTerminal)
- Status stack: open agents button (nav.agents)
- Sidebar nav: new session, skills, messaging, artifacts

* feat(desktop): move keybind panel to settings tab with search filter

Move the keyboard shortcuts panel from a Radix Dialog into a proper
Settings tab (/settings?tab=keybinds). The ⌘/ shortcut and titlebar
keyboard button now navigate to this settings tab instead of toggling
a dialog. Adds a search filter to filter shortcuts by label.

- New: src/app/settings/keybind-settings.tsx (extracted from keybind-panel.tsx)
- Delete: src/app/shell/keybind-panel.tsx (dialog wrapper removed)
- Remove: $keybindPanelOpen atom and toggle/open/close functions
- Add: IconKeyboard to lib/icons.ts
- i18n: keybinds.search + settings.nav.keybinds (en, zh, zh-hant, ja)

* refactor(desktop): unify worktree dialog into shared WorktreeDialog

Extract the worktree creation dialog from StartWorkButton (sidebar) into a
shared WorktreeDialog component. Both the sidebar's StartWorkButton and the
composer's CodingStatusRow now use the same dialog, eliminating the duplicated
UI.

The shared dialog keeps the sidebar version's full feature set:
- BaseBranchPicker (filterable base branch combobox)
- Convert mode (check out an existing branch into a worktree)
- Sanitized branch name input

The coding row passes repoPath (from cwd) and onOpenWorktree (which carries
the composer draft to the new session) so the unified dialog works everywhere
there's a repo, not just inside an entered project.
2026-07-16 18:26:21 -04:00
nousbot-eng f1315ae91e fmt(js): npm run fix on merge (#65971)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 22:26:09 +00:00
ethernet 0f05aaa2bf perf(desktop): make session switching snappy on large transcripts (#65898)
Switching between chat sessions in the desktop app froze for up to ~1–2s on
large transcripts. Profiling the switch path surfaced three main-thread
blockers, fixed here minimally and without changing behavior.

1. JSON.stringify deep-compare (worst case). chatMessagesEquivalent compared
   message parts with JSON.stringify(a) === JSON.stringify(b) on every switch.
   On image-/large-blob-bearing transcripts this serialized every part twice
   and cost well over a second. Replaced with a structural compare that never
   stringifies: array-level identity fast-path, per-part reference fast-path,
   then type-aware field comparison. The compare's only consumer asks "did the
   transcript change, should I setMessages?", so it is deliberately
   conservative — a false-negative just causes one extra idempotent
   setMessages, while a false-positive (the unsafe direction) is avoided.

2. Scroll-settle loop. thread-list ran a requestAnimationFrame settle loop up
   to 90 frames (or 5 stable frames) on every sessionKey change, each frame
   forcing a synchronous layout read + write — racing the markdown paint for
   up to ~1.5s. A normal synchronous switch stabilizes within a couple
   frames, so the ceiling is now 2 stable frames / 15 max.

3. Synchronous first paint of up to 300 parts. On switch, thread-list reset
   the render budget to the full RENDER_BUDGET=300, so up to 300 parts went
   through markdown + shiki syntax-highlighting synchronously on the switch
   commit. It now paints a small FIRST_PAINT_BUDGET=60 first, then bumps to
   the full 300 in a requestAnimationFrame after the first commit.

Salvaged from PR #49807 by professorpalmer — re-applied to the restructured
file layout (use-session-actions/utils.ts, thread/list.tsx) and tests merged
into the existing utils.test.ts.

Co-authored-by: Cary Palmer <professorpalmer@users.noreply.github.com>
2026-07-16 18:19:41 -04:00
github-actions[bot] 75f45a0692 fmt(js): npm run fix on merge (#65229)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-15 21:40:56 +00:00
ethernet c1945d410b fix(desktop): panel layout toggle bugs — terminal reveal, side collapse in column-root layouts, zone-menu restore (#65162)
* fix(desktop): un-minimize zone when revealing a collapsed tool panel

`revealTreePane()` fronted the pane's tab but never un-minimized its zone
when the pane lived in a shared zone (terminal + logs, or a tool panel
stacked with the workspace). The shared-zone branch of `setPaneCollapsed`
routes opens through `revealTreePane` instead of `toggleTreeGroupMinimized`,
so the zone stayed collapsed and the terminal appeared to "close but not
open" on Ctrl+` — and tab clicks needed a double-click because the first
click's `restoreTreePane` → opener → listener → `revealTreePane` chain
didn't un-minimize either.

The fix: `revealTreePane` now restores a minimized zone before fronting
the pane, chaining the un-minimize + activate into a single `commit` so
the second op sees the updated tree.

* fix(desktop): side collapse in column-root layouts + restore after zone-menu minimize

Two follow-up fixes for the panel layout:

1. Ctrl+B / Ctrl+J sidebar toggles didn't work in the Terminal deck (and
   Quad) layout. The side-collapse system (treeSideOfPane, paneRootSide,
   layoutHasRootSide, and the renderer's semanticSides) only operated on the
   ROOT split when it was a row — but Terminal deck's root is a column, so the
   side columns (sessions/files) nested inside a child row were invisible to
   the collapse system. Extracted a rootRow() helper that finds the row
   containing main (the root itself for row layouts, or the row child of a
   column root), and a rootRow prop propagated through TreeNode → TreeSplit so
   semanticSides fires on the right split regardless of root orientation.

2. Clicking the "logs" tab in a minimized terminal/logs zone needed a
   double-click when the zone was minimized via the zone menu (right-click →
   minimize), not the toggle. restoreTreePane called the opener
   ($logsOpen.set(true)) and returned early — but when the store was already
   true, nanostores don't fire the listener, so setPaneCollapsed/revealTreePane
   never ran and the zone stayed minimized. Now restoreTreePane also
   un-minimizes directly + calls revealTreePane when the zone is still
   minimized after the opener runs.
2026-07-15 15:37:09 -04:00
Brooklyn Nicholson a4c9b069dc fix(desktop): stop reasoning text flashing/re-typing on every delta
Reasoning ("Thinking") rendered through MarkdownTextContent with a smooth
typewriter reveal. TextMessagePartProvider mints a fresh part object on every
text change, and useSmooth resets its reveal to empty whenever the part
identity changes — so each reasoning delta restarted the animation from the
first character (h / hell / hello wo ...). Token-streaming reasoners
(R1/Qwen/GLM/Claude thinking) fire a delta per token and flash hard; GPT-5's
coarse reasoning summary updates too rarely to notice, which is why it looked
fine.

Drop smooth on the reasoning path so it plain-appends, exactly like the
assistant answer already does. Removes the now-dead smooth plumbing too.
2026-07-15 14:35:15 -04:00
Brooklyn Nicholson 7a5a6ef99b fix(desktop): keep generated images out of tool overflow
Generalize the clarify opt-out into an UNBOUNDABLE_TOOLS set so a run
containing image_generate also stays a plain, fully-visible stack instead
of collapsing into the bounded window, where the max-height + gradient mask
clipped the image the same way it clipped clarify forms.
2026-07-15 14:08:35 -04:00
Brooklyn Nicholson 5c03e27ce2 Merge remote-tracking branch 'origin/main' into bb/contrib-areas
# Conflicts:
#	apps/desktop/src/app/session/hooks/use-prompt-actions/index.ts
2026-07-15 13:22:08 -04:00
HexLab98 1011cd24e2 fix(input): strip bracketed-paste leaks before prompt persistence (#62557)
Extract shared hermes_cli/input_sanitize.py (bracketed-paste wrapper stripping
and terminal ~[[e artifact suffix collapse) and wire it into prompt.submit
and the Desktop composer so corrupted user text is cleaned before
messages.content is persisted.
2026-07-15 07:39:42 -07:00
Brooklyn Nicholson b5aef05e2c fix(desktop): layout reset reopens collapsed sidebars
resetLayoutTree() claims to "restore everything" but never reopened collapsed
SIDES. A sidebar hidden before a reset survived it, so the next ⌘B toggled from
that stale-hidden state into a SHOW — and the user's hide never persisted across
reload. Reopen every bound side through its store so the toggles stay truthful.

Adds a store-level regression test (real modules, re-import = reload) covering
hide→reload and the reset→hide→reload repro.
2026-07-15 03:30:25 -04:00
Brooklyn Nicholson 092a97ef75 fix(desktop): session-tab drag, focus sync, and pop-out isolation
Make every session tab speak the same drag language as a sidebar row, and
keep tab focus 1:1 across the sidebar, tiles, and main:

- Drag a session tile's OR the main workspace tab onto a composer to link the
  chat (@session chip), or onto a zone/edge to stack/split — tabDrag now
  returns whether it took the drag, so the workspace tab defers to the generic
  pane move on a fresh draft (nothing to link).
- A lone tool panel (terminal/logs) dragged to its own zone keeps its header,
  so it stays draggable/closable instead of stranding a dead, tab-less zone.
- The sidebar highlight follows the FOCUSED session (interacted tile, else the
  main selection) rather than only the main one.
- Clicking an already-open session jumps to its tab — an open tile, or the
  workspace tab when it's the main session and focus sits on a tile — instead
  of a dead no-op reload.
- Secondary (single-chat pop-out) windows boot to the default tree with no
  tiles and never persist their stripped-down layout back, so they can't
  inherit or clobber the primary window's tabs/splits.

The pointer drag ghost is extracted to a shared lib/drag-ghost and the drop
affordances drop their now-unused labels.
2026-07-15 03:13:33 -04:00
Brooklyn Nicholson db488df1c3 Merge origin/main into bb/contrib-areas 2026-07-15 02:30:17 -04:00
Brooklyn Nicholson 6fc24651f7 fix(desktop): add respondToApproval to clarify tool-part test mock
@assistant-ui/react 0.14 makes respondToApproval required on
ToolCallMessagePartProps; the settledClarifyProps helper still lacked it
after the upgrade cherry-picks, so tsc failed on clarify-tool.test.tsx.
2026-07-15 01:42:09 -04:00
Harry Yep b50b83fbd8 test(desktop): add math-delimiter fixtures for the react-streamdown preprocess swap
lock the four behaviors the built-in normalizeMathDelimiters/escapeCurrencyDollars introduce over the deleted custom helpers: $$<digit>$$ display math stays intact, double-backslash brackets and [/math]/[/inline] tag pairs rewrite to dollar delimiters, and currency dollars in prose are escaped. the existing preprocessMarkdown suite had no math cases.
2026-07-15 01:42:09 -04:00
Harry Yep 9eb89b8a12 docs(desktop): correct streaming-repair comments after the defer/smooth swap
the parseIncompleteMarkdown comment implied the reveal frontier is repaired; repair runs on the full accumulated text, so reword it to say that. drop the now-dead "multiple surfaces render the same content" clause from the block-cache comment (the smooth and defer wrappers that caused it were removed), and trim the math-preprocess comment to the load-bearing prose-only constraint.
2026-07-15 01:42:09 -04:00
Harry Yep 673a61edc8 fix(desktop): match renamed useClientLookup out-of-bounds throw in MessageRenderBoundary
@assistant-ui/store renamed its index-out-of-bounds throw from tapClientLookup/tapClientResource to useClientLookup in the 0.14 upgrade, so the boundary's /tapClient.../ filter stopped matching and re-threw the transient session-switch and reconnect race to root, blanking the app. broaden the regex to accept the new prefix (keeping the old one for older store versions) and point the test at the real message so it exercises the live path instead of the dead string.
2026-07-15 01:42:09 -04:00
Harry Yep 4e10a38c9e refactor: replace custom lib/remend-tail.ts with built-in tailBoundedRemend
delete lib/remend-tail.ts (108 lines) and lib/remend-tail.test.ts (105
lines). the tailBoundedRemend export from @assistant-ui/react-streamdown
0.3.4 is algorithmically identical — same findRemendWindowStart boundary
scan, same fence/math tracking, same slice-and-repair strategy. the only
differences are improvements: the built-in handles \r (CR) in line
endings for Windows compatibility, and accepts an optional RemendOptions
parameter passed through to remend.

the import in markdown-text.tsx moves from @/lib/remend-tail to
@assistant-ui/react-streamdown. the call site
(preprocessWithTailRepair) is unchanged.

verified: tsc 0 errors, eslint clean, vitest 0 new failures (15
pre-existing, 786 passing — 6 fewer than before because the deleted
remend-tail.test.ts had 6 cases), manual verification of incomplete
markdown repair during streaming.
2026-07-15 01:42:09 -04:00
Harry Yep 6c95740d9e feat: replace custom streaming wrappers with built-in defer and smooth props
delete SmoothStreamingText, DeferStreamingText, and useSmoothReveal
(~174 lines) from markdown-text.tsx. the built-in defer and smooth
props on StreamdownTextPrimitive now handle the same work:

- defer: routes streaming text through useDeferredValue so markdown
  re-parsing runs at lower priority (typing/scrolling stay responsive)
- smooth: typewriter-style reveal via useSmooth with SmoothOptions
  { drainMs: 500, maxCharsPerFrame: 30, minCommitMs: 33 }, matching
  the old useSmoothReveal constants exactly

MarkdownTextContent (reasoning text) gets both defer and smooth.
MarkdownText (assistant text) gets defer only, matching the previous
behavior where text messages had no typewriter effect.

the internal pipeline order changes from smooth → defer → preprocess
to preprocess → smooth → defer (the built-in primitive runs preprocess
first). this is functionally equivalent: the tail-bounded remend repair
runs once on the full text instead of per revealed prefix, and the
smooth reveal operates on already-repaired markdown. end result is
identical.

verified: tsc 0 errors, eslint clean, vitest 0 new failures (15
pre-existing, 792 passing), manual verification of 6 streaming
scenarios (defer, smooth reveal, typing-while-streaming, code blocks,
math, long text performance).
2026-07-15 01:42:09 -04:00
Harry Yep ddd6ad43f0 chore: upgrade @assistant-ui/react 0.12 to 0.14 and react-streamdown 0.1 to 0.3
bumps @assistant-ui/react from ^0.12.28 to ^0.14.23 and
@assistant-ui/react-streamdown from ^0.1.11 to ^0.3.4. this crosses
two minor bumps on each package and unlocks the built-in defer, smooth,
and tail-bounded remend primitives for PR 2.

breaking change from core 0.2.x: MessageRepository.appendOptimisticMessage
was removed (assistant-ui#4162). inline the three steps it did (generateId
+ fromThreadMessageLike + addOrUpdateMessage) in
incremental-external-store-runtime.ts, and set metadata.isOptimistic so
the new off-branch eviction logic cleans up the placeholder correctly.

fromThreadMessageLike and generateId graduated to the public API in
0.14.22 (assistant-ui#4414), so they now import from @assistant-ui/react
instead of @assistant-ui/core/internal. ExportedMessageRepository in the
test file moves to the public import for the same reason. the remaining
internal imports (AssistantRuntimeImpl, BaseAssistantRuntimeCore,
ExternalStoreThreadListRuntimeCore, ExternalStoreThreadRuntimeCore,
hasUpcomingMessage) are runtime construction internals with no public
equivalent and stay on @assistant-ui/core/internal.

the @assistant-ui/store npm override is removed: all transitive ranges
now resolve to 0.2.18 without it.

verified: tsc --noEmit passes, vitest shows zero new failures (15
pre-existing, 792 passing, identical to baseline before the upgrade).
2026-07-15 01:41:03 -04:00
Brooklyn Nicholson 798e602a8e feat(desktop): tool panels collapse to a persistent rail, ✕ removes them
Terminal and logs now follow the IntelliJ/VS-Code tool-window model: their
toggle (⌃`, ⌘K) COLLAPSES the zone to a rail with the tab still showing instead
of hiding it outright, so "toggle" and "the tab bar" stop fighting. Restore
routes through the pane's store opener (rail click / chevron) so the shortcut and
titlebar toggle stay truthful; the tab's ✕ dismisses the panel (comes back via
its toggle), while a session tile's ✕ still closes the session. New store
primitives (setPaneCollapsed / restoreTreePane / collapseTreePane + a
collapse-pane registry) via a bindPaneCollapse in the controller.
2026-07-14 21:06:40 -04:00
Brooklyn Nicholson 601c1f16cb fix(desktop): keep clarify prompts out of tool overflow
Treat clarify as a hard boundary for bounded tool runs so interactive forms remain fully visible and usable.
2026-07-14 21:01:59 -04:00
Brooklyn Nicholson 1813d3046c feat(desktop): add a chat backdrop on/off toggle
The faint statue backdrop behind the transcript was only switchable via
the DEV-only leva panel. Add a persisted Appearance toggle (default on)
so users can hide it; the Backdrop simply skips rendering when off.
2026-07-14 16:50:03 -04:00
Gille 2d0f2185cf fix(desktop): clear stale compaction status across session switches (#64127)
* fix(desktop): clear stale compaction status

Clear the compaction phase when a turn resumes with model or tool activity, and key response timers by session and turn so switching chats preserves elapsed time.\n\nSupersedes #48115 by porting its resumed-content approach to the current split stream hook and covering tool-first resumptions.\n\nCo-authored-by: liuhao1024 <sunsky.lau@gmail.com>

* fix(desktop): resume after thinking activity

* fix(desktop): clear turn timer on stop
2026-07-14 08:48:48 -04:00
Brooklyn Nicholson 5f37c9e85c refactor(desktop): tighten reasoning part typing, drop dead useRef
Self-review nits on the Thinking-widget fix:
- type ReasoningTextPart as ReasoningMessagePartComponent and read the
  typed useMessagePartReasoning() directly, dropping the ad-hoc cast
  (the hook already returns text/status).
- remove useRef, now unused after deleting useSmoothReveal.
- trim the autopsy comments; the PR body carries the narrative.
2026-07-13 20:38:34 -04:00
brooklyn! 19641fab72 fix(desktop): render reasoning text in the Thinking widget (#63999) 2026-07-13 20:38:26 -04:00
Brooklyn Nicholson 8bd4a419de fix(desktop): render reasoning text in the Thinking widget
The Thinking disclosure rendered blank for every reasoning-emitting model
(Fable, DeepSeek, GPT-5.5, ...). Two causes:

1. ReasoningTextPart read a `text` prop that assistant-ui never populates —
   reasoning parts arrive via context, same as text parts — so it always got
   an empty string. Read the text via useMessagePartReasoning() instead,
   mirroring how MarkdownText uses useMessagePartText().

2. The reasoning-only SmoothStreamingText / useSmoothReveal layer stalled at
   revealed="": the reasoning part stays isRunning for the whole message while
   the answer streams and thrashes re-renders, so the char-reveal never
   advanced past 0. Render reasoning through the same DeferStreamingText →
   surface path the assistant answer uses, and drop the dead smoothing code.
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
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 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 6016997a72 test(desktop): stub CSS global in .test.tsx files 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
Teknium d48bf743f2 fix(approval): scope smart deny owner overrides to one operation
Co-authored-by: Sergei Ivanov <kavi@local.hermes>
2026-07-13 04:31:55 -07:00
brooklyn! 2d9fd870b6 Merge pull request #59778 from frizikk/fix/desktop-sudo-dialog-dismiss-59765
fix(desktop): dismiss stale prompt overlays
2026-07-12 05:52:16 -05:00
Brooklyn Nicholson 3e9aec9f94 test(desktop): satisfy ToolCallMessagePartProps in clarify tests
CI typecheck requires argsText, status, addResult, and resume on rendered
tool parts.
2026-07-12 05:36:23 -04:00
Brooklyn Nicholson 192ce05d05 test(desktop): cover settled clarify answer rendering 2026-07-12 05:29:08 -04:00
Brooklyn Nicholson 9e7fe2dd01 fix(desktop): keep answered clarify Q&A visible in the transcript
Answered clarifies were collapsing into a generic tool row, hiding the
choice. Settle into a Q&A panel instead, and route freeform input through
the shared Textarea chrome.
2026-07-12 05:29:08 -04:00
Brooklyn Nicholson 65712bf788 fix(desktop): clear the OAuth partition before remote sign-in
Sign out of the dedicated OAuth partition before opening the login window so a
stale gateway/identity-provider cookie can't silently bounce an expired session
straight back into failure. Relabel the action "Sign out & sign in" and spell
out the sign-out step in the hint.

Co-authored-by: Tony Antunez <57689194+smtony@users.noreply.github.com>
2026-07-12 04:08:29 -04:00
Brooklyn Nicholson f3af066b85 fix(desktop): treat connected-but-expired remote sessions as reauth
Add isRemoteReauthError so an auth-shaped boot error counts as a remote-reauth
failure even when the session indicator still reads connected (a stale refresh
cookie / failed ws-ticket mint). Wire the boot error into the overlay's reauth
check so those sessions route to Sign in instead of the local-only recovery
buttons.

Co-authored-by: Tony Antunez <57689194+smtony@users.noreply.github.com>
2026-07-12 04:08:14 -04:00
Brooklyn Nicholson c1c74d7518 fix(desktop): recover a failed gateway from the boot-failure screen
A remote/VPS backend that failed to boot trapped the user on the recovery
screen — Retry/Repair/Use-local only target the local backend, so the only fix
was hand-editing connection.json. Add an in-place "Gateway settings" view (the
real GatewaySettings panel embedded via `embedded`, lazy-loaded) reached from
the recovery card, and shape the recovery actions by failure kind: Sign in for a
lapsed remote session, Gateway settings for any other remote failure (Retry
drops to secondary; Repair is dropped — it can't revive a remote), Retry for a
local backend. Use-local is scoped to remote failures.
2026-07-12 04:07:41 -04:00
Brooklyn Nicholson e1fa54d367 refactor(desktop): extract isRemoteConfig from the reauth predicate
Factor the "remote/cloud with a URL" check out of isRemoteReauthFailure into a
shared isRemoteConfig helper so the boot-failure overlay can tell any remote
failure apart from a local one.
2026-07-12 04:05:44 -04:00
Brooklyn Nicholson be2b73e253 fix(desktop): stop empty mispositioned tooltip on terminal rail hover
A block-level label child (e.g. `flex`) collapses TooltipContent's inline
`box-decoration-clone` wrapper, so Radix measures a zero-size chip and parks
an empty black rectangle in the panel corner instead of by the trigger
(#62022). The terminal rail's hotkey labels and the preview row's two-line
label both hit this.

Harden the shared wrapper (`[&>*]:!inline-flex`) so any call site's direct
child renders inline-flex, add a reusable `TipHintLabel` for the common
text+hotkey label, and keep the preview row's label explicitly inline-flex.

Salvages #62139 (shared-component hardening + TipHintLabel) and #62073
(inline-flex call-site fixes + rail/preview coverage).

Co-authored-by: alelpoan <alelpoan@proton.me>
Co-authored-by: zapabob <1920071390@campus.ouj.ac.jp>
2026-07-12 02:53:03 -04:00
teknium1 31152ae108 fix(providers): align Fireworks integration with project policy 2026-07-11 05:43:35 -07:00
Alex Jestin Taylor c97d9a4c07 feat(providers): add Fireworks AI as preferred provider
Bundle Fireworks AI as a first-class BYOK provider across the CLI, web/TUI,
and desktop onboarding.

- New model-provider plugin with attribution headers (HTTP-Referer / X-Title)
  so Fireworks can attribute Hermes traffic; PAYG-safe default aux + fallback
  models (accounts/fireworks/models/...), IDs tracking fw-ai/fireconnect.
- Registered in CANONICAL_PROVIDERS so it appears in the CLI/web/TUI pickers.
- Alias wiring (fireworks-ai, fw) into both CLI resolvers.
- First-class wiring: OPTIONAL_ENV_VARS, HERMES_OVERLAYS (FIREWORKS_BASE_URL
  override), doctor env hints. Live catalog + model_metadata are auto-derived.
- doctor: treat Fireworks' native slash-form IDs (accounts/fireworks/...) as
  valid, not aggregator vendor prefixes, so it no longer tells Fireworks users
  to switch to openrouter or drop the prefix.
- picker: plugin providers with no static curated list now lead with their
  profile fallback_models, so the default is an agentic chat model instead of
  whatever the live catalog returns first (Fireworks listed an image model,
  flux-*, ahead of its chat models).
- Desktop onboarding: Fireworks as a RECOMMENDED hero card with the official
  Fireworks logomark and a brand-purple badge, routing to the BYOK key form;
  i18n in en/ja/zh/zh-hant.
- Tests: profile contract, first-class wiring (both resolvers, overlay, config,
  doctor incl. the slash-form regression, aux headers, credentials), discovery
  spot-check, and a live smoke test driven through the Hermes runtime.

Fire Pass (fpk_) support is coming soon; the future wiring is kept as a
commented-out scaffold in the plugin.
2026-07-11 05:43:35 -07:00
Brooklyn Nicholson 1fa3886bce chore(desktop): remove the DEV Shift+H heart preview
The real trigger (core `reaction` event on affectionate messages) is live, so
drop the dev-only hotkey and its always-mounted listener.
2026-07-10 16:09:23 -05:00
Brooklyn Nicholson 3aaf7e3876 feat(desktop): TikTok-style vibe hearts on a reusable particle system
Add a glyph-agnostic ParticleField (float-up + organic sway/bank + springy
pop-in), skinned as pink pixel hearts. Hearts play on the pet when one is out
(in-window or popped out) and celebrate alongside; otherwise they rise from the
composer. A generic $petReaction bus mirrors the burst to the pop-out overlay
window so it reacts even while the app is minimized.

Consume the core `reaction` event to fire hearts on affectionate messages. DEV
Shift+H previews a burst.
2026-07-10 05:42:14 -05:00
Brooklyn Nicholson 29f3dc0809 fix(desktop): stop Tip from sticking open and blocking clicks
Radix's hoverable-content grace area can leave tips stuck over Electron drag regions; disable it and make tip content pointer-events-none so open state tracks the trigger only.
2026-07-10 04:00:32 -05:00