.claude/agents/opus48.md: a general-purpose implementation agent pinned to
Claude Opus 4.8, the capacity fallback for complex coding tasks dispatched
from the main session. settings.local.json — the personal permission
grants — joins .gitignore rather than the history, per its name.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
DESIGN/explorations/board-backgrounds: working material, not a numbered
design doc — the app side of background images is settled, so the
deliverable is purely artwork. The model is base layer × filler layer,
swept across hue strategy, HSB family, filler shape, density, placement
and opacity; the gallery is one self-contained HTML generator, every
swatch procedural SVG off a seeded PRNG so swatch IDs stay stable
references across reloads. Status: first sweep published, awaiting review.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
strategy/: the release-and-distribution effort, sibling to research/ but
different in kind — research gathers evidence, strategy turns it into
positions and plans. The charter records the goal (bootstrapped
sustainability with base→Pro growth in the personal garden first, Teams a
deliberate second act), the two tier anchors, what is already ruled, the
decision surface decomposed into workstreams, and the tensions to resolve.
The positioning line is the inversion: the garden's walls are around the
experience, not the data — closed polished surface, open documented
substrate, the exact inverse of the mainstream pattern and the moat
captive-state competitors cannot cheaply adopt.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
research/: long-term competitive-landscape research as a living body of
work — the kanban market, the adjacent personal todo/task market, the
file-based/local-first niche, and the AI-agent + task-management
intersection, the two axes the bet actually lives on. The first pass
(2026-08) is deliberately broad: a synthesis overview, the mainstream and
native/indie/file-based segments, the todo-adjacent field, cross-cutting
trends and success factors, the agents-and-tasks frontier, and a schema
comparison against Signboard. The overview carries the narrow-pass agenda.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
DRAG-PERF-ANALYSIS.md: a full read of the drag/drop pipeline against
DRAG-REORDER.md's model and RENDER-INSTRUMENTATION.md's measurements,
prioritized and sequenced, nothing implemented yet. The headline: the
per-sample arithmetic is microseconds on any realistic board, and the felt
latency lives almost entirely in the release — a 200ms watcher debounce
plus a whole-strip render pass, with one honesty bug able to stretch a
failed drop into a 1.5-second freeze. P1 is a surgical FolderWatcher
expedite for the echo the bracket already owes (~180ms off every drop),
with the races analyzed benign.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
The swatch's padding goes asymmetric and user-tuned — a wider berth at the
sides than above and below, out of the swatch rather than the control's
size — so the colour reads as a bar in the field instead of wall-to-wall.
The field's fill moves to controlColor, the push-button neutral grey, whose
ring stayed legible where controlBackgroundColor's near-black dark-mode
reading drowned it; and the trigger square keeps its own two-point inset,
decoupled from the swatch's padding so the indicator stays a legible
square through any padding tweak the swatch takes.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
The whole-lane wash-and-solid-ring scaled its emphasis with the lane's
area: what read as a ring on a card read as a slab on a column. Resettled
as the system focus ring's vocabulary at selection strength — a hairline
accent edge with a soft accent glow bleeding outward, riding the stroke
shape rather than the translucent plate (a shadow behind it would bleed
through as murk), and no wash at all: nothing tints the cards' backdrop,
and lane colour stays the accent band's alone. Under Increase Contrast the
halo yields to the solid full-alpha ring — "strengthens borders" means
crisper, and a blur is the one thing a border cannot become — so the
setting sees exactly the indicator it always has; cards and trash rows are
card-scale and keep the solid ring at every contrast. One selection
vocabulary, pinned by Accommodations.drawsSelectionHalo and its test.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
"At drag start it replaces the item's original space" is now true from the
very first frame: begin takes the run's own resting slot as a seed, set in
the same transaction that lifts the run out. Without it the vacated gap
closed un-animated and sprang back open at the first dropUpdated — a
shuffle carrying no information. The seed bypasses propose deliberately (a
pickup is not a new landing spot, so no alignment tick), and the first real
sample's re-propose of the same slot is the early-out's ordinary silence. A
⌥-pickup seeds nothing — a copy's resting layout keeps the originals in
place, so there is no vacated space to hold. Card pickups compute the seed
through the same LaneView.rendered rules the retargets count in; lane
pickups seed their strip slot the same way. DRAG-REORDER.md's own-slot
paragraph records the rule, and SeededPickupTests pins all four corners.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
The measured defect: LaneView's empty-space double-click was a second
sequential .onTapGesture(count: 2) stacked over the single tap, and that
recogniser held every click in the lane — its own empty space and every
card face alike — hostage to the system double-click interval while it
disambiguated (~475 ms click-to-selection on a hosted board). The fix is
structural: the empty-space surfaces live on a background layer behind the
masonry, so a card's click never shares a gesture path with a lane
recogniser; one .onTapGesture branches on PointerClick.count (AppKit's own
clickCount, read the way ClickModifier reads the keyboard) — first click
selects, second creates, Finder's cadence with nothing to disambiguate; and
the layer carries a load-bearing empty .onDrag, because without a drag
source macOS holds primary clicks pending multi-click disambiguation
(measured: never fires alone, ~90 ms with one present). A measured viewport
floor makes each lane's blank space actually belong to the layer — a
ScrollView proposes nothing along its scroll axis, so only an explicit
minimum stretches the content — with the trash column as its twin, less the
padding that sits inside its scroll content. The template chooser's stacked
pair collapses to the same one-recogniser branch, and the attachment rows
move their double-click to a simultaneous gesture (instant there, because
the row's real .onDrag forces immediate delivery). PointerLatencyTests pins
the recovery with synthetic pointer events on a hosted board.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
View ▸ Zoom In / Zoom Out / Actual Size (⌘+ / ⌘− / ⌘0): 75%–200% in eight
rungs, app-wide and persisted (the Show Comments precedent) — a viewing
comfort, not a property of any one board. The level travels as
BoardZoomContext in the environment, injected on BoardView alone so the
banner strip, search bar, sheets and popovers stay at the system size; the
environment is also what carries it through CardFaceView's equality gate,
which compares nothing that moves with the level. Every BoardMetrics figure
follows zoom.bodyPointSize — card and lane chrome, drag replicas and the
count badge, the resize handle, the trash column — and the drop registry
carries the ruler for event-time reads, with the autoscroller's three
reaches turning font-derived (reachSide named as the stripGap it always
equalled). Lanes still divide the window; zoom never moves the window or
its floor. The toolbar gains a catalog-only Zoom In/Out pair mirroring the
menu rows' predicate; zoom holds shut mid-drag (frozen geometry), each rung
announces itself to VoiceOver, and the render suite pins both invariants:
a rung repaints every face, a no-op Actual Size repaints nothing.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
background is {color:, image:} and only a mapping at every level; the board's image paints the full window under a transparent title bar, with a thin-material frost strip keeping the chrome legible and the standard accommodations intact.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Redesign card 6d5a97fd resolved off the 2026-08-01 live try-out: leading is
board identity, center is view controls, trailing stays the user's catalog.
centeredItemIdentifiers, not a flexible-space sandwich — centers against the
window, holds as catalog items install, reaches machines with a saved
arrangement; defaultItems and its pinned tests untouched. 03 records the
grammar and the accepted tensions (widget-vs-field budget in narrow windows,
HIG's trailing convention yielding).
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Four rulings close Redesign Contradiction 3452893f (2026-08-06): the in-app
escape hatch is ratified in full, reversing 2026-07-29's palette-only rule —
the combo's Other… opens the system Colors panel, a pick landing on a palette
color stores the name, anything else the hex. Free-picked colors change no
contrast story: they land on the same runtime ink computation hand-written hex
always got (10 amended to say so; no warning surface is owed). Anchor
ownership: the card sidebar's background story is the combo alone — the well
grid's background half stays with the other anchors (StyleEditorView gains
showsBackground beside showsSymbols; the popover's symbol half already went to
its inline SymbolPicker). Quick-style recents stay palette-vocabulary — a
panel pick never enters them.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
SymbolPicker: one well at rest, a 6x6 curated grid in a popover (leading
well = the level default, clearing the key), and an optional search over
the OS's full symbol inventory read from CoreGlyphs metadata. Geometry
font-derived off StyleEditorLayout's base, grid enlarged by a deliberate
1.3x. Wired inline with the rename field in the board info popover
through the StyleCommand funnel; the Title header retires. Curated set
and the search's standing vs 03's full-browser refusal await ratification.
Minor ruled into 04 ▸ Cross-board copy: the Finder volume model — a
mid-drag rename of the source board carries the live drag with it (the
drag holds its source store's identity, never a frozen key, which would
let a board opened at the vacated path compare equal — the worse,
destructive failure). Until the carry ships the residue is recorded:
key follows folder, a mid-drag rename finishes a reorder as a copy —
cosmetic, seconds-bounded. Implementation filed.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Minors ruled into 03 ▸ the git-at-a-glance section: a single-branch
board's switch picker opens onto a disabled "No other branches" row —
teaching why it is empty and where creation went — and the Pro
mode-none section stays as header plus the Board Settings… door rather
than vanishing, the git story keeping its named place.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Minor ruled into 02 ▸ the memoize ruling: a stamp-preserving rewrite is
a decision on file rather than a surprise in the field — git lives with
the same blind spot, the behavior is test-pinned, and the tightenings
(hashing, generation stamps) wait for a real-world defeat.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Minors ruled into 03 ▸ Toolbar: a space-constrained search item
collapses to a button still in the window and ⌘F expands and focuses it
— the strip fires only for a truly windowless field; and AppKit's
overflow row that widens the window is the platform's own second
answer, unsuppressable without destroying honest overflow — two
gestures, two honest resolutions, no contradiction.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Minors ruled into 01 ▸ The decision surface and Skip: (1) depth reveals
in rounds — a defective lane index leaves its subtree unwalked, so
deeper defects surface on the round after its repair, the collect-all
rule meeting an unreadable container; (2) Skip never names the root —
the case-by-case foreclosures are one general rule the loader polices,
a root skip entry ignored and the defect collected anyway; (3) the
skip's carrier is the defect's root-relative path — the string the row
displays and Reveal resolves, exactly what the user consented about.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Minor ruled into 05 ▸ Window: the no-title titlebar collapses AppKit's
title/subtitle pair as one field, so board › lane hides with it. The
window is usually beside its board; title and subtitle keep feeding the
Window menu, Exposé, VoiceOver, and restoration. The live-update wiring
stands — a sidebar placement row is the named candidate if a visible
surface is ever wanted; deliberately not built now.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Minor ruled into 06 ▸ Auto-commit: a reload in flight at bracket close
clears the gate without a started-after guarantee, and the committer
deliberately does not ask — the cadence margin makes the window
academic, the post-bracket reload is unconditional, and the deadline
already accepts a near-covering snapshot; the tree is what gets staged,
so the exposure is message precision, never content. Walk-start
bookkeeping named as the escalation.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Issues card ruled into 10 ▸ audits: the every-surface claim reaches
tier-gated UI via a launch flag honored only when isFixtureLaunch is also
set — the fixture already scopes the app to a scratch container and a
throwaway board, so the override grants Pro on a disposable sandbox and
never over real boards. Manual-checklist-forever would asterisk the
claim; debug-only would leave release unauditable. Partial surfaces pin
the free-reachable state and audit the full one under the override.
(Carries 10's in-flight zoom-control bullet from the parallel session.)
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
The pathfinder surface stack landed in e1ffe71 entangled with staged-index
placement drift: the replica's wash block sat inside replicaHeight (a CGFloat
has no .background — HEAD did not build), the body plate rode outside the cut
dim, the lanePlate property and the DESIGN bullet sat away from their kin.
Pure relocation, no new lines: every block moves to the anchor it was written
for. Verified by an isolated build of this tree.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
Gap #12 ruled into 04 ▸ the trash-drop bullet: the refusal fall-through's
honest reading (the column declines to be a target, it does not cancel
the held drag) covers only promises the refusal leaves keepable — lane
shadows keep drawing and an ⌥-copy lands where they are. A standing
proposal naming the trash itself is the promise the refusal just broke,
so it withdraws: the drag goes proposal-less over the column, release
with no proposal cancels in agreement with the empty picture, and
lifting ⌥ re-asks at the same address. Implementation filed on the
Implementation board.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Gap #11 ruled into 02 ▸ One-way flow: between a write's bracket and its
echo the snapshot describes the past, so a guard deciding whether a write
would change disk baselines on the snapshot as amended by this store's
own in-flight writes — a pending-value record of the write path's own,
never the EchoLedger. Relative gestures (step one width unit) resolve
their base against the same amended truth, and the undo step's recorded
prior reads it too. The amendment dies with its echo; failed writes never
enter it. Coalescing weighed and set aside — one gesture, one write, one
commit. Implementation filed on the Implementation board.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Gap #10 ruled into 04 ▸ Search's single-source-of-truth bullet: zones,
shadow, and write all resolve through the same filtered list — the
position the shadow shows is the landing the write performs. The slot's
meaning in the full order is its visible anchor (after the visible
predecessor, before the visible successor at the lane top, true end in a
query-emptied lane; multi-card in proposal order beside the anchor);
hidden cards keep their ranks untouched, and the gesture's meaning
survives the query's clearing. The rendered space includes the new-card
placeholder. Code diverges today — implementation filed on the
Implementation board.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Gap #9 ruled into 06 ▸ Rules: a board-wholesale operation asked from a
form is both rulings at once, and the form rule wins while the asking
surface is up — create-and-switch answers inline in the sheet's Branch
section, a picker-asked switch at the popover's caption, never both at
once (the sheet dismisses the popover). The banner stays the posture for
whatever outlives the asking surface — the form rule's own fallback
generalized. Banner-only-everywhere weighed and set aside.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
Gap #8 written into 02 ▸ FolderWatcher's reconciling bullet: the
value-equal skip's structural consequence is that walk-subject consumers
cannot key on the applied-snapshot generation, so the store carries two
counters — applied (the committed-overlay hold's event, unchanged) and
landed (every successful reload, equal or not; failures bump neither).
The choosing rule: anything outside the snapshot, or about the walk
itself, watches landed walks — the comment thread, the comment search
index, the auto-committer's covering gate.
Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
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
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
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
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
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
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
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
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
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
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
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
`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
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
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
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
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