diff --git a/DESIGN/01-storage-format.md b/DESIGN/01-storage-format.md index 5a85fad..c04c0a9 100644 --- a/DESIGN/01-storage-format.md +++ b/DESIGN/01-storage-format.md @@ -94,7 +94,7 @@ Schema-owned display fields are Lanework's to interpret — **coerce where a sen Future integrations extend the core schema. Comments — specified here 2026-07-29 ahead of the feature — have since **shipped**: the `comments/` schema below is live, rendered and authored in the app on both platforms. The remaining names stay reserved 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; shipped: thread column, composer, `.draft`, and `comments/.trash/` are all live). 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 cleanly or the board session ends (a stale-skipped step's backing waits for session end — 13's skip-purge decoupling, ruled 2026-07-31); 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 shipped 2026-08-09** (design ruling, card e729e30a; formerly WISHLIST #9): the snapshot carries a per-card `commentCount` from a readdir of `comments/`'s identity-shaped, `index.md`-bearing children (`BoardLoader.commentCount(in:)`) — never a parse of one, so this stays exactly the O(cards) walk the paragraph above requires. It agrees with the card window's parsed thread count except when a comment's `index.md` exists but fails to parse, the one case the readdir does not pay to detect (03-board-ui.md § Card face). **`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. +- **Card metadata keys `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 change narrator names edits to these keys ("Relabel card 'X'"), invisible in the UI though they are. **`labels` left this list 2026-08-09** (Pipeline cards a4462d28/28c79ffe): it is schema-owned and first-party now — a card-level YAML list of strings, read leniently (a bare scalar coerces to a one-element list; non-string entries are preserved verbatim but not rendered), written canonically as a flow list in addition order (no auto-sort), case-insensitively unique per card, and the key is removed with its last label. Managed from the card window's Labels section and the card context menu's labels submenu (05, 11); search integration and face rendering are deliberately unruled follow-ups. - **`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. ## Ordering diff --git a/DESIGN/04-interactions.md b/DESIGN/04-interactions.md index 754f47d..220b3c4 100644 --- a/DESIGN/04-interactions.md +++ b/DESIGN/04-interactions.md @@ -85,7 +85,7 @@ Custom shortcuts are **system-native, with no in-app remapping UI**: macOS's App ## Search -- Search field invoked with ⌘F (the board toolbar's sole default item; removed from the toolbar, ⌘F surfaces it transiently — 03-board-ui.md ▸ Toolbar; in the **card window**, Edit ▸ Find is find-in-text instead — 05-card-window.md), live filter: cards whose title *and* body both miss the query animate out; case/diacritic-insensitive substring. Scope is **all card content the format makes meaningful** (re-ruled 2026-07-29, superseding title-plus-body-only): title + body today; **comment bodies join when comments ship** — via a search-owned transient comment index, never the snapshot: the first live-query keystroke kicks an async sweep of `comments/*/index.md` bodies (`.draft` and `comments/.trash/` excluded), kept fresh by the same FSEvents stream while a query is active and discarded when it clears — the board walk stays O(cards), 01's window-scoped read untouched; **attributes join as they activate** (title now; labels/tags and their kin are reserved, inert keys this version — nothing to search until a future version gives them life). Attachment filenames stay unsearched. Scope options (content vs attributes, either/or) are WISHLIST #10. +- Search field invoked with ⌘F (the board toolbar's sole default item; removed from the toolbar, ⌘F surfaces it transiently — 03-board-ui.md ▸ Toolbar; in the **card window**, Edit ▸ Find is find-in-text instead — 05-card-window.md), live filter: cards whose title *and* body both miss the query animate out; case/diacritic-insensitive substring. Scope is **all card content the format makes meaningful** (re-ruled 2026-07-29, superseding title-plus-body-only): title + body today; **comment bodies join when comments ship** — via a search-owned transient comment index, never the snapshot: the first live-query keystroke kicks an async sweep of `comments/*/index.md` bodies (`.draft` and `comments/.trash/` excluded), kept fresh by the same FSEvents stream while a query is active and discarded when it clears — the board walk stays O(cards), 01's window-scoped read untouched; **attributes join as they activate** (title now; `labels` came alive 2026-08-09 — 01 § Frontmatter — but its search integration is deliberately unruled: labels stay unsearched until that pass happens; `assignees`/`due` remain reserved and inert). Attachment filenames stay unsearched. Scope options (content vs attributes, either/or) are WISHLIST #10. - The filter is the single source of truth for "what's on the board": layout, drop zones, marquee, ranges, arrow nav, and lane count badges all read it. **A drop under an active filter counts in the rendered space** (ruled 2026-08-06, closing the divergence found 2026-08-01): the zones, the shadow, and the write all resolve through the same filtered list — the position the shadow shows is the landing the write performs (the shipped interim — zones and write counting the unfiltered lane while the slots rendered filtered — was self-consistent but named a position the user could not see; retired). The slot's meaning in the full order is its **visible anchor**: the dropped card enters immediately *after* the slot's visible predecessor — or immediately *before* its visible successor when the slot has none (top of the lane) — and a lane the query emptied appends at its true end; a multi-card drop enters in proposal order beside the same anchor. Hidden cards keep their ranks untouched, never displaced by a gesture that could not see them — and the anchor is what makes the gesture's meaning survive the query's clearing: when the filter lifts, the card sits exactly beside the card it was dropped against. The rendered space means everything rendered — the new-card placeholder occupies its slot for the zones exactly as it does for layout. Hidden cards leave the selection; creating a card clears the search — creation's carve-out exists because a brand-new card must not be born invisible, and it is **stated by mechanism, not by gesture** (settled): *any* user-initiated creation on the board clears the query — ⌘N, Return-creation, empty-space double-click, paste, and Finder file drops alike — while foreign/agent-filed cards keep riding the live filter (02-architecture.md's derived-result rule). **Rename deliberately gets no carve-out**: a rename committed during an active search re-runs the predicate like any edit — a title that stops matching animates the card out and drops it from the selection, exactly as an agent's edit would; the filter stays a pure predicate with one exception, not two. **Escape is staged** (settled): in a non-empty field it clears the query, focus staying in the field; in an empty field it returns focus to the board; with *board* focus and an active search, one press clears the search and the full board returns — search takes Escape before its clear-selection meaning, which applies only when no search is active. **A lane the query empties keeps its slot** (settled): lanes are never filtered out — an all-misses lane stays on the board at its width with a 0 badge (the count reads the filter, 03-board-ui.md); the search filters cards, and the board's structure is not a search result. **A leaving card stays input-reachable for its out-transition** (settled): marquee and arrow targets deregister when the ~0.28 s animate-out ends, so a card mid-departure is briefly reachable while already out of the selection — accepted: it is literally on screen for that span, and closing the window would teach three input sites a predicate the layout already applied. **An open inline rename survives the filter hiding its card** (settled): the editor is a surface the filter doesn't reach — it stays open and focused, commits by UUID wherever the card lives, Escape abandons; keystrokes are never silently discarded for a card that still exists (the dirty-buffer courtesy), and the vanish-discard rule stays reserved for true liveness flips. The typed-query path can't even occur — focusing the search field is focus loss, which commits the rename first — so the rule covers foreign edits that stop the card matching. - **Dispatch while the search field is focused** (settled): the field is a *control*, not a content editor — the focused-editor lockdown (Grammar above) does not apply. Text-domain keys route to the field: ⌘A/⌘X/⌘C/⌘V act on the query, plain ⌫ edits the query and never reaches the board, horizontal arrows move the caret. **Every key with the field focused acts on the field — stock NSSearchField behavior, no pass-throughs** (settled): vertical arrows are caret movement, ⇧-arrows select query text, and **Return is a swallowed no-op** (the filter is live, there is nothing to submit — it never reaches the board's rename/create grammar). **Tab is the keep-filter path**: plain key-view traversal moves focus to the board with the query intact, and the whole board grammar (arrows, ⌥↑ escalation, Return, ⌘↩) then applies over the *filtered* board; ⌘F returns to the field. Board menu commands stay enabled and act on the board selection exactly as when the field is unfocused — ⌘N included (creating a card clears the search, above) — **except the caret-chord commands**: Move Left/Right ⌘←/⌘→ and the width pair ⌥⌘←/⌥⌘→ disable while the field is focused (Grammar above, caret-chords rule), so ⌘←/⌘→ stay line-start/end in the query even with a lane selected — and the Delete pair stays unambiguous by construction: plain ⌫ is query editing, ⌘⌫ is File ▸ Delete on the selection, and ⌘Z/⇧⌘Z are the field's own text undo, never git undo (06-history-undo.md ▸ Undo routing's control-class rule). diff --git a/DESIGN/05-card-window.md b/DESIGN/05-card-window.md index 4791694..9371e08 100644 --- a/DESIGN/05-card-window.md +++ b/DESIGN/05-card-window.md @@ -52,7 +52,7 @@ Key grammar in source mode, completing the window's key story: **Escape is Cance ## The attributes sidebar -Stacked sections under small-caps headers, in this order; quiet rows, read-optimized, edit affordances only where a section edits. The sidebar is the card's non-body inventory — future attributes (enhanced-schema fields, if they ever ship) slot in as new sections rather than growing new bars. +Stacked sections under small-caps headers, in this order; quiet rows, read-optimized, edit affordances only where a section edits. The sidebar is the card's non-body inventory — future attributes slot in as new sections rather than growing new bars. **Order re-ruled 2026-08-09** (Pipeline cards 8f26b029, a4462d28): **Style · Labels · Details · Attachments** — attachments moved to the bottom, Labels (the newly activated key, 01 § Frontmatter) sits above Details deliberately: Details is the section for keys the app does *not* own. The section headers below predate the reorder; the list above is the order. ### Attachments @@ -79,10 +79,13 @@ The card-level styling home: the **Background color combo** over the compact **` - The section is **absent** on boards without app-managed git (mode none, repo-nested) — same honesty rule as the popover's git section (06-history-undo.md). Rows are focusable (arrows), but carry **no actions in v1** — restoring an old version stays a git-client task for now; a per-row forward-restore and lane history are wishlist items, deliberately. - Menu path (contract): **View ▸ History** in the card window focuses the section — no default chord. -### Actions (bottom) +### Actions — retired 2026-08-09 (Pipeline card bcd3b323) -- **Delete** — moves the card to the trash (destructive styling; the window then dismisses itself per Deletion & lifecycle below; recoverable from the board's trash lane — 03-board-ui.md). -- **Reveal in Finder** — the card's folder. +The section's two rows moved to the card window's toolbar: **Delete Card** (trash symbol, a default item behind a trailing flexible space — Mail's own Delete placement; same no-confirm staged trash move, window dismissing per Deletion & lifecycle) and **Reveal in Finder** (catalog-only, customization adds it). Because every toolbar item needs a menu-row twin and ⌘⌫ stays reserved for the window's text surfaces, a chord-less **File ▸ Delete Card** row was added as Delete Card's twin (11-command-nexus.md). The sidebar toggle itself sits rightmost in the toolbar defaults (card 084d127f), and the sidebar as a whole can be hidden — Composition above. + +### Labels (added 2026-08-09 — cards a4462d28/28c79ffe) + +The card's `labels` as removable rows with an add affordance (combo/autocomplete over the board's used-label universe + free-text creation). The key is schema-owned now — 01 § Frontmatter; the twelve-slot context-menu submenu and its More… dialog are the other surface (11 ▸ Context menus). Labels on the card *face* deliberately await their own design pass. ## The comments column diff --git a/DESIGN/11-command-nexus.md b/DESIGN/11-command-nexus.md index 095e22d..342f5c6 100644 --- a/DESIGN/11-command-nexus.md +++ b/DESIGN/11-command-nexus.md @@ -32,6 +32,7 @@ The single source of truth for **every command and action the app can perform** | File | Share… (the board, staged as a `.zip`, `NSSharingServicePicker` anchored to the board window's toolbar or its center) | — (no default) | Board window; design ruling 2026-08-09, card 72691b11 — a faithful copy like Duplicate's (`.git` the sole exclusion; attachments, comments and `.trash/` carried), never gated on the read-only lock (a share is a read, `Print…`'s own posture), disabled only while an inline title editor is focused | | File | Reveal in Finder | — (no default) | Board window: the selection's folder(s), or the board root with nothing selected; card window: the card's folder — the selected attachment's file instead when the attachments section is focused; welcome: the selected recent's folder (disabled on unavailable rows) — the context-menu entry's required twin | | File | Add Attachment… | ⇧⌘A | Card window | +| File | Delete Card | — (no default, deliberately chord-less) | Card window (2026-08-09, card bcd3b323 — the retired sidebar Actions row's menu twin, required because its toolbar item needs one and ⌘⌫ stays reserved for the window's text surfaces; 05 ▸ Actions) | | File | Set as Hero | — (no default) | Card window; the attachments section's selected row, when it is an image the card is not already using (2026-08-09 — 03 ▸ Card face ▸ Hero image). Twin of the row's own context entry; **two rows rather than one that renames itself**, since titles are the remapping key | | File | Remove Hero | — (no default) | Card window; the selected row when it *is* the card's hero — the key goes, the file stays. See Set as Hero for why the pair is two rows | | File | Add Comment | — (no default) | Card window (all tiers — 12); if Show Comments is off, turns it on (persisted, the same user choice) and focuses the composer — 05 ▸ The comments column | @@ -106,8 +107,8 @@ Context menus are the per-item action inventory VoiceOver reads (10 ▸ The boar | Surface | Entries | |---|---| -| Card | Four groups, separators between (reshaped 2026-08-09, Pipeline card fe66c461): **1** Open, Copy Link (kept — same-day feature, 2026-08-09), Rename, Style ▸ Symbol / Color (both open the Style… popover; no per-section focus yet — flagged); **2** Copy, Cut, Paste, Paste Special ▸ Paste Image into Card (enabled when the pasteboard carries an image); **3** Navigation ▸ Move Left / Move Right (wired but disabled — no per-card cross-lane move exists yet; follow-up Pipeline card 06322636); **4** Send to Trash (the board-side word for the staged `.trash/` move — "Delete" stays the trash lane's). The quick-style recents row left this menu in the reshape (flagged for review; the ⌥-alternate Delete Immediately row had already retired with its command, 2026-07-30) | -| Lane | One menu, invoked on the header or lane empty space (settled — a full lane still has its header): Rename, Style…, quick-style recents row (03), Width control (stepper — menu twins Increase/Decrease Lane Width), Delete | +| Card | Four groups, separators between (reshaped 2026-08-09, Pipeline card fe66c461): **1** Open, Copy Link (kept — same-day feature, 2026-08-09), Rename, Style ▸ Symbol / Color (both open the Style… popover; no per-section focus yet — flagged); **2** Copy, Cut, Paste, Paste Special ▸ Paste Image into Card (enabled when the pasteboard carries an image); **3** Navigation ▸ Move Left / Move Right (**live since 085a84a, 2026-08-09** — a real per-card cross-lane move: clicked card widened to the selection Copy/Cut-style, index-preserving landing in the adjacent live lane through the drop's own rank machinery, disabled on lane-spanning selections, enablement via cheap per-lane compared parameters); **4** Send to Trash (the board-side word for the staged `.trash/` move — "Delete" stays the trash lane's). Amended same day (cards 28c79ffe/a4462d28): group 1 gains **Labels ▸** (twelve frequency-then-recency ranked checkmark toggles for the clicked card, then More… — a dialog over all used labels + creation) and Copy Link moved from group 1 into a new **Copy Special ▸** submenu in group 2, mirroring Paste Special. The quick-style recents row left this menu in the reshape (flagged for review; the ⌥-alternate Delete Immediately row had already retired with its command, 2026-07-30) | +| Lane | Five groups, separators between (reshaped 2026-08-09, Pipeline card 22f660b0; invocation unchanged — header or lane empty space): **1** Rename, Properties… (deliberately inert placeholder for a future pane), Style ▸ Symbol / Color (one shared popover, the card menu's v1 posture); **2** Copy, Cut, Paste (Copy/Cut widen to the clicked lane — the card menu's explicit-target twins; Paste stays generic); **3** Width ▸ Increase / Decrease / Reset (Reset = width 1 through the same write, key removed at default, disabled when already there); **4** Collapse Lane / Expand Lane (title-flip row); **5** Send to Trash (relabeled from "Delete"; lanes rejoined the trash 2026-07-29). Quick-style recents row dropped for symmetry with the card menu (flagged for review) | | Trash selection | Delete (permanent — 03's recoverability confirm), Reveal in Finder (inspection before a purge; twin of File ▸ Reveal in Finder, not edit-shaped, enabled on trash selections — 04 ▸ The trash) | | Attachment row | Open, Remove (system Trash) — twins of the focused section's grammar keys (Return / ⌫ — 05); Reveal in Finder — twin of File ▸ Reveal in Finder in its attachments-focused context; **Set as Hero / Remove Hero** (2026-08-09 — 03 ▸ Card face ▸ Hero image) — one slot with two words, present on image rows only and absent rather than greyed where neither applies; twins of the two File rows below, which address the section's *selected* row | | Comment | Edit (inline session — 05 ▸ The comments column), Delete (immediate, undoable — 01), Reveal in Finder |