Ratify the registry freshness stamp — two editions, one file, stat-checked
2026-07-30 findings-resolution ruling (Redesign card 89862cef): the
mtime+size stamp the App Group implementation introduced (566deab) is
the designed mechanism, not an invention — checked before every registry
read and mutation, re-read on change, write on top of the other
edition's latest state. The microsecond check-to-write window stays
last-writer-wins, costing at most one convenience field. Deliberately no
NSFileCoordinator and no per-record split. Code already conforms.
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -97,6 +97,7 @@ State that belongs to the app, not the user's files — the recents list, per-bo
|
||||
- **Keyed by file identity, never by path.** Bookmarks track renames and moves on the same volume; an opened URL is matched to its record by bookmark resolution / file identity, so a moved board keeps its settings. The recents list *is* this registry sorted by last-opened. **One bookmark per open board** (settled): the open flow mints it once and threads it through — the persisted record and the live store's mid-session re-resolution share the same bookmark, never two independent mints.
|
||||
- **Recents counts are registry-cached.** The lane/card counts in the welcome window come from the record, stamped at last close — no directory scan at welcome time (which would be slow or hang on big/unavailable boards). Staleness until the next open is accepted. **The counts are working items only** (settled, re-grounded 2026-07-28; extended for the lanes-era trash 2026-07-29): items in `.trash/` don't count — a trashed lane subtracts from the lane count and its nested cards subtract from the card count; the row advertises the board's working size, and the trash is an errand, not inventory. Records that can't be counted show without counts: unavailable boards per Graceful orphaning below; a board that fails to load just fails on open, fail-fast — the welcome row doesn't pre-detect it. **A first open that fails fail-fast still records** (settled): the registry record is created before loading, so the failed board lands in recents carrying fail-fast's specifics — retry after fixing the file is one click, uniform with the restored-board failure row. **The record's provisional display name is the folder name** (settled): fail-fast means the frontmatter can't be trusted, and the folder name is the Finder document name the user just picked; the first successful load replaces it with the cached title. A never-successfully-opened record is not a special class — it lingers in recents like any other, and Forget is the eraser for a genuinely mistaken open. **Records that collapse onto one file identity merge silently** (settled — a restored registry file, or an orphan whose bookmark re-resolves onto a recreated board): on detection, the record with the newest `lastOpened` wins wholesale and the others retire — recents never shows one board twice, and per-board settings are conveniences that don't earn a merge UI.
|
||||
- **The row's title and icon are registry-cached too — with live write-through** (settled): the record carries the board's `title`, `icon`, and `iconColor` beside the counts, and the welcome row reads only the record — it never opens any board's `index.md` (the same hang-avoidance that motivated the counts rule). Unlike the counts' at-close stamp, these three refresh **whenever an open board's reload changes them**: the store already holds the new snapshot, so an in-app Board rename (03-board-ui.md) lands in the record instantly — never a welcome row showing a name the user just changed away from — and a foreign rename of an *open* board rides the same path for free. The honest residual: renaming a board that isn't open (an agent editing its root `index.md`) stays stale until the next open — accepted, the counts' staleness class. The title falls back to the folder name per 01-storage-format.md, cached at the same moments.
|
||||
- **Two editions, one file — freshness by stamp** (ratified 2026-07-30): the registry is a wholesale-rewritten JSON shared by two long-lived processes (12-editions.md ▸ Both editions installed), so every registry read *and* mutation first checks a stat-cheap freshness stamp — mtime + size, read via `FileManager.attributesOfItem` (`URL.resourceValues` caches per-instance and never observes the change) — and a moved stamp re-reads the file before proceeding: a mutation always rewrites on top of the other edition's latest state. Disk is the truth, the in-memory array only a cache, every mutation still saves immediately. The residual check-to-write window is last-writer-wins — microseconds wide, costing at most one convenience field, the loss class these settings already accept. Deliberately **no `NSFileCoordinator`** (blocking presenter machinery on a file a wedged group container can hang — observed in development on this very file) and **no per-record split** (multiplies files and complicates the silent identity-collapse merge for no loss class it closes).
|
||||
- **Files-first stays absolute**: nothing app-private is ever written into the board folder — no frontmatter keys, no sidecar files, no xattrs. Two machines sharing a board via a remote each keep their own record (push-on-commit and window frames are genuinely per-machine choices).
|
||||
- **Graceful orphaning**: a record whose bookmark no longer resolves (board deleted, or moved across volumes where bookmarks can't follow) is orphaned — recents surface it as unavailable with Forget; its settings are conveniences and die with it (accepted).
|
||||
- **App-wide state has the same home.** Not everything app-side is board-scoped: quick-style recents (03-board-ui.md), the SSH host-key assignment table and TOFU fingerprint store (07-sync-collab.md — host-scoped), the last-used card-window size (05-card-window.md), the user template store (09-templates.md — plain board folders, deliberately edition-crossing), and their peers live beside the registry in the App Group container (or the group's shared `UserDefaults` suite where a scalar fits — 12-editions.md) — no per-board record involved. Secrets are the named exception: Keychain only, never here (07).
|
||||
|
||||
Reference in New Issue
Block a user