feat(demo): Vue/Pinia/Router shell bootstrap + petCare salvage (Pillar-1 slice 1.2a)#146
Conversation
…r-1 slice 1.2a)
Bootstrap the Vue 3 SFC + Pinia 2 + Vue Router 4 application shell
without rewiring the live entry yet. The Wave-0 bridge in
`examples/product-demo/src/app/main.ts` keeps booting the legacy demo
via `await import('../main.js')` — slice 1.2b is what swaps it.
Salvage (`git mv`, history preserved) of the pet-care scenario's pure
modules into `src/demo-domain/scenarios/petCare/`:
- `species.ts`, `constants.ts`
- `cognition/{heuristic,bt,bdi,learning,index}.ts` + `learning.network.json`
- `skills/ApproachTreatSkill.ts`
New `demo-domain/scenarios/petCare/buildAgent.ts` factory extracts the
random-event defs + skill-registry wiring + `createAgent` recipe from
the legacy `src/main.ts` so the upcoming Pinia store and the Wave-0
bridge can share a single source of truth without a behavioural change.
The legacy `src/main.ts` now consumes `buildAgent` rather than inlining
the recipe; `src/main.ts`'s side-effect chain (learning agent wiring,
modifier decorator, RAF loop, UI mounts) is untouched and follows in
slice 1.2b.
Vue infrastructure scaffolded but NOT live yet:
- `app/App.vue` (root component shell)
- `routes/index.ts` (route map per design contract)
- `views/{IntroView,PlayView}.vue` placeholders
- `components/shell/AppHeader.vue`
- `composables/` (empty cross-cutting hooks folder)
- `vue-shims.d.ts` for `*.vue` typing under `tsc --noEmit`
`useAgentSession` Pinia domain store wraps `buildAgent`: owns the live
`Agent` (via `markRaw`), persists the seed under
`demo.v2.session.lastSeed.<scenarioId>` per design's STO contract, and
exposes `init` / `tick` / `start` / `pause` / `resume` / `step` /
`setSpeed` / `replayFromSnapshot` / `subscribe`. Tick driver lives
outside the store on purpose so `requestAnimationFrame` stays out of
`stores/domain/**` (NFR-D-1). Pre-v1 clean break: legacy `whiskers`
and `agentonomous/seed` keys are NOT migrated.
Workspace deps:
- demo: `vue@^3`, `pinia@^2`, `vue-router@^4`, `@vue/test-utils`,
`@pinia/testing`, `vue-eslint-parser`, `eslint-plugin-vue`,
`@types/node`
- root: `vue`, `pinia`, `vue-router`, `@vue/test-utils`,
`@pinia/testing` (so root vitest + `tsc --noEmit` resolve them
without a demo workspace install)
`examples/product-demo/eslint.config.js`:
- Drops the relocated paths from `ignores` (now lint-covered under
`demo-domain/scenarios/petCare/`).
- Wires `vue-eslint-parser` + `eslint-plugin-vue` flat/recommended for
`*.vue` files.
- Opts SFCs + the Vue type shim out of the structural
`no-default-export` rule.
Tests: `examples/product-demo/test/stores/domain/useAgentSession.test.ts`
(under `@pinia/testing`) covers init / persistence / tick / pause /
resume / step / setSpeed / replayFromSnapshot / subscribe paths.
Cross-package test imports updated for the moved petCare modules.
Tracks: #132
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Riding with the same PR that lands the work, per CLAUDE.md "Plan + doc updates ride with the PR". Tracks: #132 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Coverage report
Floors live in |
|
@codex review |
Summary
Testing
|
size-limit report 📦
|
Codex flagged that `setSpeed` accepted any number: non-finite or non-positive values would either reach `agent.setTimeScale` (which throws after `speedMultiplier` had already been mutated) or — for `0` — silently shadow `pause()`'s contract. Throw `RangeError` up front when `Number.isFinite(multiplier)` is false or `multiplier <= 0`, so the store's invariants stay consistent. Test exercises `0`, negative, `NaN`, and ±`Infinity` and asserts the rejected call leaves `speedMultiplier` and `agent.getTimeScale()` unchanged. Tracks: #132 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8849aedd2e
ℹ️ 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".
| agent.value = markRaw( | ||
| buildAgent({ |
There was a problem hiding this comment.
Wire learning-mode agent context after session init
init() creates a fresh agent via buildAgent(...) but never calls setLearningAgent(...), which cognition/learning.ts relies on to set the persisted-network scope and keep mood/recent-event feature state in sync. In the upcoming Vue shell path that uses useAgentSession, switching to Learning mode will ignore previously trained snapshots (agentIdForHydration stays null) and run with stale feature inputs, unlike the legacy path in src/main.ts that explicitly wires this immediately after construction.
Useful? React with 👍 / 👎.
| if (!wasRunning) agent.value.setTimeScale(BASE_TIME_SCALE * speedMultiplier.value); | ||
| await agent.value.tick(dtSeconds); | ||
| if (!wasRunning) agent.value.setTimeScale(0); |
There was a problem hiding this comment.
Restore paused scale in step with finally
When step() is invoked while paused, it temporarily raises timeScale before await agent.tick(dtSeconds), then lowers it afterward; if tick() throws (for example from a reasoner/runtime error), the restore path is skipped and the agent stays unpaused even though running remains false. Wrapping the tick await in try/finally avoids leaving control state inconsistent after failed single-step attempts.
Useful? React with 👍 / 👎.
… round 2) P1 — useAgentSession.init now calls setLearningAgent on the freshly built agent (and again in replayFromSnapshot's reset path), matching the legacy src/main.ts contract. Without it, the upcoming Vue shell path would have switched to Learning mode with a null agentIdForHydration scope and stale mood / recent-event feature inputs. P2 — useAgentSession.step now wraps the awaited tick in try/finally so the paused-scale invariant survives a thrown tick (reasoner / runtime errors). Test mocks agent.tick to reject and asserts getTimeScale() stays at 0 + running stays false. Tracks: #132 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3deba8efb
ℹ️ 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".
| import vueParser from 'vue-eslint-parser'; | ||
| import vuePlugin from 'eslint-plugin-vue'; |
There was a problem hiding this comment.
Mirror new ESLint Vue deps in root install
Importing vue-eslint-parser and eslint-plugin-vue here makes npm run lint:demo depend on those packages, but they were only added to examples/product-demo/package.json while the repo’s root verify flow runs cd examples/product-demo && eslint . after a root npm ci (no workspaces). In a clean CI/local environment this causes module-resolution failures before linting starts. Please add these two packages to the root devDependencies (and lockfile) alongside the other mirrored demo deps.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3deba8efb
ℹ️ 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".
| // a null hydration scope and stale feature inputs (mood / recent | ||
| // events). | ||
| setLearningAgent(fresh); | ||
| running.value = true; |
There was a problem hiding this comment.
Port random-event modifier wiring into session init
init() wires only setLearningAgent, but the legacy flow also subscribes to RandomEvent and applies sick/happy-glow/dirty/disobedient modifiers (src/main.ts does this today). In the new store-driven path, mildIllness, surpriseTreat, and messyPlay will no longer mutate state, so core gameplay loops (clean/scold pressure and illness penalties) silently disappear once the shell switches to useAgentSession.
Useful? React with 👍 / 👎.
| ); | ||
| } | ||
| const fresh = markRaw(buildAgent({ seed: seed.value })); | ||
| agent.value = fresh; |
There was a problem hiding this comment.
Rebind store subscribers after rebuilding the agent
replayFromSnapshot(null) replaces agent.value with a fresh instance, but subscribe() listeners are attached to the previous agent object. Any consumer that subscribed once (e.g., AGENT_TICKED-driven view stores) will stop receiving events after a reset/replay unless it manually resubscribes, which breaks the in-place reset flow.
Useful? React with 👍 / 👎.
…int deps (Codex round 3) P1 — `buildAgent` now wires the random-event modifier reactions (`sick` / `happy-glow` / `dirty` / `disobedient`) verbatim from the legacy `src/main.ts` decorator block via a private `wirePetCareEventModifiers(agent)` helper. The duplicate listener + `unsubscribeModifierDecorator` cleanup are removed from `src/main.ts` to avoid double-applying modifiers under the Wave-0 bridge. The new `useAgentSession` store path now inherits the gameplay-critical modifier side-effects without a separate wire-up step. P1 — `vue-eslint-parser` and `eslint-plugin-vue` mirrored to root `devDependencies` so a clean `npm ci` at the repo root keeps `npm run lint:demo` resolvable from `examples/product-demo/`. Local `npm run verify` no longer requires a prior demo workspace install for the lint step. P2 — `useAgentSession.subscribe` now tracks each listener in a `SubscriberRecord` set; `init` and `replayFromSnapshot` call `rebindSubscribers(fresh)` to detach from the previous agent and re-attach to the rebuilt one. Reset/replay no longer silently breaks AGENT_TICKED-driven view stores. Test exercises the rebind across a `replayFromSnapshot(null)` and verifies the unsub stops events afterwards. Tracks: #132 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 393d649f63
ℹ️ 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".
| 'useAgentSession.replayFromSnapshot: snapshot deserialisation lands in slice 1.2b', | ||
| ); | ||
| } | ||
| const fresh = markRaw(buildAgent({ seed: seed.value })); |
There was a problem hiding this comment.
Reapply species override when rebuilding from snapshot
init() can construct the session with a speciesOverride, but replayFromSnapshot(null) rebuilds the agent with only the seed. In flows where a non-default species config is active (e.g., after loading an override), pressing reset/replay will silently revert behavior to catSpecies, so subsequent ticks no longer match the session the user configured. Persist the last applied override in store state and pass it back into buildAgent(...) during replay rebuilds.
Useful? React with 👍 / 👎.
… (Codex round 4) `init()` accepted a `speciesOverride` but discarded it after `buildAgent`, so `replayFromSnapshot(null)` rebuilt the agent with the scenario default — silently reverting any active config override on reset / replay. Track the last applied override in a `lastSpeciesOverride` ref, set it in `init` (clearing it when no override is supplied), and pass it back into `buildAgent` from `replayFromSnapshot(null)`. Tests cover the override-survives-replay path (asserting needs IDs round-trip via the rebuilt agent's getState) and the clear-on-plain-init path. Tracks: #132 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b9452ec152
ℹ️ 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 baselinePath = path.resolve( | ||
| process.cwd(), | ||
| 'examples/product-demo/src/cognition/learning.network.json', | ||
| 'examples/product-demo/src/demo-domain/scenarios/petCare/cognition/learning.network.json', | ||
| ); |
There was a problem hiding this comment.
Keep seed script output path in sync with moved baseline file
Now that the baseline is loaded from examples/product-demo/src/demo-domain/scenarios/petCare/cognition/learning.network.json, the regeneration workflow is out of sync: scripts/seed-learning-network.ts still writes to examples/product-demo/src/cognition/learning.network.json (a directory removed by this move). Running npm run seed:learning-network will therefore fail or update the wrong location, so developers cannot regenerate the JSON consumed here.
Useful? React with 👍 / 👎.
Summary
Pillar-1 slice 1.2a of the pre-v1 demo evolution: bootstrap the
Vue 3 SFC + Pinia 2 + Vue Router 4 application shell without
rewiring the live entry. The Wave-0 bridge in
examples/product-demo/src/app/main.tskeeps booting the legacy demovia
await import('../main.js')— slice 1.2b is what swaps the mount.git mvsalvage (history preserved) of the pet-care scenario'spure modules into
src/demo-domain/scenarios/petCare/:species.ts,constants.ts,cognition/{heuristic,bt,bdi,learning,index}.ts+learning.network.json,skills/ApproachTreatSkill.ts.demo-domain/scenarios/petCare/buildAgent.tsfactory extractsthe random-event defs + skill-registry wiring +
createAgentrecipefrom the legacy
src/main.tsso the upcoming Pinia store and theWave-0 bridge can share a single source of truth without a
behavioural change. Pure TS — no DOM, no Pinia.
useAgentSessionPinia domain store wrapsbuildAgent: owns thelive
Agent(viamarkRaw), persists the seed underdemo.v2.session.lastSeed.<scenarioId>per design's STO contract,and exposes
init/tick/start/pause/resume/step/setSpeed/replayFromSnapshot/subscribe. The tick driverlives outside the store on purpose so
requestAnimationFramestaysout of
stores/domain/**(NFR-D-1). Pre-v1 clean break: legacywhiskers/agentonomous/seedkeys are not migrated.app/App.vue,routes/index.ts,views/{IntroView,PlayView}.vue,components/shell/AppHeader.vue,composables/(empty),vue-shims.d.tsfortsc --noEmit.examples/product-demo/package.json(
vue,pinia,vue-router,@vue/test-utils,@pinia/testing,vue-eslint-parser,eslint-plugin-vue,@types/node) plus thesame Vue/Pinia/Router/test-utils set to root
package.jsonso rootnpm cikeepstsc --noEmitand root vitest resolution workingwithout a demo workspace install.
examples/product-demo/eslint.config.js: drops relocated pathsfrom
ignores, wiresvue-eslint-parser+eslint-plugin-vueflat/recommended for
*.vuefiles, opts SFCs + the Vue type shimout of the
no-default-exportstructural rule.examples/product-demo/test/stores/domain/useAgentSession.test.tsunder
@pinia/testingcovers init, persistence, tick, pause / resume,step (single-tick while paused), setSpeed, replayFromSnapshot (null
reset path + 1.2b-deferred snapshot path), and subscribe.
(
tests/examples/btMode.test.ts,tests/examples/learningMode.train.test.ts,tests/unit/cognition/adapters/TfjsReasoner.test.ts).Out of scope (slice 1.2b owns these)
app/main.tsrewrite (replace bridge with Vue mount).src/{main,ui,traceView,seed}.tsdeletion.TourOverlay / StepHighlight / chapter-1 step content.
useTourProgressview store + tour copy tone (OQ-P1) decision.replayFromSnapshot(today the actionthrows when given a non-null snapshot — explicit slice-1.2b TODO).
cognitionSwitcher.ts,lossSparkline.ts,predictionStrip.ts, andspeciesConfig.tsdeliberately stay in place: Pillar 2 slice 2.5 portsspeciesConfig.ts.Test plan
npm run verify(format:check + lint + lint:demo + typecheck +test + build + docs) — green locally on Windows / Node 22.
useAgentSessionsuite(10 cases) and the slice-1.1 walkthrough domain tests.
npm run lint:democlean for the newvue-eslint-parser+ Vueplugin wiring (placeholder SFCs, type shim, store, test).
@codex reviewper projectworkflow; auto-poll cron arms after the request).
Tracks: #132
🤖 Generated with Claude Code