Give the registry live display-state write-through and record-before-load

BoardRecord carries icon/iconColor; recordOpen/recordClose stamp them with the
display name, and a displayStateDelegate on the store (wired in BoardWindowHost
beside onFrameChanged) syncs all three through BoardRegistry.syncDisplayState on
every successful reload — welcome rows now wear the board's own icon and follow
in-app renames live. recordOpen now runs before the load with the folder name as
a brand-new record's provisional display name, so a first open that fails
fail-fast still lands in recents carrying the failure row-level (02's rule); an
existing record's cached name survives a failing retry, and the welcome fallback
list remains only for failures naming no record at all.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-07-27 20:44:30 -04:00
parent cacd48cb0f
commit f2d9f3ad07
10 changed files with 608 additions and 41 deletions
+27 -10
View File
@@ -264,17 +264,15 @@ private struct RecentBoardRow: View {
var body: some View {
HStack(spacing: 12) {
// The board default symbol, on every row.
//
// The record carries no icon. 02 § Per-board app state settles that it should "the
// row's title and icon are registry-cached too with live write-through" and today it
// holds only the display name and the counts. An `icon`/`iconColor` stamp joining
// `recordClose` (and the store's reload path, which is where the write-through half
// lives) is what turns this into the board's own glyph; until then a row that guessed
// would be worse than one that is honestly generic.
Image(systemName: ItemSymbol.board)
// The board's own icon and tint registry-cached with live write-through (02 §
// Per-board app state: "the row's title and icon are registry-cached too with live
// write-through"). A record with no override, or one naming a symbol this system
// cannot draw, falls back to the board-default glyph in secondary the same
// lenient-fallback shape every other icon site in the app uses (`ItemSymbol`,
// `Palette`), applied here to the registry's cached string instead of a live snapshot.
Image(systemName: iconName)
.font(.system(size: 22))
.foregroundStyle(.secondary)
.foregroundStyle(iconTint)
.frame(width: 34, height: 34)
.accessibilityHidden(true)
@@ -301,6 +299,25 @@ private struct RecentBoardRow: View {
.accessibilityElement(children: .combine)
}
/// `row.icon`'s symbol if it names one this system can draw, the board default otherwise
/// `ItemSymbol.name(_:fallback:)`'s rule, restated for a plain cached string rather than a
/// `FieldValue`: a record carries no `FieldValue`, so `missing`/`malformed`/`unrecognized`
/// have already folded into one `nil` by the time it reaches here.
private var iconName: String {
guard let icon = row.icon, ItemSymbol.exists(icon) else { return ItemSymbol.board }
return icon
}
/// `row.iconColor`'s tint, or the standard secondary one `LaneView`'s card-face `iconTint`,
/// same fallback, same reason: an uncoloured icon is chrome, and chrome is secondary.
private var iconTint: AnyShapeStyle {
if let iconColor = row.iconColor, let color = Palette.color(named: iconColor) {
AnyShapeStyle(color)
} else {
AnyShapeStyle(.secondary)
}
}
@ViewBuilder
private var caption: some View {
switch row.caption {