Conversation
… render and recording (Fixes #3432) The PTY render loop paid three allocation taxes every 16ms tick while a shell command streamed: two full JSON.stringify passes just to decide whether the terminal changed, one Cell object per terminal cell (rows*cols per tick) inside serializeTerminalToObject, and a full token-graph copy in the colorless render path. The session recorder pinned roughly 2x each pending record by retaining both the payload line and its serialized json until the async drain. Replace the double-stringify with outputsEqual, a staged allocation-free structural comparison that provably emits exactly when the old serialization comparison would have (fixed token key order and a single writer to state.output make structural equality equivalent to JSON.stringify equality; fuzzed 1200/1200 against the old spec). Reuse two scratch Cell instances instead of per-cell construction, preserving the x===0 run-start quirk, per-line null seeding, and equals()/getChars() semantics byte-for-byte. Serialize colorless output directly via the new SerializeTerminalOptions.colorless flag instead of copying every token to blank fg/bg. Session recording PendingRecords now retain only {json, bytes}; the pre-content enqueue path returns the line it built, so payload object graphs are no longer pinned until drain while on-disk bytes, ordering, and accounting are unchanged. Evidence: 84/84 focused tests, full suite green, ~15% median per-tick wall-time drop at zero allocation on no-change ticks (project-plans/issue3432/PLAN.md).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 1 remains after this review. 📝 WalkthroughWalkthroughThe change reduces transient allocations in terminal serialization and session recording. It adds structural rendered-output comparison, reusable scratch cells, colorless serialization support, pending-record retention changes, and tests for rendering, serialization, recording, and parser behavior. ChangesAllocation reduction
Priority: ⬇️ Low Estimated code review effort: 4 (Complex) | ~45 minutes Change: Refactor Merge Risk: ⚪ Minimal · up to The changed paths preserve rendered output, recording behavior, and parser input, so the PR is ready to merge. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Out of Scope Changes checkExplanation The shell, terminal serializer, recording, and related tests support
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
OpenCodeReview — PR #3697
OCR stderr excerptOCR preflight excerptOCR preview stderr excerpt |
WalkthroughThis PR changes 8 file(s).
Changes
Magnitude🎯 2 (M) RelatedNo related items found. Walkthrough generated by LLxprt PR Review. Planner issue: #2256 |
… max-lines (Fixes #3699) PR #3689's merge landed OpenAIStreamProcessor.ts at 801 eslint-counted lines after that PR's own green lint run had completed, leaving the required Lint check red on every open PR (#3699, seen on #3697 and #3698). The max-lines rule skips blank and comment lines, so the repair has to remove a counted code line: parseBufferText assigned parsingText, copied it into cleanedText, and read it exactly once at the parser call, so initializing cleanedText with the sanitized text directly is behavior-identical and brings the file back to 800.
|
Included repair for the main-level lint regression (#3699) Commit Verification for that commit: |
TLDR
Cuts constant transient allocation in the shell streaming render path and halves per-record retention in the session recorder, with byte-identical observable behavior:
maybeEmitRenderedOutputno longer runs two fullJSON.stringifypasses per 16 ms tick. It now usesoutputsEqual, a staged allocation-free structural comparison that emits exactly when the old serialization comparison would have emitted.serializeTerminalToObjectreuses two scratchCellinstances per call instead of allocating oneCellper terminal cell (rows × cols per tick), and can serialize colorless output directly via the new optionalSerializeTerminalOptions.colorlessflag, eliminating the per-token{...token, fg: '', bg: ''}strip copy inserializeTerminalForRender.SessionRecordingServicepending records retain only{json, bytes}; the pre-content enqueue path returns theSessionRecordLineit built, so payload object graphs are no longer pinned until the async drain (~2x → 1x payload retention under a slow disk).Fixes #3432
Dive Deeper
Why the emit check is exact (criterion 1). The old behavior emitted iff
JSON.stringify(finalOutput) !== JSON.stringify(outputRef.current). EveryAnsiTokenis built bybuildTokenFromCellwith a fixed key order and primitive-only fields, andstate.outputhas a single writer (the emit callback itself), so structural per-field equality is equivalent toJSON.stringifyequality for every reachable state.outputsEqualstages the check cheaply: reference identity → null/string → line count → per-line token count → per-line text lengths → per-token fields. Notably, raw cursor position is deliberately not an emit trigger: the cursor affects serialization only through the tokeninverseflag (and run splitting) at the cursor cell, so consulting the raw cursor would over-emit relative to the old behavior. This was verified by a 400-round randomized fuzz comparing the new decision against theJSON.stringify-inequality spec on 1,200 transitions (0 mismatches; cursor-only transitions 113/113), plus the pinned 15×15 exactness matrix inshellPtyHelpers.bun.test.ts.Serializer equivalence (criterion 1). The scratch-
Cellrefactor preserves the original algorithm quirks exactly: thex === 0unconditional run start, the per-line null-cell seed (fg = 0, not-1),equals()semantics (attributes + colors + cursor flag),getChars()empty-string→space normalization, wide/wrapped-character handling, and blank-line →[]replacement. Fuzz-verified against a faithful reproduction of the HEAD algorithm and against the legacy two-step colorless strip (800 checks, 0 mismatches). The colorless path also drops the structurally-deadArray.isArrayfilter — serializer output isAnsiLine[]by construction.Recorder (criterion 3).
PendingRecorddrops thelinefield; drain, byte accounting, queue byte-limit enforcement, high-water reporting,prepareContentBatchpublish/rollback/finalize, and failure paths are untouched. On-disk JSONL bytes and ordering are provably unchanged (pinned by tests).Allocation evidence (criterion 2). Per no-change render tick, before → after: 1,920
Cellobjects → 2; one strip-copy object per emitted token → 0; two fullJSON.stringifystrings → 0. Measured with a fresh-process harness (3 processes per side, 7 rounds each, fixed 80×24 terminal with mixed plain/palette/RGB/256-color/bold/dim content): median wall time per tick dropped ~15% (0.0981/0.0954/0.0982 → 0.0833/0.0799/0.0822 ms). Direct allocated-bytes measurement is not obtainable on Bun 1.3.14 (heapStats().objectCountcannot see eden allocations — control-verified; the sampling profiler returns empty traces for sub-second synchronous workloads), documented inproject-plans/issue3432/PLAN.md.Scope. No buffer bounds, queue watermarks, scrollback, retention, or render-budget changes (#3428/#854 remain open follow-ups). The only API addition is the optional serializer options parameter.
Reviewer Test Plan
shellPtyHelpers.bun.test.ts(15 mutated states × emit/no-emit vs the JSON.stringify spec) is the fastest way to check criterion 1 by hand.SessionRecordingService.test.tsshows payload objects becoming collectable while their record is still pending, then appearing on disk after dispose.make,npm install, a progress-bar script) with debug logging off exercises the render loop end to end.Testing Matrix
macOS (arm64): full
npm run testsuite green (one zed-acp file initially failed with a transient ENOENT from a dist rebuild racing the suite; re-run clean 33/33),npm run lintgreen on all touched packages (one pre-existing, unrelated max-lines failure inpackages/providers/src/openai/OpenAIStreamProcessor.tsexists on main),npm run typecheck/format/buildgreen, smoke profile zai-glm-flash green.Linked issues / bugs
Fixes #3432
Related: #3426 (profiling baseline), #3428 and #854 (out-of-scope UI-memory follow-ups)
Summary by CodeRabbit