perf(frontend): break down the startup bundle and bound frontend leaks - #829
Merged
Conversation
Bundle: - Gate the dev-only eager App import on the inline NODE_ENV test so production emits App as an async chunk again (was inlined into main.js). - Split rendering/registry/events CONTEXT_CONFIG into contextConfig.ts so the chat-projection worker's static graph no longer reaches the lazy React renderer loaders (worker reach 2388 -> 62 files); this removed ~9 MB of duplicated vendor modules across async chunks. - Deep-import at the sidebar/modal/action-system gateways that dragged xterm, CodeMirror + language packs, sql-formatter, refractor and framer-motion into the startup graph; lazy-load the toast renderer and EditorService. Leaks / bounds (behavior-preserving): - unsavedContentCache: drop the unused originalContent copy, evict clean entries past 32, never evict dirty ones. - runtimeCounters: running sum/count instead of unbounded durations[]. - searchAppendResultsAtom: enforce the 20k retained-match ceiling. - sessionTodoMapAtom: LRU 64 (never the active session). - useOutputChannels: cap 16 channels, debounce sessionStorage mirror. - TerminalCore: keep active + 4 recent panes mounted, remount the rest via the existing PTY attach/restore path. Tests: bundle-boundary guards (startup graph, worker graph, eager import), Message lazy container, cache/todo/search/output-channel bounds, terminal mount window, contextConfig boundary, lazy EditorService. Docs: docs/memory-audit-2026-08-16 with measured before/after. Pre-commit hook ran. Total eslint: 0, total circular: 0
This was referenced Aug 16, 2026
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.
Summary
Frontend RAM pass, first batch: break down the startup bundle, fix the real cause of the duplicated-vendor chunks, and bound the frontend growth paths that were leaking — all behavior-preserving except the terminal mount window (see risks). Ranked audit with measured numbers lives in
docs/memory-audit-2026-08-16/ram-optimization-findings.md.Measured with
webpack --mode productionat HEAD before/after:index.htmlscripts)Appimport set)build/Problem
src/index.tsxchose between the dev-onlywebpackMode: "eager"App import and the normal one via anisDevconst. Webpack can't fold that, walks both arms, and "eager" wins → production shippedApp(and every vendor only App needs) inlined intomain.jsas ~8 MB of synchronous startup JS.rendering/registry/events/index.tsand its lazy rendererimport()s. That made 2 388 files part of the worker's chunk graph; since the worker entry has novendors, webpack duplicated react-dom / xterm / CodeMirror / zod / sql-formatter into every shared async chunk (~9 MB).SidebarDialogs,ModalSystem,ActionSystemContextand the boot-registered editor actions imported barrels that dragged xterm + addons, all of CodeMirror + 10 language packs, sql-formatter, refractor (for a modal nobody renders) and framer-motion into the boot graph.unsavedContentCache(two full copies of file text per edited file, never evicted),runtimeCounters.durations[](one entry per RPC, no drain call site), search "load more" accumulation, per-session todo slots, output channels (+ a sessionStorage rewrite of every channel on every appended line), and every initialized terminal staying mounted forever (incl. all restored at boot).Solution
process.env.NODE_ENV === "development"at the App import branch.CONTEXT_CONFIG+ chat-config helpers toregistry/events/contextConfig.ts(pure data);ActionRegistry/registryAccessorsimport from it. Worker reach with async edges 2 388 → 62 files.splitChunksunchanged.Messagetoast renderer split into aReact.lazyMessageContainer(API unchanged);EditorServiceloaded on first editor-action invocation.unsavedContentCachedrops the never-readoriginalContent, tracksdirty, evicts only clean entries past 32 (dirty = truly unsaved edits are never evicted);runtimeCountersrunning sum/count;searchAppendResultsAtomenforces the documented 20 000-match ceiling;sessionTodoMapAtomLRU 64 (never the active session; rebuilt from events byuseTodoSync);useOutputChannelscapped at 16 channels with a debounced sessionStorage mirror.TerminalCorekeeps the active pane + 4 most-recently-active mounted (terminalMountWindow.ts); colder panes unmount and reattach through the existingattach_pty_stream+ serialized-buffer restore path.src/test/staticImportGraph.ts(fast static import walker) backsstartupGraph.test.ts(heavy packages / feature trees stay out of the boot graph, eager import stays foldable) andworkerStaticGraph.test.ts(worker never reaches the renderer loaders or react-dom again).Potential risks
EditorServiceon first use.Validation / Test plan
pnpm typecheckclean;eslintclean on all touched files (pre-commit lint-staged passed).pnpm test: full suite green before the last test additions (1104 files / 8696 tests); new suites green: startup/worker graph guards, Message lazy container, cache/todo/search/output-channel bounds, terminal mount window, contextConfig boundary, lazy EditorService.build/.