ho-06.1 — The Workshop: responsiveness and honest feedback
- Ho-06.1-AT-01.md
- Ho-06.1-AT-02.md
- Ho-06.1-AT-03.md
Make the shipped Workshop responsive and honest. This carries the full followup list from ho-05's gate (ho-05 ): async scan/materialize with scan-result caching — the one ratified ho-05 premise that failed — plus waymarking, status announce, reveal ergonomics, and creation feedback. Nothing here is new capability; everything makes what already shipped visible, honest, and non-blocking.
The ho-06 split. K4 planned ho-06 as a single polish ho and flagged it as the densest in the project. The ho-05 gate added this second list. Ratified split, three ways:
- ho-06.1 (this ho) — the gate-driven list: responsiveness + feedback.
- ho-06.2 (provisional) — three-state sidebar glyphs + heal/drift surface. Depends on this ho's scan cache; scoped when opened.
- ho-06.3 (provisional) — first-run wizard, age-key generation + backup nudge, ingest decision matrix as a GUI flow. Whether it further splits (K4's original 06.2/06.3 line) is decided when it opens.
K4's ho-06 entry needs this split recorded — an overview revision, flagged for
a ho-kamae-4-overview-collaborator pass, not edited from this session.
Relief valve. If this ho spills a session, -03 (reveal ergonomics + dialog windows) is the pre-declared drop: it moves whole to the front of ho-06.2. AT-01 and AT-02 do not split further.
Out of scope:
- Three-state glyphs, heal surface — ho-06.2.
- First-run wizard, age-key generation, backup nudge, ingest flow — ho-06.3.
- The linking picker / shared-secret browser — ho-07 (Phase 5).
- The per-entry "plain / not-a-secret" flag — schema decision; parked for its own -2-level conversation.
- Scriptable
init, unconditionalgit initin vault scaffolding, non-atomic ingest — the owed CLI ho. Zero files underSources/SharibakoCLI/change in this ho, same rule as ho-05. - Reveal auto-hide timer and clipboard-clear (named as possible ho-06 refinements in ho-05 Decision 4) — real use has not asked for them; they stay unbuilt.
- Notarization / distribution — ho-08.
Resolves deferred decisions:
- The concurrency posture. ho-05 Decision 2 ratified "synchronous on
@MainActor"; the gate proved it false for tree-walking and network operations. Decision 1 below amends it — the amendment is recorded here, ; ho-05's document stands as written. - Touch ID re-prompt frequency in the GUI (ho-05 left every operation prompting) — Decision 5.
Phase 1 — Think
Six decisions, ratified 2026-07-10. The heaviest is Decision 1; Decisions 3–6 lean on the state it creates.
Decision 1 — Concurrency: a VaultWorker actor; long operations become async intents
The property the synchronous posture provided for free — one vault operation at a time, no concurrent mutation of vault files — must survive the move off-main. Three shapes were weighed:
- A —
VaultWorkeractor. Chosen. A small actor in the app target whose only job is executingSharibakoCoreoperations off the main thread, serially by construction.WorkshopModelstays@Observable @MainActor(ho-05 Decision 2's state model is unchanged); the long-running intents —rescan,materializeSelectedScope,sync— becomeasync, set a publishedactivitystate naming the operation in flight,awaitthe worker, and publish results back on main. Serialization is a property of the type, not a discipline future code has to remember. - B —
nonisolatedwork functions + aTaskper intent. Lighter, but one-operation-at-a-time becomes a busy-flag convention every future intent must honor. Rejected: invariants by convention erode. - C — move
WorkshopModeloff@MainActor. Rejected:@Observableview observation wants main-actor publication, and it relitigates ho-05 Decision 2 wholesale rather than amending it.
Materializer and VaultCore are already Sendable structs — values cross
to the worker without any Core changes. Touch ID / age-key acquisition stays
on the main actor (it is user interaction, not CPU work); only the handle's
URL crosses. Fast single-file operations — reveal, add, rotate, — stay
synchronous: ho-05's Reflect confirmed the premise holds there. sync moves
too: git push/pull is network I/O and beach-balls exactly like scans, just
gated on network speed — the code read surfaced this even though the Reflect
list named only scans.
While activity is non-nil, the toolbar's action buttons disable and the
status surface shows progress. Two rapid clicks serialize; they cannot
interleave.
Decision 2 — Scan cache: in-memory, session-lifetime, populated at launch
materializeSelectedScope today re-walks the entire scan root to find one
scope's marker. The cache: WorkshopModel holds the scan report in memory,
populated by a non-blocking async scan at launch (the window opens
immediately; the scan fills in behind it) and refreshed by Rescan. Materialize
resolves its marker from the cache; a cache miss (marker moved or deleted
externally) falls back to one fresh scan before erroring. Nothing persists to
disk — markers change externally, and a persisted cache would lie across
sessions. This cache also feeds waymarking's jump-to-directory button now and
ho-06.2's glyphs later.
Decision 3 — Waymarking: sidebar footer, detail-pane marker target, jump-to-directory
Three pieces, answering the gate's "which repo am I on":
- Vault indicator in the sidebar footer. A fixed block at the bottom of the
scope sidebar: vault directory + remote, full paths in the tooltip. The
sidebar lists the vault's scopes; its footer names the vault. One small Core
addition rides here:
Conduit.remoteURL()—Conduitcannot report the remote today. Shellsgit remote get-url, returnsnilwhen no remote; mirrors the ho-05Conduit.logpattern (small, tested, belongs in Core). - Marker target in the detail pane. The selected scope's marker directory and materialize target, read from the Decision 2 cache, with honest empty states ("not scanned yet", "no marker found").
- Jump-to-directory button. Its own toolbar block, left of Sync (placement operator-specified at the gate). Opens the selected scope's marker directory in Finder; disabled when the cache holds no marker for the selection.
Decision 4 — Status announce: pulse, alignment, visible labels, honest icon
The status surface exists (ho-05's statusMessage); this makes it announce.
A brief green background pulse when statusMessage changes, red when
errorMessage does — the text stays, no auto-clear timer. The status line's
left edge aligns with the sidebar column instead of spanning the window.
Toolbar buttons show always-visible titles (hover tooltips failed the operator
at the gate). Rescan's magnifyingglass icon is replaced with one that reads
as re-scan, not search — execution picks the symbol.
Decision 5 — Reveal ergonomics: 5-minute Touch ID reuse, .env preview, honest fields
- Shared
LAContext, 5-minute reuse window. The Keychain provider holds oneLAContextwithtouchIDAuthenticationAllowableReuseDurationat the system maximum (LATouchIDAuthenticationMaximumAllowableReuseDuration, 5 minutes) instead of a fresh context per operation. Ratified at the cap rather than the ~2 minutes the Reflect floated, on this read: the exposure is only an unlocked, unattended Mac within minutes of a successful authentication — locking the machine gates re-entry biometrically, and software-class threats are unchanged (a process that can read files could already take the temp key file during any legitimate operation). The window applies to all key loads — reveal, add, rotate, materialize — which is coherent: they load the same key. - Scope-level reveal is a
.envpreview sheet. One Touch ID renders exactly what Materialize would write — composed merge, monospaced, copyable, naming the target path. Doubles as a materialize dry-run. Requires the second Core addition:Materializer.preview(marker:), composing without writing (the composer,buildMaterializedLines, is private and welded to the write path today). Inline reveal-all in the secret list is deferred until real use asks again. - Show-while-typing eye toggle on the secure value fields (Add Secret, Add Shared Entry, rotate).
- Rotate prefills only when revealed. The edit-value field prefills the current value only if it is already revealed; an unrevealed value never silently decrypts into an edit field — the field comes up empty.
Decision 6 — Creation announces; Add dialogs become auxiliary windows
- Every creation announces:
addScope,addSecret, andaddSharedEntrysetstatusMessage("Created shared entry openai-personal") and ride the Decision 4 pulse. The silent-success rule applied to creation — shared entries especially, which have no visible home until ho-07's browser. - The three Add sheets become real SwiftUI windows opened via
openWindow: movable, non-modal, the main window stays interactive mid-add (the operator could not check an existing secret while adding one).WorkshopModelis already environment-injected; the plumbing is modest.
Discovery (deferred to execution)
- SF Symbol choices and pulse mechanics — the honest Rescan symbol, the pulse animation (should respect Reduce Motion), auxiliary window sizing. Execution-level; no ratification needed.
LAContextinvalidation behavior. A cancelled or failed evaluation may invalidate the shared context; the adapter likely needs to recreate it on the next load. If the reuse window misbehaves at the dogfood gate in ways recreation doesn't fix, stop and surface — do not loosen the query's security posture to compensate (AT-03's Stop Condition).
Phase 2 — Execute
Branch ho-06.1 off main. Three agent tasks, executed and verified in
order — AT-02's jump button reads AT-01's cache; AT-03 closes with the gate.
Ho-06.1-AT-01 — Concurrency foundation: VaultWorker, async intents, scan cache
The actor; rescan/materializeSelectedScope/sync become async with a
published activity state and progress UI; launch-time non-blocking scan;
cache-based marker resolution with one-fresh-scan fallback (Decisions 1–2).
Model: claude-opus-4-8.
→ /agent-tasks/Ho-06.1-AT-01.md
Verifiable: a deep scan root no longer beach-balls the window — the UI stays interactive during scan/materialize/sync; materialize resolves from cache without re-walking; rapid action clicks serialize.
Ho-06.1-AT-02 — Waymarking + status announce
Conduit.remoteURL() with unit tests; sidebar footer; detail-pane marker
target; jump-to-directory toolbar block left of Sync; green/red status pulse;
sidebar-aligned status line; visible toolbar labels; honest Rescan icon;
creation announces (Decisions 3–4, 6-announce). Model: claude-sonnet-4-6.
→ ho-process/agent-tasks/Ho-06.1-AT-02.md
Verifiable: the window names its vault and remote; a selected scope shows its marker target and jumps to it in Finder; every action and creation visibly concludes with a pulse.
Ho-06.1-AT-03 — Reveal ergonomics + dialog windows
Shared LAContext with the 5-minute reuse window; Materializer.preview with
unit tests; the .env preview sheet; secure-field eye toggle; rotate
prefill-only-when-revealed; Add sheets → auxiliary windows (Decisions 5,
6-windows). Model: claude-sonnet-4-6.
→ ho-process/agent-tasks/Ho-06.1-AT-03.md
Verifiable: two reveals inside five minutes cost one Touch ID; the preview
matches sharibako materialize output byte-for-byte without writing; Add
windows move and the main window stays live. Then the gate.
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-errors →
swift-format lint --strict --recursive Sources Tests →
swiftlint lint --strict → coverage ≥90%. New branching logic lives tested in
WorkshopModel; VaultWorker is headlessly drivable and carries coverage;
new View files join ci.yml's named-EXCLUDED regex with justification
comments, the established convention. The LAContext cache lives in the
already-excluded Keychain adapter file (dogfood-only, like the rest of it).
Tests never touch live user state — the injected-home/injected-roots seams are airtight after the ho-05 config-leak fix; every new test that reads or writes config or scan roots goes through injection. The launch scan in tests runs against injected temp roots only.
Day-to-day iteration runs unsigned against the file-based age key
(SHARIBAKO_AGE_KEY); the signed build is produced once, at AT-03's gate.
Done means
- No user-triggered action blocks the main thread on tree walks or network:
scan, materialize, and sync run through
VaultWorkerwith visible progress, and the window stays interactive throughout. - Materialize resolves markers from the scan cache; the cache populates at launch without delaying the window, refreshes on Rescan, and falls back to one fresh scan on a miss.
- The window names its vault and remote (sidebar footer); the selected scope shows its marker target; jump-to-directory opens Finder at the marker, from its own toolbar block left of Sync.
- Status and error changes pulse green/red; the status line aligns with the sidebar; toolbar buttons carry visible titles; Rescan's icon reads as rescan.
- Repeated key loads within five minutes ride one Touch ID authentication.
- "Preview .env" renders the exact materialize composition for the selected scope without writing, behind one Touch ID.
- Secure fields have a show-while-typing toggle; rotate prefills only when the value is already revealed.
- Every creation announces through the status surface; the Add dialogs are movable, non-modal windows.
Conduit.remoteURL()andMaterializer.preview(marker:)exist in Core with unit tests.- Zero files under
Sources/SharibakoCLI/change. - The full rhythm is green; coverage ≥90% with new exclusions named and justified in ci.yml.
- Dogfood gate passed (below).
Verification and the dogfood gate
- The rhythm above, green, on the
ho-06.1branch. - Dogfood gate (signed install + Touch ID) — clean release build, install
via
scripts/install.sh(the~/.localpath-shadow gotcha applies: verify the installed binary is the fresh one), real vault, real Keychain:- Open the Workshop with the production scan root configured. The window is interactive immediately; the launch scan completes in the background — no beach ball at any point, including materialize and sync.
- Reveal a secret via Touch ID; reveal a second within five minutes — no second prompt. After the window lapses, a reveal prompts again.
- The sidebar footer names the production vault and the
sageframe-irori/sharibako-vaultremote correctly. - Jump-to-directory opens Finder at the selected scope's marker directory.
- "Preview .env" for a real scope matches
sharibako materializeoutput for the same scope, and writes nothing. - An Add window moves; the main window scrolls and selects behind it; a created shared entry announces in the status surface with the pulse. Not done until this passes.
Phase 3 — Reflect
Executed 2026-07-10: three agent tasks (AT-01 Opus 4.8; AT-02/03 Sonnet 4.6) driven sequentially by a Fable orchestrator, plus one orchestrator-authored gate-fix round. The session hosting AT-02's first agent crashed mid-run; the agent's uncommitted work survived in the tree, compiled, and a fresh agent audited it item-by-item against the spec and completed it — the recovery cost one relaunch, no rework.
- Did the hold? Yes. The
VaultWorkeractor shape stayed clean — no escape hatches, no ad-hoc Tasks, and strict concurrency forced zero Core changes (AT-01's Stop Condition never fired). The launch scan was imperceptible at the gate: the window rendered immediately and stayed interactive through materialize and sync — the beach ball is gone, which was this ho's reason to exist. One spec premise was simply wrong:Conduit.remoteURL()already existed (built quietly in ho-02 for the push/pull no-remote guard), so AT-02's "Core addition" reduced to unit tests over existing behavior. The ho's only genuinely new Core surface isMaterializer.preview(marker:). - Decision review. Decision 1 (actor) and Decision 2 (cache) held completely. Decision 5a's 5-minute reuse window verified at the gate — one Touch ID carried repeated reveals. Decision 6's auxiliary windows carried the ho's only real defect (below). The relief valve (dropping AT-03 to ho-06.2) was never needed.
- What broke that the tests didn't catch? All of it was View-layer —
coverage-excluded by design, which is exactly why the gate exists:
- Add Scope / Add Shared Secret buttons were dead —
WindowGrouptook the scene id positionally, which is the title initializer; no id was registered andopenWindow(id:)silently no-ops. Scene registration is invisible to headless tests. Fixed (ca1b70f), with an explanatory comment so the next scene spellsid:. - Add windows opened enormous — min-only frames under
.windowResizability(.contentSize). Fixed: fixed 440 pt width, intrinsic height. - Esc didn't close the Add windows —
.keyboardShortcut(.cancelAction)is not reliably routed in plain windows the way it is in sheets; added.onExitCommand. Also stripped minimize/zoom from their chrome (AuxiliaryWindowChrome, AppKit accessor). - The bottom bar was unreadably small and tucked into the rounded window corners — footer caption→callout, status callout→body, 16 pt insets.
- Operator ran a stale DerivedData Debug build at first gate attempt
(Spotlight indexed it; the bundle swap under a live instance produced a
zombie UI). Not a code defect; gate procedure now: quit the app, launch
from
/Applicationsexplicitly.
- Add Scope / Add Shared Secret buttons were dead —
- Followups for ho-06.2's opening list:
- Right-side collapsible tool rail (operator idea at the gate: icons when closed, replacing the top toolbar) — a chrome commitment every later surface inherits; front of ho-06.2's Think, not an execution-time call.
- Settings scene (⌘,) with a System/Light/Dark appearance override — the app follows the system theme; the operator asked for a flipper, and a Settings surface is its right home.
- Scan-root visibility — "where is it scanning?" has no answer in the UI; roots live invisibly in the GUI config. Waymarking finished the vault half; the scan-root half is owed.
- The onboarding journey is validated as missing — the operator (the project's own author) could not find the path from "I have repos" to "they're in the vault" from inside the GUI. That is ho-06.3's scope (first-run + ingest), confirmed at the gate by its target failure mode.
Closing this ho
Closing = fill this Reflect + flip status: complete + write the
to the project's K6
(ho-process/kamae-6-sharibako-state-memory.md) + append a build-record entry
to K4 (ho-process/kamae-4-sharibako-ho-overview.md). The block, verbatim
labels and order:
STATE-SUMMARY
- COMPLETED — <what this ho finished>
- NEXT — <the single pointer to what comes next>
- ACTION ITEMS / BLOCKS — <open items; blocks loudly, or
none> - PROJECT LIFECYCLE — <kamae | dev | beta | production>
Appendix — fresh-session bootstrap
To execute this ho in a new Claude Code session, load and run:
Execute ho-06.1 (Think is ratified — do NOT relitigate the decisions,
especially Decision 1: VaultWorker actor, WorkshopModel stays @MainActor, and
zero CLI files change). Read:
@ho-process/hos/ho-06.1-workshop-responsiveness.md (this doc — the plan)
@ho-process/agent-tasks/Ho-06.1-AT-01.md (concurrency foundation)
@ho-process/agent-tasks/Ho-06.1-AT-02.md (waymarking + announce)
@ho-process/agent-tasks/Ho-06.1-AT-03.md (reveal + windows)
@Sources/Sharibako/WorkshopModel.swift
@Sources/Sharibako/Views/WorkshopWindow.swift
@Sources/Sharibako/Support/GUIAgeKeyProvider.swift
@Sources/SharibakoCore/Materializer.swift @Sources/SharibakoCore/Conduit.swift
@CLAUDE.md @~/.claude/modules/languages-swift.md (conventions)
Execute AT-01 → AT-02 → AT-03 in order (AT-02 reads AT-01's cache). Run
`swift package clean && swift test` directly — never clean→build→test.
Verify each task with the full rhythm; close the ho with the signed-install +
Touch-ID dogfood gate. Branch ho-06.1 off main; PR-based. Do not sign commits
or PRs.
Authored 2026-07-10 (Think ratified same day, split from K4's planned ho-06 at opening). Execution and Reflect: pending.
Rendered from the corpus, verbatim · source on GitHub →