Edit ▸ Undo/Redo become the app's own replaced rows and the board toolbar
pair takes explicit targets, both reading the focused session's
BoardUndoManager through FocusedValues.undoStack (board windows publish the
session's manager, card windows their own) — the nil-target route died with
the SwiftUI window latch, 13-native-undo.md ▸ Rules ▸ command surface,
re-ruled 2026-08-08. The rows enact the routing predicate themselves: text
focus routes ⌘Z to the first responder's own manager, title and enablement
included, re-derived at fire time with a beep for the stale window.
NativeHistoryProvider turns @Observable so both surfaces re-derive on stack
changes; a checkpoint-notification ticker covers plain text managers.
.responderAction leaves ToolbarItemSpec with its only user;
windowWillReturnUndoManager stays wired for AppKit's own asks.
Live-probed on the fixture board (21/21): the row retitles to "Undo Add
Lane" and crosses via real ⌘Z key events, ⇧⌘Z redoes via a window-server
chord, the toolbar pair validates and fires, search-field and body-editor
⌘Z stay text undo with board stacks untouched, and a card window crosses
its own stack with no fall-through. 2698 unit tests green.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Step 7 of strategy/01-git-excision.md, the companions. The agent guide bumps to v11: the Git section teaches repo-resident etiquette alone (stage only your own paths, commit your own changes, leave app-maintained files to the app) — existing boards heal to the new text on next open. README re-anchors: the four git feature bullets out, tiers say the complete Mac experience is free, and one bullet states the format's git-friendliness promise. The changelog drops the never-shipped git entries. DESIGN re-rules: 06 retired with Undo routing migrated to 13 (now the sole substrate's doc, seam kept open), 07 retired as written pending the ops-service workstream, 14 retired as superseded record, 12 carries the second pivot note, the index reflects all of it; the charter gets a pointer note (the anchors' full re-ruling stays with the user). InertGitTests renames to GitAgnosticStorageTests — the excision restores its original claim app-wide. And the sweep: ~70 comment sites across 36 files adjudicated against the keeper list, every present-tense description of the excised machinery made past tense or repointed, keepers untouched. 2,707 tests green.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
HistoryStore.compose(boardRoot📒) returns non-optional and runs for
every session — the nil the gate produced was the only nil it ever had.
makeHistoryProvider is a one-axis decision: git-mode boards bind the git
provider, everything else native, in every tier; Session.tier stays
recorded, dormant. BoardGitSection shrinks to the four mode postures
(.absent and .proPointer die, BoardGitNote and the .git probe with them);
every board carries all three popover tabs (BoardInfoTab.available
retired); the titlebar branch shows on any git-mode board; the settings
sheet and card History section stop reading tier. InertGitTests is
repurposed as UntouchedGitTests — the file layer still never opens .git,
now load-bearing for mode-none boards. The accessibility audit reaches the
settings sheet at last: the fixture board hosts it in every tier, so the
free-fixture disabled-row test becomes an open-and-audit test.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The full bullet list from Implementation card bf080d9a — both ruling
batches, including the three appended mid-session by 16ef377:
- Restore subjects compose the inverse, never nest: crossing "Undo: S"
emits "Redo: S" and vice versa; parity, not stack depth, reads a
legacy double prefix (GitHistoryProvider.restoreSubject).
- Git-operation failures join the one-shot failure banner tier:
BannerCenter.GitFailureBanner (undo/redo/branchSwitch/addGit), error
tone at failure rank merged with write one-shots by recency; the
postLoss compromise is retired at both AppModel wirings.
- order/schema optional below the board root: append-at-end reading
(ordered siblings first, folder-name tie-break among the order-less),
schema reads 1, both coerce-tier logged; the root keeps its
requirements. Ranks.resolvedOrders materializes finite ranks so
models and placement math stay untouched; first Writer rewrite
stamps a real rank on touch, placement against an order-less sibling
stamps that sibling inline in the same bracket. Agent guide v10
teaches optional keys and zero-read filing. Hostile-YAML order
shapes become coercion tests; Fixtures/Valid/optional-keys.kanban
replaces the four retired Malformed boards.
- .gitignore is the relocation-heal noise gate: GitignoreRules pure
matcher (standard semantics, board-root file only), loader consults
it once per walk so matched loose files keep the stray posture;
seeded (.DS_Store + .*.lanework-*) at board creation and template
instantiation, healed in when missing at open — repo-nested
included; empty file honored, existing files never edited; the
committer's obedience via libgit2 status is pinned by test.
- Comments crash-residue sweep gates on step ownership: HistoryStep
derives backing from its own undo expectations, backedContent unions
both stacks, the sweep purges per-entry only what no live step owns.
- Skip-purge decoupled (16ef377): a stale-skipped coarse step strands
whole in NativeHistoryProvider.strandedSteps — still backing, retired
only at session end; clean exits purge as before.
- Coarse close step named "Changes to '<card>'"; the fine body-edit
wording never leaks onto the board menu.
- Branch-switch settle clears every open card window's fine stack on
Save All and Discard alike; the empty fold registers no coarse step.
- Close flush awaits its covering snapshot (quiesce + one generation
bump, 1s bound), and an explicit flush now queues behind an
in-flight one instead of skipping — the audit-caught interleaving
could lose a close flush permanently when the debounce fired inside
the close sequence; regression tests force both races.
- Commit comment bullets sort chronologically by created, not UUID.
- The production-unwired CardBodyEditSession.editSessionDidChange seam
is deleted with its seam-only tests.
- Composition-root pins: beginSession composes the committer with the
store's own EchoLedger and binds the announcer (the miswire class).
- Deliberate 06 conformance pass over every 2026-07-31-tagged
sentence: fixed Change-custom-key subjects (the retired named
generic was the only producer), the unbuilt Replace attachment
vocabulary, heal commits now authored Lanework Integrity, the config
reader scopes identity to plain [user] sections, add-git re-runs
detection at create (a stale mode-none could initialize inside the
user's repo), and add-git failures answer at the form or the banner.
Structural residue filed on the Redesign board.
2554 tests / 439 suites green.
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
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
Phase 1 of the 2026-07-30 one-app pivot (DESIGN 0bec9a6, card c3a3ddd5):
the KanbanPro target, LaneworkPro scheme, KanbanProTests module-alias
bundle, KanbanPro/ source root and scripts/verify-editions.sh retire
wholesale. project.yml reads as a single-target file again (anchors
inlined, header rewritten in tier vocabulary).
The edition twins merge: EditionTypes -> PasteboardTypes (one
UTType(exportedAs:) home — the one app owns the family types),
EditionAbout -> AboutBox (the quiet Pro signpost survives as the About
box's one line; "…in Settings" deferred until the StoreKit phase gives
it somewhere to point). InertGitTests drops its Base prefix — the
inert-.git posture is unconditional app behavior, unsubscribed and
lapsed being one state.
Entitlements gain com.apple.security.network.client, declared now and
dormant until Pro's remotes use it; no keychain access group. The App
Group key deliberately stays — it goes with AppGroup.swift in phase 2,
since pulling it first would silently drop the app into the fallback
container. README/RELEASE.md build-and-pipeline prose updated to the
one-record world; the subscription story lands with phase 3.
1893 tests in 322 suites green.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
Every crossing validates its expectations before writing: each step
carries per-item HistoryExpectations — folder, effective ancestor-walked
liveness, and exactly the fields the gesture set — and a mismatch pops
the step, posts the signpost ('Undo skipped — Fix login changed outside
Lanework'), and falls through to the next. Validation reads disk, not
the in-memory snapshot: the snapshot is by construction one reload
behind every app write, so a rapid second undo would false-skip against
the pre-state — disk is what current can honestly mean at press time.
Stale and failed part ways: a stale step is one the board moved past,
so dropping it loses nothing; a failed one is refused by a usually
momentary condition, so it stays put and the crossing stops with only
performWrite's own error row — which forced the provider off
NSUndoManager onto two plain arrays, since a popped group cannot be put
back. The read-only lock disables Undo/Redo through the adapter while
the stack survives to resume on clear. Delete and restore validate
presence alone — a machine timestamp is not a decision — and a
malformed field matches nothing, since it is a shape the app never
writes.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
The provider seam 12 promised: HistoryProviding speaks 13's vocabulary
— register a HistoryStep (bare 06 phrase plus undo/redo closures
returning applied or skipped), canUndo/canRedo, action names, clear —
and no UndoManager type appears anywhere in it, proven by a fake that
satisfies the seam with counters. The base provider wraps a private
UndoManager with groupsByEvent off so coalescing stays the Writer call
site's decision; undo re-registers the reversed step from inside the
undo, which makes a stale-skipped step vanish for free and the
crossing loop fall through to the next. BoardUndoManager adapts the
protocol to the responder chain — a stackless UndoManager subclass
answering from the provider — so Pro's git provider inherits menu
enablement, dynamic titles, and the nil-target toolbar pair by binding
the protocol. One stack per board session, born in beginSession,
cleared in the close flush; every window over the board answers it
through windowWillReturnUndoManager. Headless probes shaped the
routing: a real NSTextView's own manager wins natively, but a field
editor's does not — BoardUndoRouting answers the per-window text
manager while any NSText is first responder, so a search-field typo
never crosses a board step.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY