feat(demo): chapters 2-5 + typed selector registry (Pillar-1 slice 1.3)#153
Conversation
Composes the full pre-v1 walkthrough graph: chapter-1 (autonomy) now threads into chapter-2 (trace visibility), chapter-3 (cognition swap + observe), chapter-4 (JSON-preview placeholder), and chapter-5 (deterministic replay export → import). Each chapter ships as a standalone step factory under `demo-domain/walkthrough/chapters/`, composed in `petCareGraph.ts` via `defineWalkthroughGraph(...)` whose existing validation catches typos in `nextOnComplete` chains. Closes the selector-handle union (spec P1-FR-4): `RegisteredHandle` in `stores/view/selectorHandles.ts` is now the single source of truth; `useSelectorRegistry` is generic over it and `useRegisterSelector(...)` is the composable that components call to bind the handle. A renamed literal in `selectorHandles.ts` becomes a `tsc` error at every chapter + component referencing it (manually verified). Adds `/tour/:step` routing (P1-FR-6): a slim `TourView.vue` wraps `PlayView` and reconciles the URL with `useTourProgress.lastStep` via new `currentStepRoutePath` / `syncRoute(router)` / `resumeFromRoute(stepId)` actions. `resumeFromRoute` is forward-only so a hard reload at `/tour/<earlier-step>` doesn't rewind past the persisted cursor. Predicate primitives gain `eventEmittedSinceStep(type)` and `ticksSinceStepAtLeast(n)`, both reading a new `stepBaselineTick` field on `TourCtx` populated by `useTourProgress`. Baseline rebases to 0 when `session.tickIndex` resets so chapter-5 import reliably fires on the post-rebuild event buffer. `useAgentSession` gains `setCognitionMode(modeId)` (probes peer dep, constructs reasoner, calls `agent.setReasoner`, records `CognitionModeChanged` UI event) plus `recordUiEvent(type)` for synthetic UI signals consumed by chapters 2/4/5 (`TracePanelOpened`, `ConfigPreviewOpened`, `SnapshotExported`, `SnapshotImported`). `<TourOverlay>` surfaces both Skip and Restart controls and now re-evaluates predicates on every `recentEvents` change (not just `tickIndex`), so UI events advance the tour without per-component imperative `tour.next()` calls. Reset (`replayFromSnapshot(null)`) preserves cursor / completedAt / skipped (P1-FR-7); covered by a new hygiene test. Chapter-4 ships as a placeholder until Pillar-4 lands the proper preview/commit editor — a "Preview JSON" button in `<HudPanel>` emits `ConfigPreviewOpened` and the predicate keeps working when Pillar-4 swaps in the real editor. Tracks: #132 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Slice 1.3 row in the guided-walkthrough plan flips to ✅ shipped with the PR link, and the planning doc's Tracker table for the "Guided walkthrough" pillar now lists every shipped slice. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3c435b644
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const reasoner = await mode.construct(); | ||
| agent.value.setReasoner(reasoner); | ||
| cognitionModeId.value = modeId; | ||
| recordUiEvent('CognitionModeChanged'); |
There was a problem hiding this comment.
Reset cognition mode when rebuilding the agent
setCognitionMode updates cognitionModeId, but neither init() nor replayFromSnapshot() resets it when they construct a fresh agent (which starts on the default heuristic reasoner). After a user switches modes and then resets/imports/new-pet, the UI and tour predicates read a stale mode id (not(cognitionModeIs('heuristic')) becomes true immediately), so chapter-3 can auto-advance without an actual cognition switch and the HUD reports the wrong active mode.
Useful? React with 👍 / 👎.
| (next, prev) => { | ||
| if (next < prev) baselineTickIndex.value = next; | ||
| }, |
There was a problem hiding this comment.
Persist baseline rebase after tick-index resets
When session.tickIndex drops (reset/replay), the watcher rebases baselineTickIndex in memory but never calls persist(). If the page reloads before another action that persists progress, storage still contains the old baseline, so eventEmittedSinceStep/ticksSinceStepAtLeast evaluate against stale pre-reset ticks and can incorrectly block progression until the tick counter catches up.
Useful? React with 👍 / 👎.
| watch([() => session.tickIndex, () => session.recentEvents.length], () => { | ||
| tour.next(); |
There was a problem hiding this comment.
Re-evaluate on event writes, not
recentEvents.length
The overlay re-checks predicates by watching session.recentEvents.length, but the event buffer is capped at 50 and recordUiEvent trims immediately after push, so once full the length often stays unchanged. In that state, UI events like TracePanelOpened/SnapshotExported do not trigger tour.next() unless a tick also changes, which breaks event-driven progression while paused and makes step advancement depend on unrelated ticking.
Useful? React with 👍 / 👎.
Coverage report
Floors live in |
size-limit report 📦
|
Three issues raised by Codex on PR #153: 1. (P1) **Reset cognitionModeId on agent rebuild.** `init()` and `replayFromSnapshot()` build a fresh `Agent` on the default heuristic reasoner but kept the previous `cognitionModeId`. After a mode swap + reset/import, chapter-3's `not(cognitionModeIs('heuristic'))` predicate would auto-fire and the HUD readout reported the wrong active mode. Both paths now reset `cognitionModeId.value = 'heuristic'` alongside the other per-rebuild resets. 2. (P2) **Persist baselineTickIndex after the rebase.** The watcher that detects `session.tickIndex` falling (snapshot replay) updated the baseline in memory but never called `persist()`. A reload between the rebase and the next explicit cursor move would restore the pre-reset baseline from localStorage and break `eventEmittedSinceStep` / `ticksSinceStepAtLeast`. Watcher now persists immediately after the rebase. 3. (P2) **Re-evaluate predicates on event writes, not buffer length.** `recentEvents` is capped at 50 and trimmed on every overflow push, so `recentEvents.length` saturates and `<TourOverlay>`'s length watcher silently misses UI events once the buffer is full — `TracePanelOpened` / `SnapshotExported` would fail to advance the tour while paused. Added a monotonic `recentEventsVersion` ref bumped on every push (both internal AGENT_TICKED listener and `recordUiEvent`); the overlay now watches that counter alongside `tickIndex`. Tests cover all three: cognition reset on init + replayFromSnapshot, recentEventsVersion bumping past the 50-event cap, and persisted baselineTickIndex after replay. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a5ffd36db
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // fired before the user reached the step. Persisted alongside the | ||
| // cursor so a reload mid-step doesn't reset the baseline (which | ||
| // would make e.g. "wait 3 ticks" succeed instantly on resume). | ||
| const baselineTickIndex = ref<number>(persisted?.baselineTickIndex ?? 0); |
There was a problem hiding this comment.
Rebase step baseline when restoring persisted progress
baselineTickIndex is restored directly from local storage, but PlayView always calls session.init(...) on load, which resets session.tickIndex to 0. When a user reloads on /tour/<step> in chapters 2–5, eventEmittedSinceStep/ticksSinceStepAtLeast compare against a stale pre-reload baseline and can reject valid post-reload actions until the tick counter catches up to the old value, effectively stalling progression after resume. Rebase/clamp the restored baseline to the current session tick on startup.
Useful? React with 👍 / 👎.
| const reasoner = await mode.construct(); | ||
| agent.value.setReasoner(reasoner); | ||
| cognitionModeId.value = modeId; |
There was a problem hiding this comment.
Guard async cognition switches against stale completions
setCognitionMode awaits probe() and construct() and then unconditionally applies the result to agent.value. If the agent is rebuilt (init/replayFromSnapshot) or the user triggers another mode change while the first request is in flight, the older request can finish later and overwrite the newer state, producing the wrong active reasoner/mode and emitting a misleading CognitionModeChanged event. Capture/validate the target agent (or a request token) before applying the result.
Useful? React with 👍 / 👎.
…le completions Two follow-up issues raised by Codex on PR #153: 1. (P1) **Rebase step baseline when restoring persisted progress** (`useTourProgress.ts`). On a hard reload at `/tour/<step>`, `PlayView` calls `session.init(...)` which resets `session.tickIndex` to 0, but the persisted `baselineTickIndex` still carries the pre-reload value. Predicates (`eventEmittedSinceStep`, `ticksSinceStepAtLeast`) would reject valid post-reload events until the tick counter caught up. Clamp the restored baseline to `min(persisted, session.tickIndex)` on store construction. 2. (P2) **Guard async cognition swaps against stale completions** (`useAgentSession.ts`). `setCognitionMode` awaits `probe()` and `construct()` and unconditionally applied the result. A parallel `init()` / `replayFromSnapshot()` / second `setCognitionMode()` could land while those promises were in flight, causing the older request's `setReasoner` to overwrite the newer state and emit a misleading `CognitionModeChanged`. Added a monotonic `cognitionToken` bumped on every agent rebuild + at the start of each swap; both await points now drop their result if either the token has moved or `agent.value` no longer matches the captured `targetAgent`. Tests cover both: a localStorage-seeded baseline of 15 clamps to 0 when the fresh session starts at tick 0, and a `setCognitionMode` race that calls `init()` mid-flight does not invoke `setReasoner` on the discarded agent or emit a stray `CognitionModeChanged`. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
There was a problem hiding this comment.
💡 Codex Review
agentonomous/examples/product-demo/src/views/IntroView.vue
Lines 24 to 25 in f948fa6
This CTA no longer does what its label promises after the tour expanded beyond one step: skipTour() calls restart() and then a single skip(), which now only advances from chapter 1 to chapter 2. Because completedAt stays null, TourOverlay still renders on /play, so users who click “Skip to free play” still get guided steps. The skip action here should mark the whole tour complete (or iterate until completion) before routing to /play.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| title: observeCopy.title, | ||
| hint: observeCopy.hint, | ||
| highlight: COGNITION_INDICATOR_HANDLE, | ||
| completionPredicate: eventEmittedSinceStep('AgentTicked'), |
There was a problem hiding this comment.
Gate chapter-3 observe on elapsed ticks, not tick events alone
Using only eventEmittedSinceStep('AgentTicked') lets this step complete while simulation time is paused, because useAgentSession still records AgentTicked events at timeScale === 0 even though tickIndex does not advance. In that paused scenario, users can satisfy this step without observing a real post-switch decision tick, which breaks the chapter’s intended behavior; this predicate should also require positive tick progression (e.g., ticksSinceStepAtLeast(1)).
Useful? React with 👍 / 👎.
Two follow-up issues raised by Codex on PR #153: 1. (P1) **"Skip to free play" must end the entire tour, not just chapter 1.** `IntroView.skipTour()` was written when chapter 1 was the only chapter, so a single `skip()` set `completedAt`. With chapters 1-5 wired, that single `skip()` only advanced chapter 1 → chapter 2 and left `completedAt === null`, so `<TourOverlay>` still rendered on `/play`. Added a `complete()` action on `useTourProgress` that walks every remaining step into `skipped` and sets `completedAt` in one persisted shot; `skipTour()` now calls `restart() + complete()`. The action is bounded by the graph step count so a future cyclic-graph mistake short-circuits. 2. (P2) **Chapter-3 observe must require virtual-time progression, not just an `AgentTicked` event.** `useAgentSession` records `AgentTicked` events at `timeScale === 0` so the trace panel keeps refreshing on paused frames, but `tickIndex` does not advance. The previous predicate (`eventEmittedSinceStep('AgentTicked')` only) auto-completed the chapter while paused, never showing the user a real post-switch decision tick. Combined with `ticksSinceStepAtLeast(1)` so the chapter only fires after virtual time has actually advanced. Tests cover both: a `complete()` test asserts every authored step ends up in `skipped` and `completedAt` is persisted; a chapter-3 test triple covers paused-tick rejection, virtual-time-advance fire, and AgentTicked-required behaviour. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8bba87add0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (next === null) return; | ||
| const here = router.currentRoute.value.fullPath; | ||
| if (here === next) return; | ||
| if (!here.startsWith('/tour/')) return; |
There was a problem hiding this comment.
Sync
/tour root to a concrete step route
The route is declared as '/tour/:step?', but syncRoute only runs when fullPath starts with '/tour/', so '/tour' is treated like a non-tour route. In that case (e.g., bookmarked/manual '/tour' entry), the tour cursor can advance while the URL never updates to '/tour/<step>', which breaks the step-specific resume/share behavior this feature adds.
Useful? React with 👍 / 👎.
Codex P2 follow-up on PR #153. The tour route is declared `/tour/:step?`, so the bare `/tour` (bookmarked / hand-typed) is also a tour entry. `syncRoute` was checking `here.startsWith('/tour/')` which excluded the bare `/tour`, so the cursor could advance while the URL never upgraded to `/tour/<step>` — breaking step-specific resume + share. Also accept `here === '/tour'`. Bare `/tour` now gets pushed to the concrete step path on first sync; deep-link share + reload-resume work from the bare entry too. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b836a8dd5e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| watch( | ||
| () => tour.lastStep, | ||
| () => { | ||
| void tour.syncRoute(router); | ||
| }, |
There was a problem hiding this comment.
Handle
/tour/:step param updates after mount
This view only reacts to tour.lastStep, so if the user navigates between tour URLs while TourView stays mounted (e.g., browser back/forward from /tour/chapter-3... to /tour/chapter-2...), resumeFromRoute is never re-run and the store cursor remains on the old step. That leaves the URL and active walkthrough step out of sync until some later cursor change, breaking the route-resume contract for in-session navigation.
Useful? React with 👍 / 👎.
Codex P2 follow-up on PR #153. `<TourView>` only ran the URL → cursor reconciliation on mount, so browser back/forward between `/tour/<step-a>` and `/tour/<step-b>` (with the view staying mounted) left the active walkthrough step out of sync with the URL. Extracted the mount-time block into `reconcileFromRoute()` and added a `watch(() => route.params['step'], reconcileFromRoute)` so any in-session URL change re-runs `resumeFromRoute(stepId)` (forward-only) followed by `syncRoute(router)` to snap the URL back to the cursor when the URL is upstream of the persisted progress. Coverage notes: the deep-link mount-time path is verified by two tests (downstream URL fast-forwards the cursor; upstream URL leaves the persisted cursor put). The browser-back/forward path that exercises the new watcher relies on vue-router reactivity through `<router-view>`; under VTU + jsdom this only flushes reliably with `<router-view>` mounted as the test root, but even then the watcher chain (route param → resume → syncRoute → router.push) doesn't settle inside two `await flushPromises()` ticks. Slice 1.4's Playwright `tour-happy-path.spec.ts` will exercise the back/forward path in a real browser. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 91e6833708
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // The fresh agent boots on the default heuristic reasoner; sync | ||
| // the readout (and chapter-3 predicate) so a previously-selected | ||
| // mode doesn't ghost across a reset / import. | ||
| cognitionModeId.value = 'heuristic'; |
There was a problem hiding this comment.
Preserve cognition mode across snapshot imports
When a user reaches chapter 3 and switches to a non-heuristic mode, importing an exported snapshot via replayFromSnapshot(snapshot) rebuilds a fresh default agent and then unconditionally forces cognitionModeId back to 'heuristic'. That means the run after import no longer uses the same decision policy as the exported run, so replayed behavior can diverge even with the same seed/state, undermining the deterministic replay flow in chapter 5. Reapply the active cognition mode (or persist/restore it) on snapshot imports instead of always resetting to heuristic.
Useful? React with 👍 / 👎.
Codex P1 follow-up on PR #153. `replayFromSnapshot(snapshot)` unconditionally reset `cognitionModeId` to `'heuristic'` after rebuilding the agent, so a chapter-5 import overwrote the user's selected reasoner — the replayed run no longer used the same decision policy as the exported run, undermining deterministic replay. Capture `cognitionModeId.value` BEFORE the rebuild. The reset path (`snapshot === null`) still drops it to `'heuristic'`. The import path (`snapshot !== null`) re-applies the captured mode by awaiting `setCognitionMode(previousMode)` after the agent swap so the fresh agent runs the same reasoner the export was made under. If the peer dep is no longer available (uninstalled between export and import), the inner `setCognitionMode` throws and we swallow it: the import itself succeeds, `cognitionModeId` reflects the agent's actual reasoner (`'heuristic'`), and the user can retry the swap manually via the HUD picker. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 096e6dfe99
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| title: traceOpenCopy.title, | ||
| hint: traceOpenCopy.hint, | ||
| highlight: TRACE_PANEL_HANDLE, | ||
| completionPredicate: eventEmittedSinceStep('TracePanelOpened'), |
There was a problem hiding this comment.
Accept already-visible trace panel for trace-open step
The trace-open step only checks eventEmittedSinceStep('TracePanelOpened'), but TracePanel restores visibility from demo.v2.trace.visible on mount and only emits TracePanelOpened on a hidden→visible toggle. In the common returning-user case where the panel is already open from a previous session, chapter 2 appears stuck until the user closes and re-opens the panel, which breaks the intended “open the trace panel” progression flow. Consider treating restored-visible state as satisfied (or emitting the event on mount when restored visible) so chapter 2 can advance without a forced extra toggle.
Useful? React with 👍 / 👎.
Codex P2 follow-up on PR #153. `<TracePanel>` only emitted `TracePanelOpened` on a hidden→visible toggle, but `demo.v2.trace.visible` may persist `true` from a previous session. For returning users with the panel already open, chapter-2's `trace-open` predicate would stall indefinitely until they toggled the panel off and back on. Emit `TracePanelOpened` once in `onMounted` when `visible.value` is true (restored), so the predicate sees the signal as soon as the component mounts. Hidden cold-start mounts stay quiet. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 862f6cba73
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| title: traceOpenCopy.title, | ||
| hint: traceOpenCopy.hint, | ||
| highlight: TRACE_PANEL_HANDLE, | ||
| completionPredicate: eventEmittedSinceStep('TracePanelOpened'), |
There was a problem hiding this comment.
Treat already-open trace panel as chapter-2 completion
Chapter-2 trace-open still blocks returning users whose trace panel is already visible, because this predicate only accepts TracePanelOpened events emitted at or after the step baseline tick. Fresh evidence: this patch now emits TracePanelOpened on TracePanel mount, but that mount event fires at tick 0 before chapter-2 starts, so eventEmittedSinceStep('TracePanelOpened') filters it out once the baseline advances in chapter-1 and the user must still close/re-open the panel to proceed.
Useful? React with 👍 / 👎.
Codex P2 follow-up on PR #153. The previous patch emitted `TracePanelOpened` on `<TracePanel>` mount when the panel was restored visible, so a returning user's preference would surface as a UI event. But chapter-2's predicate was step-scoped (`eventEmittedSinceStep('TracePanelOpened')`), and the mount event fires at tick 0 — before chapter-2 starts — so the step baseline would advance past it and the predicate would still filter the event out. The returning user was still forced to close + reopen the panel. Switch the trace-open predicate to session-scoped (`eventEmittedSince('TracePanelOpened', 0)`) so any `TracePanelOpened` since session init counts. Cold-start users who have never opened the panel still need to toggle (no event in buffer); returning users with the panel restored visible advance without the forced extra toggle. Chapter-2 step-2 (`trace-observe`) stays step-scoped because the dwell-tick semantics are about elapsed time after the user reached THIS step, not session totals. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2106129ab5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| title: traceOpenCopy.title, | ||
| hint: traceOpenCopy.hint, | ||
| highlight: TRACE_PANEL_HANDLE, | ||
| completionPredicate: eventEmittedSince('TracePanelOpened', 0), |
There was a problem hiding this comment.
Require trace-open action to occur in chapter 2
Using eventEmittedSince('TracePanelOpened', 0) lets chapter-2 step trace-open pass from any historical open in the current session, even if the panel is currently hidden. A user who opened then closed the trace panel before reaching chapter 2 will auto-advance without actually reopening it, and trace-observe can then complete while the panel stays closed, which breaks the intended walkthrough behavior for this chapter.
Useful? React with 👍 / 👎.
| completedAt.value = null; | ||
| skipped.value = []; | ||
| lastStep.value = graph.firstStepId; | ||
| baselineTickIndex.value = session.tickIndex; | ||
| clearPersisted(); |
There was a problem hiding this comment.
Reset session evidence when restarting the tour
restart() only rewinds tour cursor state, but it keeps the existing session tick/event history intact. Since chapter-1 completion still depends on session-lifetime signals (tickAtLeast and eventEmittedSince(..., 0)), restarting after the app has already ticked will immediately re-satisfy chapter 1 on the next overlay evaluation, so the user is pushed back to chapter 2 almost instantly instead of actually replaying from chapter 1.
Useful? React with 👍 / 👎.
Two Codex P2 follow-ups on PR #153: 1. **Chapter-2 trace-open requires panel currently visible.** The previous fix used `eventEmittedSince('TracePanelOpened', 0)` to handle returning users with restored-visible panels, but a user who opened-then-closed the panel before reaching chapter-2 would still auto-advance from the historical open event. Added a new `flagOpen(openType, closeType)` predicate that scans recentEvents in reverse to find the latest matching event of either type — true iff the most recent is `openType`. Wired `<TracePanel>` to emit both `TracePanelOpened` (hidden→visible, mount-when-restored-visible) AND `TracePanelClosed` (visible→hidden), so the predicate models "panel currently visible" through the shared event stream. 2. **Chapter-1 step-scoped so restart() honestly replays.** Chapter-1 used session-lifetime predicates (`tickAtLeast(3)`, `eventEmittedSince(AGENT_TICKED, 0)`). After a long session the buffer is full of historical AgentTicked events at high tickIndex, so `tour.restart()` would auto-satisfy the chapter immediately and push the user to chapter-2 without an actual replay-from-the-start experience. Switched both predicates to step-scoped variants (`ticksSinceStepAtLeast(3)`, `eventEmittedSinceStep(AGENT_TICKED)`) so the step waits for fresh evidence after restart's baseline rebase. Tests cover both: chapter-2 has open / close / re-open scenarios hitting `flagOpen`; chapter-1 has a long-session restart scenario that asserts the predicate stays false until fresh ticks land. Tracks: #132 https://claude.ai/code/session_01TPggmKp12XqQdNjjMKVxd7
Summary
<TracePanel>, cognition picker in<HudPanel>, JSON-preview placeholder, and<ExportImportPanel>all emit the synthetic UI events that drive their chapter predicates. The composed graph (petCareWalkthroughGraph) now runs all five chapters in sequence;defineWalkthroughGraph(...)validation catches typos innextOnCompletechains at module-load time.RegisteredHandleinexamples/product-demo/src/stores/view/selectorHandles.tsis now the single source of truth.useSelectorRegistryis generic over it and the newuseRegisterSelector(handle)composable wraps the data-attribute lookup pattern. A renamed literal inselectorHandles.tsbecomes atscerror at every chapter + component referencing it (manually verified by spot-renaming'hud.needs'→ typecheck flagged 10 sites; reverted)./tour/:steprouting (P1-FR-6): a slimTourView.vuewrapsPlayViewand reconciles the URL withuseTourProgress.lastStepvia newcurrentStepRoutePath/syncRoute(router)/resumeFromRoute(stepId)actions.resumeFromRouteis forward-only — a hard reload at/tour/<earlier-step>does not rewind past the persisted cursor.eventEmittedSinceStep(type)andticksSinceStepAtLeast(n)read a newstepBaselineTickfield onTourCtxpopulated byuseTourProgress. The baseline rebases to 0 whensession.tickIndexresets, so chapter-5 import reliably fires on the post-rebuild event buffer (export → import → snapshot rebuild → predicate fires).useAgentSession.replayFromSnapshot(null)now provably leavesuseTourProgress.lastStep/completedAt/skippeduntouched. Covered by a new test attest/stores/view/useTourProgress.test.ts.<TourOverlay>surfaces both Skip and Restart buttons (P1-FR-5) and re-evaluates predicates onrecentEvents.lengthchanges in addition totickIndex, so UI events advance the tour without per-component imperativetour.next()calls.Chapter-4 placeholder
Pillar-4 owns the proper preview/commit dual-action flow against
useConfigDraft+ the editor view. Until that ships, slice 1.3 provides a single placeholder "🛠️ Preview JSON" button in<HudPanel>that emitsConfigPreviewOpenedso the walkthrough can advance. When Pillar-4 slice 4.3 lands, this chapter's step content stays valid: the real editor will emit the same event, the placeholder button is removed, and the predicate keeps working without modification.Verification
npm run verify(format:check + lint + lint:demo + typecheck + 645 tests + lib build + typedoc) green.cd examples/product-demo && npm run build(Vite production) green.Out of scope
tour-happy-path.spec.ts— slice 1.4.cognitionSwitcher.tsport with loss sparkline + prediction strip — Pillar-2 slice 2.5; this slice ships a minimal cognition<select>placeholder that callsuseAgentSession.setCognitionMode(...)(probes peer + swaps reasoner).Tracks: #132
Test plan
npm run verifygreen (645 tests).cd examples/product-demo && npm run buildgreen.RegisteredHandleliteral →tscerrors at every consumer; revert)./tour/<step>→ cursor resumes. Reset preserves cursor; Skip advances + records; Restart returns to chapter-1 step-1.mistreevous/js-son/ tfjs unavailable, the cognition<select>renders those modes disabled with an install hint and the chapter still advances when an available alternative is picked.🤖 Generated with Claude Code
Generated by Claude Code