Commit Graph
13 Commits
Author SHA1 Message Date
rzen fa834501b5 Implement tombstone delete, restore, and purge
deleteItem writes deleted: <now> in place through updateIndex — the
folder never moves, nothing beneath it is touched (hiding the subtree
is the renderer's ancestor walk, not a stored flag). restoreItem
removes the key — position-perfect by construction, every duplicate
occurrence taken so a hand-written twin cannot resurrect the
tombstone. purgeItem physically removes the tree; an already-missing
folder is success (a Finder deletion converges on the same end
state), and no prior tombstone is required (Delete Immediately skips
the stage by design). Board-root deletion is structurally unreachable
via the UUID-shape guard shared with move/copy. Neither delete nor
restore polices liveness — re-deleting refreshes the timestamp,
restoring a live item is a harmless stamped rewrite.

14 new unit tests; 234 total green.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 17:42:22 -04:00
rzen eb9e1e413f Implement move and copy identity semantics
moveItem: physical folder move, UUID and created travel unchanged;
exactly one file rewritten (the moved root's order, stamped). Import
boundary detected by resolved board-root comparison; on a cross-board
move-in, arriving UUIDs colliding with any identity in the destination
board (tombstones included) are reminted per folder at the finest
grain — folder rename only, file bytes untouched, every repair
reported in MoveResult. A colliding root moves straight to its minted
name. A same-parent move degrades to a plain reorder, self excluded
from the appended-rank scan.

copyItem: whole-tree copy minting fresh UUIDs at every depth; .fork
keeps created while stamping modified and clearing modified-by, .born
(template instantiation) stamps created fresh too. Nested uneditable
or unreadable files copy byte-verbatim rather than blocking the
gesture; the root must be rewritable. All-or-nothing at the
destination — any failure removes the partial tree.

24 new unit tests; 220 total green.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 17:37:24 -04:00
rzen 8c0cec06ee Implement create operations for board, lane, and card
createBoard fills or mints a .kanban (or extension-less) folder and
refuses to clobber an existing board; createLane/createCard mint fresh
lowercase-UUIDv4 folders and append at max+1024 among visible siblings
via the same strict scan renumber uses (extracted, shared). New files
carry schema/title?/order?/created/modified — created==modified, no
modified-by — written LF through the same atomic temp+rename path.
The editability pre-flight is now scoped to operations that rewrite
siblings: renumber refuses on an uneditable sibling, a create beside
one proceeds. Schema constant unified on BoardLoader.supportedSchema.
Info.plist declares the .kanban document type (UTI conforming to
com.apple.package) so Finder treats a board as one document.

20 new unit tests; 196 total green.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 17:01:09 -04:00
rzen 19f67bdb78 Build BoardWriter — atomic, minimal-touch writes
The single point through which every mutation becomes a filesystem
operation: read fresh from disk (strict byte-faithful UTF-8), refuse
readable-but-uneditable frontmatter shapes, apply the edit through the
span engine, stamp modified / clear modified-by, then temp-file+rename
atomically. Renumber-visible-children is the sole minimal-touch
exception, tombstones untouched. Structured BoardWriteError carries
operation + path + reason for the future banner surface.

Alongside, three edges the card surfaced:
- The loader now decodes byte-faithfully too, so a BOM'd or non-UTF-8
  index.md is rejected at load per the encoding contract, instead of
  loading via NSString's silent BOM strip and then refusing every write.
- An appended frontmatter line adopts the file's prevailing line ending
  (a CRLF file stays uniformly CRLF when a stamp first lands in it).
- Empty-but-not-blank frontmatter ({}, null, ~) is detected as
  uneditable — appending after it would be unparseable YAML.

30 new unit tests; 176 total green.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 16:49:42 -04:00
rzen 5a459f0ee4 Exclude inline comments from raw value reads
rawValue and the coercion/malformed paths now stop at the quote-aware
inline-comment boundary, so 'title: 2048  # note' coerces to "2048"
and a read-then-write no longer compounds the comment (pinned
idempotent). Strictly read-side; round-trip bytes untouched. +6 tests
(124 total).

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 16:10:32 -04:00
rzen 4085dd7a46 Engine — duplicate keys last-wins, inline-comment re-splice
Duplicates: Yams' duplicate-key error is disarmed by placeholder-
renaming first occurrences and re-composing (line count preserved, so
span layout stays exact); reads take the last occurrence, set()
rewrites the winner and clears earlier twins, remove() clears all.
Top-level only — nested duplicates stay YAML errors (doc updated).
Re-splice: an inline comment on a rewritten scalar line survives, with
quote-aware detection so a # inside quotes is never a comment. +31
tests (118 total).

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 16:07:06 -04:00
rzen 31f7691062 Gate level detection on UUID folder-name shape
Only lowercase-hex 8-4-4-4-12 folder names are lane/card candidates;
anything else is a stray — skipped with a distinct warning, never
descended, never able to fail-fast a load. UUID-shaped folders keep the
prior contract (missing index skips, malformed frontmatter fail-fasts).
Design resolution from the Redesign board. +5 tests.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 16:03:39 -04:00
rzen 8e7c565e07 Adopt read-side coercion for schema-owned fields
Design-review resolutions: schema-owned display fields coerce where a
sensible reading exists (wrong-type scalars read as source text, width
accepts exact-integer strings/doubles) and default where none does;
null reads as missing; duplicate keys last-one-wins and inline-comment
re-splicing recorded in the design (engine change follows). Strict
schema/order fail-fast unchanged. 85 tests green.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:47:33 -04:00
rzen 95418a2670 Build BoardLoader — fail-fast fractal tree loading
Pure tree walk: root → lanes → cards, level is position. Fail-fast with
path+reason for bad YAML, missing/malformed schema or order, newer
schema, rootless board; index-less folders skip with a collected+logged
warning; strays, hidden files, and symlinks ignored; board-level
deleted ignored with a warning; tombstones loaded and flagged. 17 tests.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:40:46 -04:00
rzen c7cc97aeec Define the board, lane, and card model types
Immutable snapshot value types encoding the frontmatter tables: ItemID
wraps the exact folder name (not Foundation UUID, which uppercases);
lanes/cards carry FieldValue-typed fields, display-ordered children
including flagged tombstones, and their full FrontmatterDocument so
unknown and reserved keys ride along uninterpreted. Boards are
identified by rootURL — package folders are human-named, not UUIDs.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:40:45 -04:00
rzen cb6f6100a7 Build the frontmatter engine with byte-perfect round-trip
Yams validates and reads; writes are surgical line-span edits over
retained raw text, so untouched bytes — unknown keys, comments, odd
formatting, bodies — round-trip identically by construction. Strict
schema/order readers distinguish valid/missing/malformed for the
loader's fail-fast; lenient fields preserve malformed values verbatim;
modified-by is schema-owned. Adds the Yams package and the fixture
folder-reference wiring. 44 tests (105 cases).

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:26:12 -04:00
rzen 4e016fe8fe Implement Ranks — gapped fractional ordering
Pure ordering math: append at max+1024, head-insert at min−1024,
midpoint insertion with precision-exhaustion detection (nil on ties and
rounding onto an endpoint), renumbering to whole multiples of 1024, the
shared display-order tie-break (order, then folder name), and
tombstone-excluding overloads. 18 tests.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:12:26 -04:00
rzen 01eabbe796 Scaffold the Xcode project — XcodeGen, Swift 6, sandboxed
XcodeGen project (codename Kanban, display name Lanework, bundle id
dev.rzen.indie.Kanban): macOS 26+ app target with Swift 6 strict
concurrency, app sandbox with user-selected file access and app-scoped
bookmark entitlements, plus unit-test (Swift Testing) and XCUITest
targets wired into the Kanban scheme.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 15:00:31 -04:00