test(streaming): CI-able perf-regression guard + share top-level render opts - #34
Merged
Merged
Conversation
…er opts Follow-up hardening on the merged #21 frozen/tail work (issues #31, #32). Deterministic perf guard (#31). The only defense against a committed-prefix re-render regression (the O(n²) #21 removed) was the wall-clock scaling section in bench-streaming.mts — a manual script, not run in CI, and timing-noisy. A regression that degrades every frame to a full morph passes the whole suite (output stays byte-identical, just quadratic) and ships silently; the #21 review found exactly that class of bug. Add a diagnostic `renderedChars` counter to FrozenTailRenderer (total HTML produced per commit, or the whole document on a full-morph fallback) and a CI test that drives the renderer over a doc and its double, asserting the char count grows ~2×, not ~4×. Deterministic and timing-free (measured 2.01× vs a 3× threshold). The counter is inert — never read by production. Share top-level render options (#32 item 1). RENDER_OPTS in streaming-frozen-tail.ts copied the `{ htmlFromIndent, indentedCode }` literal that renderMarkdown hardcodes; frozen/tail byte-parity with the full-morph fallback depends on the two staying identical. Export TOP_LEVEL_RENDER_OPTS from renderer.ts and reference it in both so they can't drift. 462 tests + CommonMark conformance green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LnAFvAgDY3RVYT6zJqN1fy
jonathanKingston
pushed a commit
that referenced
this pull request
Jul 5, 2026
…dedups (#32) Resolves the fragility half of #32 without duplicating render-blocks logic or changing the O(tail) walk: - settledTailStart's hardcoded kind lists (the settled whitelist and isMultiTokenGroupKind) are replaced by settleClassOf(kind), an exhaustive switch over BlockKind with no default. With noImplicitReturns, adding a new block kind is now a COMPILE ERROR until it is classified at the frozen/tail boundary — the silent mis-freeze hazard the #21 review flagged (a future grouping kind freezing mid-group with no error) is gone. The doc directs unsure authors to `grouping`, which only grows the tail (conservative, never wrong output). Behaviour is unchanged: the classification reproduces the previous conditions exactly. - morphInnerHtml now delegates to morphInnerHtmlFrom(container, 0, html) (re-landing the dedup that fell out of the #34 squash): one copy of the clone/parse/morph strategy; the empty-html trim at offset 0 is exactly replaceChildren(). - The splitCore test helper (drop the derived `blocks` array before deepEqual) was duplicated across two test files; it now lives once in tests/split-core.ts using a rest-destructure. - splitForStreaming's doc now states the widened return shape (`blocks`) is deliberate and derived, settling #32 item 3. 462 tests + conformance green; exhaustive convergence fuzz (every prefix of every baseline example) passes against the settle-class refactor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LnAFvAgDY3RVYT6zJqN1fy
jonathanKingston
added a commit
that referenced
this pull request
Jul 5, 2026
…dedups (#32) (#36) Resolves the fragility half of #32 without duplicating render-blocks logic or changing the O(tail) walk: - settledTailStart's hardcoded kind lists (the settled whitelist and isMultiTokenGroupKind) are replaced by settleClassOf(kind), an exhaustive switch over BlockKind with no default. With noImplicitReturns, adding a new block kind is now a COMPILE ERROR until it is classified at the frozen/tail boundary — the silent mis-freeze hazard the #21 review flagged (a future grouping kind freezing mid-group with no error) is gone. The doc directs unsure authors to `grouping`, which only grows the tail (conservative, never wrong output). Behaviour is unchanged: the classification reproduces the previous conditions exactly. - morphInnerHtml now delegates to morphInnerHtmlFrom(container, 0, html) (re-landing the dedup that fell out of the #34 squash): one copy of the clone/parse/morph strategy; the empty-html trim at offset 0 is exactly replaceChildren(). - The splitCore test helper (drop the derived `blocks` array before deepEqual) was duplicated across two test files; it now lives once in tests/split-core.ts using a rest-destructure. - splitForStreaming's doc now states the widened return shape (`blocks`) is deliberate and derived, settling #32 item 3. 462 tests + conformance green; exhaustive convergence fuzz (every prefix of every baseline example) passes against the settle-class refactor. Claude-Session: https://claude.ai/code/session_01LnAFvAgDY3RVYT6zJqN1fy Co-authored-by: Claude <noreply@anthropic.com>
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.
Follow-up hardening on the merged #21 frozen/tail work. Closes #31, closes #32 (item 1).
Deterministic perf guard (#31)
The only defense against a committed-prefix re-render regression — the O(n²) that #21 removed — was the wall-clock scaling section in
bench-streaming.mts, which is a manual script (not run in CI) and timing-noisy. A regression that degrades every frame to a full morph passes the whole suite (output stays byte-identical, just quadratic) and ships silently; the #21 review found exactly that class of bug (a fallback storm on non-monotonic boundaries).This adds a diagnostic
renderedCharscounter toFrozenTailRenderer— the total HTML it produces per commit (delta + tail, or the whole document on a full-morph fallback) — and a deterministic, timing-free CI test that drives the renderer over a doc and its double and asserts the char count grows ~2×, not ~4×. Measured 2.01× against a generous< 3×threshold. The counter is inert (never read by production code); a return to O(prefix)-per-commit rendering pushes the ratio toward 4× and fails the test regardless of machine speed.Share top-level render options (#32 item 1)
RENDER_OPTSinstreaming-frozen-tail.tscopied the{ htmlFromIndent, indentedCode }literal thatrenderMarkdownhardcodes; frozen/tail byte-parity with the full-morph fallback depends on the two staying identical (a silent drift would be a correctness break, not just slowness). ExportTOP_LEVEL_RENDER_OPTSfromrenderer.tsand reference it in both.Verification
Remaining #21 follow-ups tracked in #29 (bound the tail for long open lists), #30 (incremental tokenizer/link-ref scan → true O(n)), and #32 items 2–5 (shared group segmentation, split return type, dedup).
🤖 Generated with Claude Code
https://claude.ai/code/session_01LnAFvAgDY3RVYT6zJqN1fy
Generated by Claude Code