hosystem Engagements

ho-06.2 — The Workshop: three-state glyphs and the heal surface

created 2026-07-11
status complete
type ho-document
project sharibako
ho 06.2
kamae 5
shape ha
splits-from ho-06
agent-tasks
  • Ho-06.2-AT-01.md
  • Ho-06.2-AT-02.md
  • Ho-06.2-AT-03.md

Make the vault's state legible at a glance and its drift reconcilable. This reads the scan cache ho-06.1 built — glyphs on the sidebar rows tell you where each scope lives, a pulled-on-demand drift surface tells you which materialized files have wandered from the vault, and one action reconciles them. Two gate-born chrome items ride along: a Settings scene with an appearance override, and scan-root visibility in the footer.

The ho-06 split. K4 planned ho-06 as one polish ho and flagged it as the densest in the project. It split three ways at ho-06.1's opening:

K4's ho-06 section body still describes the un-split ho; the split is recorded in K4's 2026-07-10 revision header and in K6. Reconciling the section body is a ho-kamae-4-overview-collaborator pass, flagged from this session — not edited here.

Out of scope:


Phase 1 — Think

Five decisions, ratified 2026-07-11. Two facts about the Core surfaces drive most of them, both found by reading the code before deciding:

Decision 1 — Chrome: native top-toolbar; the right-side rail deferred with criteria

The operator floated a right-side collapsible tool rail (icons when closed) replacing the top toolbar. The friction behind it is real — after 06.1 the toolbar carries Rescan, Jump, Sync, Materialize, Preview, and two Add buttons, and this ho adds heal actions. But a right-side action rail cuts against macOS convention (Mac puts actions in the top toolbar or a bottom bar; the right edge is conventionally an inspector — which is exactly what the third NavigationSplitView pane already is), and it commits every later surface (06.3, ho-07) to a non-native idiom. The operator's actual pains — crowding and label legibility — are solvable within the native idiom.

Decision 2 — Glyphs: computed from the scan cache; orphaned markers get synthetic rows

Two parts. The first is forced by cost; the second is a data-model fork.

Decision 3 — Heal: pull-based, session-cached drift; sweep behind one Touch ID; reconcile via materialize

Because drift costs a full decrypt, a "drift indicator on materialized scopes" cannot be ambient — rendering badges at launch would decrypt every scope at launch, prompting Touch ID the moment the window opens, which contradicts ho-06.1's whole achievement (window interactive immediately, no work until asked). So drift is pulled, then cached for the session — parallel to scanReport:

Decision 4 — Settings: a native scene with an appearance override

Gate item. The operator asked for a System/Light/Dark flipper; a Settings scene is its native home. SwiftUI's Settings {} scene wires ⌘, automatically; a stored enum drives .preferredColorScheme; persistence is @AppStorage (UserDefaults), the right backend for a pure UI preference.

Gate item. "Where is it scanning?" has no UI answer — roots live invisibly in config.yaml. Waymarking answered the vault half (footer names vault + remote); this answers the scan-root half.

Discovery (deferred to execution)


Phase 2 — Execute

Branch ho-06.2 off main. Three agent tasks, in order — -02 extends AT-01's sidebar rows; AT-03 is independent.

Ho-06.2-AT-01 — Glyphs + orphan rows

glyphState(forScope:) computed over the scan cache ∩ scopes (Decision 2a, tested in WorkshopModel); sidebar renders live_here / live_elsewhere glyphs on scope rows and an "Unlinked markers" section for orphaned markers and scan failures (Decision 2b, surfacing only). The scope row view gains a slot for AT-02's drift badge. Cache-only, synchronous, no Touch ID. Model: claude-sonnet-4-6. → /agent-tasks/Ho-06.2-AT-01.md

Verifiable: every scope row shows a glyph reflecting whether its marker is in the scan roots; a .sharibako pointing at a nonexistent scope appears as an Unlinked-markers row, not a missing entry.

Ho-06.2-AT-02 — Heal surface

The driftReports session cache; the Check-drift sweep intent (VaultWorker + Touch ID + activity); per-key drift in the detail pane; per-scope reconcile wired to the existing materialize(force:) flow; drift-driven Materialize-all- stale with a confirmation (Decision 3). Carries the Decision 1 chrome fix — the new actions get toolbar homes, overflow if crowded. Extends AT-01's row view with the drift badge. Model: claude-opus-4-8. → ho-process/agent-tasks/Ho-06.2-AT-02.md

Verifiable: one Touch ID sweeps drift across all live-here scopes; a hand-edited owned line shows as drift on its row and per-key in the detail; reconcile writes it back and the drift clears; Materialize-all-stale reconciles the set behind one confirmation.

Native Settings {} scene with the System/Light/Dark appearance override (@AppStorage.preferredColorScheme, Decision 4); scan-root footer visibility in the waymarking extension (all roots, tooltip paths, Decision 5). Independent of glyph/heal logic. Model: claude-sonnet-4-6. → ho-process/agent-tasks/Ho-06.2-AT-03.md

