@ai-react-markdown/engine 2.5.5 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -316,207 +316,15 @@ interface FreezeScanResult {
316
316
  * re-lexing the confirmed prefix. Single-consumer; see module docs. */
317
317
  checkpoint: FreezeScanCheckpoint;
318
318
  }
319
- interface Candidate {
320
- /** Freeze boundary: start of the line after this blank line. */
321
- offset: number;
322
- /** Consecutive confirmed blank lines (outside fences/math) ending here. */
323
- blankRun: number;
324
- /** No unbalanced HTML container / comment / raw block before this point. */
325
- htmlBalanced: boolean;
326
- /** Rolling continuation-hazard verdict at emission (blocker 3). */
327
- hazard: boolean;
328
- /** Blocker-6: the run ending at this blank left balanced FLOATING raw
329
- * remnant whose hast seam is tail-dependent; reject this candidate. */
330
- seamRisk: boolean;
331
- /** Blocker-4 settle verdict, decided by the NEXT confirmed line (`null`
332
- * while that line hasn't confirmed — only the newest candidate can be
333
- * pending). Storing the verdict instead of the line lets the checkpoint
334
- * drop its lines array, which retained a full copy of the document
335
- * (round-2 review: ~2-3× doc size per mounted instance). */
336
- defListSettled: boolean | null;
337
- }
338
- interface UnresolvedRef {
339
- offset: number;
340
- label: string;
341
- footnote: boolean;
342
- }
343
- /** Mutable resume state. All fields describe the scan strictly BEFORE the
344
- * first unconfirmed character (`confirmedOffset`). */
319
+ /** Opaque resume token. Produced by one `computeFreezeBoundary` call and
320
+ * passed back on the next APPEND-ONLY call; the only supported operations
321
+ * are storing it and passing it back. The field set is an implementation
322
+ * detail of the scanner and changes between minor versions — the brand key
323
+ * (a structural string literal, safe across dual d.ts entries) is all that
324
+ * ships in the public type. */
345
325
  interface FreezeScanCheckpoint {
346
- defListEnabled: boolean;
347
- /** Grammar-profile switches baked at creation — a checkpoint is only
348
- * resumable under the exact profile that built it. */
349
- mathFlow: boolean;
350
- referenceTaint: boolean;
351
- /** Start offset of the first line NOT yet baked into this checkpoint. */
352
- confirmedOffset: number;
353
- candidates: Candidate[];
354
- defs: Map<string, number>;
355
- footnoteDefs: Map<string, number>;
356
- unresolvedRefs: UnresolvedRef[];
357
- tagBalance: Map<string, number>;
358
- openTotal: number;
359
- commentOpen: boolean;
360
- piOpen: boolean;
361
- declOpen: boolean;
362
- cdataOpen: boolean;
363
- /** parse5 BOGUS COMMENT open inside a real html-flow run: `<!` not
364
- * followed by `--` / letter / `[CDATA[`, or `</` not followed by a
365
- * letter (`<!\n`, `<!-`, `</\n`, `<//`) — eaten up to the next `>`;
366
- * a `</div>` inside is comment text, not a close (2026-08-19 review r2
367
- * P1-3: the balance zeroed past an open div). micromark has no such
368
- * construct: those bytes are just html-block content. */
369
- bogusOpen: boolean;
370
- /** The RAW_TEXT_ELEMENTS element currently open (its content is text to
371
- * parse5): tags and comment tokens are ignored until `</name>`. */
372
- rawTextOpen: string | null;
373
- inFence: boolean;
374
- fenceChar: string;
375
- fenceLen: number;
376
- inMath: boolean;
377
- /** Opening dollar-run length while inMath — the close run must match it. */
378
- mathFenceLen: number;
379
- /** Indent (0-3 spaces) of the line that opened the current fence/math
380
- * block. Not a blocker input — read by `phantomSuffixCloser` to emit a
381
- * closer at the SAME indent, which closes the block whether it sits at
382
- * top level (≤3 spaces are allowed there) or inside a list item whose
383
- * content indent the opener line already satisfies. */
384
- openIndent: number;
385
- blankRun: number;
386
- lastBlankStart: number;
387
- /** Rolling blocker-3 verdict ("nearest decisive block start so far"). */
388
- hazardVerdict: boolean;
389
- /** Previous confirmed line was blank (block-start detection). */
390
- prevLineBlank: boolean;
391
- /** Previous confirmed line was a plain text line (def-chain detection). */
392
- prevLineWasText: boolean;
393
- /** Previous confirmed line registered a VALID definition (def chains). */
394
- prevLineWasValidDef: boolean;
395
- /** An earlier line of the current paragraph left an unpaired backtick
396
- * run — masking is disabled until the paragraph ends (safety gate). */
397
- /** A `[` left unclosed at the end of a paragraph line (code spans
398
- * masked). micromark lets a reference label span soft line breaks, so
399
- * the label may close on a LATER line where the per-line REF_RE never
400
- * sees a `[…]` pair (v2.4.1 review P1: `see [foo\nbar] end` + a late
401
- * `[foo bar]: /u` retargeted frozen output). Cleared wherever the
402
- * paragraph ends. */
403
- openBracket: {
404
- offset: number;
405
- text: string;
406
- } | null;
407
- paragraphHasUnpairedRun: boolean;
408
- /** Blocker-6 pending flag: a confirmed html-flow line left balanced
409
- * floating raw remnant, and no later content line has pinned the seam
410
- * yet. Persists across blank lines (every candidate emitted while set
411
- * has the remnant as its last frozen child); cleared by the next
412
- * non-blank line that starts OUTSIDE an html-flow run. */
413
- htmlSeamPending: boolean;
414
- /** A line since the last blank started with `<` at block indent — an html
415
- * FLOW block is (approximately) running, and it only ends at a blank
416
- * line. micromark does no inline parsing there: backtick runs are
417
- * literal text, so code-span masking would hide REAL tags from the
418
- * balance scan (under-block — fuzz counterexample: `</details>` followed
419
- * by an unblanked `` `<div>` `` line). While set, masking is skipped —
420
- * which can only over-block (safe direction). */
421
- htmlFlowSinceBlank: boolean;
422
- /** Offset of the first fence/math OPEN suppressed by `htmlFlowSinceBlank`
423
- * (Infinity = none). Whether the run really swallowed that line depends
424
- * on container context the line scan cannot see (`<embed` inside a list
425
- * item is a lazy paragraph line, and the glued `$$` a REAL math open —
426
- * seed-20260757 under-block: the tracker's fence phase INVERTS from that
427
- * line on, every later close reads as an open, and the corruption never
428
- * resyncs). Candidates past this offset are rejected outright — sticky,
429
- * pure over-block; candidates before it are untouched (the ambiguous
430
- * region then re-parses inside the tail). The rolling hazard poison for
431
- * ambiguous tag names stays, but it decays at the next decisive block
432
- * start — this field is the phase-corruption backstop that does not. */
433
- phasePoisonedAt: number;
434
- /** Tag names of line-truncated opens (`<div` at EOL) counted into
435
- * tagBalance but not yet confirmed by a later `>` — reverted at the next
436
- * blank line (a tag cannot span one). See TRUNCATED_TAG_RE. */
437
- pendingTruncatedTags: string[];
438
- /** Line-truncated CLOSING tags (`</div` + EOL, no `>`) inside an html-flow
439
- * run, waiting for a `>` on a later line of the same run. Unlike opens
440
- * they are NOT counted up front: a close tag cannot carry attributes, so
441
- * `para </style` (prose to micromark, still-open element to parse5 —
442
- * RAWTEXT waits for the `>`) must not zero the balance (2026-08-19
443
- * review P1 — the boundary crossed an open `<style>`). Confirmed and
444
- * applied only when a later line of the run brings the `>` (parse5
445
- * completes the end tag; micromark's block ends at the blank anyway);
446
- * dropped unapplied at the blank (element stays counted — over-block).
447
- * Paragraph-context truncated closes are never pended: a `>` at block
448
- * indent on the next line is a blockquote to micromark, and the one
449
- * shape that would complete the inline close (`</b\n >`, a 4+-space
450
- * lazy continuation) is not modelled — the element stays counted,
451
- * over-block. */
452
- pendingTruncatedCloses: string[];
453
- /** An html-flow line ended inside a tag (`<div`, `</div`, `<br` — open,
454
- * close or void, with or without attributes): parse5's tokenizer is
455
- * still in that tag, so on the following lines everything up to the
456
- * FIRST `>` is attribute garbage — `</div>` there does NOT close
457
- * anything (oracle review of 2.4.4: `<div>\n<div>\n</div\n</div>` froze
458
- * past the still-open outer div; pre-existing, 1-char slices). While
459
- * set: a line without `>` gets no tag scan at all; the line with the
460
- * `>` completes the pending close (if any), then only its text after
461
- * that `>` is scanned. Cleared there and at the blank line. */
462
- tagAcrossLines: boolean;
463
- /** Indent of the line that set `tagAcrossLines`. A following line that
464
- * DE-INDENTS below it may have left the container (a list item's html
465
- * block ends there; hast-util-raw resets the tokenizer at the li/ul
466
- * boundary, so a `<div>` on that line is a real start tag, not garbage)
467
- * — or may still be the same html block (root-level ` </div\n</div>`,
468
- * still garbage). Unknowable here → poison (oracle 3rd pass). */
469
- tagAcrossLinesIndent: number;
470
- /** parse5's position inside that tag's attribute area at the end of the
471
- * last scanned line (see TagAttrState). A quoted value left open swallows
472
- * `>` and line endings; a `>` that arrives while `outside` ends the tag. */
473
- tagAcrossLinesState: TagAttrState;
474
- /** The html-flow run since the last blank REALLY started as a micromark
475
- * html block (type 6 / type 1 / a paragraph-not-interrupting type 7) —
476
- * as opposed to `htmlFlowSinceBlank`, which any `<tag` / `</tag` line
477
- * start sets (over-approximation, fine for its over-blocking uses). Only
478
- * in a real run are the bytes raw to parse5 across line endings; the
479
- * cross-line-tag garbage model (`tagAcrossLines`, pended closes) is
480
- * gated on it — in a paragraph starting `</i` the next line's `<div>` /
481
- * `<!--` are REAL blocks (oracle re-check of 2.4.4: gating on
482
- * htmlFlowSinceBlank swallowed them — a new under-block). Sticky to the
483
- * blank; a type 6/1 start on a later line of a non-real run promotes it. */
484
- htmlFlowReal: boolean;
485
- /** The run since the last blank was OPENED by a CommonMark type-1 block
486
- * (`<script` / `<pre` / `<style` / `<textarea`). Type 1 is the one block
487
- * that ends at its CLOSER line rather than at a blank, so `htmlFlowReal`
488
- * being "sticky to the blank" over-ran it: the line after `</script>` is
489
- * a fresh PARAGRAPH to micromark, but the scanner still treated it as
490
- * raw html flow — and in a real run end tags may carry attributes, so a
491
- * `</div a="b">` there was counted as a REAL close. `p <div> x </div
492
- * a="b"> y` after a `</script>` line therefore froze past an element the
493
- * actual parse leaves OPEN, nesting the whole rest of the document
494
- * inside it (2026-08-20 soak leg 2; identical for pre/style/textarea,
495
- * and correct as-is for a type-6 run, which really does reach the blank).
496
- * Only set when the type-1 opener is the line that STARTED the run — a
497
- * `<script>` nested inside an open type-6 block must not end it. */
498
- type1FlowOpen: boolean;
499
- /** The currently open raw-text element (`rawTextOpen`) was opened INLINE —
500
- * in paragraph context rather than in a real html-flow run. */
501
- rawTextInline: boolean;
502
- /** The open-element stack, in order. `tagBalance` and `openTotal` are
503
- * derived views kept in step with it, but the STACK is the truth: an end
504
- * tag's effect depends on what sits BETWEEN it and its match, which a
505
- * name→count bag cannot represent (see SCOPE_BARRIER_NAMES). */
506
- openStack: string[];
507
- /** parse5 is in "script data escaped": a `<!--` has been seen inside the
508
- * open `<script>`. Only meaningful while `rawTextOpen === 'script'`.
509
- * Deliberately NOT cleared by `-->` — staying escaped can only poison
510
- * more often, which is the over-blocking side. */
511
- scriptDataEscaped: boolean;
326
+ readonly '~freezeScanCheckpoint'?: never;
512
327
  }
