Write the loader fixture suite, valid and malformed

18 golden fixture boards as real on-disk trees under Fixtures/ — ten
valid/tolerated cases (rich board, interrupted create, non-UUID strays,
stray files, tombstones, tie-breaks, unknown-key order, coercion,
last-wins duplicates, board-level deleted) and eight fail-fast cases,
each pinned to its exact error reason and path; the rich board's whole
tree round-trips byte-identically. Pins two contract subtleties now
recorded in the design: tombstone flags are not inherited (renderers
walk ancestors) and last-wins resolves before order strictness. README
gains the storage-foundation feature entry. 146 tests total.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
This commit is contained in:
2026-07-26 16:20:47 -04:00
parent e6c34c0ccf
commit 7470255886
73 changed files with 900 additions and 4 deletions
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: Also Business As Usual
---
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: Business As Usual
---
@@ -0,0 +1,9 @@
---
schema: 1
title: Board With A Stray 'deleted' Key
deleted: 2026-01-01T00:00:00Z
---
'deleted' is legal per the common frontmatter table but meaningless at
board level — a board can't tombstone itself out of its own window. The
loader ignores it (and logs a warning) rather than acting on it; the rest
of the board loads completely normally.
@@ -0,0 +1,6 @@
---
schema: 1
order: 1024
title: 2048
---
An unquoted integer title coerces to its source text, "2048".
@@ -0,0 +1,7 @@
---
schema: 1
order: 2048
title: [a, b]
---
A sequence has no sensible string reading — malformed, falls back to the
untitled placeholder.
@@ -0,0 +1,7 @@
---
schema: 1
order: 3072
iconColor: 42
---
An integer iconColor coerces to its source text, "42" — not a real color,
but the value round-trips rather than being rejected.
@@ -0,0 +1,7 @@
---
schema: 1
order: 4096
background: {x: 1}
---
A mapping has no sensible string reading — malformed, falls back to no
color.
@@ -0,0 +1,5 @@
---
schema: 1
order: 5120
background: 12345
---
@@ -0,0 +1,8 @@
---
schema: 1
order: 6144
title: Deleted With An Unusable Timestamp
deleted: definitely-not-a-date
---
The user's intent to delete outranks the broken date — this card still
tombstones, its deletion date just unknown.
@@ -0,0 +1,6 @@
---
schema: 1
order: 1024
title: Width Coerces From A Quoted Number
width: "3"
---
@@ -0,0 +1,8 @@
---
schema: 1
order: 2048
title: Width Is Unusable — Not An Integer
width: 1.5
---
A malformed width is preserved on disk and simply not used — never
rejected, never rewritten.
+8
View File
@@ -0,0 +1,8 @@
---
schema: 1
title: Coercion And Lenient-Field Fallback
---
Schema-owned display fields coerce where a sensible reading exists (a
wrong-type scalar reads as its source text) and fall back to their default
where none does (a sequence/mapping, a non-integer width). A present but
unusable 'deleted' timestamp still tombstones — presence outranks validity.
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: Should Render First (folder starts with 1)
---
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: Should Render Second (folder starts with 2)
---
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: Should Render Third (folder starts with 3)
---
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: Lane With Three Tied Cards
---
@@ -0,0 +1,5 @@
---
schema: 1
order: 2048
title: Should Render Before Lane bbb... (same order, 'a' < 'b')
---
@@ -0,0 +1,5 @@
---
schema: 1
order: 2048
title: Should Render After Lane aaa... (same order, 'b' > 'a')
---
@@ -0,0 +1,7 @@
---
schema: 1
title: Duplicate Order Values, Tie-Broken By Folder Name
---
Two siblings that share an 'order' render in a deterministic order —
ascending 'order' first, folder name (lexicographic) as the tiebreak.
This board exercises the tiebreak at both lane level and card level.
@@ -0,0 +1,6 @@
---
schema: 1
order: 2048
title: First Title
title: Second Title
---
@@ -0,0 +1,9 @@
---
schema: 1
order: 1024
order: 4096
title: Dup Order Lane
---
Duplicate keys aren't special-cased by field — 'order' is a strict,
schema-owned field and still resolves via last-wins at the document layer,
before the loader ever validates it. This is NOT a fail-fast case.
@@ -0,0 +1,7 @@
---
schema: 1
title: Draft Name
title: Final Name
---
A hand-edit that types 'title' twice — strict YAML would reject the file
outright. The engine reads the last occurrence and keeps both on disk.
@@ -0,0 +1,6 @@
---
schema: 1
order: 1024
title: A Real Card
---
Body.
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: A Real Lane
---
@@ -0,0 +1,6 @@
---
schema: 1
title: Interrupted Two-Step Create
---
The app creates a folder first and writes index.md a moment later — a crash
or a killed app between those two steps must not brick the board.
@@ -0,0 +1,6 @@
---
schema: 1
order: 1024
title: Real Card
---
Body.
@@ -0,0 +1,5 @@
---
schema: 1
order: 9999
title: This looks like a card but isn't named like one
---
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: Real Lane
---
@@ -0,0 +1,7 @@
---
schema: 1
title: Non-UUID Strays At Every Depth
---
Name shape gates level detection — a folder that isn't UUIDv4-shaped is a
stray, whether or not it holds an index.md, and whether it sits at lane
depth or card depth.
@@ -0,0 +1,6 @@
---
schema: 1
order: 9999
title: This looks like a lane but isn't named like one
---
Never rendered — 'todo-notes' isn't UUIDv4-shaped.
@@ -0,0 +1,8 @@
---
schema: 1
author: rzen
created: 2026-07-21T08:30:00Z
---
Comments are enhanced-schema and out of scope for this version — this
folder is here only to prove it's preserved verbatim and never descended
into (cards are leaves; the loader never scans past index.md).
@@ -0,0 +1,17 @@
---
schema: 1
title: "Design the fixture taxonomy"
order: 1024
created: 2026-07-20T10:15:00Z
modified: 2026-07-26T20:07:30Z
modified-by: claude
source: DESIGN/01-storage-format.md # inline comment on a plain scalar
project: lanework
sphere: work
labels: [m1-storage-read]
---
Enumerate every fail-fast and tolerated case from the storage format and
build one golden fixture board per case.
- attachments/ holds a sketch
- comments/ holds a placeholder future-comment folder
@@ -0,0 +1,9 @@
---
schema: 1
title: Wire up the loader's stray tolerance
order: 2048
background: coral
icon: flag.fill
iconColor: orange
---
Plain, unquoted title this time — mixing styles on purpose.
@@ -0,0 +1,10 @@
---
schema: 1
order: 1024
title: Doing
width: 2
background: '#3478F6'
icon: hammer.fill
iconColor: blue
---
WIP limit: 3 cards. Pull from Backlog only when there's room.
@@ -0,0 +1,8 @@
---
schema: 1
order: 1024
title: 'Ship v1'
created: 2026-06-01T12:00:00Z
modified: 2026-07-15T09:00:00Z
---
Shipped. 🎉
@@ -0,0 +1,7 @@
---
schema: 1
order: 2048
title: Done
background: green
---
Completed work lives here until someone clears it out.
+22
View File
@@ -0,0 +1,22 @@
---
schema: 1
# board settings, hand-written
title: "Rich Demo Board"
created: 2026-07-01T09:00:00Z
modified: 2026-07-26T16:41:38Z
modified-by: claude
background: "#1E1E1E"
icon: rectangle.stack.fill
iconColor: purple
project: lanework # agent overlay
sphere: work
labels: [feature, docs]
template: {order: 3}
# trailing note before close
---
A demo board exercising the full breadth of the storage contract: several
lanes, several cards, body text, styling, and unknown/reserved frontmatter
keys living alongside the schema-owned ones.
Nothing here is special-cased — it's an ordinary board.
@@ -0,0 +1,6 @@
---
schema: 1
order: 1024
title: A Card With Junk Inside It
---
Body.
@@ -0,0 +1 @@
this whole subfolder is never scanned — cards are leaves
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: A Lane With Junk Beside It
---
@@ -0,0 +1 @@
board readme
@@ -0,0 +1,7 @@
---
schema: 1
title: Stray Files Everywhere
---
Stray files at board, lane, and card level are ignored — preserved
verbatim, never rendered, never warned about (only directories are level
candidates at all).
@@ -0,0 +1 @@
log line
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: A Living Card
---
@@ -0,0 +1,7 @@
---
schema: 1
order: 2048
title: A Tombstoned Card
deleted: 2026-07-10T08:00:00Z
---
Still on disk, at its recorded order, just hidden from the board.
@@ -0,0 +1,5 @@
---
schema: 1
order: 1024
title: Still Here
---
@@ -0,0 +1,7 @@
---
schema: 1
order: 1024
title: Not Itself Deleted
---
My lane is tombstoned but I am not — isDeleted keys only on my own
'deleted' field, never inherited from an ancestor.
@@ -0,0 +1,10 @@
---
schema: 1
order: 2048
title: A Tombstoned Lane
deleted: 2026-07-11T09:30:00Z
---
Everything beneath a tombstoned lane is hidden from the board too — but
that's a rendering rule, not a loader rule: the loader still reports this
lane's card structurally, un-flagged, since the card's own 'deleted' key
is absent.
@@ -0,0 +1,7 @@
---
schema: 1
title: Tombstoned Lane And Card Still On Disk
---
Deletion writes 'deleted:' into frontmatter; the item never leaves disk.
The loader keeps tombstoned items in the snapshot, flagged — hiding them
from the board is a rendering concern, not a loading one.
@@ -0,0 +1,12 @@
---
schema: 1
labels: [bug, urgent]
order: 1024
assignees: [bob, carol]
title: Card With Unknowns
due: 2026-08-15
remote: {system: github, id: 42}
agent-scratch: notes here
---
Card body describing the bug. Unknown/reserved keys are interleaved with
schema-owned ones on purpose, to prove ordering survives the interleave.
@@ -0,0 +1,9 @@
---
schema: 1
order: 1024
remote-state: syncing
title: Lane With Unknowns
assignees: [alice]
due: 2026-08-01
custom: xyz
---
@@ -0,0 +1,12 @@
---
schema: 1
project: lanework
title: Unknown Key Ordering
sphere: work
template: {order: 5}
labels: [x, y, z]
custom-note: keep me
---
Unknown keys and their relative order are preserved verbatim, on every
level — this is load-bearing for agent overlays (project:, sphere:,
custom tags).