The card face becomes real: leading SF Symbol (card default doc.text, tinted by a valid hand-written iconColor — schema yes, control no), title or the quiet untitled placeholder, and a quiet paperclip when the card has attachments — title-only by design, no body excerpt. Color is the settled K1 edge accent, not a fill: background paints a 4pt stripe down the left edge, resolved through the ported pathfinder palette (12 icon tints + 12 backgrounds carried over verbatim, plus raw #RRGGBB[AA]); anything unresolvable paints nothing and stays on disk exactly as written. The snapshot now carries each card's flat attachment names — the loader's one read inside a card folder, shared with the Writer's listing so the m5 carousel and m6 sidebar can never disagree on order (Finder order, the Writer's existing comparator). The face keeps its top-aligned structure so the sole-selection carousel can expand inside the card without moving masonry neighbors. 18 new tests. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
40 lines
4.6 KiB
Markdown
40 lines
4.6 KiB
Markdown
# Fixture boards
|
|
|
|
Golden fixture boards for the storage-contract test suite — real on-disk folder trees, not inline strings, so the same fixtures can later drive XCUITests via the `--open-board` launch hook.
|
|
|
|
Bundled into the unit-test target as a folder reference (see `project.yml`). Valid boards live under `Valid/`, fail-fast cases under `Malformed/`. Tests live in `KanbanTests/FixtureBoardTests.swift`.
|
|
|
|
Hidden fixture files are named `.hidden-*` rather than `.DS_Store`: the repo's `.gitignore` ignores `.DS_Store` everywhere, so a fixture spelled that way would exist on the authoring machine and vanish from a fresh clone. The loader's rule is `.skipsHiddenFiles` — it is about the leading dot, not the name.
|
|
|
|
Lane/card folder names are fixed literal lowercase-UUIDv4-shaped strings (never generated at test time), chosen so their lexicographic order matches the expected tie-break order — usually a leading digit (`10000000-...`, `20000000-...`, …) so folder order reads the same as array-index order in the tests. That is a fixture-authoring convention, not the loader's gate: the identity predicate is shape-only (`8-4-4-4-12` hex, **any case, any version** — 01-storage-format.md § Fractal layout ▸ Rules), and the case/version coverage lives in `KanbanTests/BoardLoaderTests.swift` rather than here.
|
|
|
|
## Valid/ — one board per tolerated/valid case
|
|
|
|
| Board | Case |
|
|
|---|---|
|
|
| `rich-board.kanban` | A full-breadth well-formed board: 2 lanes, 3 cards, bodies, styling (background/icon/iconColor/width), unknown + reserved frontmatter keys, `attachments/` and `comments/` with real content. Its `attachments/` also carries all four listing shapes — two ordinary files, a hidden one, and a subfolder with a file — so `Card.attachments`' flat rule (01-storage-format.md § Attachments) is asserted against a real tree. Also the board every `index.md` in the tree is round-tripped against. |
|
|
| `interrupted-create.kanban` | The motivating skip-not-error case: a UUID-shaped lane folder and a UUID-shaped card folder, each with no `index.md` yet (folder created, write not yet landed). |
|
|
| `non-uuid-strays.kanban` | Non-UUID-shaped folders at both lane and card depth, with and without `index.md` — name shape gates candidacy before the file is ever read. |
|
|
| `stray-files.kanban` | Stray (non-directory) files at board, lane, and card level — never level candidates, never warned about. |
|
|
| `tombstones.kanban` | A tombstoned lane and a tombstoned card, both still on disk and still in the snapshot, flagged (`isDeleted`) rather than removed. Also proves a tombstoned lane doesn't recursively flag its own un-deleted children. |
|
|
| `duplicate-order-tie-break.kanban` | Three cards sharing one `order` in one lane, and two lanes sharing one `order` — both broken by folder name, ascending. |
|
|
| `unknown-key-order.kanban` | Unknown/reserved frontmatter keys interleaved with schema-owned ones at board, lane, and card level — `document.unknownFields` must preserve exactly the order they were written in. |
|
|
| `coercion.kanban` | Lenient-field coercion and fallback: wrong-type scalars that coerce (`title: 2048`, `iconColor: 42`, `background: 12345`, `width: "3"`) versus ones with no sensible reading that fall back to the default (`title: [a, b]`, `background: {x: 1}`, `width: 1.5`), plus a `deleted` with an unusable timestamp that still tombstones. |
|
|
| `duplicate-top-level-keys.kanban` | A top-level key written twice — at board, lane (the strict `order` field), and card level. Last occurrence wins; **not** a fail-fast case (settled, newer than the original card text). Round-tripped to prove the earlier occurrence survives on disk, invisible only to reads. |
|
|
| `board-level-deleted.kanban` | A board-level `deleted:` key — legal per the frontmatter table but meaningless; ignored + warned, rest of the board loads normally. |
|
|
|
|
## Malformed/ — one board per fail-fast case
|
|
|
|
Each board is minimal: one broken thing.
|
|
|
|
| Board | Case |
|
|
|---|---|
|
|
| `unparseable-yaml.kanban` | An unterminated flow sequence in the board's frontmatter. |
|
|
| `missing-schema.kanban` | Board root `index.md` has no `schema` key. |
|
|
| `schema-newer-than-app.kanban` | Board root `schema: 2`, newer than `BoardLoader.supportedSchema`. |
|
|
| `missing-order-lane.kanban` | A UUID-shaped lane `index.md` with no `order` key. |
|
|
| `missing-order-card.kanban` | A UUID-shaped card `index.md` with no `order` key. |
|
|
| `explicit-null-order.kanban` | A lane's `order:` key present with no value — reads as missing, not malformed. |
|
|
| `non-numeric-order.kanban` | A card's `order: banana` — present but not a number. |
|
|
| `board-root-missing-index.kanban` | The board root folder itself has no `index.md`. |
|