513
- /** parse5 tokenizer position inside a tag's attribute area, tracked across
514
- * line endings for `tagAcrossLines` (2026-08-19 review r2 P1-2 / P2-3):
515
- * `outside` = before/in an attribute name (a `"` here starts a NAME, not a
516
- * value); `afterEq` = just past `=`; `unquoted` = in an unquoted value
517
- * (whitespace ends it); `"` / `'` = inside a quoted value — `>` and line
518
- * endings are value bytes there, only the matching quote leaves it. */
519
- type TagAttrState = 'outside' | 'afterEq' | 'unquoted' | '"' | "'";
520
328
  declare function computeFreezeBoundary(text: string, options: FreezeBoundaryOptions, resume?: FreezeScanCheckpoint | null): FreezeScanResult;
521
329
 
522
330
  /**
package/dist/index.d.ts CHANGED
@@ -316,207 +316,15 @@ interface FreezeScanResult {
316
316
  * re-lexing the confirmed prefix. Single-consumer; see module docs. */
317
317
  checkpoint: FreezeScanCheckpoint;
318
318
  }
319
- interface Candidate {
320
- /** Freeze boundary: start of the line after this blank line. */
321
- offset: number;
322
- /** Consecutive confirmed blank lines (outside fences/math) ending here. */
323
- blankRun: number;
324
- /** No unbalanced HTML container / comment / raw block before this point. */
325
- htmlBalanced: boolean;
326
- /** Rolling continuation-hazard verdict at emission (blocker 3). */
327
- hazard: boolean;
328
- /** Blocker-6: the run ending at this blank left balanced FLOATING raw
329
- * remnant whose hast seam is tail-dependent; reject this candidate. */
330
- seamRisk: boolean;
331
- /** Blocker-4 settle verdict, decided by the NEXT confirmed line (`null`
332
- * while that line hasn't confirmed — only the newest candidate can be
333
- * pending). Storing the verdict instead of the line lets the checkpoint
334
- * drop its lines array, which retained a full copy of the document
335
- * (round-2 review: ~2-3× doc size per mounted instance). */
336
- defListSettled: boolean | null;
337
- }
338
- interface UnresolvedRef {
339
- offset: number;
340
- label: string;
341
- footnote: boolean;
342
- }
343
- /** Mutable resume state. All fields describe the scan strictly BEFORE the
344
- * first unconfirmed character (`confirmedOffset`). */
319
+ /** Opaque resume token. Produced by one `computeFreezeBoundary` call and
320
+ * passed back on the next APPEND-ONLY call; the only supported operations
321
+ * are storing it and passing it back. The field set is an implementation
322
+ * detail of the scanner and changes between minor versions — the brand key
323
+ * (a structural string literal, safe across dual d.ts entries) is all that
324
+ * ships in the public type. */
345
325
  interface FreezeScanCheckpoint {
346
- defListEnabled: boolean;
347
- /** Grammar-profile switches baked at creation — a checkpoint is only
348
- * resumable under the exact profile that built it. */
349
- mathFlow: boolean;
350
- referenceTaint: boolean;
351
- /** Start offset of the first line NOT yet baked into this checkpoint. */
352
- confirmedOffset: number;
353
- candidates: Candidate[];
354
- defs: Map<string, number>;
355
- footnoteDefs: Map<string, number>;
356
- unresolvedRefs: UnresolvedRef[];
357
- tagBalance: Map<string, number>;
358
- openTotal: number;
359
- commentOpen: boolean;
360
- piOpen: boolean;
361
- declOpen: boolean;
362
- cdataOpen: boolean;
363
- /** parse5 BOGUS COMMENT open inside a real html-flow run: `<!` not
364
- * followed by `--` / letter / `[CDATA[`, or `</` not followed by a
365
- * letter (`<!\n`, `<!-`, `</\n`, `<//`) — eaten up to the next `>`;
366
- * a `</div>` inside is comment text, not a close (2026-08-19 review r2
367
- * P1-3: the balance zeroed past an open div). micromark has no such
368
- * construct: those bytes are just html-block content. */
369
- bogusOpen: boolean;
370
- /** The RAW_TEXT_ELEMENTS element currently open (its content is text to
371
- * parse5): tags and comment tokens are ignored until `</name>`. */
372
- rawTextOpen: string | null;
373
- inFence: boolean;
374
- fenceChar: string;
375
- fenceLen: number;
376
- inMath: boolean;
377
- /** Opening dollar-run length while inMath — the close run must match it. */
378
- mathFenceLen: number;
379
- /** Indent (0-3 spaces) of the line that opened the current fence/math
380
- * block. Not a blocker input — read by `phantomSuffixCloser` to emit a
381
- * closer at the SAME indent, which closes the block whether it sits at
382
- * top level (≤3 spaces are allowed there) or inside a list item whose
383
- * content indent the opener line already satisfies. */
384
- openIndent: number;
385
- blankRun: number;
386
- lastBlankStart: number;
387
- /** Rolling blocker-3 verdict ("nearest decisive block start so far"). */
388
- hazardVerdict: boolean;
389
- /** Previous confirmed line was blank (block-start detection). */
390
- prevLineBlank: boolean;
391
- /** Previous confirmed line was a plain text line (def-chain detection). */
392
- prevLineWasText: boolean;
393
- /** Previous confirmed line registered a VALID definition (def chains). */
394
- prevLineWasValidDef: boolean;
395
- /** An earlier line of the current paragraph left an unpaired backtick
396
- * run — masking is disabled until the paragraph ends (safety gate). */
397
- /** A `[` left unclosed at the end of a paragraph line (code spans
398
- * masked). micromark lets a reference label span soft line breaks, so
399
- * the label may close on a LATER line where the per-line REF_RE never
400
- * sees a `[…]` pair (v2.4.1 review P1: `see [foo\nbar] end` + a late
401
- * `[foo bar]: /u` retargeted frozen output). Cleared wherever the
402
- * paragraph ends. */
403
- openBracket: {
404
- offset: number;
405
- text: string;
406
- } | null;
407
- paragraphHasUnpairedRun: boolean;
408
- /** Blocker-6 pending flag: a confirmed html-flow line left balanced
409
- * floating raw remnant, and no later content line has pinned the seam
410
- * yet. Persists across blank lines (every candidate emitted while set
411
- * has the remnant as its last frozen child); cleared by the next
412
- * non-blank line that starts OUTSIDE an html-flow run. */
413
- htmlSeamPending: boolean;
414
- /** A line since the last blank started with `<` at block indent — an html
415
- * FLOW block is (approximately) running, and it only ends at a blank
416
- * line. micromark does no inline parsing there: backtick runs are
417
- * literal text, so code-span masking would hide REAL tags from the
418
- * balance scan (under-block — fuzz counterexample: `</details>` followed
419
- * by an unblanked `` `<div>` `` line). While set, masking is skipped —
420
- * which can only over-block (safe direction). */
421
- htmlFlowSinceBlank: boolean;
422
- /** Offset of the first fence/math OPEN suppressed by `htmlFlowSinceBlank`
423
- * (Infinity = none). Whether the run really swallowed that line depends
424
- * on container context the line scan cannot see (`<embed` inside a list
425
- * item is a lazy paragraph line, and the glued `$$` a REAL math open —
426
- * seed-20260757 under-block: the tracker's fence phase INVERTS from that
427
- * line on, every later close reads as an open, and the corruption never
428
- * resyncs). Candidates past this offset are rejected outright — sticky,
429
- * pure over-block; candidates before it are untouched (the ambiguous
430
- * region then re-parses inside the tail). The rolling hazard poison for
431
- * ambiguous tag names stays, but it decays at the next decisive block
432
- * start — this field is the phase-corruption backstop that does not. */
433
- phasePoisonedAt: number;
434
- /** Tag names of line-truncated opens (`<div` at EOL) counted into
435
- * tagBalance but not yet confirmed by a later `>` — reverted at the next
436
- * blank line (a tag cannot span one). See TRUNCATED_TAG_RE. */
437
- pendingTruncatedTags: string[];
438
- /** Line-truncated CLOSING tags (`</div` + EOL, no `>`) inside an html-flow
439
- * run, waiting for a `>` on a later line of the same run. Unlike opens
440
- * they are NOT counted up front: a close tag cannot carry attributes, so
441
- * `para </style` (prose to micromark, still-open element to parse5 —
442
- * RAWTEXT waits for the `>`) must not zero the balance (2026-08-19
443
- * review P1 — the boundary crossed an open `<style>`). Confirmed and
444
- * applied only when a later line of the run brings the `>` (parse5
445
- * completes the end tag; micromark's block ends at the blank anyway);
446
- * dropped unapplied at the blank (element stays counted — over-block).
447
- * Paragraph-context truncated closes are never pended: a `>` at block
448
- * indent on the next line is a blockquote to micromark, and the one
449
- * shape that would complete the inline close (`</b\n >`, a 4+-space
450
- * lazy continuation) is not modelled — the element stays counted,
451
- * over-block. */
452
- pendingTruncatedCloses: string[];
453
- /** An html-flow line ended inside a tag (`<div`, `</div`, `<br` — open,
454
- * close or void, with or without attributes): parse5's tokenizer is
455
- * still in that tag, so on the following lines everything up to the
456
- * FIRST `>` is attribute garbage — `</div>` there does NOT close
457
- * anything (oracle review of 2.4.4: `<div>\n<div>\n</div\n</div>` froze
458
- * past the still-open outer div; pre-existing, 1-char slices). While
459
- * set: a line without `>` gets no tag scan at all; the line with the
460
- * `>` completes the pending close (if any), then only its text after
461
- * that `>` is scanned. Cleared there and at the blank line. */
462
- tagAcrossLines: boolean;
463
- /** Indent of the line that set `tagAcrossLines`. A following line that
464
- * DE-INDENTS below it may have left the container (a list item's html
465
- * block ends there; hast-util-raw resets the tokenizer at the li/ul
466
- * boundary, so a `<div>` on that line is a real start tag, not garbage)
467
- * — or may still be the same html block (root-level ` </div\n</div>`,
468
- * still garbage). Unknowable here → poison (oracle 3rd pass). */
469
- tagAcrossLinesIndent: number;
470
- /** parse5's position inside that tag's attribute area at the end of the
471
- * last scanned line (see TagAttrState). A quoted value left open swallows
472
- * `>` and line endings; a `>` that arrives while `outside` ends the tag. */
473
- tagAcrossLinesState: TagAttrState;
474
- /** The html-flow run since the last blank REALLY started as a micromark
475
- * html block (type 6 / type 1 / a paragraph-not-interrupting type 7) —
476
- * as opposed to `htmlFlowSinceBlank`, which any `<tag` / `</tag` line
477
- * start sets (over-approximation, fine for its over-blocking uses). Only
478
- * in a real run are the bytes raw to parse5 across line endings; the
479
- * cross-line-tag garbage model (`tagAcrossLines`, pended closes) is
480
- * gated on it — in a paragraph starting `</i` the next line's `<div>` /
481
- * `<!--` are REAL blocks (oracle re-check of 2.4.4: gating on
482
- * htmlFlowSinceBlank swallowed them — a new under-block). Sticky to the
483
- * blank; a type 6/1 start on a later line of a non-real run promotes it. */
484
- htmlFlowReal: boolean;
485
- /** The run since the last blank was OPENED by a CommonMark type-1 block
486
- * (`<script` / `<pre` / `<style` / `<textarea`). Type 1 is the one block
487
- * that ends at its CLOSER line rather than at a blank, so `htmlFlowReal`
488
- * being "sticky to the blank" over-ran it: the line after `</script>` is
489
- * a fresh PARAGRAPH to micromark, but the scanner still treated it as
490
- * raw html flow — and in a real run end tags may carry attributes, so a
491
- * `</div a="b">` there was counted as a REAL close. `p <div> x </div
492
- * a="b"> y` after a `</script>` line therefore froze past an element the
493
- * actual parse leaves OPEN, nesting the whole rest of the document
494
- * inside it (2026-08-20 soak leg 2; identical for pre/style/textarea,
495
- * and correct as-is for a type-6 run, which really does reach the blank).
496
- * Only set when the type-1 opener is the line that STARTED the run — a
497
- * `<script>` nested inside an open type-6 block must not end it. */
498
- type1FlowOpen: boolean;
499
- /** The currently open raw-text element (`rawTextOpen`) was opened INLINE —
500
- * in paragraph context rather than in a real html-flow run. */
501
- rawTextInline: boolean;
502
- /** The open-element stack, in order. `tagBalance` and `openTotal` are
503
- * derived views kept in step with it, but the STACK is the truth: an end
504
- * tag's effect depends on what sits BETWEEN it and its match, which a
505
- * name→count bag cannot represent (see SCOPE_BARRIER_NAMES). */
506
- openStack: string[];
507
- /** parse5 is in "script data escaped": a `<!--` has been seen inside the
508
- * open `<script>`. Only meaningful while `rawTextOpen === 'script'`.
509
- * Deliberately NOT cleared by `-->` — staying escaped can only poison
510
- * more often, which is the over-blocking side. */
511
- scriptDataEscaped: boolean;
326
+ readonly '~freezeScanCheckpoint'?: never;
512
327
  }
