The remaining rows of 11-command-nexus.md § Context menus, every entry a
twin of an existing command path, never a parallel implementation:
- Card: Open (the double-click's own openCard closure, always the clicked
card alone), Rename (Board ▸ Rename's beginRename path), Style… and the
quick-style recents (already present), Delete (File ▸ Delete's
store.delete on the standard widened target — selection when the clicked
card is a member, else the card alone).
- Lane (one menu, header and empty space): Rename and Delete join the
existing Style…/recents/Width rows, in table order.
- Trash entries and welcome recents verified already exact against the
table; the attachment row's menu is marked for m6 beside its command.
- File ▸ Reveal in Finder gains its board-window scope, the branch the m4
comment deferred here: the selection's folders on either side of the
trash boundary — enabled under every lock, inspection being a read — or
the board root with nothing selected; a selection resolving to no
folders disables rather than guessing.
904 unit tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
Nexus parity, audited row by row (11-command-nexus.md § Menu commands) —
every already-built item's title, chord, and placement matched exactly;
this pass fills what remained:
- The future-window rows, present with stable titles and validation-driven
disablement until their milestones fill the actions: Save as Template
(m9), Add Attachment… ⇧⌘A, Find Next/Previous ⌘G/⇧⌘G, the View-menu
card triplet Edit Body ⌘E / Raw Source ⌥⌘E / History (m6), Board ▸
Pull/Push (git milestones) — one shared disabled-row shape in
FutureCommands.swift so later milestones only flip validation.
- No Print story in v1: the print group is removed.
- Help carries the Nexus's one remap-teaching line — Customize Keyboard
Shortcuts…, opening System Settings' Keyboard ▸ Shortcuts extension
directly (the modern extension URL, verified to launch the appex).
- The launch-restore decision now runs through a pure, tested
AppModel.shouldRestoreAtLaunch gate; the Settings pane's caption rides a
proper Form section footer.
904 unit tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The board's live title+body filter per 04-interactions.md § Search:
- SearchFilter — a pure value folding the query once (case- and
diacritic-insensitive substring, locale-stable); title OR body matches,
attachment filenames never searched; only the literal empty string is
inactive.
- One universe: the filter threads through SelectionGrammar's order lists
as a defaulted parameter, so ranges, Select All, arrow navigation, the
marquee, drop zones, count badges, and the shown trash all read the same
filtered set by construction; lanes are deliberately never filtered out
(an emptied lane keeps its slot with a 0 badge). Hidden cards leave the
selection through the existing constrain primitive, run on every query
change and as the last line of the reload resolve; the delete successor
is filtered so ⌫ never selects a hidden neighbour.
- The field: an NSSearchField-backed toolbar item (the toolbar's sole
default item); Edit ▸ Find ⌘F focuses it through a focused-value
presentation; stock field-editor dispatch — Return swallowed, Tab is the
keep-filter path to the board, board commands stay enabled except the
caret-chord pair, now one shared caretChordsYield expression.
- Escape is staged: clear the non-empty query (focus stays), hand an empty
field back to the board, clear an active search from board focus —
before Escape's clear-selection meaning.
- Creating a card clears the search (the placeholder funnel); a rename
deliberately gets no carve-out; filter reflow rides the content spring
keyed narrowly on the query.
903 unit tests (24 new).
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
⌘X/⌘C/⌘V for cards and lanes per 04-interactions.md § Clipboard:
- ClipboardStore stages full folder snapshots eagerly at the gesture into
Application Support (at most the current copy; sweep at launch and on
each copy purges what the pasteboard no longer references; a copy made
before quitting pastes whole after restart) and writes the pasteboard a
JSON manifest — every entry embedding its index.md, lane entries their
cards' too — plus plain-text titles.
- Cut is Finder-style deferred: items dim in place off pendingCut, void on
pasteboard takeover (changeCount, no timers), source-board close, or
per-item external tombstoning; the first armed paste moves the surviving
originals whole (tombstoned interior cards land in the destination's
trash), a second paste materializes copies from staging.
- Paste anchors by the shared flatten-order rule (NewCardTarget's anchor,
extracted); a tombstoned selection never anchors; lane paste reaches the
right end and stays enabled on a zero-lane board; paste into the source
board is the within-board lane duplicate; copies keep created, take
fresh GUIDs, and strip tombstoned cards; trash-sourced copies strip
deleted: at materialization; ⌘X is disabled on the trash side.
- A degraded paste is loud, never silent: staging gone → the embedded
index.md fallback lands content-intact, attachments absent, and a
BannerCenter-phrased row names what was lost.
- The standard Edit items validate through conditionally-attached
onCommand handlers, so AppKit's enablement mirrors the availability
predicates; text fields keep their own clipboard while focused.
879 unit tests (68 new).
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The second half: system drag sessions over phase 1's model, per
DRAG-REORDER.md and 04-interactions.md § Drag & drop.
- Card faces, lane headers, and trash rows drag as NSItemProvider sessions
(two exported UTTypes, JSON payload in flatten order, plain-text titles as
the secondary representation) — replacing m4's custom lane-reorder gesture
and trash drag-out wholesale; the app-wide DragSession carries the members,
the frozen dragged sizes, the live proposal, and the effective operation.
- Three drop delegates (lane masonry, strip, window fallback), each accepting
both types and routing internally per the single-target-dispatch rule; the
cursor is the physical mouse converted to strip space; proposals come from
DropSlotMath with hysteresis threaded through, and the lane-strip proposal
clamps in front of the shown trash.
- Locality picks the default — move within a board, copy across, the badge
tracking live; ⌥ forces copy (ignored on within-board lane drags), ⌘
forces move; trash rows restore within their board (positional), copy out
across boards by default, ⌘ forcing the true restore-move.
- N contiguous shadows with reflow keyed on the proposal; the
committed-overlay hold renders the dropped arrangement until the reload
echo lands (1.5 s dissolution deadline for refused writes); the
re-grounding trio: geometry re-derives per render, proposals re-validate
by liveness at release, an emptied drag cancels itself.
- Edge autoscroll (ticking driver over DragAutoScrollMath, re-targeting per
step), the mouse-up-gated late-event cleanup, and the polling watchdog —
the pathfinder's lifecycle traps, ported.
- Store: moveLanes and multi-card restoreByDrag join the one-bracket drop
commits.
784 unit tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
BoardRecord carries icon/iconColor; recordOpen/recordClose stamp them with the
display name, and a displayStateDelegate on the store (wired in BoardWindowHost
beside onFrameChanged) syncs all three through BoardRegistry.syncDisplayState on
every successful reload — welcome rows now wear the board's own icon and follow
in-app renames live. recordOpen now runs before the load with the folder name as
a brand-new record's provisional display name, so a first open that fails
fail-fast still lands in recents carrying the failure row-level (02's rule); an
existing record's cached name survives a failing retry, and the welcome fallback
list remains only for failures naming no record at all.
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
The board's fixed grammar keys and the menu-backed chords of
04-interactions.md § Keyboard, per the Command Nexus inventory:
- Spatial arrow navigation (NavigationMath.nearest over the marquee
registry's frames — one geometry source), walking across interior
masonry columns, lanes, and into the shown trash; ⇧-arrows extend via
the same range function as ⇧-click and go inert at the liveness and
kind boundaries; ⌥-jumps with the ⌥↑ lane-domain escalation and ↓
descent; the empty selection seeds at the first lane's first card;
selection scrolls into view.
- selectionHead — the navigation cursor beside the anchor, set by every
click, moved by every arrow, dropped by the reload vanish rule.
- Board ▸ Open Card ⌘↩ (the one command enabled mid-edit: commits the
placeholder or rename and opens), Move Up/Move Down ⌥⌘↑/⌥⌘↓
(within-lane sort, gather-then-step, rank-permuting writes in one
bracket), Move Left/Move Right ⌘←/⌘→ (sole lane, one slot, never the
trash) — all validating and acting off one shared answer.
- Delete now selects the Finder-style successor sibling from the
pre-write snapshot, so repeated ⌫ walks down a lane; external
vanishing still only shrinks the selection.
- handleReturn rejects modified Returns; the trash column renders
eagerly so every row stays registered for navigation and the marquee.
686 unit tests (27 new).
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The welcome window becomes the real thing: Xcode-style, hidden title
bar with background drag, branding and actions left, recents right —
rows carrying the board symbol, name, location, and the registry's
cached lane/card counts (stamped at close, never a scan at welcome
time), sorted by last opened. Launch failures surface row-level per
02: a failure joins its recents row as a warning caption, an
unresolvable bookmark renders unavailable with Forget its one
affordance, and only a failure with no row to carry it falls back to
a compact list; a board opening again heals its row. New Board
(Opt-Cmd-N) opens the Pages-style template chooser — shipped with
the single Basic template and the m9 seams marked — flowing through
the save panel into createBoard/createLane and straight into a board
window. Open Recent gains its submenu with Clear Menu (byte-identical
to forgetting every row, pinned by test), and File > Duplicate forks
the frontmost board to a Finder-style copy sibling: pending work
flushes first through the close flush's step two alone (sessions stay
open — 09's stated exception), every GUID and tombstone carries (the
whole-board carve-out from copies-remint), and the copy opens in its
own window while the original stays put. 36 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
Deletion becomes a two-stage, Finder-style story. File > Delete and
plain Backspace tombstone the live selection; View > Show Trash (no
chord — Shift-Cmd-T stays with the system's tab bar) reveals the
quasi-lane: trailing, one fixed width unit consumed only while shown,
hatched dimmed header, count badge, no new-card button, exempt from
resize and reorder alike. Its contents are a pure view over the
snapshot — the deterministic sort (deleted newest first, folder-name
ties, unparseable stamps oldest) interleaves card rows with a
tombstoned lane's single entry, whose count names what Put Back
returns; the ancestor walk is absolute, so an own-flag card beneath a
tombstoned lane has no row and recovery is deliberately two steps.
Put Back twins Delete on Cmd-Backspace with validation enabling
exactly one; restore fidelity is byte-perfect because nothing ever
moved. Delete Immediately confirms exactly where loss is real (every
board is mode-none today; the predicate names the git carve-out for
m7), Empty Trash always confirms with the true whole-board count,
and dragging a tombstoned card onto a live lane restores it there —
positional drops and cross-board locality arrive with m5's machinery.
The banner's delete phrasing drops "move to the trash" per the naming
constraint: board deletion says Delete, "Move to Trash" stays
reserved for the system Trash. 47 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The window-title widget arrives as a leading titlebar accessory — a
quiet chevron on board windows only, installed and removed by the
window controller's own attach lifecycle — anchoring the one
board-level surface as a transient popover (the board window
deliberately grows no toolbar item for it). Inside: board rename
editing frontmatter title only (the folder is never renamed; an empty
commit removes the key and the window title falls back to the folder
name), the embedded shared style editor permanently targeting the
board, and the labeled Git section that this milestone only reserves
— a mode-none explanation and a disabled stub where m7's add-git,
branch, remote, and authentication controls land. Cmd-I (File >
Board Info) toggles it per window through a focused scene value,
kept apart from board-scoped transient state since a titlebar
popover belongs to one window, not to the board. Escape reverts a
dirty rename field and falls through to dismiss otherwise; a foreign
rename resyncs the field only while unfocused. 12 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
One style-editor component, anchor-agnostic: a background grid (None
well plus the 12 palette colors) and a curated symbol grid (the
pathfinder's five-dozen set, leading well removing the icon key for
the level default), selection-aware across cards, lanes, and the
board itself. Batch edits compute per-dimension state — uniform,
mixed (no well selected), or an off-palette value labeled verbatim
outside the grids — and choosing a well applies to the whole target
set as one write bracket, skipping no-ops per field. The popover
tracks its target set live per the freshly ratified rule: targets
re-resolve by UUID on every reload, a vanished target leaves the set,
an emptied set dismisses the editor, and nothing ever silently
retargets to the board. Anchors landing now: Board > Style
(Opt-Cmd-S) and the card/lane context menus, which also carry the
quick-style recents row (app-wide, persisted, capped at six, None
never recorded) and the lane's width control twinning the menu
chords. The styling system's other two renders arrive with it: a
lane's background paints the C7 top-edge band, the board's paints
the window content background — malformed values paint nothing and
stay byte-identical on disk. 31 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The lane title bar becomes real: leading SF Symbol (hand-written names
render leniently, unknown ones fall back to the level default), title
or secondary untitled placeholder, a quiet count badge that counts
exactly the cards the body renders (so the m5 search filter is
followed by construction), and a new-card button. The whole bar is
the reorder drag surface — no grip — with click-vs-movement splitting
select from drag; a pure proposal function maps the drag to an
insertion index and release commits through the Writer's same-parent
degenerate reorder, compacting and retrying when midpoint precision
runs out. Clicking never edits: inline rename is Return on the sole
selected card or Board > Rename for either kind, a third transient
editor beside the placeholder that tracks its target by UUID, commits
on focus loss, discards silently when the target vanishes, and
removes the title key on an empty commit. The new-card placeholder
renders at last — the settled Cmd-N target rule (pure, tested) files
it after the anchor card, at a selected lane's bottom, or into the
last-active lane; Return commits and re-selects the lane, Cmd-Return
also opens the card window, and a failed create discards the overlay.
New Card / New Lane / Rename land in the menus with focused-editor
and read-only validation; rename gets its own WriteOperation case in
the banner vocabulary. 59 new tests.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The lane strip replaces the placeholder board: window width divides
across the lanes' width units (no horizontal scroll, no minimum width,
degenerate compression accepted), a lane of n units flowing its cards
into n round-robin masonry columns via a measurement-cached Layout.
Width has two deliberately opposite controls, both landing here: the
right-edge drag (ported verbatim from the pathfinder's ColumnResize)
freezes the 1x standard at drag start, snaps between integer widths
with the asymmetric shadow-leads tick and 10pt re-entry, grows the
window one standard width per snap so siblings keep their exact
pixels, and rubber-bands at the screen's visible frame — uncapped
otherwise; the Increase/Decrease Lane Width items (new Board menu,
Cmd-Opt-arrows) are the stepper's keyboard face and re-divide the
existing window width instead, never touching the window. Width
changes write through the new .resize WriteOperation ("Couldn't
resize…" in the banner vocabulary, which grows with the surfaces by
design); malformed width values render as one unit and stay untouched
on disk. 27 new tests port the pathfinder's resize-math suite onto
the uncapped range and pin the write path's fidelity.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
Four scenes (welcome, restore bootstrap, board group, card group) with
system restoration disabled in favor of the registry's open-now flags:
set when a window actually opens, cleared only on user close, so quit —
and crash — leave exactly the restoration set behind. AppModel joins
windows to sessions (shared store, registry record, card refs, held
security scope); CloseFlushCoordinator pins 02's strict close order as
a seam-injected machine (card sessions end, windows drain, store
flushes, record stamps, teardown) with named slots where m6/m7 flushes
land. HostedWindowController proxies — never replaces — SwiftUI's
window delegate to intercept windowShouldClose for the flush, report
frames, and place saved frames onto live screens. Card windows are
(board path, case-folded card id) values: reopen focuses, and a
snapshot-pure fate function dismisses on delete, tombstone, tombstoned
lane, or cross-board move.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY