diff --git a/DESIGN/02-architecture.md b/DESIGN/02-architecture.md index 63bfc73..d193c34 100644 --- a/DESIGN/02-architecture.md +++ b/DESIGN/02-architecture.md @@ -67,7 +67,7 @@ The non-modal banner named throughout the read- and write-side rules above is on - **Hosted by the window of origin.** Every window hosts a banner strip; a condition surfaces in the window whose action produced it — debounced body save, attachment drop, and raw-source Apply failures in their card window; reload breakage, one-shot write failures, commit failures, and lock states in the board window. A card window that closes while its condition persists re-homes the banner to the board window (the condition is still true; it must stay visible somewhere). - **One-shots dismiss, conditions heal.** One-shot failures ("Couldn't move 'Fix login' — disk full") carry an explicit dismiss control and no timeout — an error never evaporates unread. Persistent conditions (reload breakage, suspended auto-commit, read-only locks) have no dismiss: they describe ongoing state, standing until the next success clears them, per the rules above. -- **Concurrent conditions stack.** The strip presents independent rows, precedence-ordered: **in-progress rows (pinned) >** read-only lock > reload breakage > one-shot write failures > **loss rows** > commit and attachment failures **> passive info rows** (the remote-change signpost); newest first within a class. **Loss rows are the warning-tone class for non-failure losses** (settled 2026-07-28): content that didn't arrive though nothing failed — folders skipped from a Finder drop, a legacy migration's folded notices, their future kin (the degraded paste left the class 2026-07-29 — a snapshot-less paste now refuses outright, a one-shot failure, 04-interactions.md ▸ Clipboard). They take the one-shot's lifecycle (dismissable, untimed — a loss the user didn't notice is the harm, so it never auto-expires), rank below the true failures (an action that didn't happen outranks one that partially did), and above the ambient notices. Each row heals or dismisses independently; beyond three rows the remainder collapse behind a "+N more" disclosure. **In-progress rows are exempt from the collapse and don't count toward its budget** (settled): they are the strip's only explanation for a bracket's write lock and for a close/quit deferring teardown, and the copy rows carry the reachable Cancel — a spinner may never hide behind "+N more". They're safe to pin: few at once, self-clearing, **newest first within the class like every other** (ratified — insertion order; the class rarely holds more than two rows, and one ordering rule beats a special case). Passive info rows rank last and may collapse — calm by design, nothing gated on seeing them instantly. +- **Concurrent conditions stack.** The strip presents independent rows, precedence-ordered: **in-progress rows (pinned) >** read-only lock > reload breakage > one-shot write failures > **loss rows** > commit and attachment failures **> passive info rows** (the remote-change signpost); newest first within a class. **The one-shot failure class carries two shapes** (settled 2026-07-31): the `BoardWriteError`-shaped write failure, and a message-carrying **git-operation failure** — the operation named in the user's words plus the underlying error, phrasing still BannerCenter's — because failures rank by what they are, not by which error vocabulary threw them. A failed undo restore, branch switch, or (pro-m2) pull/push is an action that didn't happen: it presents in the error tone at the failure rank, never as a warning-tone loss row (the shipped loss-row compromise is retired). Git operations stay off the closed `WriteOperation` vocabulary — only the banner tier learns the second shape. Recovery *notices* — "a branch switch was interrupted — the previous state is restored" — report a success, not a failure, and stay warning-tone. **Loss rows are the warning-tone class for non-failure losses** (settled 2026-07-28): content that didn't arrive though nothing failed — folders skipped from a Finder drop, a legacy migration's folded notices, their future kin (the degraded paste left the class 2026-07-29 — a snapshot-less paste now refuses outright, a one-shot failure, 04-interactions.md ▸ Clipboard). They take the one-shot's lifecycle (dismissable, untimed — a loss the user didn't notice is the harm, so it never auto-expires), rank below the true failures (an action that didn't happen outranks one that partially did), and above the ambient notices. Each row heals or dismisses independently; beyond three rows the remainder collapse behind a "+N more" disclosure. **In-progress rows are exempt from the collapse and don't count toward its budget** (settled): they are the strip's only explanation for a bracket's write lock and for a close/quit deferring teardown, and the copy rows carry the reachable Cancel — a spinner may never hide behind "+N more". They're safe to pin: few at once, self-clearing, **newest first within the class like every other** (ratified — insertion order; the class rarely holds more than two rows, and one ordering rule beats a special case). Passive info rows rank last and may collapse — calm by design, nothing gated on seeing them instantly. - **Tones, not components.** The banner has kinds — error, warning, info — sharing layout and the accessibility announcement path (10-accessibility.md). The card window's remote-change signpost (07-sync-collab.md) is this same component in the info tone: visually calm, no error color. **Its lifecycle is the one-shot's — dismissable, untimed** (settled): 07's "transient" means non-modal and non-blocking, never auto-expiring; the strip has exactly two lifecycles (one-shots dismiss, conditions heal) and the signpost doesn't add a third. - **In-progress operations are info rows** (settled): bracketed git operations ("Pulling…", "Switching to 'main'…") and long non-git work (big-board Duplicate, template instantiation, large attachment imports, and cross-board transfers — drag copies and moves, staged-clipboard pastes) each show an info-tone row with a spinner — determinate where progress is knowable. Completion clears the row (the VoiceOver completion announcement of 10-accessibility.md rides the same event); failure swaps it for the error row. Sighted and VoiceOver users learn one vocabulary. - **Cancel appears on safe copies only** (settled): copy-shaped work — attachment imports, Duplicate, template instantiation, cross-board copies and pastes — carries Cancel, meaning "remove the partial copy, nothing lost"; a cross-board ⌘-drag *move* cancels the same way during its copy phase — the source deletes only after the copy lands, so Cancel leaves the original untouched. Git brackets get no Cancel: seconds long, and aborting a rebase mid-flight is a repair job, not a cancel.