Give card windows their own undo stacks and coarsen the close

Phase B of the two-level undo card: every card-window gesture — comment
post/delete/edit, body Edit sessions, style and details changes —
registers fine-grained on the window's own stack (window.undoManager
answers with it; board ⌘Z never sees mid-session card steps; an empty
window stack beeps, never falls through). Window close folds the stack
into one coarse values-based board step ("Edit card 'X'") — per-target
per-field later-wins merge, so foreign mid-session writes stay out by
construction, a no-net-change session registers nothing, and any stale
component skips the whole step. The comments/.trash purge defers with
the coarse step via a step-retirement seam on the providers: it runs
when the step leaves the board stack or the board session ends; the git
provider retires dropped steps on register, which keeps Pro's
purge-at-close-flush structural with no tier check. Interim on git
boards: gestures still auto-commit per debounce until phase C's
close-flush commit.

2432 tests in 418 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-07-31 19:39:50 -04:00
parent c0c741fe62
commit 71664dab02
23 changed files with 668 additions and 124 deletions
+23 -4
View File
@@ -68,8 +68,16 @@ enum AgentGuide {
/// Trash, re-ruled 2026-07-31; 08-agent-integration.md's own line): the trash sorts by `modified`
/// descending, so there is no rank to mint on the way in the guide's smallest-`order`-minus-1024
/// formula is replaced by "restamp `modified`, leave `order` alone", which is the same stamp
/// discipline v7 already taught, now doing the ordering as well.
static let version = 8
/// discipline v7 already taught, now doing the ordering as well. **v9 teaches the one-line
/// value**, from a real agent incident (2026-07-31): a card's `title` was a double-quoted
/// scalar wrapped across two lines, and a hand copy of that card onto another board took the
/// first line without its continuation. An unclosed quote does not stop at the key it began
/// on it runs to the end of the block so a board failed to load over a file whose only
/// defect was a missing second line, and the parser's complaint pointed at the *last* key it
/// swallowed rather than the title. The guide now teaches long values as one long line, says
/// why the wrapped shape is the dangerous one to copy, and names the unterminated quote beside
/// the unquoted colon in Hard rules.
static let version = 9
// MARK: - The version marker
@@ -381,6 +389,16 @@ enum AgentGuide {
containing `: ` or starting with `#`, `[`, `{`, or a quote — when in doubt,
double-quote.
**Keep every value on one line.** A double-quoted scalar may legally
continue on the following line (`title: "Long title` then ` the rest"`),
and some tools emit that shape for long titles — but it is the most common
way frontmatter gets broken by hand: the continuation reads as a line of
its own, so an edit or a copy that takes only the first one leaves the
quote unclosed, and an unclosed quote runs on to swallow every key below
it. The whole file then fails to parse, not just the title. Titles have no
length limit — write a long one as one long line, and when you copy a card
between boards, copy its frontmatter block whole.
Unknown keys are preserved verbatim by the app and invisible in its UI —
custom metadata (`project:`, `tags:`, `claimed-by:` …) is safe to add and
survives every app rewrite. Reserved for Lanework's upcoming tracker sync —
@@ -491,8 +509,9 @@ enum AgentGuide {
## Hard rules (the app fails loudly on violations)
- Frontmatter must parse as YAML; `schema` (plus `order` on lanes and
cards) is required. Keep `schema: 1`. The classic violation is an
unquoted colon in a title (see Frontmatter above).
cards) is required. Keep `schema: 1`. The classic violations are an
unquoted colon in a title and a quoted value left unclosed across a
line break (see Frontmatter above).
- Files must be UTF-8 without BOM.
- Never create a card folder without an `index.md`.
- Never rename UUID folders.