- Add @biomejs/biome 2.5.3 with 2-space indent, double quotes, trailing commas - Add lint and format scripts to root package.json - Update CONTEXT.md: resolve Structured Output shape, add Document AST, Block, Inline Node terms - Add ADR 0002 (remark as internal Markdown parser) - Add ADR 0003 (core defines own AST types) - Add spec: core-parser (Parser implementation and Document AST) - Add spec: cli-view (kitchen view <file> command)
97 lines
6.1 KiB
Markdown
97 lines
6.1 KiB
Markdown
## Problem Statement
|
||
|
||
`@kitchen-md/bin` has an empty CLI entry point.
|
||
Users have no way to view a Recipe File in the terminal — they must open a text editor or Markdown viewer to read a recipe.
|
||
The `kitchen` binary exists in the scaffold but accepts no commands and produces no output.
|
||
|
||
## Solution
|
||
|
||
Implement the `kitchen view <file>` subcommand.
|
||
It reads a Recipe File from disk, passes the content to `@kitchen-md/core`'s `parse` function, and walks the resulting Document AST through a renderer that produces ANSI-styled terminal output.
|
||
Markdown structure is styled for readability; KitchenMD annotations are rendered as their raw source text for now.
|
||
|
||
## User Stories
|
||
|
||
1. As a CLI user, I want to run `kitchen view <file>` and see the recipe rendered in the terminal so that I can read it without opening a text editor.
|
||
2. As a CLI user, I want headings styled distinctly by level so that I can scan the recipe's structure at a glance.
|
||
3. As a CLI user, I want bold and italic text preserved in the terminal output so that emphasis in the recipe prose is visible.
|
||
4. As a CLI user, I want frontmatter shown as raw YAML before the recipe body so that I can see the recipe's metadata.
|
||
5. As a CLI user, I want wikilinks rendered distinctly from surrounding prose so that cross-references are visually identifiable.
|
||
6. As a CLI user, I want a clear error message when the file does not exist so that I am not left with a cryptic failure.
|
||
7. As a CLI user, I want the binary to exit with a non-zero code on error so that shell scripts can detect failure.
|
||
8. As a CLI user, I want ANSI styling suppressed automatically when output is piped so that downstream tools receive clean text.
|
||
|
||
## Implementation Decisions
|
||
|
||
- commander is used for argument parsing.
|
||
The `view` subcommand takes a single required positional argument: the path to a Recipe File.
|
||
No flags or options beyond the file path are implemented at this stage.
|
||
|
||
- The entry point reads the file from disk, passes the content string to `parse` from `@kitchen-md/core`, and passes the returned `DocumentAST` to the renderer.
|
||
File I/O is confined to the entry point.
|
||
|
||
- The renderer is a pure function `render(ast: DocumentAST): string`.
|
||
It walks the Document AST and returns an ANSI-styled string.
|
||
It has no filesystem access and no side effects.
|
||
The separation between the entry point (file I/O + commander) and the renderer (pure AST walk) allows the renderer to be tested without invoking a subprocess.
|
||
|
||
- **Rendering rules:**
|
||
- Frontmatter: serialised back to YAML and printed before the document body, followed by a visual separator.
|
||
- `HeadingBlock`: bold text; level 1 is the most visually prominent, with levels 2–6 progressively less so.
|
||
- `ParagraphBlock`: inline-rendered text followed by a blank line.
|
||
- `ListBlock`: each item on its own line, preceded by a bullet character (unordered) or sequential number (ordered).
|
||
- `CodeBlock`: literal text content, rendered as-is with no syntax highlighting.
|
||
- `ThematicBreakBlock`: a horizontal rule character string.
|
||
- `TextNode`: plain string output.
|
||
- `EmphasisNode` (italic): chalk italic.
|
||
- `StrongNode` (bold): chalk bold.
|
||
- `CodeSpanNode`: a visually distinct style (e.g. chalk dim or inverse).
|
||
- `LinkNode`: rendered as the inline content only; href is not shown.
|
||
- `WikilinkNode`: display text (or target if no display text), rendered with an underline or distinct colour.
|
||
- `TransclusionNode`: rendered as its raw source text (e.g. `![[file#section:1]]`).
|
||
- `IngredientNode`, `CookwareNode`, `TimerNode`: rendered as their raw annotation source text (e.g. `@flour{200 g}`, `$pan{}`, `~2-3 mins`).
|
||
Distinct annotation styling is out of scope for this spec.
|
||
|
||
- chalk is used for all ANSI styling.
|
||
chalk auto-detects whether stdout is a TTY and disables colour codes when output is piped, satisfying user story 8 without an explicit flag.
|
||
|
||
- **Error handling:**
|
||
- If the file path argument is missing, commander prints usage to stderr and exits with code 1.
|
||
- If the file does not exist or cannot be read, the entry point prints a human-readable error message to stderr and exits with code 1.
|
||
- No other error conditions are handled at this stage; parse errors from `@kitchen-md/core` are not expected (the parser is resilient to any valid Markdown string).
|
||
|
||
## Testing Decisions
|
||
|
||
- Two seams are tested:
|
||
|
||
**Renderer unit tests** — given a `DocumentAST` constructed inline in the test, assert the `render` function's output with ANSI codes stripped.
|
||
Tests cover every Block type, every Inline Node type, frontmatter output, and the visual separator between frontmatter and body.
|
||
No subprocess invocation; no filesystem access.
|
||
|
||
**Smoke tests** — invoke the `kitchen view` binary as a subprocess, capture stdout, strip ANSI codes, and assert the plain text content.
|
||
Smoke tests use `fixtures/basic.md` as input and assert that the output contains the expected rendered text for the fixture's headings, paragraphs, frontmatter, and wikilinks.
|
||
Smoke tests use Bun's subprocess API, consistent with the project scaffold's smoke test approach.
|
||
|
||
- ANSI colour and weight choices are visual decisions verified by inspection, not automated test assertions.
|
||
Assertions operate on stripped plain text only.
|
||
|
||
- Tests are co-located with source and follow the naming conventions established in the project scaffold.
|
||
|
||
## Out of Scope
|
||
|
||
- Distinct styling for KitchenMD annotation nodes (Ingredient, Cookware, Timer).
|
||
Annotations render as raw source text in this spec.
|
||
- Pager support.
|
||
- Multiple file arguments.
|
||
- `--no-color` flag (chalk auto-detects TTY).
|
||
- Syntax highlighting for code blocks.
|
||
- Formatted frontmatter display (title, servings, tags as a styled header).
|
||
- Any subcommand other than `view`.
|
||
|
||
## Further Notes
|
||
|
||
- Annotation raw source text (`@flour{200 g}`, `$pan{}`, `~2-3 mins`) reads naturally as plain text in the terminal.
|
||
This matches how Recipe Files render in Obsidian without a plugin — the format is designed to be readable without tooling.
|
||
- The renderer's purity is intentional.
|
||
Keeping file I/O in the entry point and rendering in a pure function is the minimum seam needed for deterministic unit tests without subprocess overhead.
|