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:
2026-07-28 19:39:18 -04:00
parent e7b48d2d53
commit 3aa80db2a4
17 changed files with 1358 additions and 90 deletions
+7 -16
View File
@@ -36,22 +36,13 @@ struct FutureCommand: View {
}
}
// MARK: - File Save as Template
/// File Save as Template no default chord (11-command-nexus.md; 09-templates.md).
///
// m9-templates: copies the open board into the user templates store, close-flushed first exactly as
// Duplicate is (09 Save as Template: "The copy is preceded by the close flush"), `.git` stripped,
// `.trash/` excluded, a `template:` key stamped. Validation will be `acceptsBoardMutations` plus 09's
// one carve-out from the read-only lock live under the unwritable-location state unless an open
// Edit/raw-source session holds unsaved content so it cannot simply borrow
// `DuplicateBoardCommand`'s predicate outright.
struct SaveAsTemplateCommand: View {
var body: some View {
FutureCommand(title: "Save as Template")
}
}
// File Save as Template was the scaffold here and is now live, beside the command it mirrors
// (`SaveAsTemplateCommand`, in `AppCommands.swift` next to `DuplicateBoardCommand`, whose flush
// cancellable copy banner sequence it repeats with a different destination). The diff this file
// predicts, once more: the title did not move, the chord stayed absent, the validation and the
// action filled in the last of them being 09's carve-out from the read-only lock, which is why it
// could not simply borrow Duplicate's predicate.
//
// File Add Attachment (A) was the scaffold here and is now live, beside the focused value it
// reads (`AddAttachmentCommand`, in `CardAttachments.swift`) the diff this file predicts: the
// title and the chord did not move, the validation and the action filled in. The attachment row's