Realign code with the 2026-07-29 findings-resolution rulings

Nine rulings land as code. Reorders don't stamp — one container-change
predicate (WriteOperation.rewritesOrderOnly): within-container reorders
and the renumber rescale rewrite only order, while cross-lane, cross-board,
and trash moves stamp modified and clear modified-by; no trash special
case exists, and the m8 undo inverses conform through the same seam.
Copies are transactions: the root-strict/nested-lenient split retires for
a whole-subtree stampability preflight that refuses loudly naming the
offender, and every item-level copy severs remote/remote-state at every
level (whole-board forks carry them verbatim). Paste refuses, never
degrades: the embedded-index.md materialization and its loss row retire;
a missing staged snapshot produces nothing and posts an error-tone
one-shot named from manifest metadata. Coerce-tier fallbacks log through
the Defect stream with path context attached loader-side. Displacement is
level-uniform: a file squatting attachments inside a card heals by the
same rename ladder as board-root squatters; comments stays tolerated.
Delete Immediately joins card and lane context menus as Delete's
⌥-alternate with its own VO custom action, routed through an explicit
container so the menu target outranks standing selection. Agent guide v7
teaches the stamp discipline and the card-level attachments claim, and
sheds two stale v6 lines (lanes trash now; kind is taught). Verified
conformant, unchanged: edition-aware Undo/Redo disable, trash marquee
full-height backdrop.

Both schemes 1854 tests / 318 suites green; verify-editions 30/30.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-30 06:49:11 -04:00
parent 5ae48de0ea
commit 69084fdff7
27 changed files with 2159 additions and 542 deletions
+47 -14
View File
@@ -55,8 +55,16 @@ enum AgentGuide {
/// pointer) and supersedes it on the next open. v6 adds the one-folder-at-a-time move warning:
/// a real agent incident (2026-07-29) showed `mv <lane>/*` sweeping the lane's own `index.md`
/// along with the cards and destroying the destination lane's identity the guide now says
/// *why* the named-folder form is load-bearing, not just what to type.
static let version = 6
/// *why* the named-folder form is load-bearing, not just what to type. v7 teaches two more
/// things settled after v6 shipped: the refined stamp-discipline predicate
/// (01-storage-format.md `modified`'s scope, ruled 2026-07-29, refined 2026-07-30) a
/// within-container reorder rewrites only `order`, while a move that changes an item's
/// container (another lane, another board, into or out of `.trash/`) stamps `modified` and
/// `modified-by` like any content edit, the trash move included, since it's the same rule and
/// not a special case and the card-level `attachments` claimed name
/// (01-storage-format.md § Fractal layout Rules, "level-uniform"): that name belongs to the
/// app's own folder, so a *file* by that name is a defect the app displaces on sight.
static let version = 7
// MARK: - The version marker
@@ -325,7 +333,7 @@ enum AgentGuide {
<board>/ this folder (the board)
├── index.md board title + settings; body = board description
├── CLAUDE.md this guide (app-maintained)
├── .trash/ deleted cards (app-managed — see Deleting)
├── .trash/ deleted cards and lanes (app-managed — see Deleting)
├── <uuid>/ a LANE
│ ├── index.md lane title + order; body = lane notes/policy
│ ├── <uuid>/ a CARD
@@ -350,7 +358,7 @@ enum AgentGuide {
- Lane titles carry the workflow semantics (e.g. To Do → In Progress →
Done). Read the board's and lanes' index.md bodies for descriptions and
per-lane policy before deciding where a card belongs.
- `.trash/` holds deleted cards; everything else at board root that isn't a
- `.trash/` holds deleted cards and lanes; everything else at board root that isn't a
UUID-named folder is not part of the board's content.
## Frontmatter
@@ -397,6 +405,7 @@ enum AgentGuide {
```markdown
---
schema: 1
kind: card
title: Short imperative card title
order: 3072
created: 2026-07-24T18:00:00Z
@@ -406,11 +415,24 @@ enum AgentGuide {
The card's content — any Markdown.
```
Creating a lane is the same one level up (body optional; `order` ranks
lanes left→right).
Creating a lane is the same one level up (body optional; `kind: lane`;
`order` ranks lanes left→right).
**Always write `kind`** at creation — `kind: card`, `kind: lane`,
`kind: board` at board root. Depth already says what an item is on the
board, but `.trash/` is flat, and there the value is the only thing that
tells a trashed lane from a card. Omitting it is healable, never fatal: the
app fills a missing `kind` in the next time it rewrites that file.
## Moving and reordering
- **The stamp rule**: a move that changes an item's container — another
lane, another board, or into/out of `.trash/` — stamps `modified` and
re-stamps `modified-by`, the same as any content edit. A reorder that
keeps an item in the same container (a card among its lane's cards, a
lane among the board's) rewrites only `order`; leave `modified` and
`modified-by` alone. The trash move isn't an exception to this — it
stamps because every container change stamps.
- Move to another lane: `mv <laneA>/<card-uuid> <laneB>/` — the folder move
IS the move. Then set the card's `order` to place it among the
destination's cards, update `modified`, and re-stamp `modified-by`.
@@ -418,28 +440,39 @@ enum AgentGuide {
A lane folder holds its own `index.md` beside its cards, so a glob
sweeps the lane's identity file along with them and overwrites the
destination lane's.
- Reorder within a lane: rewrite only that card's `order`.
- Reorder within a lane: rewrite only that card's `order` — don't touch
`modified` or `modified-by`.
## Editing and deleting
- Edit bodies freely; update `modified` on every write. Preserve frontmatter
keys you don't recognize and don't reformat content you didn't change.
- **Delete a card = move it into `<board>/.trash/`**: `mv <lane>/<card-uuid>
<board>/.trash/` (create `.trash/` if missing). Arrivals go on top: set
the card's `order` to the smallest `order` already in `.trash/` minus
1024 (empty trash: any number), and update `modified`. Restore is the
same move in reverse — into a lane, with a fresh `order`.
- **Delete a card or a lane = move its folder into `<board>/.trash/`**:
`mv <lane>/<card-uuid> <board>/.trash/`, or `mv <lane-uuid>
<board>/.trash/` for a whole lane (create `.trash/` if missing). A lane
travels with its cards inside it. Arrivals go on top: set the moved
item's `order` to the smallest `order` already in `.trash/` minus 1024
(empty trash: any number). It's a container change like any other move
(Moving and reordering above): stamp `modified` and re-stamp
`modified-by`. Restore is the same move in reverse — a card into a lane,
a lane back to board root, with a fresh `order`, stamped the same way.
- **Stamp `kind: lane` when you trash a lane that lacks it.** `.trash/` is
flat, so an empty lane folder looks exactly like a card folder; the `kind`
value is what tells them apart in there.
- Never write a `deleted:` key — that convention is retired; the app
migrates any it finds.
- Remove a folder outright (`rm -r`) only when you mean permanent,
unrecoverable deletion. Lanes have no trash: deleting a lane folder is
permanent, so be sure.
unrecoverable deletion — the trash is the recoverable path for both
cards and lanes.
## Attachments
- A card's files live in `attachments/` inside the card folder, flat at its
top level. **Put files there, never beside `index.md`** — the app
relocates loose files into `attachments/` and tells the user it did.
The name `attachments` itself belongs to that folder — never create a
*file* called `attachments` in a card; the app treats one as a defect
and displaces it on sight.
- To attach a file: create `attachments/` if missing and copy the file in.
If the name is taken, pick a free one Finder-style (`shot.png` →
`shot 2.png`) — never overwrite.