Skip to content

perf: faster web delivery, smaller diffs, windowed conflicts - #32

Merged
erwin-wee merged 7 commits into
mainfrom
perf/web-delivery-and-diffs
Sep 24, 2026
Merged

erwin-wee merged 7 commits into
mainfrom
perf/web-delivery-and-diffs

Conversation

@erwin-wee

Copy link
Copy Markdown
Owner

Fixes from the performance audit (server mode on phones, large diffs, conflicts, startup, watcher).

Measured

Before After
Renderer JS 1,907 KB 919 KB (222 KB br)
CSS 126 KB 104 KB
Phone cold load (Fast 3G, CPU ×4) 12.7 s, 2.26 MB 2.8 s, 249 KB
Phone warm load 12.7 s, 2.06 MB 1.0 s, 10 KB
30k-line diff /invoke 4.17 MB 122 KB br
5,400-line conflict, mounted lines 5,400 66
File save → status refresh starts 162 ms ~6 ms

Diff compression was measured on a synthetic, highly repetitive file; expect roughly 5–10× on real code.

Changes

  • Server static delivery: in-memory cache (revalidated by mtime so git-checkout rebuilds need no restart), Brotli/gzip negotiation, strong ETags + 304, immutable for hashed /assets/*, no-cache elsewhere. /gitgood-bridge.js and /invoke stay no-store. Large /invoke JSON compressed asynchronously.
  • Renderer minification enabled (minify: 'esbuild'; electron-vite defaults it off).
  • Diff payload: modified text diffs no longer send oldContent; reconstructOldLines rebuilds the old side from newContent + hunks. Covered by test/diff-reconstruct.test.ts against real git diff output (CRLF, no EOL at EOF, deletion at start, multi-hunk, rename).
  • Conflict view windowed with useWindowedRows; lazy block highlighting extracted to lib/highlight.ts and shared with TextDiff (its cache now also resets when the reconstructed old side changes).
  • Diff spinner only appears after 150 ms.
  • Startup publishes settings/repos and opens the last repo without waiting for tool discovery or update state.
  • Watcher fires on the first change after a quiet period; bursts still coalesce over 120 ms (1,000-file burst → 1 notification).
  • Service worker (web mode only): cache-first hashed assets (max 12 entries), network-first navigations with an offline page. Never intercepts /invoke, the bridge, /version or /events.

Verification

  • npm run typecheck, npm run build, npx vitest run: 90 files, 1,121 passed, 4 skipped.
  • Isolated server + headless browser: headers/304/encodings via curl; phone load via CDP throttling; big-diff old-side highlighting and conflict windowing checked in the UI with screenshots (layout height unchanged).

Not done: the 40k-line diff still takes ~1.5 s, which is git diff itself; the spinner now covers it.

Static files are held in memory (revalidated by mtime) with lazy Brotli/gzip
variants and strong ETags; hashed /assets/* are immutable, everything else is
no-cache with 304s. Large /invoke JSON is compressed asynchronously. The
renderer bundle is now minified (electron-vite defaults minify to false).

Phone (Fast 3G, CPU x4): cold load 12.7 s / 2.26 MB -> 2.8 s / 249 KB.
…both

Modified text diffs no longer carry oldContent; the renderer rebuilds old-side
lines from newContent + hunks. Lazy block highlighting moves to
lib/highlight so the conflict view can share it, and its cache now resets
when the old side changes.
ConflictDiff mounted and highlighted every line; it now uses useWindowedRows
and lazy block highlighting. A 5,400-line conflict mounts 66 lines instead of
5,400.
@erwin-wee
erwin-wee merged commit 5a77dce into main Sep 24, 2026
2 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