Faza 4a: XState v5 — mașina declarativă înlocuiește orchestrarea manuală#43
Conversation
radioMachine.ts expresses declaratively everything radioCore did with manual timers and playId invalidation: - after-delays replace the retry/loading/recovery timers — leaving a state cancels them; the recovery backoff is a delay expression over context.recoveryCount, with the offline recheck as a reentering self-transition at fixed cadence (no escalation); - playerPlay() is an invoked promise actor — a late resolve/reject after leaving the state is discarded by construction (replaces the entire playId mechanism); - the playback watchdog and sound supervisor are invoked callback actors scoped exactly to the states that need them; - STATE_FX stays a declarative table, applied as a parametrized entry action (re-entries re-apply it, matching the old setState semantics). radioCore.ts becomes a thin adapter with the same public API — main.ts and the other modules are untouched except for two dropped deps (setTimeout/clearTimeout now live on the actor clock) and a fix the e2e suite caught that unit mocks could not: browser timer globals throw "Illegal invocation" when called as methods on deps, so main.ts wraps them. stateMachine.ts is deleted. Unit tests ported to a wrapped SimulatedClock (with visibility into scheduled delays); playId assertions became behavioral ones. 63/63 unit, 37/37 e2e untouched, machine coverage 99%, preview smoke green. Bundle cost of xstate: 5.7 -> 19.3KB gzip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
CI Summary
|
There was a problem hiding this comment.
Pull request overview
Refactors the radio player core from manually orchestrated timers/race-avoidance into a declarative XState v5 machine, while keeping the existing radioCore public API and updating unit tests/coverage settings accordingly.
Changes:
- Introduces
src/js/radioMachine.tsimplementing the radio player as an XState v5 machine (delays, invoked actors, declarative state side effects). - Reworks
src/js/radioCore.tsinto a thin adapter that translates the existing API into machine events and starts an actor instance. - Updates tests and tooling: ports unit tests to use
SimulatedClock, adjusts Vitest coverage includes, and wires browser timer deps accordingly.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.js | Updates coverage include list to track radioMachine.ts instead of the removed generic stateMachine.ts. |
| src/js/stateMachine.ts | Removes the previous generic state-machine helper now superseded by XState. |
| src/js/radioMachine.ts | Adds the XState v5 machine defining states, delays, actors (watchdog/supervisor), and declarative side effects. |
| src/js/radioCore.ts | Converts the core into an adapter over an XState actor; removes manual timers/playId orchestration. |
| src/js/radioCore.test.ts | Ports tests to advance XState “after” delays via a SimulatedClock wrapper and updates assertions accordingly. |
| src/js/main.ts | Updates deps wiring for timer functions used by invoked callback actors (interval-based watchdog/supervisor). |
| plan.md | Documents completion notes for phase 4a and implementation decisions. |
| package.json | Adds xstate dependency. |
| package-lock.json | Locks xstate dependency resolution. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Run `npm run dev` and open /?inspect — a stately.ai window renders the live machine diagram with transitions and events in real time. Doubly gated: import.meta.env.DEV (compile-time dead code in production — verified zero stately bytes in dist) and an explicit URL param, so the e2e runs against the dev server never open it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Adăugat și Stately Inspector (dev-only, opt-in): |
- Shared domain types (RadioState, RadioDeps, FeedbackSound) and the timing constants move to radioMachine.ts — their natural owner — and radioCore re-exports them, so every other module keeps its single import surface. The dependency is now one-directional (adapter -> machine); no more runtime value imports going both ways. - Remove the unused recentUserPauseIntent guard (its logic already lives inline in unexpectedOfflinePause). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ce face
Faza 4a din
plan.md: toată orchestrarea manuală dinradioCore(timeri,playId, contoare de retry/recovery) devine mașină XState v5 declarativă (radioMachine.ts), cu paritate 100% de comportament — dovedită de suita e2e neatinsă.Ce dispare și cu ce se înlocuiește
clearTimeoutpeste totafter-delays — ieșirea din stare le anulează automatplayIdincrementat manual contra race-urilorplayerPlay()e promise actor invocat — un resolve întârziat după ieșirea din stare e aruncat prin construcțiesetState()care putea fi chemat de oriunde, fără validareSTATE_FXrămâne tabel declarativ (entry action parametrizată per stare — exact filozofia "un state = efecte precise"). Backoff-ul de recovery e o expresie de delay pestecontext.recoveryCount; recheck-ul offline e self-transition cu cadență fixă, fără escaladare.radioCore.tsdevine adaptor subțire cu același API public —main.tsși celelalte module neatinse (minus 2 deps dispărute:setTimeout/clearTimeout, care acum trăiesc în clock-ul actorului).Capcana pe care doar e2e-ul a prins-o
Timerele globale de browser aruncă
Illegal invocationcând sunt apelate ca metode pedeps(certhis === window). Unit testele (mock-uri) nu aveau cum s-o vadă — suita e2e neatinsă a prins-o instant (18/37 roșii la prima rulare, 37/37 după wrapper-ul de o linie). Exact rolul ei de dovadă.Verificare
tsc --noEmitcurat • ✅ Unit 63/63 portate (SimulatedClock injectat, aserțiunileplayIddevenite comportamentale) • ✅ E2E 37/37 — NEATINSE • ✅ Coverage 99% pe mașină • ✅ Smoke complet pevite previewRămâne 4b (separat): redesign
audioInstance+ canalul de feedback cutoneîn STATE_FX — cere re-validare pe device.🤖 Generated with Claude Code