Commit Graph
11 Commits
Author SHA1 Message Date
rzen bec75e4282 Realign code with the 2026-07-31 rulings
The trash sorts by modified descending — the arrival rank mint retires
(Ranks.isOrderedForTrash one comparator, loader + merged order agree;
the legacy deleted: migration stamps modified from the tombstone
timestamp where parseable; delete undo steps validate existence-only;
agent guide v8). Trash selection goes kind-blind — ranges, marquee,
Select All, and the successor walk sweep both kinds; the guard moves to
the exits (mixed-payload drop refusal, copy/cut validation). The copy
stamping preflight widens back to comment depth (load-scoped posture —
the board always loads, the gesture refuses whole). Fixes a latent
no-op: trashed-lane drag restore never fired (DragSession.beginLanes
hard-coded the board container).

2403 tests in 413 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-07-31 18:35:07 -04:00
rzen f7c8088783 Lanes delete into the trash — rendering, grammar, drag, clipboard, a11y
Phase 2 completes the lanes-in-trash card. TrashEntry merges the
trash's two kinds by rank in exactly ONE place (ItemPath.resolve's
own merge deleted in favor of it — the three-merge-points finding
shrinks instead of growing). TrashLaneRowView renders the opaque
row — tertiary plate, level-default lane glyph never the lane's own
icon, title + card count, no accents, no expansion; the column badge
counts rendered rows. Selection grammar: kind-homogeneous trash
selections — ranges skip the other kind, ⇧-extension stops at the
kind boundary, plain arrows walk the merged order, marquee stays
card-only (now load-bearing: rows register frames for arrows),
Select All card-scoped; successor-on-purge crosses kinds like
navigation as the interim for open Gap 7b5cbc90. Drag: TrashDrop
accepts lane sessions (drop on shown trash deletes), restoreLanes
routes a trash-sourced strip drop as an arrival-ranked within-board
move with an undo step. Clipboard: ⌘X/⌘V lane restore via opaque
lane subjects; fixed boardRoot(ofLaneFolder:) returning .trash as
the root — a same-board restore looked like an import and would
have reminted the lane it was restoring (pinned by test). A11y:
row = one flattened "title, deleted lane, N cards" element with
Delete/Reveal actions; BoardDiff crossings read lanes as
deleted/restored, shown-trash churn digested at row level. Agent
guide stays v7 — the literal already teaches lanes-trash-by-move
and kind stamping; drift-guard pins those lines. README trash
paragraph notes lanes.

Both schemes 1893 tests / 322 suites green.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-30 17:04:30 -04:00
rzen 797d020d01 Materialize the trash — faces, menus, and grammar
Phase 3 finishes the pivot at the surface. One card face serves two
containers: CardFaceView extracted with a role — board or trash — so
stripe, tint, chip, selection stroke, cut dim, marquee registration,
and drag are shared by construction, the trash side differing only in
its absences: no Open, no rename, no Style, no file-hover highlight,
and a Delete that goes through the confirmation host. The column
rewrote around the lanes' own single-column masonry so drag reflow
reads as positional slides; chrome stays the hatched header, symbol,
and count — 11 gives Empty Trash to the File menu alone. Two real
grammar bugs die here: plain Backspace on a trash selection purged
without the confirmation the menu raises, and the context menu's
Delete resolved against the standing selection, so right-clicking a
trash card under a board selection silently did nothing — it now
stages the clicked set explicitly. Open, Rename, Style, and Empty
Trash validation became testable store seams; the column is one named
accessibility container of ordinary card elements. The tombstone era
is swept: deleteItem, restoreItem, stripTombstonedChildren — dead
since lane copies stopped nesting trash — the restore verb, the
unreachable put-back banner row, and every quasi-lane doc comment.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 18:18:39 -04:00
rzen 53bc71f7fb Materialize the trash — store, undo, and the container universe
Phase 2 swaps every consumer: Liveness and its ancestor walk are gone,
replaced by ItemContainer — a UUID set plus the container side it
lives on, presence the whole test, one selection boundary instead of
the old liveness law. Deletion stages by place: board cards move to
the trash at a store-minted head rank, trash-side delete is permanent
behind its confirmation, Delete Immediately skips the trash from
anywhere, lane delete captures the subtree and removes the folder.
Restore has no method at all — moveCards resolves members in either
container, so drag-out and cut-paste are the ordinary moves 13 calls
them, registering ordinary Move steps. The delete inverse moves the
card back to its captured lane and rank; redo replays the captured
trash rank, a value the gesture actually wrote; lane undo recreates
the subtree byte-faithfully in session. Purges register nothing —
where 13's trash section contradicts its own Rules on that, Rules
wins, filed for ruling. Staleness collapsed to present-or-absent: a
container is a path, so a foreign restore fails the delete step's
expectation structurally. Legacy tombstones migrate on the loose-file
tail hook, cards oldest-first so minting above top reproduces the
retired newest-first column, lanes returning live, one folded loss
row naming both directions. Put Back, restoreByDrag,
receiveRestoredCards, TrashEntry, and the kind machinery are deleted;
the trash column renders the container correctly with its full face
rework left to phase 3.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 17:47:56 -04:00
rzen 7f1adf47c5 Back out the render-at-release drop presentation
The settle rule 3f4125e introduced — at release the shadow swaps for the
dropped card(s) drawn in place immediately — is withdrawn on review. The
board returns to the presentation the committed-overlay hold has always
had: the shadows keep standing at their landing slot and the originals
keep their lift until the echo reload lands, whatever the operation was,
and the real faces arrive with the snapshot that carries the write.

