An unopenable repository fails loudly — the standing row, the paused surface, the honest heal

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-06 18:44:12 -04:00
parent 52df210284
commit 0d8ecdb78b
20 changed files with 938 additions and 42 deletions
+31 -1
View File
@@ -462,10 +462,40 @@ public final class BoardStore: HealHost {
suspension: banners.historySuspension,
operations: banners.operations,
signposts: banners.signposts,
gitFailures: banners.gitFailures
gitFailures: banners.gitFailures,
repositoryUnreadable: banners.isRepositoryUnreadable
)
}
/// **The unreadable repository, raised or healed** (06-history-undo.md Rules, "A `.git` that
/// isn't a valid repository still reads as git mode and fails loudly", ruled 2026-07-31) the
/// session's one call for a condition that is the *git state's* truth rather than this store's
/// (`HistoryStore.isRepositoryUnreadable`, wired in `AppModel.beginSession`).
///
/// It does two things because the ruling asks for two: the row stands on the strip, and it is
/// **announced** "announced per 10-accessibility.md", which makes a standing banner "an
/// accessibility element announced when it appears and when it clears". This condition never
/// arrives on a reload (it is detected at open and healed by the paused engine's own re-read), so
/// it takes `announceLockChange(from:)`'s path exactly: through `BoardAnnouncer`'s ladder rather
/// than posting directly, so the sentence a user hears and the sentence they read off the row are
/// one string.
///
/// Idempotent, and silent when nothing changed: the 15 s re-read that keeps confirming an
/// unreadable repository must not say so every 15 s.
public func noteRepositoryUnreadable(_ unreadable: Bool) {
guard banners.isRepositoryUnreadable != unreadable else { return }
if unreadable {
Self.logger.error("this board's git repository could not be opened — history is paused")
banners.raiseRepositoryUnreadable()
} else {
banners.clearRepositoryUnreadable()
}
var facts = BoardAnnouncer.ReloadFacts()
facts.repositoryUnreadableBefore = !unreadable
facts.repositoryUnreadableAfter = unreadable
announce(BoardAnnouncer.speech(for: facts))
}
// MARK: Wiring
/// The watcher's bracket calls, injected rather than owned: the registry holds the watcher and