Build the template chooser and Save as Template
The chooser completes its three tiers: bundled by template order, then keyed user templates, then keyless boards by display name — and a malformed user template still lists, by folder name with the loader's own sentence on the row, never failing its neighbours. The store is re-scanned on every presentation and on app activation, the Reveal round trip made honest without watching a folder 09 deliberately leaves unwatched; Reveal lives in the chooser's header and mints the store on first press. Save as Template repeats Duplicate's sequence — progress row with Cancel, flush, detached cancellable copy — through the engine: mint the store, read the next user order before the copy can count itself, Finder-ladder the name, copy excluding .git and .trash/, then stamp the whole template: mapping on the landed copy through updateIndex, with no bracket because the copy lives outside every watched board. Folder attributes deliberately don't carry — the one lock the command stays live under is the read-only-DMG one, and carrying its mode bits would mint a read-only template in the user's own store; the command gates instead on the real hazard, unsaved card content. A signpost names the template only when the ladder renamed it. One name ladder now serves Duplicate and the store. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -710,6 +710,22 @@ public final class AppModel {
|
||||
await coordinator(for: ref).flushPendingWork()
|
||||
}
|
||||
|
||||
/// Whether any of this board's card windows is holding content the files do not have — a dirty
|
||||
/// Edit buffer or a typed-in raw-source outlet (`CardSessionFlushing.holdsUnsavedContent`).
|
||||
///
|
||||
/// **One caller, one rule**: File ▸ Save as Template's carve-out from the read-only lock. Under
|
||||
/// the unwritable-location lock the item stays live — "reads the board, writes Application
|
||||
/// Support" — but only while no such session exists, because the lock has suspended exactly the
|
||||
/// saves that would flush one and 09-templates.md's never-misses-keystrokes guarantee outranks
|
||||
/// the item's availability (02-architecture.md ▸ Live-reload resilience, settled scoping).
|
||||
///
|
||||
/// It asks the sessions rather than the store: the content in question is in *memory*, in the
|
||||
/// card windows, which is the whole reason the flush cannot reach it.
|
||||
public func hasUnsavedCardContent(for ref: BoardWindowRef) -> Bool {
|
||||
guard let session = sessions[ref] else { return false }
|
||||
return session.cardRefs.contains { cardSessions[$0]?.holdsUnsavedContent == true }
|
||||
}
|
||||
|
||||
/// Quit: the same sequence, once per open board, **sequentially**.
|
||||
///
|
||||
/// Sequential rather than concurrent so each board's ordering is the one 02 fixes rather than
|
||||
|
||||
Reference in New Issue
Block a user