1817d6f0b9049ec47cef5026fb4e6a244cdcc969
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1817d6f0b9 |
The Paste row says what it is about to do — one card, one lane, or a count of either
Both context menus (`CardFaceView`'s pointer menu and VoiceOver twin, `LaneView`'s own pair) twin a plain "Paste" row over `ClipboardStore.paste(into:)`. It now reads the clipboard's own manifest and titles itself "Paste Card" / "Paste Lane" for a single copied item, "Paste N Cards" / "Paste N Lanes" for several, and stays plain "Paste" for no app payload — a Finder file copy, a screenshot, anything else the paste command still accepts but never decodes to a `ClipboardManifest`. `ClipboardManifest.pasteMenuTitle(for:)` is the one pure function both menus call — `kind` and `entries.count` are the whole of it, since `SelectionKind` is singular by construction (`SelectionGrammar.mixesKinds` refuses a mixed copy), so there is no mixed shape to compose a plural for. `CardFaceView.pasteTitle` rides the exact Observable read `pasteEnabled` already makes (`appModel.clipboard.payload`) — no new subscription on a builder that is not lazy. `LaneView.pasteTitle` reads the same field directly rather than through `canPaste(into:)`, since this view is already unconditionally subscribed to selection/snapshot and there is no reduction to preserve. Edit ▸ Paste on the menu bar stays plain — it is a responder attached to the platform's own Edit menu row (`ClipboardCommands.boardClipboardCommands`), not a `Button` this app titles, and the card's scope is context menus only. Flagged for a follow-up: DESIGN/11-command-nexus.md's Card and Lane rows (lines 110-111) describe Paste generically and could note the dynamic title. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy |
||
|
|
0fe92bdf38 |
A file copied in Finder becomes a card's attachment — ⌘V's reserved clause finally answers
The image branch's precedence ladder always had a second clause: a file URL on the pasteboard suppresses it, "a different gesture with a different answer" that the code deliberately declined rather than guessed at. This fills it in: one or more file URLs paste through the same `importAttachments` a Finder drop takes — one collision ladder, one folder refusal (`FinderDrop.partition`), one set of banners — outranking raw image data riding beside it (a Finder-copied image file carries both; the actual file lands, not a re-encoded copy of its bytes) while still deferring to the app's own clipboard type. Both ⌘V surfaces read the same `ClipboardStore.fileURLPayload`, so the board's fallback and the card window's own branch stay in step by construction rather than by two hand-kept-in-sync checks. Fixed a real leak in the body editor's paste yield along the way: `public.file-url` conforms to `public.url`, which `NSTextView` legitimately reads for a pasted hyperlink, and `NSPasteboard.availableType(from:)` matches by conformance rather than exact type — so a Finder copy carrying a generic URL representation beside its file URL would have been silently swallowed as text and never reached the window's attachment branch at all. The yield now declines outright on any file-URL pasteboard before the generic capability check runs. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy |
||
|
|
ca5d45156b |
A picture off the pasteboard becomes a card's file, its hero, or the board's backdrop
⌘V grows an image-data branch, below the app's own clipboard format and refused outright while a file URL is on the pasteboard: a screenshot or a browser's Copy Image lands as "Pasted Image.png" in the anchor card's attachments/, through the very import path Finder file drops and ⇧⌘A take — one bracket, one Finder-style collision ladder, one set of banners, and the same silence a drop's arrival has. A card window's ⌘V pastes onto its own card; a focused text field still wins the selector natively. A file-shaped flavor travels byte for byte, PNG preferred when several are offered; TIFF and BMP are re-encoded to PNG, being interchange encodings rather than files anyone wants in a folder. The hero key gets the setter it was born owing: "Set as Hero" on any image row of the attachment list, "Remove Hero" on the row that holds it, with menu-bar twins so the context entry is nobody's only home. It writes as a restyle — one key, one bracket, one invertible step on the window's own stack — and replaces rather than refusing, because a card has one hero and the row that has it says Remove instead. Edit ▸ Paste as Board Background is the same payload's other destination, taking the existing background.image convention at its word: the picture into the board folder as "Pasted Background.png", the colour subkey untouched, the generator's overwrite-our-own-name rule inherited and its echo memo taught to tell the two producers apart. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy |
||
|
|
2c6b8fe63a |
Remove the App Group wholesale — one sandbox, one bookmark, one flag
Phase 2 of the one-app pivot (DESIGN 12 ▸ App-side state, re-ruled
2026-07-30; reworks
|
||
|
|
092300c7d2 |
Collapse the edition split — one target, twins merged, verify-editions retired
Phase 1 of the 2026-07-30 one-app pivot (DESIGN
|
||
|
|
69084fdff7 |
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 |
||
|
|
546ed94412 |
The pasteboard UTType constants become edition twins — Pro stops claiming ownership it doesn't have
Latent since the m7 split: base's Info.plist exports the three family pasteboard types (cards, lanes, clipboard) and Pro's imports them, but the shared constants declared all three UTType(exportedAs:) — a claim of ownership the system checks against the running app's plist. In Pro that claim is false, and the first touch (the clipboard type, via the launch sweep) raised a runtime fault that blocked board loading under the debugger. EditionAbout's twin-file pattern, applied to an initializer: the constants move to Kanban/App/EditionTypes.swift (exportedAs) with a KanbanPro/Edition/EditionTypes.swift twin (importedAs, identifiers verbatim); project.yml excludes base's copy from Pro. Type identity is the string, so payloads cross editions unchanged. Verified: Pro fixture launch opens the board with zero UTI warnings; both suites green; verify-editions 30/30. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY |
||
|
|
566deab506 |
Home app-side state in the shared App Group container
Every edition declares group.dev.rzen.indie.Kanban and homes its
app-side state there from day one (12-editions.md ruling 2026-07-29):
- AppGroup namespace: container resolution with per-edition fallback
when unprovisioned, shared UserDefaults suite, edition identity, and
a unit-test-host redirect (the test host IS the app — its launch
sweep and recents refresh must not touch the real shared container).
- BoardRecord: bookmark/isOpenNow replaced by per-edition grants and
openNow keyed by bundle id; hand-written Codable keeps legacy keys
decoding (adopted in memory as the running edition's slots, upgraded
on first save); every other field stays common.
- RecentBoard gains needsReopen: no grant of ours but somebody's —
first click runs an open panel pre-anchored at the recorded path,
prompt "Grant"; recordOpen mints this edition's slot onto the
matched shared record (path fallback only after identity fails and
only against records holding no grant of ours, so re-granting never
forks the record).
- Cross-edition freshness: stat-cheap mtime+size stamp re-reads the
registry when the sibling edition wrote it, so one edition's save
never erases the other's records wholesale.
- restorables() filters on this edition's open-now flags; the board
popover gains BoardEditionPresence ("Also open in Lanework Pro"),
pid-liveness-checked so crash residue never lies.
- Clipboard staging store moves to the group container; the sweep
claims doomed trees by atomic rename into .sweeping/ then deletes,
so the sibling's concurrent sweep is a non-event.
- Template store re-homed to the group container per the 09-templates
re-ruling; scalars (quick-style recents, window size) move to the
shared suite.
- verify-editions.sh: 30 checks (each edition carries exactly the
family group). No pathfinder 1.x migrator: 1.x predates the
registry; state starts fresh in the group container.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
|
||
|
|
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 |
||
|
|
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 |
||
|
|
7eee0934ee |
Implement the hybrid clipboard with deferred cut
⌘X/⌘C/⌘V for cards and lanes per 04-interactions.md § Clipboard: - ClipboardStore stages full folder snapshots eagerly at the gesture into Application Support (at most the current copy; sweep at launch and on each copy purges what the pasteboard no longer references; a copy made before quitting pastes whole after restart) and writes the pasteboard a JSON manifest — every entry embedding its index.md, lane entries their cards' too — plus plain-text titles. - Cut is Finder-style deferred: items dim in place off pendingCut, void on pasteboard takeover (changeCount, no timers), source-board close, or per-item external tombstoning; the first armed paste moves the surviving originals whole (tombstoned interior cards land in the destination's trash), a second paste materializes copies from staging. - Paste anchors by the shared flatten-order rule (NewCardTarget's anchor, extracted); a tombstoned selection never anchors; lane paste reaches the right end and stays enabled on a zero-lane board; paste into the source board is the within-board lane duplicate; copies keep created, take fresh GUIDs, and strip tombstoned cards; trash-sourced copies strip deleted: at materialization; ⌘X is disabled on the trash side. - A degraded paste is loud, never silent: staging gone → the embedded index.md fallback lands content-intact, attachments absent, and a BannerCenter-phrased row names what was lost. - The standard Edit items validate through conditionally-attached onCommand handlers, so AppKit's enablement mirrors the availability predicates; text fields keep their own clipboard while focused. 879 unit tests (68 new). Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY |