docs: add language spec, domain glossary, and fixture update

This commit is contained in:
2026-07-08 22:35:35 -04:00
parent 220f73d8f7
commit d37988ec59
4 changed files with 382 additions and 12 deletions

114
.claude/CONTEXT.md Normal file
View File

@@ -0,0 +1,114 @@
# KitchenMD — Domain Glossary
A living glossary of canonical terms for this project.
Implementation details belong in specs or code, not here.
---
## Annotation
An inline sigil-based marker embedded in recipe prose that carries structured data.
There are exactly three annotation types: **Ingredient**, **Cookware**, and **Timer**.
An annotation is valid Markdown plain text — it does not break rendering in Obsidian or any standard Markdown renderer.
Annotations follow CommonMark's inline processing rules: parsed wherever CommonMark processes inline content (paragraphs, headings, list items, blockquotes); ignored wherever CommonMark treats content as literal (code spans, code blocks, HTML comments, raw HTML blocks).
## Ingredient Annotation
Syntax: `@name{quantity}` or `@name{quantity unit}`
The `{` character terminates the name, so multi-word names work without escaping.
The `{}` wrapper is mandatory — bare `@name` without braces is not valid.
Quantity is optional; `@black pepper{}` is valid with no quantity.
Valid quantity formats: integer (`2`), decimal (`0.5`), simple fraction (`1/2`), mixed number (`1 1/2`).
Unit is optional and free-form — the format places no constraints on what unit string is written.
When both quantity and unit are present, they are delimited by the last space inside the braces — so `@butter{1 1/2 tbsp}` parses as quantity `1 1/2`, unit `tbsp`.
Valid name characters: any character except `{`, `}`, `@`, `$`, `~`.
## Cookware Annotation
Syntax: `$name{}`, `$name{quantity}`, or `$name{quantity unit}`
Same `{`-terminated name rule as Ingredient.
The `{}` wrapper is mandatory.
Quantity is optional; `$pan{}` is valid with no quantity.
Quantity and unit follow the same rules as Ingredient: valid formats are integer, decimal, simple fraction, mixed number; unit is free-form; delimited by the last space inside the braces.
Valid name characters: any character except `{`, `}`, `@`, `$`, `~`.
## Timer Annotation
Syntax: `~N unit` or `~N-N unit`
No braces.
The `~` sigil is followed immediately by a number or `NN` range, a space, then a time unit word from the known set.
N follows the same format rules as ingredient/cookware quantity: integer, decimal, simple fraction, or mixed number.
Supports natural range syntax: `~10-15 mins`.
In range form, the `-` separator follows the first quantity expression — since negative quantities don't exist, the `-` is unambiguous. `~1/2-1 hr` parses as a range of 1/2 hr to 1 hr.
The annotation terminates after the unit word — trailing prose is ignored. `~1 min more` parses as a timer of 1 min; "more" is plain text.
Known unit set (aliases → canonical abbreviation): `sec`, `secs`, `second`, `seconds``s`; `min`, `mins`, `minute`, `minutes``min`; `hr`, `hrs`, `hour`, `hours``hr`.
## Recipe File
A valid `.md` file that may contain any combination of Obsidian Flavored Markdown, YAML frontmatter, and Annotations.
The file renders correctly in Obsidian without a plugin.
A Recipe File contains prose instructions only — there is no separate ingredients list section.
Ingredients are derived entirely by the Parser from inline Ingredient Annotations.
## Step
The atomic unit of a recipe instruction.
A step is a single non-empty line in a prose context, or a single ordered list item.
Headings are not steps.
Step numbers are section-local — they reset at each heading and do not change when steps are added to other sections.
## Combined Recipe
A Recipe File whose purpose is to sequence steps from two or more other Recipe Files.
It contains no steps or ingredients of its own — only Step References in the order they should be executed.
## Step Reference
A KitchenMD extension of Obsidian transclusion syntax for embedding a specific step from another Recipe File.
Syntax:
- `![[filename#section-heading:N]]` — step N within a named section.
- `![[filename#N]]` — step N in a headingless recipe (bare number, no section prefix).
- `filename` — the Recipe File to reference (no `.md` extension, matching Obsidian wikilink convention).
- `section-heading` — the heading text of the section containing the step.
- `N` — the 1-based step number within that section or file.
Examples:
- `![[italian meatballs#rolling:2]]` — second step of the "rolling" section.
- `![[basic brine#2]]` — second step of a headingless recipe.
This syntax is a compatibility break with vanilla Obsidian: `#section:N` does not match any Obsidian heading anchor, so Step References will not render correctly without KitchenMD tooling.
## Frontmatter
YAML metadata block at the top of a Recipe File (standard `---` delimiters).
The parser passes frontmatter through as-is with no schema enforcement.
Consumers are responsible for reading whatever fields they need.
## Sigil
The single punctuation character that opens each annotation type:
- `@` — Ingredient
- `$` — Cookware
- `~` — Timer
## Parser
The TypeScript library (`@kitchen-md/core`) that accepts a Recipe File string and returns a structured data object.
It is a pure function of its input string — no filesystem access.
## Structured Output
The data object returned by the Parser for a given Recipe File.
Shape is not yet fully specified — see open questions below.
---
## Open / Unresolved
- **Structured Output shape** — exact fields, types, and nesting of the parse result.
- **Unit normalisation** — the parser should normalise known aliases to a canonical abbreviation (`grams``g`, `kilograms``kg`); exact alias table and canonical forms are TBD. This is a parser concern, not a format constraint.

