Commit Graph
288 Commits
Author SHA1 Message Date
rzen 3c64e5ae68 The skip consent rides the session — reloads never relitigate a standing decision
Gap #7 blessed into 01 ▸ Malformed input ▸ Skip: the store retains the
open's skip set and every live reload's walk carries it, so a
still-broken skipped file never re-trips the reload-breakage banner. The
skip names a defect at a path, not the file — a fixed file rejoins
mid-session on its own, and a new defect at an unconsented path is
ordinary reload breakage. The notice stays the open's one-time residue;
consent lapses at close, and the next open meets the surface again.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 20:36:15 -04:00
rzen bb936068db The environmental carve-out — no walk to collect from, the welcome row answers
Gap #6 ratified into 01 ▸ Malformed input ▸ The decision surface: a root
that is gone/unreadable or is a file throws immediately as a single-defect
failure — nothing to walk, nothing a surface could repair or reveal — so
it retires to the welcome failure row on every open, attended or not.
Judged per failure, not per defect (the 31fee00 refinement, ratified): an
environmental defect among repairable ones still shows the surface, and
the vocabulary keeps the unreachable-root seat for the re-walk that meets
one under a standing surface.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 20:26:24 -04:00
rzen 0d8ecdb78b An unopenable repository fails loudly — the standing row, the paused surface, the honest heal
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 18:44:12 -04:00
rzen 52df210284 The fifth fail-fast class gets its row — a garbled schema is evidence, never a mint target
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 18:11:59 -04:00
rzen 26239200ea Denial is not absence — detection learns the unverifiable answer 06 ruled for it
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 17:53:23 -04:00
rzen 503ec4872c Window-of-origin banner hosting reaffirmed — the board strip was an interim, naming survives it
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 17:28:51 -04:00
rzen ea15d1ac74 Every comment-trash purge kneels to the ownership gate — the container-whole retirement retires
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 17:10:09 -04:00
rzen a60d97689e The identity writer appends as 06 promised — clearing is the one sanctioned edit
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-06 16:57:20 -04:00
rzen c60553f17f An alignment tick when the drop proposal lands somewhere new
NSHapticFeedbackManager .alignment at propose()'s change edge — the
one funnel every retarget commits through, whose existing guards
(value-equal refusal, hold freeze) already make firings per-landing-
spot rather than per-pixel; withdrawal stays silent (losing a target
is not an alignment), and the external-file mode gets nothing. Force
Touch hardware only, silent no-op elsewhere; performanceTime .default
syncs the tick to the reflow the change triggers. The performer rides
an injectable seam beside holdTimeout for the same reason it has one.

Drag-perf card 11b85111 — the cluster's last.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-03 13:41:06 -04:00
rzen 43e6ad229e The drag replicas cast the shadow the pickup lift already promised
DragReplicaStyle spells the pathfinder's constant ambient shadow once
(black 16%, radius 2.5, y 1.5) and all three replica faces — card,
lane, trashed lane — wear it via one modifier; the ghost fans compose
from the same faces so every copy inherits it, dimmed by its own
opacity. 03 § Motion's "pickup lift (scale + shadow)" supplies the
scale from the system session but no shadow on the preview image — the
replica is the only place it can come from. BoardMetrics.replicaPadding
was already written to keep exactly this shadow from clipping and was
simply unused until now.

Drag-perf card 24397310.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-03 13:33:52 -04:00
rzen 1ffb64913b A stationary modifier flip answers at the keystroke — the third client of the one shared retarget
Modifiers were sampled only inside resolveOperation, reached from
dropUpdated and the commit — and drop callbacks arrive only while the
mouse moves, so ⌥ pressed against a still pointer changed nothing
until the next twitch. DragSession now arms a local .flagsChanged
watch for exactly the session's lifetime (begin arms, end stops, the
watchdog guarantees end; ⇧/⌃/caps don't count, a settled hold
swallows) and publishes modifierGeneration; the three retargets record
which window's surface resolved the proposal (RetargetOrigin — weak
registry identity, never board root, so cross-board and two-windows-
one-board both answer correctly), and the hovered window replays that
same retarget with the operation re-resolved FIRST, since the index
space is a function of it. The monitor is injectable — the real one
needs a live event stream no test bundle has.

Drag-perf card d491e7d3.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-03 13:24:53 -04:00
rzen 1fc8aaa249 Autoscroll ticks on the display's own clock — the 16ms sleeping loop retires
DragAutoScroller.run() consumes an AsyncStream of CADisplayLink
targetTimestamps from NSView.displayLink(target:selector:) on the
lane's existing anchor view (which was already the right NSView — no
new plumbing). The stream is the lifetime: cancellation ends the
iteration, termination invalidates the link, the link releases its
target. DragAutoScrollClock keeps the retired loop's two non-trivial
rules — first tick scrolls nothing, no tick integrates past 50ms (a
paused link resumes with the whole gap as its delta) — and the math is
frame-rate independent by test: one second at the edge moves 800.0
points at 120Hz and at 60Hz alike. Run-loop mode .common is
load-bearing: a drag runs AppKit's event-tracking mode.

Drag-perf card 2e08fd31.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 20:22:42 -04:00
rzen 0218ae4c21 Drag instrumentation — render counters, hot-path signposts, and the invariants that prove the gates
BoardRenderMetrics (DEBUG-only, the pathfinder's counter bag plus a
strip-body discriminator that tells a failed gate from a direct
Observation invalidation) counted at BoardView/LaneView/CardFaceView/
TrashLaneView bodies and MasonryLayout's callbacks. DragSignposts wraps
dropUpdated, retargetCards, commitDrop, and the commit-to-covering-
snapshot release pause; input latency reports honestly against
NSApp.currentEvent's mach base or labels itself base=none — no event
timestamp rides the drop path. BoardRenderPerformanceTests hosts the
real BoardView off-screen: a value-equal reload runs zero lane and zero
card bodies, a one-card edit repaints one card of 180. The lane-body
budget is <= laneCount with the headerInk chain documented and a
two-way tripwire that fails when the fix lands. Methodology in
RENDER-INSTRUMENTATION.md. Release build proves it all compiles out.

Drag-perf card a450ad09.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 20:04:01 -04:00
rzen 409f430813 The lane-resize release gets its own hold — the strip stops two-stepping through the stale snapshot
LaneWidthHold: after the release the session's frozen standard and the
WRITTEN unit count keep governing the strip until a landed snapshot
carries that width back — dividing the already-grown window by the
stale unit total in between was the visible two-step. A deliberately
separate type from CommittedHold: that hold stands in for an
arrangement and any landing retires it; this one stands in for a value
and only a landing that carries it will do. Echo reads through
LaneLayoutMath.displayUnits so the width-1 key-removal case compares
right; the watch rides landedReloads so a value-equal echo still
answers; width writes now return whether bytes reached disk so a
refused or no-op write dissolves the hold instead of arming it; the
1500ms timeout family covers the rest.

Drag-perf card 231e3693.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 19:33:33 -04:00
rzen f6105d4389 The marquee registry stops being observable — reflow writes cost a dictionary store and nothing else
Drops @Observable from MarqueeTargetRegistry (MarqueeSession keeps it —
its rect genuinely renders the band). The audit found no body read
anywhere: the begin guard and sample loop read from inside the drag
gesture, the arrows from inside a key handler, so nothing ever needed
invalidating when a frame moved — while every make-room reflow had each
sliding face re-firing onGeometryChange per display frame, each write
paying Observation registrar bookkeeping on top of the reflow's own
render work (the confirmed A/B culprit of 2026-07-31). Write-gating on
drag-active was rejected: a suppressed write never replays, leaving the
band and arrows navigating stale rectangles. A tripwire test pins the
registry against anyone re-adding the macro.

