perf(app): stop the per-second shell re-render, memoize CLI resolution, halve renderer assets - #1352
Open
ozymandiashh wants to merge 2 commits into
Conversation
…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.
…d asset-weight changes
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.
Three quiet desktop inefficiencies, each measured before and after, each pinned by a regression test.
1. The shell stops re-rendering once a second
AppMainowned a wall-clocksetInterval(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
RefreshedAtcomponent 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.tsxpins 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
codeburnbinary 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:__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 --noEmitcleanapp/suite green on this branch rebased onto currentmain: 997 passed, 4 skippednpm run buildgreenChanged
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 leafRefreshedAtcomponent 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
codeburnbinary 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.