513
- /** parse5 tokenizer position inside a tag's attribute area, tracked across
514
- * line endings for `tagAcrossLines` (2026-08-19 review r2 P1-2 / P2-3):
515
- * `outside` = before/in an attribute name (a `"` here starts a NAME, not a
516
- * value); `afterEq` = just past `=`; `unquoted` = in an unquoted value
517
- * (whitespace ends it); `"` / `'` = inside a quoted value — `>` and line
518
- * endings are value bytes there, only the matching quote leaves it. */
519
- type TagAttrState = 'outside' | 'afterEq' | 'unquoted' | '"' | "'";
520
328
  declare function computeFreezeBoundary(text: string, options: FreezeBoundaryOptions, resume?: FreezeScanCheckpoint | null): FreezeScanResult;
521
329
 
522
330
  /**
@@ -651,7 +651,8 @@ function classifyBlockStart(text, indent, defListEnabled) {
651
651
  function computeFreezeBoundary(text, options, resume) {
652
652
  const mathFlow = options.mathFlow ?? true;
653
653
  const referenceTaint = options.referenceTaint ?? true;
654
- const cp = resume && resume.defListEnabled === options.defListEnabled && resume.mathFlow === mathFlow && resume.referenceTaint === referenceTaint && resume.confirmedOffset <= text.length ? resume : freshCheckpoint(options.defListEnabled, mathFlow, referenceTaint);
654
+ const prev = resume;
655
+ const cp = prev && prev.defListEnabled === options.defListEnabled && prev.mathFlow === mathFlow && prev.referenceTaint === referenceTaint && prev.confirmedOffset <= text.length ? prev : freshCheckpoint(options.defListEnabled, mathFlow, referenceTaint);
655
656
  let start = cp.confirmedOffset;
656
657
  let tailLine = null;
657
658
  while (start < text.length) {
@@ -714,6 +715,14 @@ function computeFreezeBoundary(text, options, resume) {
714
715
  }
715
716
  return { boundary, checkpoint: cp };
716
717
  }
718
+ function pendingFenceCloser(checkpoint) {
719
+ const cp = checkpoint;
720
+ if (cp.phasePoisonedAt !== Infinity) return "";
721
+ if (cp.openIndent !== 0) return "";
722
+ if (cp.inFence) return cp.fenceChar.repeat(cp.fenceLen);
723
+ if (cp.inMath) return "$".repeat(cp.mathFenceLen);
724
+ return "";
725
+ }
717
726
  function floatingResidue(text, commentOpenAtStart) {
718
727
  let out = "";
719
728
  let open = commentOpenAtStart;
@@ -2265,12 +2274,10 @@ function phantomSuffixCloser(content) {
2265
2274
  const endsWithNewline = content.endsWith("\n");
2266
2275
  const confirmed = endsWithNewline ? content : content + "\n";
2267
2276
  const { checkpoint } = computeFreezeBoundary(confirmed, { defListEnabled: false, referenceTaint: false });
2268
- if (checkpoint.phasePoisonedAt !== Infinity) return "";
2269
- if (checkpoint.openIndent !== 0) return "";
2270
- const nl = endsWithNewline ? "" : "\n";
2271
- if (checkpoint.inFence) return `${nl}${checkpoint.fenceChar.repeat(checkpoint.fenceLen)}`;
2272
- if (checkpoint.inMath) return `${nl}${"$".repeat(checkpoint.mathFenceLen)}`;
2273
- return "";
2277
+ const closer = pendingFenceCloser(checkpoint);
2278
+ if (closer === "") return "";
2279
+ return endsWithNewline ? closer : `
2280
+ ${closer}`;
2274
2281
  }
2275
2282
 
2276
2283
  // src/components/extractContributions.ts