From 5ae48de0eadd71b71b6635985f1b6fb3e8772b79 Mon Sep 17 00:00:00 2001 From: rzen Date: Thu, 30 Jul 2026 02:15:38 -0400 Subject: [PATCH] =?UTF-8?q?Reorders=20don't=20stamp;=20container=20changes?= =?UTF-8?q?=20do=20=E2=80=94=20the=20moves-don't-stamp=20rule=20refined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-ruled 2026-07-30: only within-container order-only rewrites (a reorder among a lane's siblings, a lane reorder on the board, the renumber rescale) skip the modified stamp and the modified-by clear. A move that changes the item's container — cross-lane, cross-board, into or out of .trash/ — stamps and clears both: which lane a card lives in is state, where it stands in line is presentation. The trash move stops being an exception and becomes the container rule's plainest instance. 01 scope/modified-by/Ordering/ Deletion/comments, 03 Trash, and 08's v7 bullet updated. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY --- DESIGN/01-storage-format.md | 10 +++++----- DESIGN/03-board-ui.md | 2 +- DESIGN/08-agent-integration.md | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/DESIGN/01-storage-format.md b/DESIGN/01-storage-format.md index fae3e5b..26fe407 100644 --- a/DESIGN/01-storage-format.md +++ b/DESIGN/01-storage-format.md @@ -54,9 +54,9 @@ MyBoard.kanban/ ← board = the document | `iconColor` | string | no | Palette name or hex | | `kind` | string | no | The object's kind — `board`, `lane`, `card` (`comment` — its storage schema now specified, Enhanced schema below). **Written at creation of every object** (re-ruled 2026-07-29 — consistency across the schema, even where position already answers). The value — never the key's mere presence — names the kind, and consumers that consult it trust the value outright: no stripping, no corroboration machinery. Only consequential inside `.trash/` today, where position can't answer (Deletion below); everywhere else it is redundant with position (level is position) and carried for uniformity. Missing on an older object, it **backfills on touch** — the integrity service's on-touch heal (Validation and healing below), never a scheduled sweep. | -**`modified`'s scope — content provenance, not file activity (settled).** The stamp updates on every app write that rewrites the item's `index.md`, and only those — it describes the item's *content* (title, body, frontmatter), never its files. Two designed app writes therefore don't bump it, deliberately: **raw-source Apply** writes the validated buffer byte-for-byte (05-card-window.md — the verbatim contract outranks stamping, the same reason it doesn't clear `modified-by` below), and **attachment operations** never touch `index.md` at all — load-bearing, not an oversight: Add Attachment stays enabled during an open raw-source edit precisely because it can't fight the buffer (03-board-ui.md ▸ toolbar). The third is a class, not a write: **order-only rewrites don't stamp — the moves-don't-stamp rule** (ruled 2026-07-29; the rule Deletion below and 03 ▸ Trash already cited). `order` is logically the *container's* property — a relationship among the lane's members that the format happens to store as an attribute inside each member's file — so a reorder, a cross-lane move, and a renumber's whole-lane rescale (Ordering below) rewrite `index.md` without touching content: no stamp, and no `modified-by` clear (the pairing below — attribution can't change when content didn't). The trash move is the one deliberate exception (Deletion below): deletion is an edit to the item's story, so it stamps and clears both. Considered and declined: stamping the container's own `modified` on reorder (the lane's ordering did change) is a fair reading, declined pragmatically — it fans one member's write out to the container's file and is hard to enforce without adding risk, for a signal nothing reads. Accepted consequence: the card window's "Modified ⟨date⟩" line (05-card-window.md) can read older than the card's latest attachment activity — attachment history lives where files do (the sidebar shows them; on git boards the Attach/Remove commits are the activity trail — 06-history-undo.md). +**`modified`'s scope — content provenance, not file activity (settled).** The stamp updates on every app write that rewrites the item's `index.md`, and only those — it describes the item's *content* (title, body, frontmatter), never its files. Two designed app writes therefore don't bump it, deliberately: **raw-source Apply** writes the validated buffer byte-for-byte (05-card-window.md — the verbatim contract outranks stamping, the same reason it doesn't clear `modified-by` below), and **attachment operations** never touch `index.md` at all — load-bearing, not an oversight: Add Attachment stays enabled during an open raw-source edit precisely because it can't fight the buffer (03-board-ui.md ▸ toolbar). The third is a class, not a write: **order-only rewrites don't stamp — the reorders-don't-stamp rule** (ruled 2026-07-29 as moves-don't-stamp; refined 2026-07-30 — container changes stamp; Deletion below and 03 ▸ Trash cite the rule). `order` is logically the *container's* property — a relationship among the lane's members that the format happens to store as an attribute inside each member's file — so a reorder within the item's container (a card among its lane's siblings, a lane among the board's lanes) and a renumber's whole-lane rescale (Ordering below) rewrite `index.md` without touching content: no stamp, and no `modified-by` clear (the pairing below — attribution can't change when content didn't). **A move that changes the item's container stamps both** (the 2026-07-30 refinement): a cross-lane move, a cross-board arrival, and the trash move (Deletion below) change the item's story — which lane a card lives in is state; where it stands in line is presentation — so they stamp `modified` and clear `modified-by` like any content write. The trash move thus stops being an exception and becomes the container rule's plainest instance. Considered and declined: stamping the container's own `modified` on reorder (the lane's ordering did change) is a fair reading, declined pragmatically — it fans one member's write out to the container's file and is hard to enforce without adding risk, for a signal nothing reads. Accepted consequence: the card window's "Modified ⟨date⟩" line (05-card-window.md) can read older than the card's latest attachment activity — attachment history lives where files do (the sidebar shows them; on git boards the Attach/Remove commits are the activity trail — 06-history-undo.md). -**`modified-by` — self-reported provenance (settled).** An external writer (agent, script) may stamp a file it writes with a short free-form identity (`modified-by: claude`). The app never sets it and **clears it on every app-mediated write** — absence means "the board's user, via the app"; the file is being rewritten anyway, so clearing costs no extra write. Consequences compose from existing rules: copies arrive cleared (paste and duplicate are app writes), template instantiation strips it alongside its timestamp restamp (09-templates.md), Save as Template keeps it inertly. The key is schema-owned, not an unknown key — unknown-key preservation doesn't apply to it. Downstream surfaces: the card window's modified line renders it (05-card-window.md), and it refines foreign commit attribution (06-history-undo.md). Honest limit: it is self-reported — a hand edit that leaves an old stamp in place inherits its attribution until the next app write clears it. Two carve-outs: **raw-source Apply** (05-card-window.md) writes byte-for-byte and does *not* clear a stamp the user typed or kept — the outlet exists to write anything, and the validated-then-verbatim contract outranks the clearing rule; and **order-only rewrites** (the moves-don't-stamp rule above) — position changes touch no content, so a standing stamp survives a reorder or move, while the trash move, stamping `modified`, clears here too. +**`modified-by` — self-reported provenance (settled).** An external writer (agent, script) may stamp a file it writes with a short free-form identity (`modified-by: claude`). The app never sets it and **clears it on every app-mediated write** — absence means "the board's user, via the app"; the file is being rewritten anyway, so clearing costs no extra write. Consequences compose from existing rules: copies arrive cleared (paste and duplicate are app writes), template instantiation strips it alongside its timestamp restamp (09-templates.md), Save as Template keeps it inertly. The key is schema-owned, not an unknown key — unknown-key preservation doesn't apply to it. Downstream surfaces: the card window's modified line renders it (05-card-window.md), and it refines foreign commit attribution (06-history-undo.md). Honest limit: it is self-reported — a hand edit that leaves an old stamp in place inherits its attribution until the next app write clears it. Two carve-outs: **raw-source Apply** (05-card-window.md) writes byte-for-byte and does *not* clear a stamp the user typed or kept — the outlet exists to write anything, and the validated-then-verbatim contract outranks the clearing rule; and **order-only rewrites** (the reorders-don't-stamp rule above) — position changes touch no content, so a standing stamp survives a within-container reorder, while a container-changing move (cross-lane, cross-board, into or out of the trash), stamping `modified`, clears here too. ### Board (`/index.md`) @@ -91,7 +91,7 @@ Schema-owned display fields are Lanework's to interpret — **coerce where a sen Future integrations extend the core schema; this version reserves their names — and, for comments, specifies the storage schema ahead of the feature — so nothing else squats on them. The app treats reserved keys as ordinary unknown keys (preserved verbatim, invisible in the UI) and reserved folders as ignored non-level content: -- **`comments/` card child — the comment thread** (storage specified 2026-07-29, the card-window UI designed the same day — 05-card-window.md ▸ The comments column; no comments functionality ships in this version). A comment is a **UUID-named folder** under `comments/`, holding `index.md` (frontmatter + Markdown body, the card-body subset) and optionally `attachments/` — a card's anatomy one level down, so the fractal rules apply verbatim: two-step-create tolerance, stray tolerance, loose-file relocation, displacement of a squatted `attachments`, shape-only identity. `comments/` itself stays a plain reserved child — never a level, never identity. **Field table for `kind: comment`** (exercising IntegrityRules' per-kind hook for the first time): `schema` required; `kind: comment` written at creation (the common rule); **`author`** lenient — self-reported *content*, not overlay: unlike `modified-by` it survives app writes; the app writes the macOS account's full name (the identity 06-history-undo.md's derived default already uses), agents write their own (the guide teaches it), tracker sync writes the remote author verbatim, and missing renders unattributed; `created`/`modified` as everywhere — `created` is load-bearing (ordering below), and the edited indicator is simply `modified` differing, no extra field; `remote`/`remote-state` ride as reserved tracker keys. **No `title`, no `order`.** The rules, all ruled 2026-07-29: **Ordering is chronology, not ranks** — the thread sorts by `created` ascending; chronology *is* a conversation's semantics, and cross-machine or tracker-synced comments carry independent clocks where minted ranks would interleave arbitrarily (the trash's no-timestamp-sort rationale inverts here); ties and missing/malformed `created` (coerce-tier fallback, logged) sort after dated siblings, folder-name order. **Flat this iteration** — no reply nesting, deliberately deferred with the door open (a future reply relation would be frontmatter, and the window-scoped read below holds either way). **Comment defects never refuse the board** — worst case is the stray posture (tolerated, logged, unrendered): a broken leaf annotation must not brick a load; deliberate, proportionate divergence from card fail-fast. **Delete skips the board trash, not the pattern** (re-ruled 2026-07-29 — supersedes the earlier capture parenthetical): the root `.trash/` holds cards and lanes; a deleted comment **moves into `comments/.trash/`** — the materialized-trash pattern one level down, joining `.draft` in the claimed names (a squatter displaces by the ladder). The move is immediate, no confirm, and undo is the net: the inverse is the ordinary move back — **no byte capture on any edition** (13-native-undo.md's no-capture rule holds; on git boards the move is a commit like any other). `comments/.trash/` is undo's backing store, never a UI surface: excluded from the thread, **purged when the card window closes** (rides the close flush; crash residue sweeps at the next card-window open, armed-then-cleared like every heal memo), and **stripped at every copy boundary** (clipboard staging, Duplicate, Save as Template — a copy must not carry ghosts no window session will ever purge; sibling of the `remote`-severing rule). The trash-move stamping exception applies — the move stamps `modified`. **Copies carry the thread** — a copy is a fork, and dropping a subtree would be the one place a copy loses content; comment folders remint like every copied folder, and the copy's comments drop `remote`/`remote-state` (an instance of the item-level sever rule — Identity lifecycle above); whole-board forks carry verbatim as always. **Comments are window-scoped, outside the board snapshot** — the walk stays O(cards): the card window reads its own thread and live-reloads it from the same FSEvents stream, and the board snapshot never loads comment content. This is the stated exception to snapshot completeness, costs named: the snapshot never carries comment content — board search reaches comment bodies through its own transient search-time sweep instead (04-interactions.md ▸ Search, re-ruled 2026-07-29), the card window's find covers the focused thread (05) — and foreign comment changes are described by **path shape** — the "Update agent guide (vN)" mechanism: a changed path under `…/comments//` composes "Comment on '⟨card title⟩'" / "Edit comment on…" / "Delete comment on…" (06 gains the verb family when the feature ships), and the announcer speaks arrivals the same way. Git protection needs nothing new — the committer already stages the whole root and its condition is the tree. Trash interplay is free: a trashed card carries its `comments/`, purge kills it, restore returns it. Comments never count in the welcome counts. A comment-count chip on card faces is WISHLIST #9. **`comments/.draft/` is the card's single comment draft** (ruled 2026-07-29, the UI pass — user content with a lifecycle name, the `.trash` pattern one level down): a reserved dot-named folder holding ordinary comment schema, `attachments/` included; excluded from the thread; **posting renames it to a fresh lowercase UUID and restamps `created`/`modified` in the same bracket** — chronology is post time, not drafting time — one commit. `.draft` joins the claimed names (a wrong-kind node squatting it displaces by the ladder); copies and the trash carry it like any comment folder (fork-lossless); a draft emptied of text with no attachments is deleted by the app, never litter. The schema is **edition-blind** (one format, no edition-specific keys), and so is the feature: **comments render and author in every edition** (ruled 2026-07-29 — 05-card-window.md ▸ The comments column); only tracker sync is edition-gated (Teams — 12-editions.md). +- **`comments/` card child — the comment thread** (storage specified 2026-07-29, the card-window UI designed the same day — 05-card-window.md ▸ The comments column; no comments functionality ships in this version). A comment is a **UUID-named folder** under `comments/`, holding `index.md` (frontmatter + Markdown body, the card-body subset) and optionally `attachments/` — a card's anatomy one level down, so the fractal rules apply verbatim: two-step-create tolerance, stray tolerance, loose-file relocation, displacement of a squatted `attachments`, shape-only identity. `comments/` itself stays a plain reserved child — never a level, never identity. **Field table for `kind: comment`** (exercising IntegrityRules' per-kind hook for the first time): `schema` required; `kind: comment` written at creation (the common rule); **`author`** lenient — self-reported *content*, not overlay: unlike `modified-by` it survives app writes; the app writes the macOS account's full name (the identity 06-history-undo.md's derived default already uses), agents write their own (the guide teaches it), tracker sync writes the remote author verbatim, and missing renders unattributed; `created`/`modified` as everywhere — `created` is load-bearing (ordering below), and the edited indicator is simply `modified` differing, no extra field; `remote`/`remote-state` ride as reserved tracker keys. **No `title`, no `order`.** The rules, all ruled 2026-07-29: **Ordering is chronology, not ranks** — the thread sorts by `created` ascending; chronology *is* a conversation's semantics, and cross-machine or tracker-synced comments carry independent clocks where minted ranks would interleave arbitrarily (the trash's no-timestamp-sort rationale inverts here); ties and missing/malformed `created` (coerce-tier fallback, logged) sort after dated siblings, folder-name order. **Flat this iteration** — no reply nesting, deliberately deferred with the door open (a future reply relation would be frontmatter, and the window-scoped read below holds either way). **Comment defects never refuse the board** — worst case is the stray posture (tolerated, logged, unrendered): a broken leaf annotation must not brick a load; deliberate, proportionate divergence from card fail-fast. **Delete skips the board trash, not the pattern** (re-ruled 2026-07-29 — supersedes the earlier capture parenthetical): the root `.trash/` holds cards and lanes; a deleted comment **moves into `comments/.trash/`** — the materialized-trash pattern one level down, joining `.draft` in the claimed names (a squatter displaces by the ladder). The move is immediate, no confirm, and undo is the net: the inverse is the ordinary move back — **no byte capture on any edition** (13-native-undo.md's no-capture rule holds; on git boards the move is a commit like any other). `comments/.trash/` is undo's backing store, never a UI surface: excluded from the thread, **purged when the card window closes** (rides the close flush; crash residue sweeps at the next card-window open, armed-then-cleared like every heal memo), and **stripped at every copy boundary** (clipboard staging, Duplicate, Save as Template — a copy must not carry ghosts no window session will ever purge; sibling of the `remote`-severing rule). The container-change stamping rule applies — the move stamps `modified` (`modified`'s scope above). **Copies carry the thread** — a copy is a fork, and dropping a subtree would be the one place a copy loses content; comment folders remint like every copied folder, and the copy's comments drop `remote`/`remote-state` (an instance of the item-level sever rule — Identity lifecycle above); whole-board forks carry verbatim as always. **Comments are window-scoped, outside the board snapshot** — the walk stays O(cards): the card window reads its own thread and live-reloads it from the same FSEvents stream, and the board snapshot never loads comment content. This is the stated exception to snapshot completeness, costs named: the snapshot never carries comment content — board search reaches comment bodies through its own transient search-time sweep instead (04-interactions.md ▸ Search, re-ruled 2026-07-29), the card window's find covers the focused thread (05) — and foreign comment changes are described by **path shape** — the "Update agent guide (vN)" mechanism: a changed path under `…/comments//` composes "Comment on '⟨card title⟩'" / "Edit comment on…" / "Delete comment on…" (06 gains the verb family when the feature ships), and the announcer speaks arrivals the same way. Git protection needs nothing new — the committer already stages the whole root and its condition is the tree. Trash interplay is free: a trashed card carries its `comments/`, purge kills it, restore returns it. Comments never count in the welcome counts. A comment-count chip on card faces is WISHLIST #9. **`comments/.draft/` is the card's single comment draft** (ruled 2026-07-29, the UI pass — user content with a lifecycle name, the `.trash` pattern one level down): a reserved dot-named folder holding ordinary comment schema, `attachments/` included; excluded from the thread; **posting renames it to a fresh lowercase UUID and restamps `created`/`modified` in the same bracket** — chronology is post time, not drafting time — one commit. `.draft` joins the claimed names (a wrong-kind node squatting it displaces by the ladder); copies and the trash carry it like any comment folder (fork-lossless); a draft emptied of text with no attachments is deleted by the app, never litter. The schema is **edition-blind** (one format, no edition-specific keys), and so is the feature: **comments render and author in every edition** (ruled 2026-07-29 — 05-card-window.md ▸ The comments column); only tracker sync is edition-gated (Teams — 12-editions.md). - **Card metadata keys `labels`, `assignees`, `due`** — issue-style metadata belonging to the tracker-integration story (the pathfinder shipped them natively; the rewrite reserves them instead). One deliberate carve-out: the commit-message composer names changes to these three keys ("Relabel card 'X'" — 06-history-undo.md), invisible in the UI though they are. - **`remote` key** (board, card) and **`remote-state` key** (lane) — future tracker connectors (Gitea/GitHub/GitLab/Jira), per 07-sync-collab.md's out-of-scope note. @@ -101,7 +101,7 @@ Carried over unchanged — gapped fractional ranks: - New items append at `max + 1024`; insert at head = `min − 1024`; insert between two = midpoint. - A reorder rewrites **only the moved item's** `index.md`. -- Midpoint precision exhausted → renumber that one lane's cards (or the board's lanes) to whole multiples of 1024. Rare, local — and **the one exception to the only-the-moved-item rule**: a renumber rewrites every sibling's `index.md` in that lane — order-only rewrites, so no `modified` stamp and no `modified-by` clear (moves-don't-stamp, `modified`'s scope above). On a remote-shared board the burst is conflict-prone in principle but benign in practice: each rewritten file changes only its `order`, the local-wins rebase (07-sync-collab.md) resolves per file, orders stay valid floats, and deterministic tie-breaking keeps rendering stable even if two machines' renumbers interleave. The batch is atomic per file, not per lane — an interrupted renumber (crash, volume error mid-lane) leaves some siblings rescaled and some not, which is accepted the same way: every order still a valid float, display order still deterministic via the tie-break, and the next renumber completes the job. It lands inside the triggering operation's single commit (06-history-undo.md): the renumber batches with the insert or move that exhausted the midpoint, and its sequence-preserving `order` rewrites are bookkeeping to the message composer — the commit reads as the triggering event ("Move card 'Fix login' to Doing"), never as N separate edits or a standalone renumber. +- Midpoint precision exhausted → renumber that one lane's cards (or the board's lanes) to whole multiples of 1024. Rare, local — and **the one exception to the only-the-moved-item rule**: a renumber rewrites every sibling's `index.md` in that lane — order-only rewrites, so no `modified` stamp and no `modified-by` clear (reorders-don't-stamp, `modified`'s scope above). On a remote-shared board the burst is conflict-prone in principle but benign in practice: each rewritten file changes only its `order`, the local-wins rebase (07-sync-collab.md) resolves per file, orders stay valid floats, and deterministic tie-breaking keeps rendering stable even if two machines' renumbers interleave. The batch is atomic per file, not per lane — an interrupted renumber (crash, volume error mid-lane) leaves some siblings rescaled and some not, which is accepted the same way: every order still a valid float, display order still deterministic via the tie-break, and the next renumber completes the job. It lands inside the triggering operation's single commit (06-history-undo.md): the renumber batches with the insert or move that exhausted the midpoint, and its sequence-preserving `order` rewrites are bookkeeping to the message composer — the commit reads as the triggering event ("Move card 'Fix login' to Doing"), never as N separate edits or a standalone renumber. - Ties broken by folder name (lexicographic, **comparing the canonical lowercase spelling** — settled: ordering follows the value-based identity model, never an uppercase folder's ASCII accident) for deterministic rendering. The same canonicalization applies to every folder-name tie-break in the corpus (the trash sort included — 03-board-ui.md). ## Attachments @@ -114,7 +114,7 @@ Carried over unchanged — gapped fractional ranks: The tombstone model is **retired** — no `deleted:` key, no in-place flags, no ancestor-walked hiding. Deletion is a **move**: -- **Deleting a card or a lane moves its folder into `/.trash/`** (lanes joined 2026-07-29 — retiring the design's sole destructive delete) — a reserved, app-claimed name at board root (joining `CLAUDE.md` and the seeded `.gitignore` on the claimed list; agents and hand-editors should treat it as the app's). `.trash/` holds card and lane folders interleaved directly, no `index.md` of its own. A non-directory node squatting the name — a file, a symlink — is an invalid artifact healed by displacement (the claimed-names rule above): until the heal lands, the loader reads the board as having an empty trash and a delete's directory mint fails as an ordinary one-shot — a window measured in one reload, not a standing state. Every arrival lands at the **top** regardless of kind: the move mints an `order` rank above the current topmost, so the trash sorts newest-first by ordinary ranks — no timestamp sort, no tie-break rules. The move **stamps `modified` and clears `modified-by`** (deletion is an edit to the item's story — the deliberate exception to the moves-don't-stamp rule, `modified`'s scope above), which is what a future age-based auto-purge will read (deliberate follow-up, not in the initial release). A trashed lane keeps its subtree untouched — its cards ride along and come back with it. +- **Deleting a card or a lane moves its folder into `/.trash/`** (lanes joined 2026-07-29 — retiring the design's sole destructive delete) — a reserved, app-claimed name at board root (joining `CLAUDE.md` and the seeded `.gitignore` on the claimed list; agents and hand-editors should treat it as the app's). `.trash/` holds card and lane folders interleaved directly, no `index.md` of its own. A non-directory node squatting the name — a file, a symlink — is an invalid artifact healed by displacement (the claimed-names rule above): until the heal lands, the loader reads the board as having an empty trash and a delete's directory mint fails as an ordinary one-shot — a window measured in one reload, not a standing state. Every arrival lands at the **top** regardless of kind: the move mints an `order` rank above the current topmost, so the trash sorts newest-first by ordinary ranks — no timestamp sort, no tie-break rules. The move **stamps `modified` and clears `modified-by`** (a container-changing move — the stamping half of reorders-don't-stamp, `modified`'s scope above; deletion is an edit to the item's story), which is what a future age-based auto-purge will read (deliberate follow-up, not in the initial release). A trashed lane keeps its subtree untouched — its cards ride along and come back with it. - **`kind:` discriminates inside `.trash/`** (ruled 2026-07-29): depth defines meaning *on the live board*, but the trash is flat, and an empty lane folder is shape-identical to a card folder (`/index.md` both). Every object carries `kind` from creation (re-ruled 2026-07-29 — the common-schema row, Frontmatter above); inside `.trash/` it is where the key earns its keep. A lane's `kind: lane` is **backfilled on touch when absent** (the integrity service's on-touch heal, Validation and healing below: any Writer rewrite of that lane's `index.md`, the trash move's rank mint included, stamps the missing key — **on-touch only, never a scheduled backfill sweep**) and never stripped (restore stays a pure move; the key is inert on the live board where position wins). The trash reader's rule **trusts the value** (re-ruled 2026-07-29 — the value-names-the-kind posture): `kind: lane` → lane; `kind: card` → card; an unrecognized value or no key falls through to shape — UUID-shaped children with their own `index.md` → lane (the key backfills on the next touch), else card. Trusting the value means an external writer's `kind: lane` on what looks card-shaped is honored, never policed. Honest limit of on-touch-only backfill, accepted: a kind-less trashed lane emptied of its children before any touch becomes indistinguishable from a card. - **Restore is a move out**: any move of a card folder from `.trash/` into a lane, or of a lane folder to board level — drag, or cut+paste — is an ordinary move; there is no restore-specific machinery and no Put Back. Position (and, for cards, lane) are chosen at restore time. - A folder that disappears entirely (Finder deletion) is also a delete; the app reflects it — such items are gone, they never enter the trash. diff --git a/DESIGN/03-board-ui.md b/DESIGN/03-board-ui.md index 9902569..aeeefda 100644 --- a/DESIGN/03-board-ui.md +++ b/DESIGN/03-board-ui.md @@ -62,7 +62,7 @@ The window-title widget opens the **board popover** — the one board-level surf **Resettled 2026-07-28 — the materialized trash.** The tombstone model (a `deleted:` flag on items left in place, rendered by a pure-view quasi-lane) is **retired**: it generated a standing tax of nesting rules — ancestor walks, effective liveness, entry-vs-universe splits, kind-homogeneous selection — that this design replaces wholesale. Deletion is now a **move**: deleting a card moves its folder into **`/.trash/`**, a reserved, materialized container (01-storage-format.md). A trashed card is an ordinary card in a special place — search, selection, rendering, styling, and clipboard all treat it exactly like any other card, and `.trash/` is self-describing in Finder and to agents. - **Lanes trash too** (re-ruled 2026-07-29, retiring "cards only" and with it the design's sole destructive delete): deleting a lane moves its folder — subtree intact — into `.trash/`, exactly as a card moves; `kind: lane` in its frontmatter is what tells a trashed lane from a card in the flat container (01-storage-format.md ▸ Deletion), stamped on the way in when absent. The no-dialog posture survives for a better reason: the move is recoverable, so nothing needs confirming. Native undo's inverse is the ordinary move back (13-native-undo.md — the capture/recreate machinery retires). **A trashed lane is an opaque unit**: one distinct dimmed row showing its title and held-card count ("Doing — 5 cards"), no styling accents, never expandable; its cards are invisible to search and not individually addressable — it restores whole or purges whole. The row matches the search filter by lane title only. Lane rows and cards interleave in the one trash column purely by trash rank. -- **Entry is always at the top** (settled): every arrival, card or lane — ⌫/⌘⌫ delete and drag-to-trash alike — lands at the trash's topmost position, minting an `order` rank above the current top. Newest-first ordering falls out of ordinary ranks by construction: **there is no `deleted:` key and no timestamp sort** — the trash sorts by `order` like any lane. The move **stamps `modified`** (the one exception to moves-don't-stamp, now stated in 01's `modified` scope — deletion is an edit to the card's story), which is what a future age-based auto-purge will read (deferred, 01-storage-format.md). +- **Entry is always at the top** (settled): every arrival, card or lane — ⌫/⌘⌫ delete and drag-to-trash alike — lands at the trash's topmost position, minting an `order` rank above the current top. Newest-first ordering falls out of ordinary ranks by construction: **there is no `deleted:` key and no timestamp sort** — the trash sorts by `order` like any lane. The move **stamps `modified`** (a container-changing move — 01's `modified` scope; deletion is an edit to the card's story), which is what a future age-based auto-purge will read (deferred, 01-storage-format.md). - **Rendering**: trailing (rightmost) position when shown, visually distinct — dimmed/hatched header, trash SF Symbol, count badge; no new-card button; not draggable, not resizable, excluded from lane reordering. Fixed one width unit, consumed only while shown; Show/Hide Trash is a re-divide trigger (Layout above). **Visibility**: hidden by default; View ▸ Show Trash toggles (no default chord — ⇧⌘T belongs to the system's Show Tab Bar, 11-command-nexus.md); per-open transient state, resets to hidden, never persisted. Hidden, the trash is invisible to every gesture and to search; shown, its cards participate in the filter **exactly like any other card** — the point of the pivot. - **No Put Back** (settled): the valuable item is the card (or lane); where it goes on the way out is the user's cheap decision. Restoring is an ordinary move out: **drag** a trash card into any lane at any position — or a trashed lane row to a lane-strip slot — or **⌘X in the trash, ⌘V** — into a lane for cards, after the anchor lane for a trashed lane (04-interactions.md's lane-paste rule verbatim); the clipboard works on trash items like on any item, which is also the keyboard-native restore path (10-accessibility.md). Dropped on another board it follows the drag locality model (04-interactions.md). - **No editing in the trash**: trash cards don't open — double-click stops at selection; move it out first — and a trashed lane row never expands. Moving a card to the trash dismisses its open card window (05-card-window.md), and an external move-in observed by reload does the same; a lane entering the trash dismisses the open card windows of every card it carries (they entered the trash with it). diff --git a/DESIGN/08-agent-integration.md b/DESIGN/08-agent-integration.md index 7d5bf3a..c911a98 100644 --- a/DESIGN/08-agent-integration.md +++ b/DESIGN/08-agent-integration.md @@ -22,6 +22,7 @@ The app silently maintains a `CLAUDE.md` in every board — a condensed, agent-f - New in the rewrite: **attachments** (the `attachments/` convention, importing files) — including the rule that **card files belong in `attachments/`**: a loose file written beside `index.md` will be relocated there by the app with a notice (01-storage-format.md's loose-file carve-out), so agents should put it there in the first place. - New in the rewrite: **`modified-by` self-stamping** — stamp files you write; re-stamp every write *and every move* (the app clears it on its own writes; a bare folder move leaves the card unstamped); self-commit instead when you need exact authorship. - New in the rewrite: a pointer to the optional **`CLAUDE.user.md`** (see below), instructing agents to read it when present. +- v7 additionally teaches the stamp discipline of 01 ▸ `modified`'s scope (ruled 2026-07-29, refined 2026-07-30): **reordering within a lane rewrites only `order`** — leave `modified` and `modified-by` alone — while **a move between lanes, between boards, or into/out of `.trash/` updates both** (stamp `modified`, re-stamp `modified-by`); and the card-level **`attachments` claimed name** — `attachments` inside a card folder is the app's (the card's file folder); never create a *file* by that name. Mechanics: version marker in the first line (`lanework-agent-guide vN`); rewritten when missing or older, never downgraded, left untouched when current or newer. To the loader it's just another ignored file.