Skip to content

perf(app): stop the per-second shell re-render, memoize CLI resolution, halve renderer assets - #1352

Open
ozymandiashh wants to merge 2 commits into
getagentseal:mainfrom
ozymandiashh:perf/desktop-render-clock-and-resolution-cache
Open

perf(app): stop the per-second shell re-render, memoize CLI resolution, halve renderer assets#1352
ozymandiashh wants to merge 2 commits into
getagentseal:mainfrom
ozymandiashh:perf/desktop-render-clock-and-resolution-cache

Conversation

@ozymandiashh

Copy link
Copy Markdown
Collaborator

Three quiet desktop inefficiencies, each measured before and after, each pinned by a regression test.

1. The shell stops re-rendering once a second

AppMain owned a wall-clock setInterval(1000) whose only consumer was the footer's "refreshed Ns ago" label, but every tick reconciled the whole tree — sidebar, hero, daily chart, activity heatmap, every table — 60 times a minute whether or not any data had changed.

The per-second tick now lives in a leaf RefreshedAt component that owns just that label. The shell re-renders on real state changes plus a 15-second day check that fires exactly when the local calendar rolls over; that rollover matters because the overview memo keys bake in a today/month boundary, so midnight must still produce one re-render to keep "Today" honest.

App.renderChurn.test.tsx pins both properties: on the base the shell re-rendered on every clock tick and on same-day checks, on this branch neither.

2. CLI resolution stops re-scanning the filesystem per request

Every read (each section poll, each prefetch warm) resolved the codeburn binary from scratch: a stat sweep over every PATH entry plus a readdir of the nvm versions tree — per request, purely to return the same path.

Resolution and the derived spawn PATH are memoized against the full set of inputs that can change the answer (CODEBURN_BIN, CODEBURN_BUNDLED_CLI, the dev-server and persisted-path overrides, CODEBURN_PATH_DIRS, PATH, NVM_DIR), so an unchanged environment costs a string compare. Safety valves:

  • a miss is never cached, so a CLI installed — or a path persisted via locate-CLI — while the app is open is still discovered on the next read;
  • a spawn error drops the memo, so a deleted binary cannot pin the app to a dead path (covered by a test that deletes the resolved binary and watches the next request find the successor);
  • __resetCliResolutionForTests() keeps one test's filesystem layout out of the next.

Measured on this machine (18 PATH entries): 26.5 µs → 2.6 µs per resolution, and the syscalls leave the steady-state path entirely.

3. Renderer assets halve

The brand mark was an 880×880 713 kB PNG (3.1 MB RGBA decode) rendered into 20–76 px boxes, so every mount decoded megabytes to draw a 20 px sidebar logo; the cold-start splash shipped a 2.6 MB VP9 clip.

  • flame-mark.png: a 192 px lanczos cut of the same art (53 kB, −93%), verified side-by-side against the original at all three rendered sizes (20/52/76 px).
  • splash-loader.webm: re-encoded at VP9 CRF 30 — 1.29 MB, −50%, SSIM 0.994 against the source; video-only file, no audio track affected.

dist/renderer: 4.0 MB → 2.2 MB. The JS bundle is untouched.

Verification

  • tsc --noEmit clean
  • full app/ suite green on this branch rebased onto current main: 997 passed, 4 skipped
  • npm run build green
  • churn tests verified to fail on the pre-change code and pass after
  • deliberately out of scope: bundle code-splitting (gsap ~50 kB of the 510 kB chunk) — separate PR material

Changed

  • The desktop shell stops re-rendering the whole tree once a second. AppMain owned a wall-clock setInterval(1000) that existed only so the footer could print "refreshed Ns ago", but every tick re-rendered the sidebar, the hero, the daily chart, the heatmap and every table 60 times a minute whether or not any data had changed. The per-second tick now lives in a leaf RefreshedAt component that owns just that label, and the shell itself re-renders only on real state changes plus a 15-second day check that fires exactly when the local calendar rolls over. Renderer render-count regressions are pinned by a churn test.

Fixed

  • The desktop app stops re-scanning the filesystem for the CLI on every request. Each of the app's JSON reads resolved the codeburn binary from scratch: a stat sweep over every PATH entry plus a readdir of the nvm versions tree, repeated per request. Resolution and the derived spawn PATH are now memoized against the full set of inputs that can change the answer; a miss is never cached, so a CLI installed while the app is open is still discovered, and a spawn error drops the memo.
  • The renderer ships a 53 kB flame, not a 712 kB one, and half the splash video weight. A 192 px lanczos cut of the same art replaces the 880 px original and the cold-start splash video is re-encoded at VP9 CRF 30 (SSIM 0.994), together cutting the renderer's bundled assets roughly in half.

…n, halve bundled renderer assets

The desktop app's three quiet inefficiencies, measured and fixed:

- AppMain owned a 1s wall-clock interval whose only consumer was the
  footer's 'refreshed Ns ago' label, but every tick reconciled the whole
  tree - sidebar, hero, chart, heatmap, tables - 60 times a minute. The
  tick now lives in a leaf RefreshedAt component, and the shell
  re-renders on real state plus a 15s day check that fires only when the
  local calendar rolls over (the overview memo keys bake in a
  today/month boundary, so midnight must re-render exactly once).
  A churn test pins shell render counts: base re-rendered on every
  clock tick, the branch on none.

- Every read resolved the codeburn binary from scratch: a stat sweep
  over each PATH entry plus an nvm readdir, per request, per poll.
  Resolution and the derived spawn PATH are memoized against the full
  env key set that can change the answer; a miss is never cached, so a
  CLI that appears while the app is open is still discovered, and a
  spawn error drops the memo so a deleted binary cannot pin the app to
  a dead path. 26.5us -> 2.6us per resolution on this machine, and the
  syscalls leave the steady-state path entirely.

- The brand mark rendered a 880x880 713kB PNG (3.1MB RGBA decode) into
  20-76px boxes and the splash shipped a 2.6MB VP9 clip; a 192px
  lanczos cut (verified side-by-side at all three rendered sizes) and a
  CRF 30 re-encode (SSIM 0.994) cut dist/renderer from 4.0MB to 2.2MB.
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