Drag-perf confirmed culprit, card eb7b75ce.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 19:14:11 -04:00
rzen a51ad750ad The resting layout is built once per snapshot — retargets stop rebuilding it per mouse sample
RestingLayoutCache (session-scoped, @ObservationIgnored on DragSession,
cleared at begin and end) holds each lane's resting layout as ids +
heights — never [Card] — keyed on the hovered board's applied
snapshotGeneration, the registry's new heightsGeneration, the lane, and
the operation-following hidden set. The grid stays event-time on purpose
(autoscroll moves a lane's origin with the snapshot standing still), so
re-grounding rule 1 holds exactly: nothing survives a reload, only the
per-sample repetition goes. The steady-state cost of a hover is now the
containment scan plus four stores and a divide.

Drag-perf suspect #3, card b9f48fd1.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 18:56:08 -04:00
rzen 84f909a720 Proposal changes stop rebuilding the whole board — lanes and card faces gate on their values
LaneView and CardFaceView become Equatable and are instantiated through
.equatable(): the strip's body re-runs on every drop-proposal change, and
without the gates that rebuilt every lane and every card face on every
cursor move of a drag. The == compares value inputs and window-lived
collaborator identities; the closures BoardView rebuilds each pass are
deliberately excluded (BoardDropContext.isEquivalent / MarqueeControl.
isEquivalent / CardFaceRole.isEquivalent own that judgment). Observation
reads inside the bodies still self-invalidate — the lane under the drag
keeps re-running; the other lanes stop.

Ports the pathfinder's CardView/ColumnView gating pattern (drag-perf
suspect #2, card cbb6e476).

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 18:28:33 -04:00
rzen ef423bb9d2 Every board root keys once at its store — the hover path stops walking the filesystem
`DragLocality.isSameBoard` resolved symlinks on both URLs, which stats every
path component. It ran five-plus times per `dropUpdated` and again per lane per
body evaluation through `renderedCards` → `hiddenMembers` — order of 50–100
stat calls per mouse-move, and worse on iCloud-backed paths. A pickup stall
sample had it on ~47 of 943 stacks.

`BoardRootKey` mints that canonical spelling once, where the root is, and every
locality comparison downstream is an `==` on two strings. The drag carriers hold
keys rather than URLs, so re-deriving under the cursor is no longer expressible.
`rootURL` keeps the user's spelling — the folder name is the display-name
fallback, and canonicalizing there would visibly rename a board opened through
a pin. The key follows the folder through `relocate(to:)`: a key frozen at open
would collide with a new board opened at the vacated path, and two boards
comparing as one is a cross-board drag silently behaving as a move.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 17:55:48 -04:00
rzen 8a8ec4dfd1 The two-homes width rule becomes focused-width parity
Ruled 2026-08-01: the width the user types in is the em-derived figure in
both of ⌘F's homes; the toolbar field's resting width is AppKit's, outside
the invariant — the transient strip never rests, so it has no collapsed
state to mirror. 03 also records the shipped grow-on-focus behavior beside
the default-item sentence.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 16:54:37 -04:00
rzen 8345378972 The board settings sheet — setup leaves the popover for a home a stray click can't dismiss
The popover/sheet split lands: a board-scoped, titled, sectioned sheet on
the board window hosts everything setup-shaped, opened from the popover's
Board Settings… row and the new Board ▸ Board Settings… menu row. The three
existing setup controls relocate — add-git (whose noteFormVisible lines now
make the sheet the form-anchored failure surface), branch creation (a
standing field; create-and-switch runs the identical settle sequence), and
the commit-identity fields (the 2s visibility-scoped poll rides with them).
The popover keeps the daily face and its postures; its Pro/mode-none
section becomes header + door (.addGit renamed .noRepository). Availability
is derived from the section inventory (Pro + mode none or git), so pro-m2's
sections can't drift from the doors; the sheet's fields join the
caret-chord disable set. The audit suite pins what the free fixture can
reach; the sheet's own audit is manual until a tier override is ruled.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 12:07:12 -04:00
rzen 988a7245a3 Memoize the reload parse and short-circuit value-equal snapshots
The loader gains a ParseMemo — the previous walk's parsed documents keyed
by root-relative path, trusted on the git-index heuristic (mtime + size,
no hashing) and passed as an input so the loader stays stateless. A hit
skips exactly one file read; schema, order, coercions, dedupe, and every
directory listing run fresh, so memoized and cold walks are output-
identical (golden-corpus equivalence suite). Entries record only past the
schema gate, so a defect can never be answered from the memo.

The store skips the snapshot assignment wholesale when the fresh model is
value-equal — no @Observable churn, no render pass, no snapshotGeneration
bump — and a new landedReloads counter carries walk-completion for the
three consumers whose subject is the walk, not the snapshot: the card
window's comment thread, the comment search index, and the auto-committer's
covering gate (which now counts a completed walk as covering even when
nothing changed). Warnings and defects move on their own equality; failed
reloads bump neither counter. An injectable ParseCounter makes the
single-file-echo claim a test.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 11:38:25 -04:00
rzen 5e6417e749 The board search item becomes the stock NSSearchToolbarItem — the field grows on focus
The toolbar's search field now lives in AppKit's own NSSearchToolbarItem:
em(17) is the focused width (preferredWidthForSearchField), the resting
width is the item's own, and ⌘F becomes beginSearchInteraction — one call
that expands and focuses. Escape's empty-field exit collapses the field as
the keyboard leaves. resignsFirstResponderWithCancel is off so the staged
Escape survives the cancel button. The item ships its own live overflow row
and an above-.high visibility priority, so both custom-view workarounds
retire. The palette copy stays inert through an enablement round-trip the
item's searchField setter would otherwise overwrite.

The centering half of the card is blocked on a Design ruling (03 settles
"trailing") and is not built; the two-homes width question is filed.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 11:16:58 -04:00
rzen 31fee00c73 The decision surface — a refused open becomes a live repair, in place
Phase 3 of the decision surface, completing the card (01 ▸ Malformed
input, settled 2026-07-31). An attended open's fail-fast walk transforms
the loading window's content into one aggregated surface — never a
sheet, never a chain: defects grouped by class, each class stated once
with its files listed (Reveal in Finder + Open in Editor per row), a
class-level default preselected, per-item override behind a disclosure.
Only honest choices: YAML and malformed-schema get Editor + Re-check
(Skip below the root); newer-than-app gets Skip alone and blocks the
board at the root; the two root repairs — minted index, schema: 1 stamp
— are defaults. Repair and Open applies fixes in one store-less write
bracket and re-walks: clean proceeds, remainder re-aggregates into the
same surface. Cancel and ⌘W retire to welcome's row; restored opens
never see the surface at all (OpenOrigin rides the PendingOpen carrier).

Skips are per-open consent that rides the session — the store retains
the skip set and every reload passes it — and the opened board posts a
warning-tone notice naming what was left out, each item's Reveal riding
the banner strip's new reveal control. On Pro boards the repair bracket
binds its own EchoLedger, heal-marks everything, and the store adopts it
before the committer starts, so repairs land as one separate commit
authored Lanework Integrity — pinned end to end. Also fixed en route: a
retired loading window left its close interception installed and
returned false from windowShouldClose forever, blocking quit.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 10:52:02 -04:00
rzen 0933ac1b01 Every open passes through a real loading window — the walk moves off the main actor
Phase 2 of the decision surface: the pre-snapshot loading state ruled
2026-07-29 (02 § Launch and window lifecycle), built. The board window
appears immediately at its saved frame, titled with the registry
record's cached name, its content a centered spinner behind an
injectable ~200ms grace — no skeletons, and the first snapshot snaps in
place. The tree walk runs off-main via BoardStoreRegistry.acquireOffMain
(per-board single-flight keyed by file identity — concurrent opens of
one root share a walk, restoration of many boards is genuinely
parallel), landing in BoardStore's new designated init(rootURL:loaded:);
the self-walking init survives as a convenience for its ~470 callers.

⌘W during the walk is real: configureWindow split into a loading half
(frame restore, frame tracking, close interception — installed before
the walk) and a store half (toolbar, widget, hideTitle, undo — installed
at the snap), and the walk lives in an explicitly held BoardOpenWalk so
the user's close and SwiftUI's teardown end in one cancel().
Cancellation is discard-on-completion: nil from acquireOffMain means
nothing was built, nothing retained, and no open-now flag was ever set.
Failure keeps today's sequence exactly: record the launch failure,
welcome's row carries it, the window retires.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 09:53:26 -04:00
rzen ba1726fa77 The loader collects every fail-fast defect and honors per-open skips
Phase 1 of the decision surface (01 ▸ Malformed input, settled
2026-07-31): BoardLoadFailure aggregates the walk's defects in walk
order — stop-at-first retires. Environmental failures (unreadable root,
not-a-directory) stay immediate single-defect throws: there is no walk
to collect from. A defective root index is recorded and the walk
continues into the children (nothing in the walk consults the parsed
root document — verified); a defective lane, card, or trash-entry index
records and skips its subtree, Re-check's whole-walk re-aggregation
being the designed loop for what hides beneath. load(skipping:) is the
per-open skip channel: a skipped path's item is omitted from the model
and surfaces as LoadWarning.userSkipped; root paths are unskippable by
construction. The reload-breakage banner carries the aggregate ("…and
N more"), single-defect sentences byte-identical to before. Two new
multi-defect fixture boards; suite 2591 green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 09:12:49 -04:00
rzen 94e60cd444 The whole board-name area opens the popover — the chevron alone was the trigger
The titlebar widget grows from a 20×18 chevron into one button saying
the board's name and, on a git-mode Pro board, its branch — click
anywhere across it and the popover opens as before, anchored to the
widget. BoardInfoTitlebarSummary is the pure seam for both strings
(title falls back to the folder name per 01's naming rule; branch only
under pro + git mode, live off the observable HistoryStore.branch).
Board windows now hide the system title display through the same
hideTitle slot card windows adopted — the widget says the name, so the
chrome would only repeat it — while navigationTitle keeps feeding
window.title to the Window menu, Exposé, VoiceOver and restoration.

The widget also refreshes the branch eagerly at appearance: it used to
populate only once the popover had been opened, which would have left
the new branch line empty on a freshly opened board.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 08:28:53 -04:00
rzen 6f2e0d15da Card windows drop the title from the title bar — the body already says it
The card's name renders as part of the card body, so the chrome said it
twice. HostedWindowController gains a titleVisibility slot on the same
retained-slot pattern as the accessory and toolbar (reapplied at attach,
so the macOS 26 provisional-window swap can't lose it); CardWindowHost
opts in beside installToolbar, board windows keep AppKit's default.
window.title itself is untouched — navigationTitle still feeds the
Window menu, Exposé, VoiceOver and restoration.

Known consequence, probed empirically: under .hidden AppKit collapses
title and subtitle into one hidden field, so the "board › lane"
navigationSubtitle disappears with it — and the details sidebar carries
no placement rows, so a card window currently shows its home nowhere.
Filed on the Redesign board to bless or relocate; 05-card-window.md's
subtitle sentence needs reconciling either way.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 08:10:31 -04:00
rzen 8206a78ecc Redraw a copy's originals at the source — the resting layout follows the operation
The modifier flip (⌥ copy / ⌘ move) now reflows the source board once:
a copy re-admits the dragged originals into the resting layout, standing
dimmed in place, and a move lifts them out as before. This retires
DRAG-REORDER.md's operation-blind carve-out and makes 04-interactions.md's
"originals stay" true in flight, not just at echo. The one seam is
DragSession.hiddenMembers reading the observed operation.

Consequences carried honestly: copyCards now takes its index in the
lane's full rendered space (the zones counted the originals, so the
geometry's number is the writer's number — the old neighbour remap is
deleted); resolveOperation freezes under the committed hold exactly as
propose does, fixing a real bug where a settled within-board ⌥-copy drew
the move arrangement until the echo and visibly re-shuffled. Stationary
flips still wait for the next dropUpdated (their own Backlog card).

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 08:07:54 -04:00
rzen adf4fc5973 README: optional below-root keys, the gitignore noise gate, and the true fixture count
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 07:45:02 -04:00
rzen 153d12558c Changelog: optional frontmatter keys and the seeded .gitignore join the 2.0 block
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 07:44:35 -04:00
rzen 274ccd9ff5 Realign code with the 2026-07-31 findings-resolution rulings
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
2026-08-01 07:43:45 -04:00
rzen 16ef3779e8 Rule the session-commit await, switch-clears-stacks, and skip-purge decoupling
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 21:46:23 -04:00
rzen bc27a0cbd2 Bless the two-stack shape, the raw-apply omission, and name the coarse step
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 21:41:05 -04:00
rzen 01f1194a56 Gate the comment-trash sweep on live-step ownership
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 21:38:42 -04:00
rzen d076427ee0 Realign undo with the evening rulings — repo-nested and identity anchors
Repo-nested boards bind native undo in every tier (25d2513): the
no-undo case is gone, makeHistoryProvider answers git or native, and
the native path provably never touches the enclosing repository's
.git. Session undo steps anchor by card identity, never by path
(9119aa1): HistoryAnchor carries the card UUID (plus comment/draft
vocabulary) and apply-time validation resolves the current folder via
the same both-container walk writeCardBody uses — a board-side lane or
trash move no longer stales the coarse close step, while a genuine
field collision still skips it whole.

2448 tests in 423 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-07-31 21:19:42 -04:00
rzen 54951e92ef Changelog: window-session undo joins the 2.0 block
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-07-31 20:24:41 -04:00
rzen a381fac742 Make the card window the commit unit on Pro boards
Phase C of the two-level undo card: the committer stages around the
whole open card folder — comments included — so gestures in an open
window never land in interim commits; window close flushes the session
as one semantically-named commit ("Edit card 'X'" with the thread as
body bullets, "Mixed update — N changes to card 'X'" when events mix),
with the two-commit foreign/user split preserved and the
comments/.trash purge riding the same bracket. Comment gestures lose
their per-gesture commits structurally (they write inside the held
folder). Branch-switch settle releases every window's staging before
checkout and re-arms on resume.

Fixes two latent pro-m1 defects: the committer was composed without
the store's EchoLedger, so every production commit classified foreign
and was authored Lanework External; and interim flushes dropped
harvest receipts they had not spent, unvouching the session's own
writes at close. Also lands 06's mixed-subject re-ruling (the retired
"Update board" fallback) and phase B's two files missed by the
previous commit's pathspec.

2444 tests in 422 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-07-31 20:23:45 -04:00
rzen 9119aa1e9a Anchor session undo steps by card identity, not path
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 20:10:33 -04:00
rzen 25d2513ccc Repo-nested boards bind native undo in every tier
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 19:44:51 -04:00
rzen 71664dab02 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
2026-07-31 19:39:50 -04:00
rzen c0c741fe62 Bless the six 2026-07-31 minor findings
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 19:34:14 -04:00
rzen fecedab60d Split git setup out of the popover into a board settings sheet
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 19:22:45 -04:00
rzen 986347a95a Bless the reload parse memo and value-equal snapshot skip
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 19:15:51 -04:00
rzen cb87ea79c1 Gate relocation noise on a universally seeded .gitignore
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 19:06:14 -04:00
rzen 11981a2a99 Make order and schema optional below the board root
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 19:01:20 -04:00
rzen c1d4d7be7b Wishlist board export and reporting
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 18:55:31 -04:00
rzen 9a35b52261 Wishlist the board/lane/card nature idea
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 18:55:07 -04:00
rzen c6b5f6b691 Wishlist the AI-aided design template set
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 18:53:53 -04:00
rzen 2e4dde5655 Bind the undo provider to the board, not the tier
The 2026-07-31 re-ruling: gitless boards bind the native stack in every
tier — a Pro upgrade no longer removes undo from mode-none boards — and
Pro git boards bind the git provider; repo-nested stays the no-undo
case under Pro, while the free tier (which never runs detection) binds
native there too, per 12's inert posture. Add-git now swaps a live
native substrate mid-session: the in-flight stack is cleared with the
discarded provider, the git trail seeds from the root commit, and the
same BoardUndoManager instance keeps nil-target menu validation fresh.

2405 tests in 413 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-07-31 18:53:38 -04:00
rzen 3bd6187b94 Settle the fail-fast decision surface design
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-31 18:44:41 -04:00