The decision surface — a refused open becomes a live repair, in place

Phase 3 of the decision surface, completing the card (01 ▸ Malformed
input, settled 2026-07-31). An attended open's fail-fast walk transforms
the loading window's content into one aggregated surface — never a
sheet, never a chain: defects grouped by class, each class stated once
with its files listed (Reveal in Finder + Open in Editor per row), a
class-level default preselected, per-item override behind a disclosure.
Only honest choices: YAML and malformed-schema get Editor + Re-check
(Skip below the root); newer-than-app gets Skip alone and blocks the
board at the root; the two root repairs — minted index, schema: 1 stamp
— are defaults. Repair and Open applies fixes in one store-less write
bracket and re-walks: clean proceeds, remainder re-aggregates into the
same surface. Cancel and ⌘W retire to welcome's row; restored opens
never see the surface at all (OpenOrigin rides the PendingOpen carrier).

Skips are per-open consent that rides the session — the store retains
the skip set and every reload passes it — and the opened board posts a
warning-tone notice naming what was left out, each item's Reveal riding
the banner strip's new reveal control. On Pro boards the repair bracket
binds its own EchoLedger, heal-marks everything, and the store adopts it
before the committer starts, so repairs land as one separate commit
authored Lanework Integrity — pinned end to end. Also fixed en route: a
retired loading window left its close interception installed and
returned false from windowShouldClose forever, blocking quit.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-01 10:52:02 -04:00
parent 0933ac1b01
commit 31fee00c73
17 changed files with 2448 additions and 107 deletions
+28
View File
@@ -43,6 +43,34 @@ enum AccessibilityPhrases {
/// would leave that window silent.
static let boardLoading = "Loading board"
/// **What the decision surface is called** (01-storage-format.md § Malformed input, settled
/// 2026-07-31; `BoardDecisionSurface`) its heading *and* the group label a VoiceOver user hears
/// on entering it, which are one string here for the banner row's reason exactly: the sentence
/// heard and the sentence read are one, or they are two descriptions of one thing waiting to
/// disagree.
///
/// It says the board did not open and stops there. What is wrong is the sections' to say the
/// surface exists precisely because there is usually more than one answer to that.
static let decisionSurfaceLabel = "Lanework couldn't open this board"
/// The surface's own subtitle: how much is wrong, and what to do about it. The count is the
/// number of affected *files*, which is what the rows below it are.
static func decisionSurfaceSummary(defects count: Int) -> String {
let subject = count == 1 ? "One file needs" : "\(count) files need"
return "\(subject) a decision before this board can open."
}
/// One affected file, as one utterance: **path then reason**, in that order, because the path is
/// what identifies the row and the reason is what the user is deciding about
/// (01: "lists the affected files (path + specifics )").
///
/// The reason is the loader's own sentence, unrewritten the same specifics the row shows for
/// `BannerCenter.causePhrase`'s reason: fail-fast's diagnostics are specific in a way a
/// re-phrasing would not be, and the alternative to showing one is a shrug.
static func decisionRowLabel(path: String, reason: String) -> String {
"\(path): \(reason)"
}
/// "3 cards", "1 card" the app's **one** plural folding for a card count, borrowed from
/// `TrashModel.phrase` rather than restated so a lane's spoken count and the trash column's
/// cannot drift apart.