state-scenarios-cli 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +10 -1
  2. package/dist/browser.d.ts +27 -0
  3. package/dist/chunk-GCYOLAFU.js +40 -0
  4. package/dist/chunk-GCYOLAFU.js.map +1 -0
  5. package/dist/chunk-O6QTNAAZ.js +67 -0
  6. package/dist/chunk-O6QTNAAZ.js.map +1 -0
  7. package/dist/chunk-YFNROT3R.js +115 -0
  8. package/dist/chunk-YFNROT3R.js.map +1 -0
  9. package/dist/cli.js +260 -43
  10. package/dist/cli.js.map +1 -1
  11. package/dist/doctor-URGIFA3W.js +200 -0
  12. package/dist/doctor-URGIFA3W.js.map +1 -0
  13. package/dist/doctor.d.ts +21 -0
  14. package/dist/errors-G7ODRBLT.js +11 -0
  15. package/dist/errors-G7ODRBLT.js.map +1 -0
  16. package/dist/errors.d.ts +33 -0
  17. package/dist/init-MRK4F5SV.js +96 -0
  18. package/dist/init-MRK4F5SV.js.map +1 -0
  19. package/dist/init.d.ts +6 -0
  20. package/dist/inspect-LN4EACWA.js +13 -0
  21. package/dist/inspect-LN4EACWA.js.map +1 -0
  22. package/dist/inspect.d.ts +63 -0
  23. package/dist/mcp-HRQIWAIR.js +342 -0
  24. package/dist/mcp-HRQIWAIR.js.map +1 -0
  25. package/dist/mcp-protocol.d.ts +51 -0
  26. package/dist/mcp.d.ts +19 -0
  27. package/dist/promote-GSF5UXJL.js +78 -0
  28. package/dist/promote-GSF5UXJL.js.map +1 -0
  29. package/dist/promote.d.ts +12 -0
  30. package/dist/record-WEZRD2VT.js +59 -0
  31. package/dist/record-WEZRD2VT.js.map +1 -0
  32. package/dist/record.d.ts +41 -0
  33. package/dist/skills-NQRJHKHG.js +80 -0
  34. package/dist/skills-NQRJHKHG.js.map +1 -0
  35. package/dist/skills.d.ts +13 -0
  36. package/package.json +5 -4
  37. package/skills/create-scenario-map/SKILL.md +88 -0
  38. package/skills/state-scenarios/SKILL.md +91 -0
  39. package/skills/state-scenarios/references/auth.md +52 -0
  40. package/skills/state-scenarios/references/capture.md +45 -0
  41. package/skills/state-scenarios/references/debugging.md +52 -0
  42. package/skills/state-scenarios/references/mcp.md +39 -0
  43. package/skills/state-scenarios/references/media.md +50 -0
  44. package/skills/state-scenarios/references/network.md +67 -0
  45. package/skills/state-scenarios/references/setup.md +94 -0
  46. package/skills/state-scenarios/references/verify.md +47 -0
  47. package/skills/state-scenarios/references/writing-scenarios.md +55 -0