Gone with it: the DropLanding/DroppedItem seam and the three landing
accessors, the hold's landing/operation payload and the removesOriginals
and keepsIdentity halves it decided, the pickup-captured titles a
cross-board arrival's face needed, LaneSlot.dropped with DroppedFace and
DroppedCardFace, the strip's dropped-lane run, and the trash column's
settled tombstone rows — the same presentation at the delete gesture's
end, which goes for the same reason while the drop-to-delete write and
its shadow row stay exactly as they were.

Kept: the hold itself, which predates the commit, along with the 1500 ms
failed-write timeout, its injectable duration and its extracted expire,
and the guard that keeps a settled release past retargeting. The tests
pinning those stay; the ones asserting what the landing slot drew are
gone, and the suite now pins the hold's own claim — the arrangement does
not move at the release.

The pause between the release and the echo is therefore an open design
question again, filed on the Redesign board.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-07-28 09:57:32 -04:00
rzen 524488122f Land Finder file drops positionally, header release topmost
04's settled clauses were mostly shipped already — the create landing
resolved through DropSlotMath.cardSlot with one nominal shadow per
importable file — but a release on the lane header fell through to the
card zones, which clamp inward, so a scrolled lane could propose behind
the header stripe. FileDropZones now folds header, attach hit-test, and
card-slot resolution into one pure seam asked in that order, the header
answering topmost per the ruling; lane headers register their frames
for it. FinderDrop.shadowCount names the floor-at-one rule. New tests
pin the header boundary, a differential against cardSlot's own zones
(same zones, not similar), and a store-level differential proving a
file landing takes the very ranks a card move there takes.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 08:28:28 -04:00
rzen 33bf425f25 Drop a card on the shown trash to delete it
04's ruling makes the drag the pointer's delete gesture: the shown
trash column accepts live same-board card drags, the shadow pinned
topmost — honest, since the trash sorts by deleted newest-first — and
release tombstones through the same write path as Backspace, extracted
so the two gestures cannot drift. DropTarget grew a container case for
the quasi-lane (it has no lane id by construction); lane drags,
cross-board arrivals, option-copies (re-checked at release, the one
input that can flip without a callback), trashed-side payloads, hidden
trash, and the read-only lock all refuse — and a refusal falls through
to the strip retarget, never cancelling the drag. The settle draws the
tombstoned rows in the trash under the cards' own GUIDs, so the echo is
an invisible content swap and nothing winks out for a round trip.
Selection needs no surgery: the reload's resolve rule ejects tombstoned
members as the vanish it is, pinned by a test contrasting both gestures.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 08:12:09 -04:00
rzen 3f4125e324 Render the dropped card at release, and pin the hold's timeout
03's sharpened settle rule: rendering the arrangement means rendering
the card — at release the shadow swaps for the dropped card(s) drawn in
place immediately, the appear never waiting for the echo reload. The
committed hold now carries the landing (ids, payload titles, operation)
and surfaces read one DropLanding seam: within-board moves draw the
real faces at their proposed slots under the arriving card's own key,
so the echo is an invisible content swap; cross-board card arrivals
draw payload-titled faces keyed positionally, so the echo reads as an
ordinary arrival. Cross-board lane arrivals deliberately keep their
shadow until the echo — a lane's face is a whole column with no honest
payload equivalent. The 1500 ms failed-write timeout is now seamed
(injectable duration, extracted expire) and pinned by tests.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 07:51:05 -04:00
rzen 1487b391ad Refuse folder drops at hover, skip them in mixes
04's settled ruling: the attachment model is flat top-level files, so a
drag containing only folders never engages — no highlight, no proposal,
the standard incompatible-payload cursor — and a mixed drag proposes for
its files only, importing them at the drop while a loss row names the
skipped folders. Hover reads the providers' registered types (anything
conforming to public.directory refuses, packages included); commit
re-partitions authoritatively from the filesystem, so a synthetic
payload that hides its type still can't land a folder. The create path
now only ever fires with at least one importable file — the
mint-fail-remove dance is gone from the folder case and stays reserved
for genuine mid-batch failures.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-28 07:29:43 -04:00
rzen 8f116934b4 Implement Finder file drops
Files from Finder land on the board per 04-interactions.md § Drag & drop:

- Dropped on a card, they copy into its attachments/ (any type, multi-file),
  the face highlighting while hovered; the hovered card is resolved by
  hit-testing the same analytic masonry frames the card zones are built
  from, so attach-beats-create adds no drop region and cannot drift from
  the dispatch.
- Dropped on lane empty space, one card per file — filename minus extension
  as the title (a blank stem omits the key), fresh GUID, rank at the drop
  position through the ordinary insertion machinery, the file attached —
  all in one bracket; a failed import removes the just-minted card, so
  creating-then-abandoning never leaves an empty card behind.
- Every board drop surface now declares .fileURL beside the two board
  types (the single-target-dispatch rule); tombstoned surfaces are inert;
  file sessions ride a distinct session mode with their own watchdog and
  no hysteresis, leaving the board-drag machinery untouched.
- Also: four empty fixture directories pinned with .keep files so git
  preserves them, and a test-only visibility fix in DragSessionTests.

811 unit tests (12 new).

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-07-27 21:20:46 -04:00
rzen 90cf82d740 Implement drag & drop with the locality model
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
2026-07-27 20:58:26 -04:00