Realign code with the 2026-07-29 findings-resolution rulings
Nine rulings land as code. Reorders don't stamp — one container-change predicate (WriteOperation.rewritesOrderOnly): within-container reorders and the renumber rescale rewrite only order, while cross-lane, cross-board, and trash moves stamp modified and clear modified-by; no trash special case exists, and the m8 undo inverses conform through the same seam. Copies are transactions: the root-strict/nested-lenient split retires for a whole-subtree stampability preflight that refuses loudly naming the offender, and every item-level copy severs remote/remote-state at every level (whole-board forks carry them verbatim). Paste refuses, never degrades: the embedded-index.md materialization and its loss row retire; a missing staged snapshot produces nothing and posts an error-tone one-shot named from manifest metadata. Coerce-tier fallbacks log through the Defect stream with path context attached loader-side. Displacement is level-uniform: a file squatting attachments inside a card heals by the same rename ladder as board-root squatters; comments stays tolerated. Delete Immediately joins card and lane context menus as Delete's ⌥-alternate with its own VO custom action, routed through an explicit container so the menu target outranks standing selection. Agent guide v7 teaches the stamp discipline and the card-level attachments claim, and sheds two stale v6 lines (lanes trash now; kind is taught). Verified conformant, unchanged: edition-aware Undo/Redo disable, trash marquee full-height backdrop. Both schemes 1854 tests / 318 suites green; verify-editions 30/30. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -2495,21 +2495,25 @@ public final class BoardStore: HealHost {
|
||||
|
||||
/// Where one arriving item's bytes come from.
|
||||
///
|
||||
/// **Two producers, one arrival path.** A drag names folders in the source board; a paste names
|
||||
/// folders in the clipboard's staging directory — and, when that snapshot is missing or
|
||||
/// unreadable, the manifest's embedded `index.md` instead (04-interactions.md ▸ Clipboard's
|
||||
/// staging-less fallback). Modelling the fallback as a second kind of *source* rather than as a
|
||||
/// second arrival method is what keeps the rank insertion, the tombstone stripping and the
|
||||
/// `deleted:` clearing stated once: everything downstream of "where do the bytes come from" is
|
||||
/// identical, and a paste that half-falls-back mixes the two cases inside one bracket.
|
||||
/// **One producer shape, because there is only one kind of source left.** A drag names folders in
|
||||
/// the source board; a paste names folders in the clipboard's staging directory. There used to be a
|
||||
/// second case — the manifest's embedded `index.md`, materialized when the staged snapshot was
|
||||
/// missing or unreadable — and it is **retired with the degraded paste** (04-interactions.md ▸
|
||||
/// Clipboard, re-ruled 2026-07-29: "A paste whose staged snapshot is missing or unreadable refuses
|
||||
/// loudly — never degrades"). An item arrives **whole — index, attachments, loose files — or not at
|
||||
/// all**, so a paste can no longer half-fall-back inside one bracket, and the loss-accounting
|
||||
/// problem the second case created dissolves rather than being solved. The manifest still embeds
|
||||
/// `index.md`, now purely as identification metadata: menu validation, the refusal's wording, and
|
||||
/// the plain-text flavor.
|
||||
///
|
||||
/// It stays a single-case enum rather than collapsing to a bare `URL`: the arrival paths read as
|
||||
/// "where do these bytes come from", the drag and the clipboard each say so at their own call
|
||||
/// sites, and a future third producer (an import, a drop from another document type) has a place to
|
||||
/// land that is not a rewrite of every signature in between.
|
||||
public enum ItemSource: Sendable, Equatable {
|
||||
|
||||
/// A folder on disk — the source board's own, or a staged snapshot of it.
|
||||
case folder(URL)
|
||||
|
||||
/// The manifest's embedded text: the item's `index.md`, and (for a lane) its cards'.
|
||||
/// Materialized by `BoardWriter.materializeItem`, byte-faithfully.
|
||||
case text(index: String, cards: [String])
|
||||
}
|
||||
|
||||
/// A cross-board card drop, landing contiguously at `index` among `laneID`'s rendered cards.
|
||||
@@ -2535,10 +2539,11 @@ public final class BoardStore: HealHost {
|
||||
/// independently (04-interactions.md ▸ Clipboard).
|
||||
///
|
||||
/// It is the same commit as a drop's, deliberately: `.copy` materializes from the staged snapshot
|
||||
/// (or, per entry, from the embedded `index.md`) and `.move` is the armed cut's — "the ⌘-drag
|
||||
/// move path — identity travels", which is also the keyboard restore when the cut was made in the
|
||||
/// trash (▸ The trash: "cut in the trash, paste into a lane is the keyboard-native restore, an
|
||||
/// ordinary folder move").
|
||||
/// and `.move` is the armed cut's — "the ⌘-drag move path — identity travels", which is also the
|
||||
/// keyboard restore when the cut was made in the trash (▸ The trash: "cut in the trash, paste into a
|
||||
/// lane is the keyboard-native restore, an ordinary folder move"). A copy whose staged snapshot is
|
||||
/// missing never reaches here at all: the clipboard refuses the whole paste in front of this call
|
||||
/// (04 ▸ Clipboard, re-ruled 2026-07-29), so every source this sees is a folder that exists.
|
||||
///
|
||||
/// **The trash's old copy-out rule is gone with the tombstone it stripped** (resettled
|
||||
/// 2026-07-28): a trashed card carries no `deleted:` key, so a card copied out of the trash is
|
||||
@@ -2616,15 +2621,14 @@ public final class BoardStore: HealHost {
|
||||
}
|
||||
}
|
||||
|
||||
/// One arrival's materialization — the two `ItemSource` kinds crossed with the two operations,
|
||||
/// in the one place both the card path and the lane path can share.
|
||||
/// One arrival's materialization — the source crossed with the two operations, in the one place
|
||||
/// both the card path and the lane path can share.
|
||||
///
|
||||
/// **`.move` of a `.text` source is unreachable and answers `nil`.** A move needs a folder whose
|
||||
/// identity travels, and the only producer of text sources is the clipboard's fallback, which is
|
||||
/// a *copy* by construction (04-interactions.md ▸ Clipboard: an armed cut moves the surviving
|
||||
/// originals, and a cut that cannot find them is void). Skipping is the standing posture for an
|
||||
/// arrival that names nothing — the same silent no-op every other drop commit gives a source that
|
||||
/// has gone.
|
||||
/// A copy is `copyItem` (fresh GUIDs throughout, the copy contract applied to every folder it
|
||||
/// materializes) and a move is `moveItem` (identity travels, the import boundary reminting only
|
||||
/// what collides). The `ItemID?` return survives the retired text case because the lane and card
|
||||
/// arrival loops read it as "did this arrival land": a `nil` is the standing silent no-op for a
|
||||
/// source that names nothing.
|
||||
private static func materialize(
|
||||
_ source: ItemSource,
|
||||
operation: TransferOperation,
|
||||
@@ -2644,15 +2648,6 @@ public final class BoardStore: HealHost {
|
||||
destinationBoardRoot: destinationBoardRoot,
|
||||
order: order
|
||||
).id
|
||||
case let (.text(index, cards), .copy):
|
||||
return try BoardWriter.materializeItem(
|
||||
inParent: parent,
|
||||
indexText: index,
|
||||
children: cards,
|
||||
order: order
|
||||
)
|
||||
case (.text, .move):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2677,7 +2672,7 @@ public final class BoardStore: HealHost {
|
||||
)
|
||||
}
|
||||
|
||||
/// **The clipboard's lane arrival** — `receiveLanes` with the staging-less fallback folded in
|
||||
/// **The clipboard's lane arrival** — `receiveLanes` with the paste's own axis folded in
|
||||
/// (04-interactions.md ▸ Clipboard).
|
||||
///
|
||||
/// The two operations keep their drag semantics exactly, because 04 says they are the same
|
||||
@@ -3445,8 +3440,19 @@ public final class BoardStore: HealHost {
|
||||
/// The selection is cleared rather than walked to a successor: unlike ⌫, this is the command a
|
||||
/// confirmation stands in front of, and what follows it is reading the board rather than pressing
|
||||
/// the key again.
|
||||
public func deleteImmediately(_ ids: Set<ItemID>) {
|
||||
let container = selection.container
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - ids: the items to purge — the caller's explicit set, not this store's selection.
|
||||
/// - container: **which side those ids live on**, supplied rather than read off the selection
|
||||
/// (added with the context menu's ⌥-alternate — 11-command-nexus.md ▸ Context menus). The
|
||||
/// menu-bar caller passes the selection's own container and behaves exactly as before; a
|
||||
/// context-menu caller passes `.board`, because "the click names its target" and the target can
|
||||
/// legitimately sit on the other side of the container boundary from a standing selection
|
||||
/// (797d020's explicit-set resolution, whose whole point is that the two can disagree). Reading
|
||||
/// it off the selection here would silently resolve a board card against `.trash` and purge
|
||||
/// nothing — a confirmed destructive command turning into a no-op, which is the one outcome a
|
||||
/// confirmation must never lead to.
|
||||
public func deleteImmediately(_ ids: Set<ItemID>, in container: ItemContainer) {
|
||||
let paths = ItemPath.resolve(ids, in: container, snapshot: snapshot).filter { !$0.isLane }
|
||||
guard !paths.isEmpty else { return }
|
||||
let root = rootURL
|
||||
@@ -3604,19 +3610,28 @@ public final class BoardStore: HealHost {
|
||||
|
||||
// MARK: - The claimed-name displacement
|
||||
|
||||
/// Moves a squatter off a board-root name the app claims, and posts the notice naming old and
|
||||
/// new — the **act** half of the claimed-names ruling (01-storage-format.md § Fractal layout ▸
|
||||
/// Rules, ruled 2026-07-29: "Lanework owns the board, so an invalid artifact on a claimed name is
|
||||
/// a defect, not a resident").
|
||||
/// Moves squatters off the names the app claims, and posts the notice naming old and new — the
|
||||
/// **act** half of the claimed-names ruling (01-storage-format.md § Fractal layout ▸ Rules, ruled
|
||||
/// 2026-07-29: "Lanework owns the board, so an invalid artifact on a claimed name is a defect, not
|
||||
/// a resident").
|
||||
///
|
||||
/// Today that is `.trash` and only `.trash`: a file or symlink sitting on the name the trash
|
||||
/// needs, which breaks deletion for as long as it stands — which is exactly why the timing is
|
||||
/// *scheduled* rather than on-touch (§ Validation and healing: "proactive when the defect is
|
||||
/// load-bearing now"). `CLAUDE.md`'s squatter is displaced by the guide's own heal, which owns
|
||||
/// that file end to end.
|
||||
/// **Two levels, one heal** (extended 2026-07-29 — "the rule is level-uniform"): the board root's
|
||||
/// `.trash`, and any card's `attachments`. Each is a file or symlink sitting on a name the app needs
|
||||
/// — breaking deletion in the first case, and every import, Finder drop and sidebar listing for that
|
||||
/// card in the second — which is exactly why the timing is *scheduled* rather than on-touch
|
||||
/// (§ Validation and healing: "proactive when the defect is load-bearing now"). `CLAUDE.md`'s
|
||||
/// squatter is displaced by the guide's own heal, which owns that file end to end; a wrong-kinded
|
||||
/// `comments` is a tolerated stray until the feature consumes the name.
|
||||
///
|
||||
/// **Displacement, never destruction**, and never a mint: the freed name is left empty and the
|
||||
/// *next delete* creates the real `.trash/`, exactly as it does on a board that never had one.
|
||||
/// **One bracket over every displacement the load found**, whatever their levels: the batch is one
|
||||
/// app-mediated reload and, on git boards, one heal commit — the loose-file relocation's rule, and
|
||||
/// this heal's own memo is board-wide anyway.
|
||||
///
|
||||
/// **Displacement, never destruction**, and never a mint: the freed name is left empty and the next
|
||||
/// gesture that needs it creates the real folder — the next delete mints `.trash/`, the next import
|
||||
/// mints `attachments/` — exactly as on a board that never had one. The displaced file, now an
|
||||
/// ordinary loose file beside the card's `index.md`, is picked up by the next load's loose-file
|
||||
/// relocation and lands in the real `attachments/`: the heals compose, which is 01's own word for it.
|
||||
public func displaceClaimedNames() {
|
||||
let work = claimedNameSquatters
|
||||
let root = rootURL
|
||||
|
||||
Reference in New Issue
Block a user