From 68942c9919b3a80929d5ad4f3120fa4d0ee77893 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Thu, 24 Sep 2026 00:05:08 +0200 Subject: [PATCH 1/4] 27 - a placed block carries no headroom, so the list's directive items write none --- src/markdown/emit/adf-to-markdown.ts | 4 ++-- todo-history.md | 6 ++++++ todo.md | 6 +----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/markdown/emit/adf-to-markdown.ts b/src/markdown/emit/adf-to-markdown.ts index 89afb50..b74f75b 100644 --- a/src/markdown/emit/adf-to-markdown.ts +++ b/src/markdown/emit/adf-to-markdown.ts @@ -19,7 +19,7 @@ type BlockContainer = 'directive' | 'document' | 'list-item' type BlockSpelling = 'commonmark' | 'directive' | 'list' type EmittedBlock = { headroom: number; spelling: BlockSpelling; text: string } type KeptSpelling = { block: EmittedBlock | undefined; depth: number } -type PlacedBlock = EmittedBlock & { node: AdfNode } +type PlacedBlock = Omit & { node: AdfNode } export type SpellingMemo = Map type Walk = { blocks: readonly PlacedBlock[]; headroom: number } type WalkedItem = { node: AdfNode; walk: Walk } @@ -256,7 +256,7 @@ function tryList(node: AdfNode, path: ConvertErrorPath, depth: number, memo: Spe // The directive form sinks each item's blocks a level below where the walk read them. function directiveItems(items: readonly WalkedItem[]): PlacedBlock[] { - return items.map((item) => ({ ...directivePair(item.node, listItemOpener, joinBlocks(item.walk.blocks, 'directive'), item.walk.headroom - 1), node: item.node })) + return items.map((item) => ({ ...directivePair(item.node, listItemOpener, joinBlocks(item.walk.blocks, 'directive')), node: item.node })) } function listStart(node: AdfNode, items: number): number | undefined { diff --git a/todo-history.md b/todo-history.md index 71a80c4..8293943 100644 --- a/todo-history.md +++ b/todo-history.md @@ -1066,6 +1066,12 @@ The done `todo.md` items in full, as they were written. `todo.md` keeps a one-li pass's set read-only and returns the whole. `containerStack` is the one writer of the stack, `edges` and the open directives' depths by name, all read-only elsewhere; deriving `edges` from the stack instead makes every line walk the open directives, quadratic in their nesting. +- [x] **27 — The dead `headroom` write goes (`0.2.0`).** `directiveItems` + (`emit/adf-to-markdown.ts:258`) writes `item.walk.headroom - 1` onto each `PlacedBlock`, and + nothing on that path reads a block's `headroom`: `joinBlocks` and `separationBetween` read + `text` and `spelling`, and `emitDirectiveBlock` takes the level from the `Walk`. Of the two + subtractions three readers flagged as double-counting, this is the one that is dead. + `PlacedBlock` carries no `headroom`, so a placed block cannot be read for one. ## 5 — Ship `0.1.0` diff --git a/todo.md b/todo.md index 6487747..388410d 100644 --- a/todo.md +++ b/todo.md @@ -59,11 +59,7 @@ chunk clearing a §11 seam. - [x] **24 — The conformance gates have a directory (`0.2.0`).** - [x] **25 — AGENTS.md §8 and §11 are findable (`0.2.0`).** - [x] **26 — The two mutable structures say what they guarantee (`0.2.0`).** -- [ ] **27 — The dead `headroom` write goes (`0.2.0`).** `directiveItems` - (`emit/adf-to-markdown.ts:258`) writes `item.walk.headroom - 1` onto each `PlacedBlock`, and - nothing on that path reads a block's `headroom`: `joinBlocks` and `separationBetween` read - `text` and `spelling`, and `emitDirectiveBlock` takes the level from the `Walk`. Of the two - subtractions three readers flagged as double-counting, this is the one that is dead. +- [x] **27 — The dead `headroom` write goes (`0.2.0`).** - [x] **0 — Scaffold.** - [x] **1a — The directive grammar.** - [x] **1b — Block node syntaxes.** -- 2.52.0 From 3d0f82cbd454acb99c79feeb59126ea6c36c0735 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Thu, 24 Sep 2026 00:14:46 +0200 Subject: [PATCH 2/4] 27 - the history keeps the item as written --- todo-history.md | 1 - 1 file changed, 1 deletion(-) diff --git a/todo-history.md b/todo-history.md index 8293943..d110e33 100644 --- a/todo-history.md +++ b/todo-history.md @@ -1071,7 +1071,6 @@ The done `todo.md` items in full, as they were written. `todo.md` keeps a one-li nothing on that path reads a block's `headroom`: `joinBlocks` and `separationBetween` read `text` and `spelling`, and `emitDirectiveBlock` takes the level from the `Walk`. Of the two subtractions three readers flagged as double-counting, this is the one that is dead. - `PlacedBlock` carries no `headroom`, so a placed block cannot be read for one. ## 5 — Ship `0.1.0` -- 2.52.0 From 0fd412e4267db352e374bba7f300b3fad5385c02 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Thu, 24 Sep 2026 00:19:05 +0200 Subject: [PATCH 3/4] 27 - directiveItems loses the comment that explained the removed subtraction --- src/markdown/emit/adf-to-markdown.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/markdown/emit/adf-to-markdown.ts b/src/markdown/emit/adf-to-markdown.ts index b74f75b..d476d28 100644 --- a/src/markdown/emit/adf-to-markdown.ts +++ b/src/markdown/emit/adf-to-markdown.ts @@ -254,7 +254,6 @@ function tryList(node: AdfNode, path: ConvertErrorPath, depth: number, memo: Spe return success({ headroom, spelling: 'list', text: lines.join('\n') }) } -// The directive form sinks each item's blocks a level below where the walk read them. function directiveItems(items: readonly WalkedItem[]): PlacedBlock[] { return items.map((item) => ({ ...directivePair(item.node, listItemOpener, joinBlocks(item.walk.blocks, 'directive')), node: item.node })) } -- 2.52.0 From b1dc6a1f454effea89605e4d706f300456acd017 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Thu, 24 Sep 2026 00:19:45 +0200 Subject: [PATCH 4/4] 27 - the level the list's directive form spends is stated at the check that pays it --- AGENTS.md | 9 ++++----- src/markdown/emit/adf-to-markdown.ts | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e7927c5..e762ced 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -296,11 +296,10 @@ someone spells it or pins it. attribute's and a carried node's alike — are all held to 500 levels (`largestNesting`), so a deep document is a `Result` rather than the stack overflow that waits near 2000. An attribute is counted from its value; a spelling that nests it deeper — the block directive's `marks`, the - carry — refuses in its own format, as its parser does. A list giving way after its walk owes - the directive form a level the walk did not count, and the walk reports its headroom — the - least slack any depth guard below it has — for the fallback to refuse at zero rather than walk - again; counting every list twice halved the list limit, counting the directive form once - doubled the parser's frames per level (the maintainer, 2026-09-18). + carry — refuses in its own format, as its parser does. A list giving way to the directive form + refuses at zero headroom rather than walking again; counting every list twice halved the list + limit, counting the directive form once doubled the parser's frames per level (the maintainer, + 2026-09-18). - Nothing spreads an unbounded array into a call — a node's siblings, a code block's held lines, a mark run's segments: the argument list caps near 125k and throws a `RangeError` where a `Result` is owed. A walk pushes one at a time. A literal spread (`[...value]`) is not the same thing and diff --git a/src/markdown/emit/adf-to-markdown.ts b/src/markdown/emit/adf-to-markdown.ts index d476d28..49a461d 100644 --- a/src/markdown/emit/adf-to-markdown.ts +++ b/src/markdown/emit/adf-to-markdown.ts @@ -246,6 +246,7 @@ function tryList(node: AdfNode, path: ConvertErrorPath, depth: number, memo: Spe for (const [offset, item] of walked.entries()) { const line = tryListItemLines(item.walk.blocks, ordered ? `${start + offset}. ` : '- ') if (line === undefined) { + // The directive form spends a level the walk did not count. if (headroom < 1) return tooDeep(path) return emitDirectiveBlock(node, ordered ? blockNodes.orderedList : blockNodes.bulletList, path, depth, () => success({ blocks: directiveItems(walked), headroom: headroom - 1 })) } -- 2.52.0