Summary
In comment-block-transformer@0.7.0 (via markdown-magic@4.8.0), any file that contains a TOC block plus at least one other block whose replacement content differs in length from its previous content gets corrupted: content is spliced at wrong offsets, producing fused/duplicated text like:
<!-- /docs -->te](#using-this-template)
This happens whenever the TOC block appears earlier in the document than another block — the common "TOC at the top, generated tables below" layout.
Root cause
Two pieces of comment-block-transformer/src/index.js don't compose:
sortTransforms() always moves blocks typed TOC/sectionToc to the end of processing order, regardless of document position (so TOC can see other blocks' final content). This reordering happens even when no TOC transform is registered — it keys on the block's type string alone.
- The splice loop assumes strict document-order processing: it keeps a single running
cumulativeOffset and adds it to each block's original block.start. When TOC is processed last but positioned first, the accumulated offset includes length deltas from blocks after it in the document, so its splice position is garbage.
Because every block is re-spliced (including missing-transform blocks, whose reconstruction is not byte-identical), the corruption triggers even with TOC unregistered, registered as an identity function, or processed in a separate call.
Repro
const { markdownMagic } = require("markdown-magic");
// file.md:
// <!-- docs TOC -->
// <!-- /docs -->
// ## Section
// <!-- docs CODE src=./snippet.js --> (any block whose output length differs)
// <!-- /docs -->
await markdownMagic(["./file.md"], { dryRun: true, dry: true });
// results[0].updatedContents contains fused/duplicated text
Bisect: {TOC} alone → clean; {TOC + short-delta block} → subtly wrong offsets; {TOC + long-delta block} → visibly corrupted every time.
Fix (patch we're running in production)
Track per-block deltas and, when splicing, apply only the deltas of blocks earlier in the document than the block being spliced — preserving the TOC-last processing order while making the offset math order-independent:
- let cumulativeOffset = 0
+ const appliedDeltas = []
+ const offsetBefore = (start) => appliedDeltas.reduce((sum, edit) => (edit.start < start ? sum + edit.delta : sum), 0)
...
- const adjustedStart = block.start + cumulativeOffset
- const adjustedEnd = block.end + cumulativeOffset
+ const adjustedStart = block.start + offsetBefore(block.start)
+ const adjustedEnd = block.end + offsetBefore(block.start)
...
- cumulativeOffset += (newCont.length - block.value.length)
+ appliedDeltas.push({ start: block.start, delta: newCont.length - block.value.length })
With this applied, a TOC-at-top + generated-tables file reaches a byte-stable fixed point in one pass. Happy to open a PR if useful.
Environment: markdown-magic@4.8.0 / comment-block-transformer@0.7.0, Node 25.
Summary
In comment-block-transformer@0.7.0 (via markdown-magic@4.8.0), any file that contains a
TOCblock plus at least one other block whose replacement content differs in length from its previous content gets corrupted: content is spliced at wrong offsets, producing fused/duplicated text like:This happens whenever the TOC block appears earlier in the document than another block — the common "TOC at the top, generated tables below" layout.
Root cause
Two pieces of
comment-block-transformer/src/index.jsdon't compose:sortTransforms()always moves blocks typedTOC/sectionTocto the end of processing order, regardless of document position (so TOC can see other blocks' final content). This reordering happens even when no TOC transform is registered — it keys on the block's type string alone.cumulativeOffsetand adds it to each block's originalblock.start. When TOC is processed last but positioned first, the accumulated offset includes length deltas from blocks after it in the document, so its splice position is garbage.Because every block is re-spliced (including missing-transform blocks, whose reconstruction is not byte-identical), the corruption triggers even with TOC unregistered, registered as an identity function, or processed in a separate call.
Repro
Bisect:
{TOC}alone → clean;{TOC + short-delta block}→ subtly wrong offsets;{TOC + long-delta block}→ visibly corrupted every time.Fix (patch we're running in production)
Track per-block deltas and, when splicing, apply only the deltas of blocks earlier in the document than the block being spliced — preserving the TOC-last processing order while making the offset math order-independent:
With this applied, a TOC-at-top + generated-tables file reaches a byte-stable fixed point in one pass. Happy to open a PR if useful.
Environment: markdown-magic@4.8.0 / comment-block-transformer@0.7.0, Node 25.