20 - the CommonMark link takes the try prefix too, and §11's rule names it
CI / gate (push) Successful in 29s
CI / publish (push) Has been skipped

This commit is contained in:
2026-09-21 21:07:54 +02:00
parent 6391928fb4
commit 97bf2440fd
4 changed files with 19 additions and 14 deletions
+9 -8
View File
@@ -307,14 +307,15 @@ someone spells it or pins it.
opener search stay whole: broken into named steps they drift from the algorithm being faithful is opener search stay whole: broken into named steps they drift from the algorithm being faithful is
the whole point of. the whole point of.
- A readable spelling tried ahead of a general one — a CommonMark block, the image, the pipe - A readable spelling tried ahead of a general one — a CommonMark block, the image, the pipe
table, a pipe cell — gives way with `undefined` for every shape it cannot spell, and fails only table, a pipe cell, the CommonMark link — gives way with `undefined` for every shape it cannot
where the general form fails on the same node; it takes the `try` prefix, so both arms read at spell, and fails only where the general form fails on the same node; it takes the `try` prefix,
the call site (20). Refusing there refuses a document the general form spells, so a refusal the so both arms read at the call site (20). Refusing there refuses a document the general form
general form does not share belongs in the general form or nowhere — save the nested list a spells, so a refusal the general form does not share belongs in the general form or nowhere —
tight spelling would swallow, whose refusal the tight-versus-blank answer owns (`todo.md` 2b). A readable spelling that must spell its subtree save the nested list a tight spelling would swallow, whose refusal the tight-versus-blank answer
before it can give way — the list, whose thematic-break first line and blank lines exist only owns (`todo.md` 2b). A readable spelling that must spell its subtree before it can give way —
spelled — hands that one walk to the general form instead: giving way after the walk walks the list, whose thematic-break first line and blank lines exist only spelled — hands that one
again at every level, doubling per level (4b). walk to the general form instead: giving way after the walk walks again at every level, doubling
per level (4b).
- Nothing recurses unbounded: the guards walk iteratively, and blocks, marks and JSON values — an - Nothing recurses unbounded: the guards walk iteratively, and blocks, marks and JSON values — an
attribute's and a carried node's alike — are all held to 500 levels, so a deep document is a attribute's and a carried node's alike — are all held to 500 levels, so a deep document is a
`Result` rather than the stack overflow that waits near 2000. A level is one block-list `Result` rather than the stack overflow that waits near 2000. A level is one block-list
+2 -3
View File
@@ -258,7 +258,7 @@ function emitMarkedRun(nodes: readonly AdfNode[], mark: AdfMark, depth: number,
if (attributes === undefined) return success({ carry: range }) if (attributes === undefined) return success({ carry: range })
if (spelling.kind === 'code') return emitCodeSpan(nodes, depth, range, path) if (spelling.kind === 'code') return emitCodeSpan(nodes, depth, range, path)
if (spelling.kind === 'emphasis') return emitEmphasis(nodes, spelling.spelling, depth, range, context) if (spelling.kind === 'emphasis') return emitEmphasis(nodes, spelling.spelling, depth, range, context)
const link = spelling.kind === 'link' ? emitLink(nodes, mark, depth, range, context) : undefined const link = spelling.kind === 'link' ? tryLink(nodes, mark, depth, range, context) : undefined
if (link !== undefined) return link if (link !== undefined) return link
const inner = emitRun(nodes, depth + 1, index, { ...context, bracketed: true, spansLines: false }) const inner = emitRun(nodes, depth + 1, index, { ...context, bracketed: true, spansLines: false })
if (!inner.ok) return inner if (!inner.ok) return inner
@@ -300,8 +300,7 @@ function needsPadding(text: string): boolean {
return text.startsWith(' ') && text.endsWith(' ') && /[^ ]/.test(text) return text.startsWith(' ') && text.endsWith(' ') && /[^ ]/.test(text)
} }
// `undefined` where the link takes the directive form the caller spells. function tryLink(nodes: readonly AdfNode[], mark: AdfMark, depth: number, range: NodeRange, context: InlineContext): Result<Emission> | undefined {
function emitLink(nodes: readonly AdfNode[], mark: AdfMark, depth: number, range: NodeRange, context: InlineContext): Result<Emission> | undefined {
const href = linkHref(nodeAttrs(mark)) const href = linkHref(nodeAttrs(mark))
if (href === undefined) return success({ carry: range }) if (href === undefined) return success({ carry: range })
const opening = depth === 0 && range.first === 0 && context.openingLinkAsDirective const opening = depth === 0 && range.first === 0 && context.openingLinkAsDirective
+5 -2
View File
@@ -933,8 +933,11 @@ The done `todo.md` items in full, as they were written. `todo.md` keeps a one-li
directive form shares — a walk, `codeBlockText`, `emitInlineLine` — so moving it out is the directive form shares — a walk, `codeBlockText`, `emitInlineLine` — so moving it out is the
second walk 4b removed, and they take the prefix alone. `tryRule` refuses nothing, so it second walk 4b removed, and they take the prefix alone. `tryRule` refuses nothing, so it
takes the narrower type as well, `string | undefined` through `readableText`, which is what takes the narrower type as well, `string | undefined` through `readableText`, which is what
`tryImage` and `tryPipeTable` already return. §11's readable-spelling rule carries the `tryImage` and `tryPipeTable` already return. `emitLink` is the same channel outside the
prefix, so the next spelling is named by the rule rather than by the six beside it. six — the CommonMark link tried ahead of the inline directive form — so it takes the prefix
with them, and the comment naming its `undefined` arm goes with the rename. §11's
readable-spelling rule carries the prefix, so the next spelling is named by the rule rather
than by the seven beside it.
- [x] **28 — `emitLine`'s retry loop cannot spin (`0.2.0`).** `emit/inline-line.ts:67` is a - [x] **28 — `emitLine`'s retry loop cannot spin (`0.2.0`).** `emit/inline-line.ts:67` is a
`for (;;)` that re-emits the line until every unspellable node has been carried, and its `for (;;)` that re-emits the line until every unspellable node has been carried, and its
termination rests on a comment: each pass carries at least one more node, or flips termination rests on a comment: each pass carries at least one more node, or flips
+3 -1
View File
@@ -8,7 +8,9 @@ milestone. A done item shrinks to its title here; its full text moves to `todo-h
Start a session with: `Read AGENTS.md and todo.md, then do what todo.md's "Next session" says.` Start a session with: `Read AGENTS.md and todo.md, then do what todo.md's "Next session" says.`
1. `git fetch origin` first and read this file at `origin/main`, then branch off it, not the 1. `git fetch origin` first and read this file at `origin/main`, then branch off it, not the
worktree left behind: a checkout behind the remote reads a merged item as unchecked. worktree left behind: a checkout behind the remote reads a merged item as unchecked. `tea pr
list` in the same breath — an open PR is a chunk already in flight whatever line 3 says, and
finishing it is the session.
2. The first unchecked item in shipping order, per AGENTS.md §15 — the order the Milestones line 2. The first unchecked item in shipping order, per AGENTS.md §15 — the order the Milestones line
states, which wins over where an item's bullet sits: a newly filed item is written beside the states, which wins over where an item's bullet sits: a newly filed item is written beside the
one it came in with, not at its own place in the order. Where that item has no release, the one it came in with, not at its own place in the order. Where that item has no release, the