View File

@@ -0,0 +1,44 @@
# ADR 0001 — Step Reference Syntax
**Status**: Accepted
## Context
KitchenMD supports Combined Recipes: files whose sole purpose is to sequence steps from two or more other Recipe Files into a single cooking order (e.g. "penne alla vodka + italian meatballs").
To express this, a Combined Recipe needs to embed specific steps from other files — not entire files or sections, but individual lines of instruction.
Obsidian's existing transclusion syntax (`![[file#heading]]`) transclude a section from heading to the next heading.
It has no native concept of step-level granularity.
Two approaches were considered:
**Option A — Extend Obsidian's anchor syntax with a `:N` suffix**
`![[file#section:N]]` for a named section, `![[file#N]]` for a headingless recipe.
The `:N` part is a KitchenMD-specific extension; vanilla Obsidian does not recognise it and renders an "unable to find" error for the anchor.
**Option B — Use Obsidian block references**
Obsidian supports `![[file#^blockid]]` to transclude a specific block.
Authors would manually assign block IDs to every step (e.g. `^step-1`, `^step-2`).
This is valid Obsidian syntax and would render correctly without KitchenMD tooling.
## Decision
**Option A** — extend Obsidian's anchor syntax with `:N` for section-scoped steps and bare `#N` for headingless recipes.
## Rationale
Option B (block references) requires authors to manually annotate every step with a block ID, which is tedious, error-prone, and pollutes the source file with machine IDs (`^step-1`) that are meaningless to human readers.
The authoring cost is too high for a format designed to be written naturally.
Option A requires no author effort beyond normal recipe writing.
Step numbers are derived automatically by the parser from document structure.
The compatibility cost is acceptable: vanilla Obsidian renders a visible "unable to find" error rather than silently corrupting content, so the break is obvious and recoverable.
Combined Recipes are a specialised document type — authors who create them are expected to use KitchenMD tooling.
Step numbers are **section-local** (reset at each heading) rather than global, so adding or removing steps in one section does not renumber steps in other sections and does not break existing Step References elsewhere.
## Consequences
- Combined Recipe files will not render correctly in vanilla Obsidian without the KitchenMD plugin; they will show "unable to find" errors for step anchors.
- Individual Recipe Files (non-combined) are unaffected — they contain no Step References and render correctly in vanilla Obsidian.
- The KitchenMD parser and future Obsidian plugin must implement step counting to resolve `![[file#section:N]]` and `![[file#N]]` references.