docs: sync core-parser spec and add unit normalisation

Bring core-parser.md in line with the resolved design in CONTEXT.md and
ADRs 0004/0005, and promote a new decision to normalise ingredient/cookware
units.

- Full sync of the core-parser spec: diagnostics/total-function channel,
  container blocks (ListItemBlock, BlockquoteBlock), RawInline alongside
  RawBlock via position-slicing, the unified WikilinkNode/TransclusionNode
  shape as two typed nodes, and the minimal remark plugin set.
- New ADR 0006: normalise ingredient/cookware units via a known-alias table
  with passthrough, case-insensitive, canonical-only; align Timer matching
  to case-insensitive too.
- SPEC.md: units are normalised (new Units section, alias table); Timer table
  marked case-insensitive.
- CONTEXT.md: add Unit Normalisation entry, resolve the open item.
- fixtures/basic.md: add a blockquote (callout) carrying an annotation and a
  non-canonical unit so the integration test exercises containers and
  normalisation.
- Expand the core test.todo checklists to cover the new behaviour (containers,
  raw fallbacks, diagnostics, unit normalisation, wikilink/transclusion
  variants); still pending, suite stays green.
This commit is contained in:
2026-07-14 21:42:12 -04:00
parent 3ffd2b3f9f
commit 9958b6e6d5
7 changed files with 330 additions and 60 deletions

View File

@@ -1,15 +1,82 @@
import { describe, test } from "bun:test";
describe("parser", () => {
test.todo("parses frontmatter fields as-is");
test.todo("extracts ingredient name, quantity, and unit");
test.todo("extracts ingredient without unit");
test.todo("extracts multi-word ingredient name");
test.todo("extracts cookware without quantity");
test.todo("extracts cookware with quantity");
test.todo("extracts multi-word cookware name");
test.todo("extracts timer as single value");
test.todo("extracts timer as range");
test.todo("annotations embedded mid-sentence are captured");
describe("frontmatter", () => {
test.todo("parses frontmatter fields as-is");
test.todo("returns an empty object for empty frontmatter");
test.todo("returns an empty object when there is no frontmatter");
test.todo(
"does not throw on malformed frontmatter: frontmatter is {}, body still parses, and an invalid-frontmatter diagnostic preserves the raw YAML",
);
});
describe("blocks", () => {
test.todo("parses headings at every level (1-6)");
test.todo("parses paragraphs with typed inline nodes");
test.todo("parses an ordered list");
test.todo("parses an unordered list");
test.todo("models a list item as a container wrapping a paragraph, not a bare inline array");
test.todo("parses a code block and does not annotate its content");
test.todo("parses a thematic break");
test.todo("parses a blockquote as a container block");
test.todo("parses a callout (> [!note]) as an ordinary blockquote");
});
describe("inline nodes", () => {
test.todo("parses plain text");
test.todo("parses emphasis");
test.todo("parses strong");
test.todo("parses a code span and does not annotate its content");
test.todo("parses a link with href and inline content");
test.todo("parses a wikilink");
test.todo("parses a wikilink with an anchor");
test.todo("parses a wikilink with a display alias");
test.todo("parses a transclusion");
test.todo("parses a transclusion with a display alias");
});
describe("raw fallbacks", () => {
test.todo("preserves an unmodelled block (e.g. a GFM table) as a RawBlock with verbatim source");
test.todo("preserves an unmodelled inline (e.g. strikethrough) as a RawInline with verbatim source");
});
describe("ingredient annotations", () => {
test.todo("extracts ingredient name, quantity, and unit");
test.todo("extracts multi-word ingredient name");
test.todo("extracts ingredient with a unit-less quantity");
test.todo("extracts ingredient with no quantity");
});
describe("cookware annotations", () => {
test.todo("extracts cookware with quantity and unit");
test.todo("extracts cookware with no quantity");
test.todo("extracts multi-word cookware name");
});
describe("timer annotations", () => {
test.todo("extracts timer as a single value");
test.todo("extracts timer as a range");
test.todo("normalises timer unit aliases to canonical form");
test.todo("matches timer units case-insensitively (~5 Mins -> min)");
});
describe("unit normalisation", () => {
test.todo("normalises a known alias to canonical (grams -> g)");
test.todo("matches unit aliases case-insensitively (Tbsp -> tbsp)");
test.todo("normalises a multi-word alias (fluid ounces -> fl oz)");
test.todo("passes an unknown unit through verbatim");
});
describe("annotation scope", () => {
test.todo("captures annotations embedded mid-sentence");
test.todo("captures annotations inside a container (blockquote or list item)");
test.todo("does not extract annotations inside a code span");
test.todo("does not extract annotations inside a code block");
});
describe("transclusion", () => {
test.todo("passes a Step Reference anchor through as-is");
});
test.todo("standard Markdown elements pass through without interference");
});

View File

@@ -1,5 +1,8 @@
import { describe, test } from "bun:test";
describe("parser — integration", () => {
test.todo("parses the primary fixture and extracts all annotations");
test.todo("parses the primary fixture into the complete Document AST");
test.todo("extracts every annotation type from the primary fixture");
test.todo("extracts an annotation from inside the fixture's blockquote");
test.todo("normalises the fixture's non-canonical unit (tablespoons -> tbsp)");
});