@junejuly-lockstep/reconciler 0.1.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -0
  3. package/dist/boot.d.ts +51 -0
  4. package/dist/boot.d.ts.map +1 -0
  5. package/dist/boot.js +162 -0
  6. package/dist/boot.js.map +1 -0
  7. package/dist/cli.d.ts +4 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +180 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +55 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +140 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/contract.d.ts +61 -0
  16. package/dist/contract.d.ts.map +1 -0
  17. package/dist/contract.js +154 -0
  18. package/dist/contract.js.map +1 -0
  19. package/dist/determinism.d.ts +23 -0
  20. package/dist/determinism.d.ts.map +1 -0
  21. package/dist/determinism.js +102 -0
  22. package/dist/determinism.js.map +1 -0
  23. package/dist/errors.d.ts +25 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +35 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/exitCodes.d.ts +33 -0
  28. package/dist/exitCodes.d.ts.map +1 -0
  29. package/dist/exitCodes.js +49 -0
  30. package/dist/exitCodes.js.map +1 -0
  31. package/dist/extract.d.ts +30 -0
  32. package/dist/extract.d.ts.map +1 -0
  33. package/dist/extract.js +376 -0
  34. package/dist/extract.js.map +1 -0
  35. package/dist/index.d.ts +35 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +19 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/init.d.ts +17 -0
  40. package/dist/init.d.ts.map +1 -0
  41. package/dist/init.js +55 -0
  42. package/dist/init.js.map +1 -0
  43. package/dist/journeys.d.ts +34 -0
  44. package/dist/journeys.d.ts.map +1 -0
  45. package/dist/journeys.js +276 -0
  46. package/dist/journeys.js.map +1 -0
  47. package/dist/paths.d.ts +26 -0
  48. package/dist/paths.d.ts.map +1 -0
  49. package/dist/paths.js +80 -0
  50. package/dist/paths.js.map +1 -0
  51. package/dist/reconcile.d.ts +63 -0
  52. package/dist/reconcile.d.ts.map +1 -0
  53. package/dist/reconcile.js +316 -0
  54. package/dist/reconcile.js.map +1 -0
  55. package/dist/report.d.ts +33 -0
  56. package/dist/report.d.ts.map +1 -0
  57. package/dist/report.js +97 -0
  58. package/dist/report.js.map +1 -0
  59. package/dist/sync.d.ts +46 -0
  60. package/dist/sync.d.ts.map +1 -0
  61. package/dist/sync.js +110 -0
  62. package/dist/sync.js.map +1 -0
  63. package/dist/upload.d.ts +33 -0
  64. package/dist/upload.d.ts.map +1 -0
  65. package/dist/upload.js +86 -0
  66. package/dist/upload.js.map +1 -0
  67. package/dist/util.d.ts +24 -0
  68. package/dist/util.d.ts.map +1 -0
  69. package/dist/util.js +97 -0
  70. package/dist/util.js.map +1 -0
  71. package/dist/visual.d.ts +49 -0
  72. package/dist/visual.d.ts.map +1 -0
  73. package/dist/visual.js +145 -0
  74. package/dist/visual.js.map +1 -0
  75. package/dist/walk.d.ts +147 -0
  76. package/dist/walk.d.ts.map +1 -0
  77. package/dist/walk.js +433 -0
  78. package/dist/walk.js.map +1 -0
  79. package/package.json +78 -0
  80. package/templates/example.journey.ts +90 -0
  81. package/templates/lockstep-reconcile.yml +219 -0
  82. package/templates/lockstep.config.example.ts +46 -0
  83. package/templates/msw-handlers.example.ts +113 -0
  84. package/templates/scenarios.example.ts +32 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"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"}
package/dist/walk.js ADDED
@@ -0,0 +1,433 @@
1
+ import path from 'node:path';
2
+ import { bootApp } from './boot.js';
3
+ import { InfrastructureError } from './errors.js';
4
+ import { enumeratePaths } from './paths.js';
5
+ import { ensureDir, safeSegment } from './util.js';
6
+ /* ------------------------------------------------------------------------------------------
7
+ * Determinism constants. Everything here is part of the "same commit + same seed => identical
8
+ * bytes" guarantee; see README "Determinism rules". Change them only with a baseline reset.
9
+ * ---------------------------------------------------------------------------------------- */
10
+ /** 2026-01-15T09:30:00.000Z. Every walk's `Date.now()` starts here. */
11
+ export const FROZEN_EPOCH_MS = Date.UTC(2026, 0, 15, 9, 30, 0);
12
+ export const VIEWPORT = { width: 1280, height: 800 };
13
+ export const DEVICE_SCALE_FACTOR = 1;
14
+ export const LOCALE = 'en-US';
15
+ export const TIMEZONE = 'UTC';
16
+ export const COLOR_SCHEME = 'light';
17
+ /** Injected on every document: no animations, no transitions, no blinking caret, no smooth scroll. */
18
+ export const DISABLE_MOTION_CSS = [
19
+ '*, *::before, *::after {',
20
+ ' animation: none !important;',
21
+ ' animation-duration: 0s !important;',
22
+ ' transition: none !important;',
23
+ ' transition-duration: 0s !important;',
24
+ ' caret-color: transparent !important;',
25
+ ' scroll-behavior: auto !important;',
26
+ '}',
27
+ 'html { scroll-behavior: auto !important; }',
28
+ 'video, canvas { visibility: hidden !important; }',
29
+ ].join('\n');
30
+ /**
31
+ * Chromium flags that remove machine-dependent text rendering, plus `--disable-partial-raster`:
32
+ * with partial raster on, a tile whose content changed after first paint (a page that renders its
33
+ * state root once its data arrived) is re-rastered only inside the damage rect, and the
34
+ * anti-aliasing along that rect's edges differs by a unit from a full raster of the same pixels,
35
+ * depending on whether a frame was produced in between. Invisible, but not byte-identical.
36
+ */
37
+ export const CHROMIUM_ARGS = [
38
+ '--font-render-hinting=none',
39
+ '--disable-font-subpixel-positioning',
40
+ '--disable-lcd-text',
41
+ '--force-color-profile=srgb',
42
+ '--disable-gpu',
43
+ '--disable-partial-raster',
44
+ '--hide-scrollbars',
45
+ '--disable-skia-runtime-opts',
46
+ ];
47
+ export const STEP_TIMEOUT_MS = 15_000;
48
+ export const MAX_ATTEMPTS = 2;
49
+ /** Script injected before any page script: adds the motion-killing stylesheet as soon as there is a head. */
50
+ const INIT_SCRIPT = `(() => {
51
+ const css = ${JSON.stringify(DISABLE_MOTION_CSS)};
52
+ const add = () => {
53
+ if (!document.head || document.getElementById('__lockstep_motion')) return;
54
+ const s = document.createElement('style');
55
+ s.id = '__lockstep_motion';
56
+ s.textContent = css;
57
+ document.head.appendChild(s);
58
+ };
59
+ if (document.readyState !== 'loading') add();
60
+ else document.addEventListener('DOMContentLoaded', add, { once: true });
61
+ new MutationObserver(add).observe(document, { childList: true, subtree: true });
62
+ })();`;
63
+ /** Default deps: real Playwright Chromium (imported lazily) and the real app boot. */
64
+ export const defaultWalkDeps = {
65
+ async launch() {
66
+ let pw;
67
+ try {
68
+ pw = await import('playwright');
69
+ }
70
+ catch (err) {
71
+ throw new InfrastructureError('playwright is not installed; run `npx playwright install --with-deps chromium`', err);
72
+ }
73
+ try {
74
+ return (await pw.chromium.launch({ headless: true, args: CHROMIUM_ARGS }));
75
+ }
76
+ catch (err) {
77
+ throw new InfrastructureError('could not launch Chromium; run `npx playwright install --with-deps chromium`', err);
78
+ }
79
+ },
80
+ boot: bootApp,
81
+ };
82
+ export function screenshotPath(journeyKey, stateKey, scenario) {
83
+ return `shots/${safeSegment(journeyKey)}/${safeSegment(stateKey)}/${safeSegment(scenario)}.png`;
84
+ }
85
+ export function tracePath(journeyKey, scenario, pathIndex) {
86
+ return `traces/${safeSegment(journeyKey)}/${safeSegment(scenario)}/${String(pathIndex).padStart(3, '0')}.zip`;
87
+ }
88
+ export function triggerTestId(journey, from, event) {
89
+ const binding = journey.meta.bindings.find((b) => b.from === from && b.event === event);
90
+ return binding?.testId ?? event.toLowerCase();
91
+ }
92
+ export function testIdSelector(testId) {
93
+ return `[data-testid="${testId.replace(/"/g, '\\"')}"]`;
94
+ }
95
+ /** Pull GraphQL operation names out of a request whose path ends in /graphql. */
96
+ export function operationNamesOf(req) {
97
+ let url;
98
+ try {
99
+ url = new URL(req.url());
100
+ }
101
+ catch {
102
+ return [];
103
+ }
104
+ if (!/\/graphql\/?$/.test(url.pathname) && !url.pathname.includes('/graphql'))
105
+ return [];
106
+ const names = [];
107
+ const fromDoc = (doc) => {
108
+ if (!doc || typeof doc !== 'object')
109
+ return;
110
+ const d = doc;
111
+ if (typeof d.operationName === 'string' && d.operationName) {
112
+ names.push(d.operationName);
113
+ return;
114
+ }
115
+ if (typeof d.query === 'string') {
116
+ const m = /\b(query|mutation|subscription)\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(d.query);
117
+ if (m)
118
+ names.push(m[2]);
119
+ }
120
+ };
121
+ if (req.method() === 'GET') {
122
+ const op = url.searchParams.get('operationName');
123
+ if (op)
124
+ names.push(op);
125
+ else
126
+ fromDoc({ query: url.searchParams.get('query') ?? undefined });
127
+ return names;
128
+ }
129
+ const body = req.postData();
130
+ if (!body)
131
+ return names;
132
+ try {
133
+ const parsed = JSON.parse(body);
134
+ if (Array.isArray(parsed))
135
+ parsed.forEach(fromDoc);
136
+ else
137
+ fromDoc(parsed);
138
+ }
139
+ catch {
140
+ /* not JSON */
141
+ }
142
+ return names;
143
+ }
144
+ async function settle(page, timeout) {
145
+ await page.waitForLoadState('networkidle', { timeout }).catch(() => { });
146
+ await page.addStyleTag({ content: DISABLE_MOTION_CSS }).catch(() => { });
147
+ await page
148
+ .evaluate('(document.fonts && document.fonts.ready ? document.fonts.ready : Promise.resolve()).then(() => true)')
149
+ .catch(() => { });
150
+ // Two frames so layout settles after fonts swap in.
151
+ await page
152
+ .evaluate('new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r(true))))')
153
+ .catch(() => { });
154
+ }
155
+ /**
156
+ * The state root may appear only after `settle` ran (an app that fetches through a mock worker
157
+ * renders its root once the data is in). A shot taken in the very frames after a large layout
158
+ * change can catch tiles rasterised during the change, which differ by a unit or two of
159
+ * anti-aliasing from the steady state: not visible, but not byte-identical either. So the walker
160
+ * settles once more after the root is visible: network idle, fonts, two frames, and two more
161
+ * frames after that.
162
+ */
163
+ async function settleVisible(page, timeout) {
164
+ await settle(page, timeout);
165
+ await page
166
+ .evaluate('new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r(true))))')
167
+ .catch(() => { });
168
+ }
169
+ /** Park the pointer in the top-left corner so no hover state depends on where the last click landed. */
170
+ async function park(page) {
171
+ await page.mouse?.move(0, 0).catch(() => { });
172
+ }
173
+ /**
174
+ * One line for the walk result and the verdict detail: Playwright's first line plus the call-log
175
+ * lines that say what it was waiting for or navigating to (the selector lives there, not in the
176
+ * first line). Anything else in the call log is noise for a card.
177
+ */
178
+ export function summarizeError(err) {
179
+ const text = err instanceof Error ? err.message : String(err);
180
+ const lines = text
181
+ .split('\n')
182
+ .map((l) => l.trim().replace(/^-\s*/, ''))
183
+ .filter(Boolean);
184
+ const first = lines[0] ?? 'unknown error';
185
+ const context = lines
186
+ .slice(1)
187
+ .filter((l) => /^(waiting for|navigating to|locator resolved to)/i.test(l))
188
+ .slice(0, 2);
189
+ return [first, ...context].join(' ').replace(/\s+/g, ' ');
190
+ }
191
+ /**
192
+ * Playwright's message no longer carries the call log (what it was waiting for), so the walker
193
+ * attaches its own: the selector it waited for, the trigger it clicked, the route it was still on.
194
+ */
195
+ async function describing(what, fn) {
196
+ try {
197
+ return await fn();
198
+ }
199
+ catch (err) {
200
+ throw new Error(`${summarizeError(err)} (${what()})`, { cause: err });
201
+ }
202
+ }
203
+ async function attemptStep(fn, log, label) {
204
+ let error = null;
205
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
206
+ try {
207
+ await fn(attempt);
208
+ return { status: 'pass', attempts: attempt + 1, error: null };
209
+ }
210
+ catch (err) {
211
+ error = summarizeError(err);
212
+ log(` ${label}: attempt ${attempt + 1} failed: ${error}`);
213
+ }
214
+ }
215
+ return { status: 'fail', attempts: MAX_ATTEMPTS, error };
216
+ }
217
+ function pathnameOf(url) {
218
+ try {
219
+ return new URL(url).pathname;
220
+ }
221
+ catch {
222
+ return null;
223
+ }
224
+ }
225
+ /**
226
+ * Execute every enumerated path of every journey, per scenario, and record a `WalkResult`.
227
+ * Order: scenarios by name, journeys by key, paths by target state. Boot happens once per
228
+ * scenario so `LOCKSTEP_SCENARIO`/`LOCKSTEP_SEED` reach server-side mocks too.
229
+ */
230
+ export async function walk(opts) {
231
+ const log = opts.log ?? (() => { });
232
+ const deps = opts.deps ?? defaultWalkDeps;
233
+ const timeout = opts.stepTimeoutMs ?? STEP_TIMEOUT_MS;
234
+ const baseUrl = (opts.baseUrl ?? `http://127.0.0.1:${opts.config.port}`).replace(/\/+$/, '');
235
+ const scenarios = [...opts.scenarios].sort((a, b) => a.name.localeCompare(b.name));
236
+ const journeys = [...opts.journeys].sort((a, b) => a.key.localeCompare(b.key));
237
+ const enumerated = new Map(journeys.map((j) => [j.key, enumeratePaths(j.machine)]));
238
+ const paths = [];
239
+ for (const scenario of scenarios) {
240
+ log(`scenario ${scenario.name} (seed ${scenario.seed})`);
241
+ const app = opts.noBoot
242
+ ? null
243
+ : await deps.boot({
244
+ command: opts.config.bootCommand,
245
+ port: opts.config.port,
246
+ cwd: opts.rootDir,
247
+ env: {
248
+ LOCKSTEP_SCENARIO: scenario.name,
249
+ LOCKSTEP_SEED: String(scenario.seed),
250
+ TZ: TIMEZONE,
251
+ },
252
+ log,
253
+ });
254
+ let browser = null;
255
+ try {
256
+ browser = await deps.launch();
257
+ for (const journey of journeys) {
258
+ const journeyPaths = enumerated.get(journey.key) ?? [];
259
+ log(` journey ${journey.key}: ${journeyPaths.length} paths`);
260
+ for (let i = 0; i < journeyPaths.length; i++) {
261
+ // A dead app is an infrastructure failure, not a row of failed transitions.
262
+ if (app && !app.alive()) {
263
+ const tail = app.output().slice(-10).join('\n');
264
+ throw new InfrastructureError(`app under test exited during the walk (scenario ${scenario.name})${tail ? `\nlast output:\n${tail}` : ''}`);
265
+ }
266
+ paths.push(await walkPath({
267
+ browser,
268
+ journey,
269
+ scenario,
270
+ path: journeyPaths[i],
271
+ index: i,
272
+ baseUrl,
273
+ timeout,
274
+ log,
275
+ artifactsDir: opts.artifactsDir,
276
+ }));
277
+ }
278
+ }
279
+ }
280
+ catch (err) {
281
+ throw err instanceof InfrastructureError ? err : new InfrastructureError('walk failed', err);
282
+ }
283
+ finally {
284
+ await browser?.close().catch(() => { });
285
+ await app?.stop().catch(() => { });
286
+ }
287
+ }
288
+ return { viewport: { ...VIEWPORT }, frozenEpochMs: FROZEN_EPOCH_MS, paths };
289
+ }
290
+ async function walkPath(args) {
291
+ const { browser, journey, scenario, timeout, log } = args;
292
+ const context = await browser.newContext({
293
+ viewport: { ...VIEWPORT },
294
+ deviceScaleFactor: DEVICE_SCALE_FACTOR,
295
+ reducedMotion: 'reduce',
296
+ colorScheme: COLOR_SCHEME,
297
+ locale: LOCALE,
298
+ timezoneId: TIMEZONE,
299
+ hasTouch: false,
300
+ isMobile: false,
301
+ });
302
+ const traceRel = tracePath(journey.key, scenario.name, args.index);
303
+ const traceAbs = path.join(args.artifactsDir, traceRel);
304
+ ensureDir(path.dirname(traceAbs));
305
+ let traceKey = null;
306
+ const steps = [];
307
+ try {
308
+ await context.addInitScript(INIT_SCRIPT);
309
+ await context.tracing.start({
310
+ screenshots: true,
311
+ snapshots: true,
312
+ sources: false,
313
+ title: `${journey.key}/${scenario.name}/${args.index}`,
314
+ });
315
+ const page = await context.newPage();
316
+ await page.clock.install({ time: FROZEN_EPOCH_MS });
317
+ await page.clock.setFixedTime(FROZEN_EPOCH_MS);
318
+ const pendingOps = [];
319
+ page.on('request', (req) => pendingOps.push(...operationNamesOf(req)));
320
+ const drainOps = () => {
321
+ const ops = [...new Set(pendingOps)].sort();
322
+ pendingOps.length = 0;
323
+ return ops;
324
+ };
325
+ const shoot = async (stateKey) => {
326
+ const rel = screenshotPath(journey.key, stateKey, scenario.name);
327
+ const abs = path.join(args.artifactsDir, rel);
328
+ ensureDir(path.dirname(abs));
329
+ try {
330
+ await page.screenshot({
331
+ path: abs,
332
+ fullPage: true,
333
+ animations: 'disabled',
334
+ caret: 'hide',
335
+ scale: 'css',
336
+ });
337
+ return rel;
338
+ }
339
+ catch (err) {
340
+ log(` screenshot ${rel} failed: ${err instanceof Error ? err.message : String(err)}`);
341
+ return null;
342
+ }
343
+ };
344
+ // Step 0: land on the initial state.
345
+ const initial = journey.machine.initial;
346
+ const initialMeta = journey.meta.states[initial];
347
+ if (!initialMeta)
348
+ throw new InfrastructureError(`journey ${journey.key} has no meta for initial state ${initial}`);
349
+ const url = new URL(`${args.baseUrl}${initialMeta.route.startsWith('/') ? initialMeta.route : `/${initialMeta.route}`}`);
350
+ url.searchParams.set('lockstep_scenario', scenario.name);
351
+ url.searchParams.set('lockstep_seed', String(scenario.seed));
352
+ const initialSelector = testIdSelector(initialMeta.testId);
353
+ const first = await attemptStep(async () => {
354
+ await describing(() => `navigating to ${url.pathname}`, () => page.goto(url.toString(), { waitUntil: 'networkidle', timeout }));
355
+ await settle(page, timeout);
356
+ await describing(() => `waiting for ${initialSelector} on ${pathnameOf(page.url()) ?? url.pathname}`, () => page.locator(initialSelector).first().waitFor({ state: 'visible', timeout }));
357
+ await settleVisible(page, timeout);
358
+ }, log, `${journey.key}/${scenario.name} -> ${initial}`);
359
+ steps.push({
360
+ stateKey: initial,
361
+ transition: null,
362
+ status: first.status,
363
+ attempts: first.attempts,
364
+ error: first.error,
365
+ screenshotKey: first.status === 'pass' ? await shoot(initial) : null,
366
+ traceKey: null,
367
+ operations: drainOps(),
368
+ routeObserved: pathnameOf(page.url()),
369
+ });
370
+ let failed = first.status === 'fail';
371
+ for (const step of args.path.steps) {
372
+ const transition = { from: step.from, event: step.event, to: step.to };
373
+ if (failed) {
374
+ steps.push({
375
+ stateKey: step.to,
376
+ transition,
377
+ status: 'skipped',
378
+ attempts: 0,
379
+ error: null,
380
+ screenshotKey: null,
381
+ traceKey: null,
382
+ operations: [],
383
+ routeObserved: null,
384
+ });
385
+ continue;
386
+ }
387
+ const targetMeta = journey.meta.states[step.to];
388
+ if (!targetMeta)
389
+ throw new InfrastructureError(`journey ${journey.key} has no meta for state ${step.to}`);
390
+ const trigger = testIdSelector(triggerTestId(journey, step.from, step.event));
391
+ const targetSelector = testIdSelector(targetMeta.testId);
392
+ const target = page.locator(targetSelector).first();
393
+ const result = await attemptStep(async (attempt) => {
394
+ // A retry after a click that did land must not click twice.
395
+ if (attempt > 0 && (await target.isVisible().catch(() => false)))
396
+ return;
397
+ await describing(() => `clicking ${trigger} on ${pathnameOf(page.url()) ?? '?'}`, () => page.locator(trigger).first().click({ timeout }));
398
+ await park(page);
399
+ await settle(page, timeout);
400
+ await describing(() => `waiting for ${targetSelector} after clicking ${trigger}; still on ${pathnameOf(page.url()) ?? '?'}`, () => target.waitFor({ state: 'visible', timeout }));
401
+ await settleVisible(page, timeout);
402
+ }, log, `${journey.key}/${scenario.name} ${step.from} --${step.event}--> ${step.to}`);
403
+ steps.push({
404
+ stateKey: step.to,
405
+ transition,
406
+ status: result.status,
407
+ attempts: result.attempts,
408
+ error: result.error,
409
+ screenshotKey: result.status === 'pass' ? await shoot(step.to) : null,
410
+ traceKey: null,
411
+ operations: drainOps(),
412
+ routeObserved: pathnameOf(page.url()),
413
+ });
414
+ if (result.status === 'fail')
415
+ failed = true;
416
+ }
417
+ await page.close().catch(() => { });
418
+ }
419
+ finally {
420
+ try {
421
+ await context.tracing.stop({ path: traceAbs });
422
+ traceKey = traceRel;
423
+ }
424
+ catch (err) {
425
+ log(` trace ${traceRel} failed: ${err instanceof Error ? err.message : String(err)}`);
426
+ }
427
+ await context.close().catch(() => { });
428
+ }
429
+ for (const s of steps)
430
+ s.traceKey = traceKey;
431
+ return { journeyKey: journey.key, scenario: scenario.name, steps };
432
+ }
433
+ //# sourceMappingURL=walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.js","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@junejuly-lockstep/reconciler",
3
+ "version": "0.1.0",
4
+ "description": "Lockstep CI reconciler: extract the navigation graph, snapshot the GraphQL contract, walk journeys in Playwright against seeded mocks, diff screenshots, and post the run to the platform.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/JuneJulyHealth/lockstep.git",
9
+ "directory": "packages/reconciler"
10
+ },
11
+ "homepage": "https://github.com/JuneJulyHealth/lockstep/tree/main/packages/reconciler#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/JuneJulyHealth/lockstep/issues"
14
+ },
15
+ "keywords": [
16
+ "lockstep",
17
+ "reconciler",
18
+ "ci",
19
+ "playwright",
20
+ "xstate",
21
+ "journey",
22
+ "visual-regression",
23
+ "graphql",
24
+ "msw"
25
+ ],
26
+ "type": "module",
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "bin": {
30
+ "lockstep": "./dist/cli.js"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "templates",
41
+ "README.md"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "provenance": false
46
+ },
47
+ "engines": {
48
+ "node": ">=20.18"
49
+ },
50
+ "dependencies": {
51
+ "@graphql-inspector/core": "^6.2.1",
52
+ "@xstate/graph": "^3.0.4",
53
+ "commander": "^13.1.0",
54
+ "fast-glob": "^3.3.3",
55
+ "graphql": "^16.10.0",
56
+ "jiti": "^2.4.2",
57
+ "pixelmatch": "^7.1.0",
58
+ "playwright": "^1.50.0",
59
+ "pngjs": "^7.0.0",
60
+ "ts-morph": "^25.0.1",
61
+ "xstate": "^5.19.4",
62
+ "zod": "^3.24.1",
63
+ "@junejuly-lockstep/model": "0.1.0",
64
+ "@junejuly-lockstep/sdk": "0.1.0"
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^22.10.2",
68
+ "@types/pngjs": "^6.0.5",
69
+ "typescript": "^5.7.2",
70
+ "vitest": "^2.1.8"
71
+ },
72
+ "scripts": {
73
+ "build": "tsc -p tsconfig.json",
74
+ "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
75
+ "lint": "eslint src",
76
+ "test": "vitest run"
77
+ }
78
+ }
@@ -0,0 +1,90 @@
1
+ import { createMachine } from 'xstate';
2
+ import type { JourneyMeta } from '@junejuly-lockstep/reconciler';
3
+
4
+ /**
5
+ * Lockstep client contract, item 2: one file per journey exporting `machine` and `meta`.
6
+ * The file name gives the journey key (`book-a-visit.journey.ts` -> `book-a-visit`);
7
+ * export `key` / `name` to override.
8
+ *
9
+ * The app can execute this machine natively (XState v5). Lockstep reads only its config:
10
+ * flat states, `on` transitions, string guard names. Guard implementations stay here.
11
+ */
12
+ export const name = 'Book a visit';
13
+
14
+ export const machine = createMachine(
15
+ {
16
+ id: 'bookAVisit',
17
+ initial: 'start',
18
+ states: {
19
+ start: {
20
+ on: { BEGIN: 'pickTime' },
21
+ },
22
+ pickTime: {
23
+ on: {
24
+ CHOOSE: { target: 'confirm', guard: 'slotAvailable' },
25
+ BACK: 'start',
26
+ },
27
+ },
28
+ confirm: {
29
+ on: {
30
+ SUBMIT: 'done',
31
+ BACK: 'pickTime',
32
+ },
33
+ },
34
+ done: { type: 'final' },
35
+ },
36
+ },
37
+ {
38
+ guards: {
39
+ slotAvailable: () => true,
40
+ },
41
+ },
42
+ );
43
+
44
+ export const meta: JourneyMeta = {
45
+ narrative:
46
+ 'Nora, an eligible patient with no visit history, books a visit in under a minute without talking to anyone.',
47
+ states: {
48
+ start: {
49
+ route: '/visits',
50
+ testId: 'visits-start',
51
+ intent: 'Entry point: one clear call to action to begin booking.',
52
+ rules: ['No more than one primary button on screen.'],
53
+ },
54
+ pickTime: {
55
+ route: '/visits/time',
56
+ testId: 'visits-time',
57
+ intent: 'Pick a slot from the next seven days; unavailable slots are visible but disabled.',
58
+ rules: [
59
+ 'Slots render in the patient time zone.',
60
+ 'At least three slots are shown when any exist.',
61
+ ],
62
+ },
63
+ confirm: {
64
+ route: '/visits/confirm',
65
+ testId: 'visits-confirm',
66
+ intent: 'Review the chosen slot and clinician; one tap confirms.',
67
+ rules: ['Shows date, time, clinician and location.', 'Back keeps the chosen slot selected.'],
68
+ },
69
+ done: {
70
+ route: '/visits/done',
71
+ testId: 'visits-done',
72
+ intent: 'Confirmation with the booking reference and a calendar link.',
73
+ rules: ['Booking reference is visible without scrolling.'],
74
+ },
75
+ },
76
+ /** Which element fires each transition. Missing bindings fall back to data-testid="<event lowercased>". */
77
+ bindings: [
78
+ { from: 'start', event: 'BEGIN', testId: 'cta-begin', label: 'Book a visit' },
79
+ {
80
+ from: 'pickTime',
81
+ event: 'CHOOSE',
82
+ testId: 'slot-first-available',
83
+ label: 'First available slot',
84
+ },
85
+ { from: 'pickTime', event: 'BACK', testId: 'nav-back' },
86
+ { from: 'confirm', event: 'SUBMIT', testId: 'cta-confirm', label: 'Confirm booking' },
87
+ { from: 'confirm', event: 'BACK', testId: 'nav-back' },
88
+ ],
89
+ assets: [],
90
+ };