From 65c09bdbae5c6b919d817690cfa4972d63e6c28e Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 16 Sep 2026 10:03:49 +0200 Subject: [PATCH] 12b: the inline opener speller takes its reader's name, and todo.md records 12c's collapses --- src/markdown-property.test.ts | 6 +++--- src/markdown/directive-syntax.ts | 2 +- src/markdown/emit/inline-line.ts | 6 +++--- todo.md | 11 +++++++++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/markdown-property.test.ts b/src/markdown-property.test.ts index 914f121..ee2e885 100644 --- a/src/markdown-property.test.ts +++ b/src/markdown-property.test.ts @@ -12,7 +12,7 @@ import { adfToMarkdown } from './markdown/emit/adf-to-markdown.ts' import { blockArgument } from './markdown/block-directive-arguments.ts' import { blockDirectives } from './adf/block-directives.ts' import { carryFence, carryName } from './markdown/opaque-carry.ts' -import { directivePrefix, spellAttributes, spellDirectiveOpener, spellJsonAttribute, spellLeafDirective, spellStringAttribute, spellVocabulary } from './markdown/directive-syntax.ts' +import { directivePrefix, spellAttributes, spellInlineDirectiveOpener, spellJsonAttribute, spellLeafDirective, spellStringAttribute, spellVocabulary } from './markdown/directive-syntax.ts' import { fencedCodeBlock } from './markdown/backtick-runs.ts' import { inlineDirectives } from './adf/inline-directives.ts' import { listBreakName } from './markdown/list-break.ts' @@ -227,11 +227,11 @@ function inlineMarkdown(hostile: boolean): InlineMarkdown { ...Object.entries(inlineDirectives).map(([name, directive]) => fc .tuple(directive.textAttribute === undefined ? fc.constant(null) : fc.option(hostile ? word : prose), tableAttributes(directive.attributes, directive.textAttribute)) - .map(([slot, attrs]) => (slot === null ? spellLeafDirective(name, attrs) : `${spellDirectiveOpener(name)}${slot}]${attrs}`)), + .map(([slot, attrs]) => (slot === null ? spellLeafDirective(name, attrs) : `${spellInlineDirectiveOpener(name)}${slot}]${attrs}`)), ), ...Object.entries(markAttributes) .filter(([name]) => hostile || markSpelling(name)?.kind === 'directive') - .map(([name, vocabulary]) => fc.tuple(tie('inlines'), tableAttributes(vocabulary)).map(([content, attrs]) => `${spellDirectiveOpener(name)}${content}]${attrs}`)), + .map(([name, vocabulary]) => fc.tuple(tie('inlines'), tableAttributes(vocabulary)).map(([content, attrs]) => `${spellInlineDirectiveOpener(name)}${content}]${attrs}`)), ), weight: 2, }, diff --git a/src/markdown/directive-syntax.ts b/src/markdown/directive-syntax.ts index f4d5fbb..cc418c6 100644 --- a/src/markdown/directive-syntax.ts +++ b/src/markdown/directive-syntax.ts @@ -114,7 +114,7 @@ export function spellJsonAttribute(value: JsonValue): string { return quote(serializeCanonicalJson(value, 'compact')) } -export function spellDirectiveOpener(name: string): string { +export function spellInlineDirectiveOpener(name: string): string { return `${directivePrefix}${name}[` } diff --git a/src/markdown/emit/inline-line.ts b/src/markdown/emit/inline-line.ts index 2743a2f..1bd0582 100644 --- a/src/markdown/emit/inline-line.ts +++ b/src/markdown/emit/inline-line.ts @@ -5,7 +5,7 @@ import { carriedInline } from '../opaque-carry.ts' import { claimsLine, holdsNullCharacter, isAutolink } from '../commonmark-grammar.ts' import { escapeUnbalanced, spellDestination, spellLinkTarget } from '../link-syntax.ts' import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts' -import { holdsDirectivePrefix, slotLineEndingFault, spellDirectiveOpener, spellLeafDirective } from '../directive-syntax.ts' +import { holdsDirectivePrefix, slotLineEndingFault, spellInlineDirectiveOpener, spellLeafDirective } from '../directive-syntax.ts' import { holdsEntityReference } from '../entity-references.ts' import { inlineDirective } from '../../adf/inline-directives.ts' import { largestNesting } from '../../nesting.ts' @@ -214,7 +214,7 @@ function emitInlineDirective(node: AdfNode, directive: InlineDirective, index: n if (spans !== undefined) return faulted(spans, path) if (holdsNullCharacter(slot)) return failure('unspellable-character', `a ${node.type} content slot holds a null character CommonMark replaces`, path) const content: InlineSegment[] = slot === '' ? [] : [{ escaping: 'bracketed', text: slot }] - return success({ segments: [syntax(spellDirectiveOpener(node.type)), ...content, syntax(`]${attributes}`)] }) + return success({ segments: [syntax(spellInlineDirectiveOpener(node.type)), ...content, syntax(`]${attributes}`)] }) } function emitText(node: AdfNode, context: InlineContext, index: number, path: ConvertErrorPath): Result { @@ -241,7 +241,7 @@ function emitMarkedRun(nodes: readonly AdfNode[], mark: AdfMark, depth: number, const inner = emitRun(nodes, depth + 1, index, { ...context, bracketed: true, spansLines: false }) if (!inner.ok) return inner if (inner.value.carry !== undefined) return inner - return success({ segments: [syntax(spellDirectiveOpener(mark.type)), ...inner.value.segments, syntax(`]${attributes}`)] }) + return success({ segments: [syntax(spellInlineDirectiveOpener(mark.type)), ...inner.value.segments, syntax(`]${attributes}`)] }) } function emitEmphasis(nodes: readonly AdfNode[], spelling: string, depth: number, range: NodeRange, context: InlineContext): Result { diff --git a/todo.md b/todo.md index 1b4897c..bc0face 100644 --- a/todo.md +++ b/todo.md @@ -247,6 +247,9 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c container parses. - Split by construct, each sub-item both directions: 55 of 78 round-trip fixtures feed both the emit and the read-back test, so an emit-only chunk cannot land green. + - The spec leads the code from 12a to 12d: `spec/flavour.md` and `AGENTS.md` §4 spell the + `!adf:` grammar whole, while code and fixtures reach it one form at a time. A reader landing + in either without `todo.md` sees a gap that is the plan, not a defect. - [x] **12a — The spec and the decision.** - [ ] **12b — The inline form.** Inline nodes, directive marks, `text` and the inline carry `!adf:carry{json=…}` spelled and read as `!adf:name[content]{attrs}`, with the prefix claim @@ -258,6 +261,14 @@ proves 12, 13 spells 11's gaps in 12's grammar, and 12 rewrites code 4b and 4c c model, empty pairs, `listBreak` and the `carry` fence, spelled and read; the fence-length rule and the corpus test's fence nesting check deleted; the remaining fixtures re-spelled and `errors/` re-derived under the shifted codes, and the gate green. + 12b's two temporary seams expire here: `carryFence` folds back into `carryName` once the + fence reads `carry`, and `directiveLineEscape` into `inlineDirectiveEscape` once one escape + serves both forms. `spellLeafDirective` takes the `Inline` its reader-side regex already + carries, and `header` versus `opener` settles as one word in spec and code. While + `readNestedDirective` is open, its `[content]` and `{attrs}` reads lift out as named steps, + and the two `charAt`-against-`!` fast paths ahead of `claimsDirectivePrefix` — in + `readDirectiveContent` and `line-escaping`'s `bracketed-link-target` arm — either earn a + reason or go (the systems-architect, 2026-09-16). - [ ] **12d — The README, `MIGRATION.md` and the sweep.** The README's examples and error tables follow, `MIGRATION.md` linked from one README line; docs and fixtures swept for any stale `::`/`:name` spelling.