Verifiable: ⌘, opens Settings and the appearance flipper switches the app between System, Light, and Dark, surviving relaunch; the sidebar footer names every configured scan root with full paths in the tooltip.

Testing and iteration approach

Per task, the rhythm: swift package clean && swift test directly (clean→build→test still trips the known SwiftPM incremental-link bug after SharibakoCore changes) → swift build -Xswiftc -warnings-as-errorsswift-format lint --strict --recursive Sources Testsswiftlint lint --strict → coverage ≥90%. New branching logic — glyphState, orphan derivation, the drift-cache reads, Materialize-all-stale set selection — lives tested in WorkshopModel and its extension files (the ho-06.1 file-split precedent). New View files join ci.yml's named-EXCLUDED regex with justification comments, the established convention.

Tests never touch live user state — injected home / roots / temp vaults only. Drift and glyph tests build ephemeral vaults with markers whose targets have been hand-drifted, and assert the computed state, not the rendering. Day-to-day iteration runs unsigned against the file-based age key (SHARIBAKO_AGE_KEY); the signed build is produced once, at the gate.

Done means

Verification and the gate

  1. The rhythm above, green, on the ho-06.2 branch.
  2. Dogfood gate (signed install + Touch ID) — clean release build via xcodebuild -project xcode/Sharibako.xcodeproj -scheme Sharibako -configuration Release, installed and launched from /Applications explicitly (Spotlight resurfaces stale DerivedData Debug builds — the ho-06.1 gate lesson), real vault, real Keychain:
    • Open the Workshop against the production scan root. Every scope row shows a glyph; a scope that lives on another machine reads as live_elsewhere; a stray .sharibako appears under Unlinked markers.
    • Check drift with one Touch ID; a scope whose .env you hand-edited shows drift on its row and per-key in the detail; reconcile writes it back and the drift clears. A second check within five minutes costs no new Touch ID.
    • Materialize all stale reconciles the drifted set behind one confirmation.
    • ⌘, opens Settings; flip to Dark, quit, relaunch — the app stays Dark.
    • The footer names the production scan root(s) with full paths in the tooltip. Not done until this passes.

Phase 3 —

Filled at ho close, 2026-07-11, after the signed-install + Touch-ID gate.

The pull-based drift model held. One Touch ID swept every live-here scope and a reconcile within the window cost no new prompt — Decision 3's whole bet (drift is pulled + session-cached, never ambient-at-launch, one auth for the sweep via ho-06.1's reuse window) survived contact with a real vault and real Keychain. Glyphs read correctly (all scopes live-here on the dogfood vault); Check-drift → per-key drift → reconcile → clear was the tight loop as designed. The orphan/failure "Unlinked markers" rows were not exercised at this gate (the dogfood vault had no orphaned markers) — surfacing-only, tested at the unit level, but unverified in the running UI; carry to the next gate that has a stray .sharibako.

The native-chrome bet did NOT hold — the rail revisit criterion is met. The gate proved Decision 1 wrong in practice: the overflow » → "More" → nested submenu holding Add Shared Secret / Preview .env / Jump to Directory reads as bad, buried UI ("NOT an acceptable UI"). The top toolbar cannot carry the Workshop's action set legibly, and the operator's original right-side collapsible panel instinct is the answer. This is the standing-chrome-the-toolbar-can't-hold trigger Decision 1 named. Forward-only: owed a dedicated chrome/right-panel ho (not a reopening of 06.2), which also reconsiders whether appearance belongs as a persistent control at the base of that panel rather than only in Settings (the operator floated a top-bar light/dark/system toggle; Settings is the Mac-native home, but a panel changes the calculus).

What the tests didn't catch — View-layer, by (that is what the gate is for):

Followups (for ho-06.3's opening list / the K4 overview pass):

Closing this ho

Closing = fill this Reflect + flip status: complete + write the state-summary block to the project's K6 (ho-process/kamae-6-sharibako-state-memory.md, refreshing the block at the top, verbatim labels and order) + append a build-record entry to K4 (ho-process/kamae-4-sharibako-ho-overview.md, , append-only). The block:

STATE-SUMMARY


Appendix — fresh-session bootstrap

To execute this ho in a new Claude Code session, load and run prompts/ho-06.2-execution-driver.md.


Authored 2026-07-11 (Think ratified same day, five decisions, split from K4's planned ho-06 at ho-06.1's opening). Executed and closed 2026-07-11: AT-01 → AT-02 → AT-03 + two gate fixes, signed-install + Touch-ID gate passed (chrome finding owed a followup ho, ).

Rendered from the corpus, verbatim · source on GitHub →

ingested: sharibako @ 75a79d08a2b3 · ho-system @ 0f93b7fa32f7 · the glossary · the colophon