perf(desktop): cut GUI streaming & interaction lag#45343
Merged
Conversation
Contributor
🔎 Lint report:
|
During a token stream $messages is replaced ~30x/s. Subscribing the whole chat view to it re-rendered the composer, runtime boundary, and every message on every delta. - Derive coarse facts (empty thread? tail is user?) via nanostores `computed` atoms so per-token flushes don't re-render their consumers. - Move the $messages subscription + runtime wiring into a dedicated ChatRuntimeBoundary; the composer reads $messages imperatively. - Drive message rows off stable useAuiState selectors and a lazy getMessageText getter instead of eagerly materialized text. - Feed ResizeObserver entry sizes into measureClamp / FadeText and dedupe the style writes, killing the read-write-read reflow cascade.
Re-parsing the full message markdown every reveal frame is O(N^2) over a long answer and dominated stream CPU. - Throttle useSmoothReveal commits to ~1 frame (REVEAL_MIN_COMMIT_MS). - Memoize block parsing with an LRU keyed on source text so only changed blocks re-parse. - Replace Streamdown's full-text parseIncompleteMarkdown with a tail-bounded remend: scan to the last top-level boundary outside fences/math and repair only the trailing open block. New remend-tail.ts is proven render-equivalent to full remend at every streaming prefix (remend-tail.test.ts), minus an intentional, documented divergence on cross-block dangling openers.
- Resume: fire the REST transcript prefetch and the session.resume RPC in parallel, and skip the redundant message conversion + reconciliation when the prefetch already hydrated the transcript. - Haptics: web-haptics builds its AudioContext lazily on first trigger, paying the ~850ms CoreAudio spin-up on the first streamStart haptic as the first token paints. Open/close a throwaway context at idle so the real one connects to an already-warm audio service.
Adding remend changed package-lock.json, so the flake's pinned npm deps hash went stale and `nix flake check` failed. Bump it to match.
austinpickett
approved these changes
Jun 13, 2026
Liao-Jun-Tao
added a commit
to Liao-Jun-Tao/hermes-agent
that referenced
this pull request
Jun 13, 2026
… fixes
Squash-merge of origin/main into fix/desktop-gateway-reconnect.
Keeps local WIP commit (7c49f0d1d) at the top of the squash.
Local fixes preserved (vs origin/main):
- apps/desktop/electron/main.cjs:
* Fix IPC 'An object could not be cloned' on hermes:sys:env by
returning { ...process.env } (process.env has prototype getters
that break Electron's structured-clone algorithm).
* Fix IPC 'callback must be of type function' in _execAsync by
merging args into the command string (exec(file, args, callback)
has no (file, args, options, callback) overload).
- apps/desktop/src/app/settings/constants.ts: drop 'sysmgr' from
SECTIONS (dedicated view, not a ConfigSettings view), with comment
explaining why; remove now-unused Cpu import.
- apps/desktop/src/app/settings/index.tsx: register SysmgrSettings
in the platform/extension sidebar group (providers/gateway/keys/
mcp/sessions), not the config group.
- apps/desktop/src/app/settings/types.ts: add 'sysmgr' to SettingsView.
- apps/desktop/src/app/settings/sysmgr/*: new System Manager panel
with 4 sub-views (Processes / Environment / Services / Doctor).
- apps/desktop/src/lib/sys-manager.ts: renderer-side IPC bridge.
- pnpm-lock.yaml: dependency sync.
No conflicts (X theirs on 3-way merge handled everything cleanly).
Upstream commits included (highlights, last 7 days):
- 7d183f6 fix(desktop): theme the image-gen placeholder (NousResearch#45354)
- 492c402 perf(desktop): cut GUI streaming & interaction lag (NousResearch#45343)
- 3cf7d43 perf(desktop): faster session resume & warm AudioContext at idle
- edc36f3 perf(desktop): incremental markdown rendering during streams
- 7c226cc perf(desktop): isolate streaming re-renders & cut layout thrash
- d14f6c9 fix(desktop): stop streaming autoscroll bounce (NousResearch#45251)
- 78ce917 fix(desktop): crisp terminal text via opaque xterm canvas
- 956af7f fix(agent): add metadata flag to context compression (NousResearch#38389)
- 691ff7c fix(compressor): keep last visible assistant reply in summary (NousResearch#29824)
- bba9b51 fix(delegation): remove default subagent wall-clock timeout (NousResearch#45149)
- 9b01c4d fix(update): never spawn interactive polkit prompt (NousResearch#45145)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Six focused fixes to desktop GUI streaming/interaction latency, profiled end-to-end. Measured deltas (879-msg session, synthetic gateway events through the real pipeline):
Fixes
markdown-text.tsx) — throttle smooth-reveal commits to ~33 ms; LRU-cacheparseMarkdownIntoBlocksso remounts/republishes don't re-lex.chat/index.tsx,thread.tsx,composer/,store/session.ts) — a newChatRuntimeBoundaryowns the$messagessubscription so per-token flushes stop re-rendering the chat shell;AssistantMessagereads only streaming-stable selectors; footer/copy/read-aloud read text lazily; composer reads$messagesimperatively;$messagesEmpty/$lastVisibleMessageIsUsercomputeds for coarse consumers.use-resize-observer.ts,thread.tsx,fade-text.tsx) — pass RO entries through; use entry sizes, cache line-height, skip redundant writes.use-session-actions.ts) — run the REST prefetch andsession.resumeRPC concurrently; skip re-converting the resume payload when the prefetch already hydrated.haptics-provider.tsx) — warm a throwawayAudioContextat idle so web-haptics' lazyensureAudiodoesn't pay the first-context spin-up mid-stream.lib/remend-tail.ts) — replace Streamdown's whole-messageparseIncompleteMarkdown(~18% of script on 50 KB+ messages) with a single char-scan that boundsremendto the trailing open block (widening over open fences /$$math). Block-exact render equivalence vs full remend is tested at every streaming prefix.Test plan
remend-tail.test.ts— block-exact equivalence at every prefix of a mixed corpus; fence/math window casestsc --noEmit+ eslint clean on touched filesmain