Skip to content

test(streaming): CI-able perf-regression guard + share top-level render opts - #34

Merged
jonathanKingston merged 1 commit into
mainfrom
claude/issue-21-ccvlpo
Jul 5, 2026
Merged

jonathanKingston merged 1 commit into
mainfrom
claude/issue-21-ccvlpo

Conversation

@jonathanKingston

Copy link
Copy Markdown
Collaborator

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 renderedChars counter to FrozenTailRenderer — 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_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 (a silent drift would be a correctness break, not just slowness). Export TOP_LEVEL_RENDER_OPTS from renderer.ts and reference it in both.

Verification

  • 462 tests + CommonMark conformance green.
  • New guard test is deterministic (byte counts, not timing).

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

…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
jonathanKingston merged commit 979e718 into main Jul 5, 2026
1 check passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants