From 01f1194a566125ef78c475ad13cfa37ad5727a3d Mon Sep 17 00:00:00 2001 From: rzen Date: Fri, 31 Jul 2026 21:38:42 -0400 Subject: [PATCH] Gate the comment-trash sweep on live-step ownership Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY --- DESIGN/01-storage-format.md | 2 +- DESIGN/13-native-undo.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESIGN/01-storage-format.md b/DESIGN/01-storage-format.md index fb68e04..e5fb758 100644 --- a/DESIGN/01-storage-format.md +++ b/DESIGN/01-storage-format.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. **The heals run window-scoped at this depth** (ruled 2026-07-31): comments are outside the board walk, so the thread read is the detector — it reports loose files and claimed-name squatters as window-scoped defects, and the card window relocates/displaces them memo-less on the read-cadence guard (02-architecture.md ▸ HealScheduler's window-scoped pattern); a loose file beside a comment's `index.md` moves into that comment's `attachments/` with the relocation family's notice ("Moved 'notes.txt' into attachments — comment on 'Fix login'"), and the heals compose as at board level. `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 joined the timestamp family 2026-07-31 — it sorts by `modified` descending, Deletion below); 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. The posture is **load-scoped** (ruled 2026-07-31): a user-initiated copy is a transaction whose stamping preflight refuses whole at comment depth too (Identity lifecycle above) — the board always loads; the gesture may refuse. **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 in any tier** (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 undo no longer needs it** (re-ruled 2026-07-31 — the session-coarsening model, 13-native-undo.md: on free-tier boards the close step's undo restores deleted comments, so the purge waits until that coarse step leaves the board stack or the board session ends; on Pro the close commit nets delete-plus-purge and revert restores from history, so the purge 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…" — and a comment leaving the tree from `comments/.trash/` composes **"Permanently delete comment on…"** (blessed 2026-07-31 — the board trash-pair's leaving-the-tree rule one level down, so purge-only windows never fall to a shrug and foreign purges read honestly); plurals fold noun-first where the post subject forces it (blessed 2026-07-31): "3 comments on 'Fix login'" / "Comment on 3 cards" / "Edit 3 comments on 'Fix login'" / "Draft comment on 3 cards" — (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 **tier-blind** (one format, no tier-specific keys), and so is the feature: **comments render and author in every tier** (ruled 2026-07-29 — 05-card-window.md ▸ The comments column); only tracker sync is tier-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. **The heals run window-scoped at this depth** (ruled 2026-07-31): comments are outside the board walk, so the thread read is the detector — it reports loose files and claimed-name squatters as window-scoped defects, and the card window relocates/displaces them memo-less on the read-cadence guard (02-architecture.md ▸ HealScheduler's window-scoped pattern); a loose file beside a comment's `index.md` moves into that comment's `attachments/` with the relocation family's notice ("Moved 'notes.txt' into attachments — comment on 'Fix login'"), and the heals compose as at board level. `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 joined the timestamp family 2026-07-31 — it sorts by `modified` descending, Deletion below); 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. The posture is **load-scoped** (ruled 2026-07-31): a user-initiated copy is a transaction whose stamping preflight refuses whole at comment depth too (Identity lifecycle above) — the board always loads; the gesture may refuse. **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 in any tier** (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 undo no longer needs it** (re-ruled 2026-07-31 — the session-coarsening model, 13-native-undo.md: on free-tier boards the close step's undo restores deleted comments, so the purge waits until that coarse step leaves the board stack or the board session ends; on Pro the close commit nets delete-plus-purge and revert restores from history, so the purge rides the close flush; crash residue sweeps at the next card-window open, armed-then-cleared like every heal memo — residue defined as content no live coarse step owns, ruled 2026-07-31: 13-native-undo.md's sweep gate), 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…" — and a comment leaving the tree from `comments/.trash/` composes **"Permanently delete comment on…"** (blessed 2026-07-31 — the board trash-pair's leaving-the-tree rule one level down, so purge-only windows never fall to a shrug and foreign purges read honestly); plurals fold noun-first where the post subject forces it (blessed 2026-07-31): "3 comments on 'Fix login'" / "Comment on 3 cards" / "Edit 3 comments on 'Fix login'" / "Draft comment on 3 cards" — (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 **tier-blind** (one format, no tier-specific keys), and so is the feature: **comments render and author in every tier** (ruled 2026-07-29 — 05-card-window.md ▸ The comments column); only tracker sync is tier-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. diff --git a/DESIGN/13-native-undo.md b/DESIGN/13-native-undo.md index b44759c..b88f0a6 100644 --- a/DESIGN/13-native-undo.md +++ b/DESIGN/13-native-undo.md @@ -17,7 +17,7 @@ The undo/redo substrate for **every board without app-managed git, in every tier ⌫'s undo is the move back — a delete is a move into `.trash/` (cards resettled 2026-07-28; lanes rejoined 2026-07-29), so its undo is the ordinary inverse move, returning a card to its source lane and rank, a lane to its strip position (subtree intact — it never left the folder); a restore-by-move undoes the same way in reverse. The stack and the trash never conflict — they are the same folder moves addressed by recency instead of by selection. The old lane-delete recreate-from-capture inverse is **retired** — no destructive delete remains outside a trash, so nothing needs byte capture. A **permanent delete registers no step** — the trash's Delete and Empty Trash are not undoable (Rules above), lanes and their freight included; the confirm is the safety. -**Comments keep the no-capture rule true — on the window stack** (re-ruled 2026-07-31, superseding the board-stack routing): a comment delete is a move into the card's `comments/.trash/` (01-storage-format.md ▸ Enhanced schema — the materialized-trash pattern one level down), its inverse the ordinary move back, and the step lives on the **card window's own stack** (Rules above) — the board stack never carries a granular comment step, so the old stale-after-close skip scenario cannot arise. **The purge of `comments/.trash/` defers with the coarse step** (re-ruled 2026-07-31, superseding purge-at-close): the coarse close step's undo restores deleted comments, so their backing lives as long as the step does — the purge runs when the coarse step leaves the board stack (undone-and-superseded, dropped off the end, or gone stale) or the board session ends; crash residue still sweeps at the next card-window open (the armed-then-cleared memo, unchanged). On Pro the substrate is history: the close commit nets delete-plus-purge to a removal, revert restores it, so purge rides the close flush there as before — purge timing follows the undo substrate's need. +**Comments keep the no-capture rule true — on the window stack** (re-ruled 2026-07-31, superseding the board-stack routing): a comment delete is a move into the card's `comments/.trash/` (01-storage-format.md ▸ Enhanced schema — the materialized-trash pattern one level down), its inverse the ordinary move back, and the step lives on the **card window's own stack** (Rules above) — the board stack never carries a granular comment step, so the old stale-after-close skip scenario cannot arise. **The purge of `comments/.trash/` defers with the coarse step** (re-ruled 2026-07-31, superseding purge-at-close): the coarse close step's undo restores deleted comments, so their backing lives as long as the step does — the purge runs when the coarse step leaves the board stack (undone-and-superseded, dropped off the end, or gone stale) or the board session ends; crash residue still sweeps at the next card-window open — **and residue is defined by the purge-deferral condition itself** (ruled 2026-07-31): `comments/.trash/` content referenced by a live coarse step on the board stack is a step's backing, not residue — the open-time sweep consults the stack and skips owned content, re-arming when the owning step leaves the stack (which is exactly when the deferred purge wanted to run; one condition, two consumers). Reopening a window can therefore never destroy its prior session's undo backing. Unowned content sweeps as before, armed-then-cleared like every heal memo. On Pro the substrate is history: the close commit nets delete-plus-purge to a removal, revert restores it, so purge rides the close flush there as before — purge timing follows the undo substrate's need. ## Out of scope