Skip to content

Make the desktop app faster: lazy screenshots, fewer refetches and re-renders, React Compiler - #352

Merged
niclas-j merged 5 commits into
mainfrom
c/desktop-perf
Nov 1, 2026
Merged

niclas-j merged 5 commits into
mainfrom
c/desktop-perf

Conversation

@niclas-j

@niclas-j niclas-j commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator
  • Chats load screenshots on demand (?lite=images, messages.images on the socket) and refetch once per event burst instead of on every message: one chat went from 19.5 MB to <1 MB.
  • Past messages and the sidebar no longer re-render on every streamed chunk (memoized thread rows, useLiveRunsMap): long tasks during a streamed answer 43 → ~5.
  • React Compiler via Oxc's Rust port (oxc-transform-react) with the codebase made compatible: 418 of 460 files compile, the TanStack virtualizer is the only skipped function.

The desktop fetched every chat whole, with each screenshot as base64 (one chat was 19.5 MB of JSON vs 0.78 MB light),
and refetched it on every message, queue, run and conversation event (three times when a turn ends). Desktop now asks
for ?lite=images (screenshots as imageUrl, tool output whole) and says messages.images on the socket so pushed
messages come without pictures too. Message events patch the cache and cancel an in-flight fetch instead of
refetching; run and conversation events refetch the chat once per 250 ms burst. Reading messages no longer scans all
of message_images, and a picture request reads its block with json_each instead of parsing the whole message.
Thread rows are memoized and the visible message list only changes with the live run's ids, so a delta re-renders the
live turn, not all earlier messages (each with its markdown). Measured in a 56-message chat over one ~11 s answer:
long tasks 43 (3.7 s) -> 8 (0.8 s), React render time 2.96 s -> 1.46 s.
The sidebar, workspace switcher, agent cards, activity and home lists subscribed to the whole live-runs map, which
changes with every delta. useLiveRunsMap hands them a new map only when a run starts, ends or moves to its next step.
oxc-transform-react runs React Compiler on src/ before plugin-react (JSX and Fast Refresh stay with it), so components
and hooks memoize on their own. 398 of 459 files compile today; the rest are skipped function by function and keep
working as before.
The audit (oxc-transform-react with reportDiagnostics) skipped 172 functions in 80 files: refs read or written during
render, try/finally, dynamic import() in components, ??=, rule suppressions and memo-dependency mismatches. They are
rewritten to compile (latest-value refs updated in layout effects or useEffectEvent, render-relevant refs as state,
cleanup after try/catch, module-level loaders). 417 of 459 files compile; the one function left is the TanStack
virtualizer in the logins page, which the compiler skips by design.
@niclas-j
niclas-j merged commit 2e262b8 into main Nov 1, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant