Both editions installed is a supported steady state — handler, races, clipboard ruled
Ruled 2026-07-29, new block in 12: Pro claims the .kanban default on first launch once (the purchase is the consent; a manual reassignment back is respected; base never claims). The same board open in both apps is the designed foreign-writer story — base has no git so there is no committer contention, no board-level lock (files-first stays absolute), no gate; open-now flags go per-edition on the shared record so restoration stays edition-local, and the popover gains a pid-liveness-checked "Also open in Lanework Pro" line. The clipboard staging store homes in the App Group container, making cross-edition copy/paste full-fidelity; the degraded fallback remains for genuinely missing snapshots. Board: Gap card 7a244f9f → Resolved (Gaps lane now empty); App Group Implementation card gains the flags/staging/handler bullets. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -80,7 +80,7 @@ The non-modal banner named throughout the read- and write-side rules above is on
|
||||
### Launch and window lifecycle (settled)
|
||||
|
||||
- **Restoration is a preference** — "Restore open boards at launch" in Settings (⌘, — 11-command-nexus.md), default on. On: boards open at last quit reopen (bookmark-resolved), with their per-board frames and 05's card-window restoration. Off: every launch starts at welcome.
|
||||
- **The restoration set is a live open marker, never an at-quit write** (settled): each registry record (Per-board app state below) carries an open-now flag, set when the board's window opens and cleared on *user-initiated* close — quit's teardown closes deliberately leave it standing (the boards were open at quit by definition; teardown distinguishes user-close from quit-close, and that distinction is the whole mechanism). Restoration reads the flagged records, reopening by `lastOpened` order. Crash recovery falls out for free: after a crash the flags describe what was open at crash time, so relaunch restores it — no separate recovery logic, no once-at-quit stamp to race teardown or miss on a crash. The preference gates only whether the flagged set is consulted; the flags are maintained regardless.
|
||||
- **The restoration set is a live open marker, never an at-quit write** (settled): each registry record (Per-board app state below) carries an open-now flag — **per-edition**, like the grant slots (12-editions.md): an edition restores only the boards *it* had open — set when the board's window opens and cleared on *user-initiated* close — quit's teardown closes deliberately leave it standing (the boards were open at quit by definition; teardown distinguishes user-close from quit-close, and that distinction is the whole mechanism). Restoration reads the flagged records, reopening by `lastOpened` order. Crash recovery falls out for free: after a crash the flags describe what was open at crash time, so relaunch restores it — no separate recovery logic, no once-at-quit stamp to race teardown or miss on a crash. The preference gates only whether the flagged set is consulted; the flags are maintained regardless.
|
||||
- **Welcome appears only when nothing restores** — restoration off, nothing was open, or every restoration failed. Always reachable via Window ▸ Welcome to Lanework. Opening a board from welcome closes welcome.
|
||||
- **Every open passes through a pre-snapshot loading state** (ruled 2026-07-29): the board window appears **immediately** — welcome click, File ▸ Open…, Finder double-click, restoration alike — at its saved frame, its chrome carrying the registry record's cached title and icon (the same no-scan sources the welcome row reads; a first-ever open shows the folder name, the record's provisional display name). The content area holds a quiet loading surface: a centered system spinner appearing only after a short grace (~200 ms) so ordinary fast opens never flash it — no skeleton lanes, the motion language animates real data only. The first snapshot replaces the surface in place (a snap — there is no prior arrangement to animate from). **The walk is cancellable**: ⌘W during loading cancels it and closes the window, an ordinary user-initiated close clearing the open-now flag. **Restoration is parallel**: every flagged window appears at once in loading state (stacked by `lastOpened` order), each walk independent — a slow network board never delays the others and stays closeable while it loads. Failure keeps its settled surface: a fail-fast walk retires its loading window and lands row-level on welcome — welcome returning if the open came from it or from Finder — never a loading window morphing into an error display. The open walk earns no in-progress info row: the loading state is the surface, and the info-row class stays scoped to copy-shaped work.
|
||||
- **Closing the last board window leaves the app windowless** (menu bar alive) — the close is respected. Reactivation (Dock click) with no windows shows welcome.
|
||||
|
||||
@@ -25,7 +25,7 @@ Selection, drag & drop, keyboard, clipboard, search. This is where the old app s
|
||||
|
||||
## Clipboard
|
||||
|
||||
- ⌘X/⌘C/⌘V on cards **and lanes** (resettled — lanes joined the clipboard so cross-board structure transfer has a keyboard path under the every-function contract; the cards-XOR-lanes selection rule means the clipboard holds cards or lanes, never both). Hybrid clipboard: pasteboard carries a JSON manifest + plain text; full folder snapshots staged in Application Support so paste reproduces the item byte-for-byte — cards, attachments and all — across boards. Each manifest entry embeds the full `index.md` as a staging-less fallback (a lane entry embeds its cards' too, attachment-less). **Staging lifecycle** (settled): snapshots are staged **eagerly at ⌘C/⌘X time** — copy captures the source as it is at the gesture, immune to later deletion or unmount — and the store holds at most the *current* copy: a new Lanework copy replaces the previous snapshot, and a sweep at launch and on each copy purges entries the pasteboard no longer references (another app taking the pasteboard orphans the snapshot; the next sweep collects it). The snapshot survives relaunch exactly as long as the pasteboard still points at it — a copy made before quitting pastes whole after restart. **A paste is an import boundary, so normalization applies** (settled 2026-07-28 — 01-storage-format.md's loose-file rule): loose files the staged snapshot carries beside a card's `index.md` land in the pasted card's `attachments/`, Finder-renamed on collision — nothing the snapshot preserved is dropped on arrival. **A degraded paste is loud, never silent** (the banner vocabulary — 02-architecture.md): if the staged snapshot is missing or unreadable at paste time, paste falls back to the embedded `index.md` — content intact, files absent — and a **loss row** (warning tone, user-dismissed) names what was lost with true totals, attachments and loose files alike, folding plurals ("Pasted 'Fix login' without its 3 attachments"; "Pasted 2 items without their 5 files" — BannerCenter owns the phrasing, the manifest records each entry's staged file count so the totals stay honest); the user never discovers an empty `attachments/` later. **The pasteboard is re-read lazily, and the brief lie is accepted** (settled): changeCount is checked on activation, on menu validation, and before paste — no timers; a background app taking the pasteboard while Lanework stays frontmost can leave Edit ▸ Paste enabled until the next check, and the paste itself re-validates and no-ops — nothing stale ever lands, which is the guarantee that matters.
|
||||
- ⌘X/⌘C/⌘V on cards **and lanes** (resettled — lanes joined the clipboard so cross-board structure transfer has a keyboard path under the every-function contract; the cards-XOR-lanes selection rule means the clipboard holds cards or lanes, never both). Hybrid clipboard: pasteboard carries a JSON manifest + plain text; full folder snapshots staged in the shared App Group container (12-editions.md — so a copy in one edition pastes full-fidelity in the other) so paste reproduces the item byte-for-byte — cards, attachments and all — across boards. Each manifest entry embeds the full `index.md` as a staging-less fallback (a lane entry embeds its cards' too, attachment-less). **Staging lifecycle** (settled): snapshots are staged **eagerly at ⌘C/⌘X time** — copy captures the source as it is at the gesture, immune to later deletion or unmount — and the store holds at most the *current* copy: a new Lanework copy replaces the previous snapshot, and a sweep at launch and on each copy purges entries the pasteboard no longer references (another app taking the pasteboard orphans the snapshot; the next sweep collects it). The snapshot survives relaunch exactly as long as the pasteboard still points at it — a copy made before quitting pastes whole after restart. **A paste is an import boundary, so normalization applies** (settled 2026-07-28 — 01-storage-format.md's loose-file rule): loose files the staged snapshot carries beside a card's `index.md` land in the pasted card's `attachments/`, Finder-renamed on collision — nothing the snapshot preserved is dropped on arrival. **A degraded paste is loud, never silent** (the banner vocabulary — 02-architecture.md): if the staged snapshot is missing or unreadable at paste time, paste falls back to the embedded `index.md` — content intact, files absent — and a **loss row** (warning tone, user-dismissed) names what was lost with true totals, attachments and loose files alike, folding plurals ("Pasted 'Fix login' without its 3 attachments"; "Pasted 2 items without their 5 files" — BannerCenter owns the phrasing, the manifest records each entry's staged file count so the totals stay honest); the user never discovers an empty `attachments/` later. **The pasteboard is re-read lazily, and the brief lie is accepted** (settled): changeCount is checked on activation, on menu validation, and before paste — no timers; a background app taking the pasteboard while Lanework stays frontmost can leave Edit ▸ Paste enabled until the next check, and the paste itself re-validates and no-ops — nothing stale ever lands, which is the guarantee that matters.
|
||||
- **Cut is Finder-style deferred**: cut items dim in place until paste moves them; voided if another app takes the pasteboard or the source board closes; second paste materializes copies. **Deletion voids per item**: a cut item that is deleted (moved to the trash or destroyed) or vanishes externally before paste drops out of the pending cut — 02-architecture.md's UUID-set rule; transient state never resurrects what's gone — so paste moves only the survivors, and a cut voided down to nothing is simply void (paste disabled, no error).
|
||||
- Paste lands after the anchor card (or appends to a selected lane); a multi-selection anchors at its last member in flatten order — the ⌘N target rule's shared anchor (The map below). Copies keep `created` (a duplicate is a fork) and take fresh GUID/`order`/`modified`. **A trash selection never anchors paste** (settled — the ⌘N target rule's own wording, returned to the precedent it cites): ⌘V stays enabled and behaves exactly as with nothing selected — a card payload appends to the last-active lane, a lane payload lands at the board's right end; the trash is never the destination (▸ The trash), and a trashed card's live disk-lane never leaks in as "the selected card's lane".
|
||||
- **Lane paste** lands after the anchor lane — the selected lane, or the selected card's lane (several selected: the last, per the shared anchor rule); nothing selected = the board's right end. Semantics mirror the drag pair above exactly: a pasted *copy* takes fresh GUIDs throughout; a cut-paste is the ⌘-drag move — the folder moves whole (nothing lane-nested to strip or carry — the trash is board-level, resettled 2026-07-28). **Pasting into the source board is supported and is the within-board lane duplicate** (settled): fresh GUIDs apply as anywhere else, no menu-validation special case — the drag path deliberately lacks this operation (⌥ ignored on lane drags, above), the clipboard is its one home.
|
||||
|
||||
@@ -12,7 +12,13 @@ Why a real split and not feature flags: the editions differ at the binary level
|
||||
|
||||
## Distribution (settled)
|
||||
|
||||
Separate Mac App Store apps — real Xcode targets, not an IAP unlock. Both editions declare the same `.kanban` package UTI (`dev.rzen.indie.kanban-board`) verbatim, so any board opens in either app; the format is one format, owned by 01-storage-format.md, with no edition-specific keys. Upgrade path is buying the other app; boards need no migration whatsoever (files are the interchange). **App-side state crosses the boundary through a shared App Group** (ruled 2026-07-29): every edition declares the family group — `group.dev.rzen.indie.Kanban` — and the board registry with its Application Support peers (02-architecture.md ▸ Per-board app state) homes in the group container **from day one**, so there is no migration and no ordering dependency; the entitlement must be in base's entitlements before base 2.0 ships, since base ships first. A paying upgrader launches Pro onto their full recents, frames, and settings — never an empty home screen. The honest caveat, stated: **security-scoped bookmarks never cross apps** (minted per sandbox, App Group or not), so the *list* transfers while *access* re-grants per board — a record another edition minted resolves unavailable-until-reopened, and the first click runs an open panel pre-anchored at the recorded path: one click + Grant per board, once per edition. Teams joins the same group when it arrives — the container is team-scoped, and a future edition's bundle id declares the same group id with zero further ceremony. Secrets stay Keychain-only as ever (07-sync-collab.md — the keychain access group is its own, existing mechanism); whether the clipboard's staging store joins the group container is the cross-edition clipboard question, tracked separately (the both-editions-installed ruling).
|
||||
Separate Mac App Store apps — real Xcode targets, not an IAP unlock. Both editions declare the same `.kanban` package UTI (`dev.rzen.indie.kanban-board`) verbatim, so any board opens in either app; the format is one format, owned by 01-storage-format.md, with no edition-specific keys. Upgrade path is buying the other app; boards need no migration whatsoever (files are the interchange). **App-side state crosses the boundary through a shared App Group** (ruled 2026-07-29): every edition declares the family group — `group.dev.rzen.indie.Kanban` — and the board registry with its Application Support peers (02-architecture.md ▸ Per-board app state) homes in the group container **from day one**, so there is no migration and no ordering dependency; the entitlement must be in base's entitlements before base 2.0 ships, since base ships first. A paying upgrader launches Pro onto their full recents, frames, and settings — never an empty home screen. The honest caveat, stated: **security-scoped bookmarks never cross apps** (minted per sandbox, App Group or not), so the *list* transfers while *access* re-grants per board — a record another edition minted resolves unavailable-until-reopened, and the first click runs an open panel pre-anchored at the recorded path: one click + Grant per board, once per edition. Teams joins the same group when it arrives — the container is team-scoped, and a future edition's bundle id declares the same group id with zero further ceremony. Secrets stay Keychain-only as ever (07-sync-collab.md — the keychain access group is its own, existing mechanism); the clipboard's staging store joins the group container too (Both editions installed below).
|
||||
|
||||
**Both editions installed** (ruled 2026-07-29) — a supported steady state, not a transition to hurry past:
|
||||
|
||||
- **Pro claims the `.kanban` default on first launch, once.** The purchase is the consent — `.kanban` is the family's own exported UTI, not a shared public type, so the premium edition claiming its own documents is legitimate, and never asking would make the upgrade feel broken (boards keep opening in base). The claim is one-time: a user who later reassigns the default back to base in Finder is respected — Pro never re-asserts. Base never claims and never asks; until Pro's claim, LaunchServices' own resolution stands.
|
||||
- **The same board open in both apps at once is fine — it is the designed foreign-writer story, not a new hazard.** Base has no git (the inert-`.git` posture above): it never commits and never touches the repo, so there is no committer contention — base writing into a Pro-open board is exactly an agent writing from a terminal, which Pro's machinery (index.lock retry, flush-before-overwrite, abort-own-leftovers, "both versions exist as commits" — 06/07) was designed for; base sees Pro's writes as ordinary foreign reloads. No board-level lock — 02's files-first rule is absolute (no sidecar files), and no gate: "Open with…" into the second edition just works. Two conveniences ride the shared registry: **open-now flags are per-edition** (like the grant slots — an edition restores only the boards *it* had open), and the board popover carries a contextual awareness line ("Also open in Lanework Pro") read from the other edition's flag, pid-liveness-checked so crash residue never lies — a line, never a gate.
|
||||
- **The clipboard staging store homes in the group container** beside the registry, so ⌘C in one edition pastes **full-fidelity** in the other — snapshot, attachments and all (04-interactions.md's staging lifecycle unchanged; both editions' launch/copy sweeps compute the same answer from the same pasteboard). The degraded embedded-`index.md` fallback and its loss row remain for genuinely missing snapshots, no longer the structural cross-edition outcome.
|
||||
|
||||
## The provider seam
|
||||
|
||||
|
||||
Reference in New Issue
Block a user