@@ -0,0 +1,67 @@
1
+ # Network: strict mode, third parties and existing MSW
2
+
3
+ ## Strict mode
4
+
5
+ `?scenario-strict` (or `startScenarios({ strict: true })`) answers every request the scenario
6
+ doesn't cover with a 501, so nothing reaches a real backend. Page loads and assets (any path with a
7
+ file extension) pass. `shoot`, `openScenario` and the MCP server run strict by default.
8
+ `unhandledRequests(page)`, the panel and `shoot` list what wasn't covered.
9
+
10
+ ## Requests that should pass or be silenced
11
+
12
+ ```ts
13
+ import { type ScenarioInput, startScenarios } from 'state-scenarios';
14
+
15
+ declare const scenarios: ScenarioInput[];
16
+
17
+ await startScenarios({
18
+ scenarios,
19
+ allow: ['images.example-cdn.com'], // reaches the real network, even in strict mode
20
+ ignore: [/posthog|segment|sentry|google-analytics|googletagmanager|recaptcha|hcaptcha|intercom/i],
21
+ });
22
+ ```
23
+
24
+ - `allow`: requests the screen needs from the real network, such as CDN images or a maps API.
25
+ - `ignore`: analytics, error reporting, captcha and chat SDKs. Never reported as unhandled; in strict
26
+ mode they get an empty 204 instead of a 501, so most SDKs stay quiet and don't retry.
27
+ - An SDK that still logs errors (some expect a JSON body): don't start it in scenarios,
28
+ `if (!currentSession()?.active) analytics.init(...)`.
29
+
30
+ Patterns are URL substrings or regular expressions. Match the hosts the app actually calls.
31
+
32
+ ## Apps that already use MSW
33
+
34
+ Start your worker and pass it in. Scenario entries answer first, then your handlers, then the strict
35
+ catch-all; `destroy()` restores your handlers.
36
+
37
+ ```ts
38
+ await worker.start();
39
+ await startScenarios({ scenarios, worker });
40
+ ```
41
+
42
+ ## Reusing handlers from tests
43
+
44
+ Pass handlers by name, then name them in scenarios. They answer after the scenario's `network`.
45
+
46
+ ```ts
47
+ import { type ScenarioInput, startScenarios } from 'state-scenarios';
48
+ import { statementsEmpty } from './test/handlers'; // the handlers your unit tests use
49
+
50
+ const scenarios: ScenarioInput[] = [
51
+ { name: 'no-statements', handlers: ['statementsEmpty'], ready: 'text=No statements yet' },
52
+ ];
53
+
54
+ await startScenarios({ scenarios, handlers: { statementsEmpty } });
55
+ ```
56
+
57
+ ```json
58
+ {
59
+ "name": "no-statements",
60
+ "extends": "signed-in",
61
+ "handlers": ["statementsEmpty"],
62
+ "ready": "text=No statements yet"
63
+ }
64
+ ```
65
+
66
+ An unknown name fails when the scenario opens and lists the known ones. `validate` can't see
67
+ handlers, so it only checks the JSON.
@@ -0,0 +1,94 @@
1
+ # Setup
2
+
3
+ ## Install and init
4
+
5
+ ```bash
6
+ npm i state-scenarios zod msw # msw only when scenarios mock the network
7
+ npm i -D state-scenarios-cli @playwright/test
8
+ npm i state-scenarios-react # useScenarioState, for component state (React, Preact)
9
+
10
+ npx state-scenarios init src/scenarios
11
+ ```
12
+
13
+ `init` writes `src/scenarios/default.json` and `src/scenario.schema.json`, prints a wiring snippet,
14
+ installs these skills into `.agents/skills/` (and `.claude/skills/`), and registers the MCP server
15
+ in `.mcp.json`. It never edits your source files. `--no-skills` skips the skills and the MCP server.
16
+
17
+ Vite: serve the MSW worker and let the panel save into the project.
18
+
19
+ ```ts
20
+ // vite.config.ts
21
+ import { msw } from 'msw/vite';
22
+ import { stateScenarios } from 'state-scenarios/vite';
23
+
24
+ export default { plugins: [msw({ mode: 'worker-only' }), stateScenarios({ dir: 'src/scenarios' })] };
25
+ ```
26
+
27
+ Without the MSW Vite plugin: `npx msw init public`.
28
+
29
+ ## Wire the runtime before render
30
+
31
+ ```ts
32
+ import { type ScenarioInput, startScenarios } from 'state-scenarios';
33
+ import { mountPanel } from 'state-scenarios/panel';
34
+
35
+ if (import.meta.env.DEV) {
36
+ const session = await startScenarios({
37
+ scenarios: Object.values(
38
+ import.meta.glob<ScenarioInput>('/src/scenarios/*.json', { eager: true, import: 'default' }),
39
+ ),
40
+ adapters: [], // see below
41
+ refresh: () => queryClient.resetQueries(), // when scenarios mock the network
42
+ });
43
+ mountPanel(session, { schemaPath: '../scenario.schema.json' });
44
+ }
45
+ // then create the router and render
46
+ ```
47
+
48
+ - `await` it before rendering. The first fetches must already hit the scenario's mocks.
49
+ - Routers read the URL once, when they're created. If a module creates the router as soon as it
50
+ is imported (`export const router = createBrowserRouter(...)`), a static import runs before the
51
+ session. Import it after:
52
+
53
+ ```ts
54
+ // main.ts: start the session first, then import the router
55
+ import type { ScenarioInput } from 'state-scenarios';
56
+
57
+ if (import.meta.env.DEV) {
58
+ const { startScenarios } = await import('state-scenarios');
59
+ await startScenarios({
60
+ scenarios: Object.values(
61
+ import.meta.glob<ScenarioInput>('/src/scenarios/*.json', { eager: true, import: 'default' }),
62
+ ),
63
+ });
64
+ }
65
+
66
+ const { router } = await import('./routing/router'); // created now, with the scenario's URL
67
+ ```
68
+
69
+ - Next.js (App Router): start it from a `'use client'` wrapper in the root layout and render
70
+ children once it resolves. The package README has the component and the save route.
71
+ - Apps that already run MSW: start your worker and pass it as `worker`. See topic `network`.
72
+
73
+ ## Connect app state
74
+
75
+ Mock the network first. Add adapters for state no response can produce, such as a panel left open
76
+ or cached data during a refetch.
77
+
78
+ | State lives in | Connect with |
79
+ | -------------- | ------------------------------------------------------------------------------------------------------------ |
80
+ | TanStack Query | `tanstackQuery(queryClient)` from `state-scenarios/tanstack-query` |
81
+ | Zustand | `zustandAdapter(store, { schema: StoreSchema.partial() })` from `state-scenarios/zustand` |
82
+ | Redux | `withScenarioState(rootReducer, StateSchema)`, then `reduxAdapter(store)` |
83
+ | XState | `xstateAdapter(machine, { context: ContextSchema })`, then `createActor(machine, { snapshot: xs.snapshot })` |
84
+ | Cookies | `cookiesAdapter()` from `state-scenarios/cookies` |
85
+ | A component | `useScenarioState('page', state, (s) => dispatch({ type: 'set', state: s }), Schema)` |
86
+
87
+ Pass a Zod schema wherever an adapter accepts one: invalid scenario state is then rejected before
88
+ anything changes. `scenarioJsonSchema({ state: { key: Schema } })` regenerates the editor schema
89
+ with typed `state`.
90
+
91
+ ## Check
92
+
93
+ `npx state-scenarios doctor src/scenarios --base-url http://localhost:5173` passes. Opening
94
+ `/?scenario=default` sets `<html data-scenario="default">`.
@@ -0,0 +1,47 @@
1
+ # Verify a UI change across every state
2
+
3
+ Start the dev server, then:
4
+
5
+ ```bash
6
+ npx state-scenarios shoot src/scenarios --base-url http://localhost:5173 --out .scenario-shots
7
+ ```
8
+
9
+ For each scenario `shoot` opens the link in strict mode, waits for `ready`, saves `<name>.png`, and
10
+ checks for unmocked requests, uncaught exceptions and `console.error` calls. `--only a,b` limits
11
+ the run; `--json` prints the report for scripts.
12
+
13
+ ## When a scenario fails
14
+
15
+ The terminal names a page for it: `❯ details .scenario-shots/<name>.md`. Read that page. It has:
16
+
17
+ - the codes (`READY_TIMEOUT`, `UNMOCKED_REQUEST`, `SESSION_NOT_STARTED`, `SCENARIO_NOT_IN_APP`, `PAGE_ERROR`,
18
+ `CONSOLE_ERROR`, `ELEMENT_NOT_FOUND`) and the link that reopens the state;
19
+ - for a ready timeout, whether the selector matched anything, plus distinctive selectors on the
20
+ page now;
21
+ - for unmocked requests, `network` entries ready to paste (fill in real bodies, or record them);
22
+ - uncaught exceptions and console errors.
23
+
24
+ Look at the screenshot before changing the scenario: a ready timeout can mean the selector is
25
+ wrong, or that the UI change broke the state. Fix the app when the screenshot shows a bug.
26
+
27
+ ## Evidence
28
+
29
+ Look at the PNGs for the states the change touches, plus loading, empty and error. Attach the
30
+ relevant ones to the PR. Pages for passing scenarios are deleted on each run, so the `.md` files
31
+ in the output folder are always the current failures.
32
+
33
+ ## In Playwright tests
34
+
35
+ ```ts
36
+ import { openScenario, unhandledRequests } from 'state-scenarios/playwright';
37
+
38
+ await openScenario(page, 'orders-empty');
39
+ expect(await unhandledRequests(page)).toEqual([]);
40
+ ```
41
+
42
+ ## Missing states
43
+
44
+ Ask which states lack scenarios: loading, empty, error, partial data, long content, permissions,
45
+ signed out. Capture them (topic `capture`). `npx state-scenarios suggest` asks a model to list
46
+ candidate states from the source and drafts them into `proposed/`; treat drafts as ideas, then
47
+ `promote` the reviewed ones.
@@ -0,0 +1,55 @@
1
+ # Writing scenarios
2
+
3
+ | Field | Meaning | Through `extends` |
4
+ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
5
+ | `name` | kebab-case, unique | – |
6
+ | `extends` | Scenario name(s) to build on; later parents win | – |
7
+ | `path` | Route it opens at, e.g. `/orders/42` | inherited |
8
+ | `url` | Query params the app starts with | merged; `null` removes |
9
+ | `network[]` | `method`, `path` (MSW syntax: `/api/users/:id`, absolute URLs, `*`), optional `query`, then one of `response`, `sequence`, `remove: true` | a child entry replaces the parent's with the same method, path and query |
10
+ | `handlers` | Names of MSW handlers passed to `startScenarios({ handlers })` | combined |
11
+ | `state` | Per-adapter state, keyed by adapter key | deep-merged; `null` removes |
12
+ | `ready` | Playwright selector visible once this state shows | not inherited |
13
+ | `viewport` | `{ "width": 390, "height": 844 }` for `shoot` and `record` | inherited |
14
+ | `description`, `tags` | For people and the panel | not inherited |
15
+
16
+ Responses: `body` is sent as JSON, `text` as text (an HTML error page). `status` defaults to 200.
17
+ `headers` sets headers. `delay` is milliseconds, or `"infinite"` to hold the request open for a
18
+ loading state. `sequence` answers successive calls in order and repeats the last one, e.g. fail
19
+ then succeed on retry:
20
+
21
+ ```json
22
+ {
23
+ "name": "flaky-then-recovers",
24
+ "extends": "default",
25
+ "network": [{
26
+ "path": "/api/orders",
27
+ "sequence": [{ "status": 503, "body": { "error": "busy" } }, { "body": [{ "id": 1 }] }]
28
+ }],
29
+ "ready": "text=Order #1"
30
+ }
31
+ ```
32
+
33
+ Loading state: `"response": { "delay": "infinite" }` and `"ready": "text=Loading orders…"`.
34
+
35
+ ## Ready selectors
36
+
37
+ `text=No orders yet`, `role=heading[name="Orders"]`, `[data-testid=empty-orders]`, or CSS. Pick
38
+ something that is visible only in this state: a count like "0" or a heading every state shares is
39
+ too generic. Check it against the running app (panel, or MCP `check_ready`) before saving.
40
+
41
+ ## Combining
42
+
43
+ `?scenario=signed-in,server-error` combines scenarios like `extends`, later names winning.
44
+
45
+ ## Rules and validation
46
+
47
+ `defineScenarioRules` checks business rules on every resolved scenario. Export `rules` from a module
48
+ and pass it to `startScenarios({ rules })` and `--rules` on the CLI.
49
+
50
+ ```bash
51
+ npx state-scenarios validate src/scenarios --rules src/scenario-rules.ts
52
+ ```
53
+
54
+ Schema shape, `extends` lookups and rules are checked. Whether production can actually reach a
55
+ state is your call: a valid scenario can describe a response the backend never sends.