DavidWells / DavidWells/markdown-magic
TOC block corrupts multi-block files: sortTransforms reorders TOC last but splice offsets assume document order
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 868
- Forks
- 223
- PR merge metrics
- No merged PRs in 30d
Description
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 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.- The splice loop assumes strict document-order processing: it keeps a single running
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
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in comment-block-transformer/src/index.js, inspect sortTransforms and the splice loop, then reproduce the provided TOC-at-top case through markdownMagic with a length-changing block. Done means the generated file is not corrupted and reaches a byte-stable fixed point in one pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100