Skip to content

perf(web): grouped low-impact findings — cold-start manifest fetch, bundle budget, editor drafts, mounted panes [low] #538

Description

@thedancingdeveloper

Impact: low (grouped) · Confidence: confirmed

Grouped low-impact PWA performance findings from the holistic review.

1. Production cold start blocks on a serialized /demo-manifest.json fetch

web/src/index.tsx:8await initializeRuntimeTransport() (runtimeTransport.ts:87-93, cache: "no-store") completes before Promise.all([import("./App"), …]) even starts. In a non-demo deployment this adds one guaranteed round trip (a 404) to first paint, in series with the App/Board chunk fetches. Fix: start the manifest fetch and import("./App") in parallel; install the transport before render.

2. scripts/check_bundle.py's "first screen" budget under-reports the real first screen

It reports 47.3KB JS, but index.tsx:19-27 unconditionally await import("./App") — the 145KB App chunk statically includes the eager Board, CommandPalette, Sessions, and FileTree (deliberate, per the comment at App.tsx:125-135) — so real pre-paint JS is ~200KB. The budget passes honestly today, but the dynamic-import indirection means the gate cannot see growth in the chunks that actually gate first paint. Fix: have the budget count App + its static imports as first-screen.

3. Editor captures a full-document draft on every keystroke

web/src/Editor.tsx:194-208onDidChangeModelContent calls editor.getValue() (O(document) string build), compares against savedContent (O(document)), and saveViewState() per keystroke. Fine for source files; janks on multi-MB files. Fix: debounce ~300ms; use model.getValueLength()/version-id as a cheap pre-filter before materializing the string.

4. All terminal panes stay mounted and attached forever on desktop

web/src/tabLifecycle.ts:8 (terminal → retention "always") + App.tsx:1809 (display: none when inactive): each hidden pane keeps a live WebSocket receiving and parsing PTY output into its xterm buffer (scrollback 5000). Deliberate and documented (Terminal.tsx:106-112); with many busy sessions the background parse + WS bandwidth adds up. If it shows in profiles: detach hidden panes after N minutes idle (close WS, keep the cache) and rely on the existing snapshot-resume path.

5. filteredSessions parses dates inside a sort comparator

web/src/History.tsx:481-490 (WIP code) — Date.parse in the comparator is O(n log n) parses; bounded by the 200/page load, so a micro-nit. Precompute the timestamp once per row when convenient.

(Verified well-done, for contrast: xterm.js with capped scrollback and binary WS frames; one shared SSE stream with backoff; Fenwick-tree MeasuredWindow virtualization on Board/Backlog; process-wide taxonomy cache; route-lazy heavy surfaces with a budget-gated 47KB first screen; no-cache service worker; historyReplay.ts linear and bounded.)


From a holistic security & performance review, 2026-09-02, branch feat/product-version @ 3933a09; line numbers refer to that tree.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions