@pygmalionjs/pygmalion 0.7.4 → 0.8.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 (133) hide show
  1. package/README.ko.md +17 -2
  2. package/README.md +18 -2
  3. package/dist-lib/CameraLayer-nkNrazn8.js +159 -0
  4. package/dist-lib/pygmalion.js +14628 -16995
  5. package/dist-lib/runtime-DdYnT6k4.js +19705 -0
  6. package/dist-lib/style.css +1 -1
  7. package/dist-lib/testing.js +20 -21
  8. package/dist-lib/types/EditorShell.d.ts +1 -1
  9. package/dist-lib/types/canvas/CameraLayer.d.ts +3 -3
  10. package/dist-lib/types/canvas/Canvas.d.ts +2 -2
  11. package/dist-lib/types/canvas/FrameLabelBanner.d.ts +1 -1
  12. package/dist-lib/types/canvas/FrameView.d.ts +8 -2
  13. package/dist-lib/types/canvas/FrozenRoutePreview.d.ts +1 -1
  14. package/dist-lib/types/canvas/InspectOverlay.d.ts +4 -2
  15. package/dist-lib/types/canvas/LightweightCanvas.d.ts +1 -1
  16. package/dist-lib/types/canvas/SectionBoxes.d.ts +1 -1
  17. package/dist-lib/types/canvas/ShadowRoutePreview.d.ts +2 -2
  18. package/dist-lib/types/canvas/useFlowSession.d.ts +1 -1
  19. package/dist-lib/types/canvas/useFrameLod.d.ts +1 -1
  20. package/dist-lib/types/contract/transport.d.ts +5 -5
  21. package/dist-lib/types/contract/types.d.ts +1 -1
  22. package/dist-lib/types/contract/validate.d.ts +3 -3
  23. package/dist-lib/types/core/context.d.ts +2 -2
  24. package/dist-lib/types/core/debug.d.ts +14 -14
  25. package/dist-lib/types/core/hostBindings.d.ts +2 -2
  26. package/dist-lib/types/core/runtime.d.ts +34 -3
  27. package/dist-lib/types/core/runtimeContext.d.ts +3 -3
  28. package/dist-lib/types/core/schedulers.d.ts +67 -0
  29. package/dist-lib/types/editor/assetEditing.d.ts +2 -2
  30. package/dist-lib/types/editor/automaticMotion.d.ts +0 -11
  31. package/dist-lib/types/editor/automaticPseudoStates.d.ts +8 -2
  32. package/dist-lib/types/editor/captureSupply.d.ts +16 -7
  33. package/dist-lib/types/editor/catalog.d.ts +2 -2
  34. package/dist-lib/types/editor/codegen.d.ts +6 -3
  35. package/dist-lib/types/editor/componentConnections.d.ts +13 -8
  36. package/dist-lib/types/editor/componentInstances.d.ts +2 -2
  37. package/dist-lib/types/editor/declarations.d.ts +107 -0
  38. package/dist-lib/types/editor/designCompiler.d.ts +9 -4
  39. package/dist-lib/types/editor/designImport.d.ts +48 -12
  40. package/dist-lib/types/editor/documentSync.d.ts +28 -25
  41. package/dist-lib/types/editor/domImport.d.ts +2 -2
  42. package/dist-lib/types/editor/fiberMap.d.ts +1 -1
  43. package/dist-lib/types/editor/flowSessionScheduler.d.ts +106 -159
  44. package/dist-lib/types/editor/flowSessions.d.ts +147 -84
  45. package/dist-lib/types/editor/frameInteraction.d.ts +22 -32
  46. package/dist-lib/types/editor/frameLabels.d.ts +11 -5
  47. package/dist-lib/types/editor/frameLanes.d.ts +14 -17
  48. package/dist-lib/types/editor/frameLod.d.ts +17 -0
  49. package/dist-lib/types/editor/frameLodRuntime.d.ts +38 -38
  50. package/dist-lib/types/editor/framePreviewKeys.d.ts +14 -5
  51. package/dist-lib/types/editor/frozenImport.d.ts +13 -14
  52. package/dist-lib/types/editor/heldPseudoStates.d.ts +1 -1
  53. package/dist-lib/types/editor/host.d.ts +10 -55
  54. package/dist-lib/types/editor/implementationSourceMap.d.ts +2 -2
  55. package/dist-lib/types/editor/importFidelity.d.ts +1 -1
  56. package/dist-lib/types/editor/inspect.d.ts +7 -2
  57. package/dist-lib/types/editor/interactiveSessionSurface.d.ts +26 -34
  58. package/dist-lib/types/editor/interactiveStates.d.ts +12 -10
  59. package/dist-lib/types/editor/livePreviewInstances.d.ts +13 -3
  60. package/dist-lib/types/editor/liveScreens.d.ts +25 -16
  61. package/dist-lib/types/editor/mountScheduler.d.ts +8 -11
  62. package/dist-lib/types/editor/nodeSelector.d.ts +1 -1
  63. package/dist-lib/types/editor/pendingSourceChanges.d.ts +19 -0
  64. package/dist-lib/types/editor/previewBootstrap.d.ts +12 -22
  65. package/dist-lib/types/editor/previewCaptureProgress.d.ts +10 -6
  66. package/dist-lib/types/editor/previewEnvironmentControls.d.ts +14 -17
  67. package/dist-lib/types/editor/previewIntent.d.ts +11 -16
  68. package/dist-lib/types/editor/previewSweep.d.ts +7 -3
  69. package/dist-lib/types/editor/previewWarmup.d.ts +19 -7
  70. package/dist-lib/types/editor/projectRuntime.d.ts +2 -2
  71. package/dist-lib/types/editor/registry.d.ts +6 -2
  72. package/dist-lib/types/editor/revisionCatalog.d.ts +19 -19
  73. package/dist-lib/types/editor/revisionCatalogInstall.d.ts +18 -17
  74. package/dist-lib/types/editor/routePreview.d.ts +85 -139
  75. package/dist-lib/types/editor/scenarioCoverage.d.ts +52 -20
  76. package/dist-lib/types/editor/screenCards.d.ts +10 -6
  77. package/dist-lib/types/editor/screenDimensions.d.ts +9 -5
  78. package/dist-lib/types/editor/screenFlows.d.ts +8 -5
  79. package/dist-lib/types/editor/screenInteractions.d.ts +9 -1
  80. package/dist-lib/types/editor/screenLists.d.ts +8 -4
  81. package/dist-lib/types/editor/screenStateGroups.d.ts +1 -1
  82. package/dist-lib/types/editor/sectionHeaders.d.ts +10 -6
  83. package/dist-lib/types/editor/sessionPresets.d.ts +12 -32
  84. package/dist-lib/types/editor/shadowPreview.d.ts +7 -10
  85. package/dist-lib/types/editor/sharedSource.d.ts +4 -2
  86. package/dist-lib/types/editor/sourceChangeSummary.d.ts +1 -1
  87. package/dist-lib/types/editor/sourceJournal.d.ts +2 -2
  88. package/dist-lib/types/editor/stateSwitchMetrics.d.ts +68 -82
  89. package/dist-lib/types/editor/store.d.ts +88 -22
  90. package/dist-lib/types/editor/storyboardAuthored.d.ts +2 -2
  91. package/dist-lib/types/editor/storyboardDiscovery.d.ts +4 -3
  92. package/dist-lib/types/editor/storyboardEnvironment.d.ts +9 -5
  93. package/dist-lib/types/editor/storyboardGraph.d.ts +1 -1
  94. package/dist-lib/types/editor/storyboardGraphRuntime.d.ts +8 -10
  95. package/dist-lib/types/editor/storyboardGraphView.d.ts +1 -1
  96. package/dist-lib/types/editor/storyboardJourney.d.ts +1 -1
  97. package/dist-lib/types/editor/surfaceDemands.d.ts +196 -31
  98. package/dist-lib/types/editor/tokens.d.ts +24 -25
  99. package/dist-lib/types/editor/variantAxes.d.ts +70 -0
  100. package/dist-lib/types/editor/variantPrefetch.d.ts +26 -43
  101. package/dist-lib/types/editor/viewportPresets.d.ts +6 -6
  102. package/dist-lib/types/editor/visualQa.d.ts +2 -2
  103. package/dist-lib/types/lib.d.ts +104 -108
  104. package/dist-lib/types/shell/CatalogAssetPanel.d.ts +1 -1
  105. package/dist-lib/types/shell/CodePanel.d.ts +1 -1
  106. package/dist-lib/types/shell/ComponentStateControls.d.ts +7 -53
  107. package/dist-lib/types/shell/FrameInspector.d.ts +17 -0
  108. package/dist-lib/types/shell/FrameReviewDetails.d.ts +28 -0
  109. package/dist-lib/types/shell/LayerTree.d.ts +1 -1
  110. package/dist-lib/types/shell/StoryboardGraphPanel.d.ts +2 -2
  111. package/dist-lib/types/shell/VariantPanel.d.ts +23 -0
  112. package/dist-lib/types/shell/inspectorControls.d.ts +34 -0
  113. package/dist-lib/types/shell/qaPresentation.d.ts +1 -1
  114. package/dist-lib/types/testing.d.ts +5 -6
  115. package/dist-lib/types/ui/SourceRefControl.d.ts +1 -1
  116. package/docs/coverage-contracts.md +431 -0
  117. package/docs/screen-state-contract.md +14 -16
  118. package/node/component-branches.mjs +7 -13
  119. package/node/design-session.mjs +3 -5
  120. package/node/dev-mirror.mjs +9 -6
  121. package/node/envelope.mjs +43 -0
  122. package/node/impact-frames.mjs +94 -0
  123. package/node/inspect-plugin.mjs +12 -6
  124. package/node/preview-artifact-plugin.mjs +3 -9
  125. package/node/qa-capture-plugin.mjs +3 -6
  126. package/node/source-graph.mjs +1 -0
  127. package/node/storyboard-environment.mjs +33 -6
  128. package/node/vite.mjs +1 -0
  129. package/package.json +7 -2
  130. package/dist-lib/CameraLayer-D2wrEPwG.js +0 -415
  131. package/dist-lib/frameLodRuntime-CU0IYaSu.js +0 -272
  132. package/dist-lib/runtime-DRkTU9eq.js +0 -16195
  133. package/dist-lib/types/core/defaultRuntime.d.ts +0 -3
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
9
9
  import { promisify } from 'node:util';
10
10
  import { execFile } from 'node:child_process';
11
11
  import { resolvePygmalionPreviewViteCacheDir } from './preview-vite-cache.mjs';
12
+ import { sendJson as sendJsonEnvelope } from './envelope.mjs';
12
13
 
13
14
  const execFileAsync = promisify(execFile);
14
15
 
@@ -253,11 +254,8 @@ export function resolveDevMirrorWorktreePaths({
253
254
  };
254
255
  }
255
256
 
256
- function json(res, statusCode, body) {
257
- res.statusCode = statusCode;
258
- res.setHeader('content-type', 'application/json; charset=utf-8');
259
- res.end(JSON.stringify(body));
260
- }
257
+ // One envelope for every endpoint; see envelope.mjs.
258
+ const json = sendJsonEnvelope;
261
259
 
262
260
  async function run(command, args, options = {}) {
263
261
  const result = await execFileAsync(command, args, {
@@ -613,7 +611,12 @@ async function reclaimStaleDevMirrorSyncLock({
613
611
  }
614
612
  }
615
613
 
616
- /** Serializes mirror mutation and exact read-lease acquisition across processes. */
614
+ /**
615
+ * Serializes mirror mutation and exact read-lease acquisition across processes.
616
+ * @param {string} repoRoot
617
+ * @param {() => Promise<unknown>} task
618
+ * @param {{ onLockCandidateReady?: (info: unknown) => unknown; onStaleLockConfirmed?: (info: unknown) => unknown; lockTimeoutMs?: number }} [options]
619
+ */
617
620
  export async function withDevMirrorSyncLock(
618
621
  repoRoot,
619
622
  task,
@@ -0,0 +1,43 @@
1
+ // One JSON envelope for every Pygmalion endpoint.
2
+ //
3
+ // Success bodies are whatever the operation returns. Failures always carry
4
+ // `ok: false`, a machine `error` code the transport maps to TransportError,
5
+ // and a prose `message`; `retryable`, `preparing`, and `details` ride along
6
+ // when the operation sets them. Plugins that used to write prose into `error`
7
+ // still can — it moves to `message` here and the code becomes request_failed.
8
+ const MACHINE_CODE = /^[a-z][a-z0-9_]*$/;
9
+
10
+ /** Shapes a failure body: machine code in `error`, prose in `message`. */
11
+ export function errorBody(body) {
12
+ const source = body && typeof body === 'object' && !Array.isArray(body) ? body : { error: String(body) };
13
+ const { error, message, ...rest } = source;
14
+ const code = typeof error === 'string' && MACHINE_CODE.test(error) ? error : 'request_failed';
15
+ const prose =
16
+ typeof message === 'string' && message
17
+ ? message
18
+ : typeof error === 'string' && !MACHINE_CODE.test(error)
19
+ ? error
20
+ : undefined;
21
+ return {
22
+ ...rest,
23
+ ok: false,
24
+ error: code,
25
+ ...(prose === undefined ? {} : { message: prose }),
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Writes a JSON response. A status at or above 400, or a body that says
31
+ * `ok: false`, is written through `errorBody`; anything else is written as is.
32
+ */
33
+ export function sendJson(response, statusCode, body) {
34
+ const failed =
35
+ statusCode >= 400 || (body && typeof body === 'object' && !Array.isArray(body) && body.ok === false);
36
+ const payload = `${JSON.stringify(failed ? errorBody(body) : body)}\n`;
37
+ response.statusCode = statusCode;
38
+ response.setHeader('content-type', 'application/json; charset=utf-8');
39
+ response.setHeader('content-length', String(Buffer.byteLength(payload)));
40
+ response.setHeader('cache-control', 'no-store');
41
+ response.setHeader('x-content-type-options', 'nosniff');
42
+ response.end(payload);
43
+ }
@@ -0,0 +1,94 @@
1
+ // Which frames a source change reaches, decided where the catalog lives.
2
+ //
3
+ // The editor used to take the dependency closure from `inspect.impact` and
4
+ // match it against each page's sourcePaths in the browser; the QA capture
5
+ // then took the frames the browser had decided on. Deciding here, from the
6
+ // revision catalog the capture worker also reads, gives both the same list.
7
+ import fs from 'node:fs/promises';
8
+ import path from 'node:path';
9
+
10
+ export function normalizeSourcePath(value) {
11
+ return String(value)
12
+ .replaceAll('\\', '/')
13
+ .replace(/[?#].*$/, '')
14
+ .replace(/^\.?\//, '')
15
+ .replace(/\/+/g, '/');
16
+ }
17
+
18
+ export function sameSourcePath(a, b) {
19
+ const left = normalizeSourcePath(a);
20
+ const right = normalizeSourcePath(b);
21
+ return left === right || left.endsWith(`/${right}`) || right.endsWith(`/${left}`);
22
+ }
23
+
24
+ /**
25
+ * The catalog screens whose declared source paths intersect `files`, in
26
+ * catalog order. Each entry is `{ id, name }`: the canonical frame id the
27
+ * editor and the capture worker both use, and the screen's name.
28
+ */
29
+ export function impactFramesFromCatalog(catalog, files) {
30
+ const pages = catalog?.screens?.pages;
31
+ if (!Array.isArray(pages) || !Array.isArray(files) || files.length === 0) return [];
32
+ const frames = [];
33
+ for (const page of pages) {
34
+ if (!page || typeof page !== 'object') continue;
35
+ const id = typeof page.importPageId === 'string' ? page.importPageId : null;
36
+ if (!id) continue;
37
+ const sourcePaths = Array.isArray(page.sourcePaths) ? page.sourcePaths : [];
38
+ const hit = sourcePaths.some(
39
+ (sourcePath) =>
40
+ typeof sourcePath === 'string' &&
41
+ files.some((file) => typeof file === 'string' && sameSourcePath(sourcePath, file)),
42
+ );
43
+ if (!hit) continue;
44
+ frames.push({ id, name: typeof page.name === 'string' ? page.name : id });
45
+ }
46
+ return frames;
47
+ }
48
+
49
+ async function containedFile(root, file) {
50
+ const resolvedRoot = path.resolve(root);
51
+ const resolved = path.resolve(resolvedRoot, file);
52
+ if (resolved !== resolvedRoot && !resolved.startsWith(`${resolvedRoot}${path.sep}`)) {
53
+ throw new Error('Revision catalog file must stay inside the source root.');
54
+ }
55
+ return resolved;
56
+ }
57
+
58
+ /**
59
+ * Reads the revision catalog on demand and answers impact frames from it. The
60
+ * file is re-read only when its modification time changes; a missing or
61
+ * unreadable catalog answers no frames rather than failing the impact call,
62
+ * because the dependency closure is still useful on its own.
63
+ */
64
+ export function createImpactFrameResolver({ root, catalogFile }) {
65
+ let cached = null;
66
+ async function readCatalog() {
67
+ if (!catalogFile) return null;
68
+ const file = await containedFile(root, catalogFile);
69
+ let stat;
70
+ try {
71
+ stat = await fs.stat(file);
72
+ } catch {
73
+ cached = null;
74
+ return null;
75
+ }
76
+ if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
77
+ return cached.catalog;
78
+ }
79
+ try {
80
+ const catalog = JSON.parse(await fs.readFile(file, 'utf8'));
81
+ cached = { mtimeMs: stat.mtimeMs, size: stat.size, catalog };
82
+ return catalog;
83
+ } catch {
84
+ cached = null;
85
+ return null;
86
+ }
87
+ }
88
+ return {
89
+ async resolve(files) {
90
+ const catalog = await readCatalog();
91
+ return catalog ? impactFramesFromCatalog(catalog, files) : [];
92
+ },
93
+ };
94
+ }
@@ -4,7 +4,9 @@ import path from 'node:path';
4
4
  import MagicString from 'magic-string';
5
5
  import ts from 'typescript';
6
6
  import { findAffectedSourceFiles } from './source-graph.mjs';
7
+ import { createImpactFrameResolver } from './impact-frames.mjs';
7
8
  import { writeSourceOperations } from './source-operations.mjs';
9
+ import { sendJson as sendJsonEnvelope } from './envelope.mjs';
8
10
 
9
11
  export { findAffectedSourceFiles } from './source-graph.mjs';
10
12
  export { writeSourceOperations } from './source-operations.mjs';
@@ -338,8 +340,13 @@ export function pygmalionInspectPlugin({
338
340
  legacyEndpoints = [],
339
341
  normalizeValue,
340
342
  sourceDirectory = 'src',
343
+ catalogFile = null,
341
344
  }) {
342
345
  const controls = [endpoint, ...legacyEndpoints];
346
+ // Impact answers frames as well as files when the revision catalog is
347
+ // known: the same catalog the capture worker reads names each screen's
348
+ // source paths, so the editor and the QA capture agree on the frames.
349
+ const impactFrames = createImpactFrameResolver({ root, catalogFile });
343
350
  return {
344
351
  name: 'pygmalion-inspect',
345
352
  apply: 'serve',
@@ -449,7 +456,9 @@ export function pygmalionInspectPlugin({
449
456
  const files = await findAffectedSourceFiles(root, input?.componentFiles, {
450
457
  sourceDirectory,
451
458
  });
452
- sendJson(res, 200, { files });
459
+ const componentFiles = Array.isArray(input?.componentFiles) ? input.componentFiles : [];
460
+ const frames = await impactFrames.resolve([...componentFiles, ...files]);
461
+ sendJson(res, 200, { files, frames });
453
462
  return;
454
463
  }
455
464
 
@@ -517,11 +526,8 @@ async function readJson(req) {
517
526
  return JSON.parse(raw);
518
527
  }
519
528
 
520
- function sendJson(res, status, body) {
521
- res.statusCode = status;
522
- res.setHeader('content-type', 'application/json; charset=utf-8');
523
- res.end(JSON.stringify(body));
524
- }
529
+ // One envelope for every endpoint; see envelope.mjs.
530
+ const sendJson = sendJsonEnvelope;
525
531
 
526
532
  export async function writeStyleEdit(
527
533
  root,
@@ -19,6 +19,7 @@ export {
19
19
  PYGMALION_REVISION_CATALOG_SUFFIX,
20
20
  PREVIEW_CAPTURE_PRIORITY,
21
21
  } from './contract.mjs';
22
+ import { sendJson as sendJsonEnvelope } from './envelope.mjs';
22
23
  export const DEFAULT_PYGMALION_PREVIEW_ARTIFACT_FILE =
23
24
  'artifacts/pygmalion-route-previews.json';
24
25
 
@@ -57,15 +58,8 @@ function isLocalRequestHost(value) {
57
58
  }
58
59
  }
59
60
 
60
- function sendJson(response, statusCode, body) {
61
- const payload = `${JSON.stringify(body)}\n`;
62
- response.statusCode = statusCode;
63
- response.setHeader('content-type', 'application/json; charset=utf-8');
64
- response.setHeader('content-length', String(Buffer.byteLength(payload)));
65
- response.setHeader('cache-control', 'no-store');
66
- response.setHeader('x-content-type-options', 'nosniff');
67
- response.end(payload);
68
- }
61
+ // One envelope for every endpoint; see envelope.mjs.
62
+ const sendJson = sendJsonEnvelope;
69
63
 
70
64
  function requestedIdentity(params, key) {
71
65
  const value = params.get(key);
@@ -6,6 +6,7 @@ import os from 'node:os';
6
6
  import path from 'node:path';
7
7
  import pixelmatch from 'pixelmatch';
8
8
  import pngjs from 'pngjs';
9
+ import { sendJson as sendJsonEnvelope } from './envelope.mjs';
9
10
 
10
11
  export const PYGMALION_QA_CAPTURE_ENDPOINT = PYGMALION_ENDPOINTS.qaCapture;
11
12
  export const PYGMALION_QA_ARTIFACT_PREFIX = PYGMALION_ENDPOINTS.qaArtifactPrefix;
@@ -1177,12 +1178,8 @@ async function readJson(req) {
1177
1178
  }
1178
1179
  }
1179
1180
 
1180
- function sendJson(res, statusCode, body) {
1181
- res.statusCode = statusCode;
1182
- res.setHeader('content-type', 'application/json; charset=utf-8');
1183
- res.setHeader('cache-control', 'no-store');
1184
- res.end(JSON.stringify(body));
1185
- }
1181
+ // One envelope for every endpoint; see envelope.mjs.
1182
+ const sendJson = sendJsonEnvelope;
1186
1183
 
1187
1184
  function sendPng(res, artifact) {
1188
1185
  res.statusCode = 200;
@@ -379,6 +379,7 @@ export function directSourceUsers(usage, targetFile, { maxDepth = MAX_BARREL_DEP
379
379
  const seen = new Set();
380
380
  // 'star' means every symbol of the target is in play, which is what the
381
381
  // target itself exposes to a direct importer.
382
+ /** @type {Array<{ module: string; names: 'star' | Set<string>; depth: number }>} */
382
383
  const queue = [{ module: targetFile, names: 'star', depth: 0 }];
383
384
 
384
385
  while (queue.length > 0) {
@@ -16,6 +16,7 @@ import {
16
16
  canonicalizeStoryboardExecutions,
17
17
  PYGMALION_STORYBOARD_CANONICAL_CONTROL,
18
18
  } from './storyboard-canonical.mjs';
19
+ import { sendJson as sendJsonEnvelope } from './envelope.mjs';
19
20
 
20
21
  export const PYGMALION_STORYBOARD_ENVIRONMENT_CONTROL =
21
22
  PYGMALION_ENDPOINTS.storyboardEnvironment;
@@ -1306,6 +1307,36 @@ export function storyboardEnvironmentBootstrapSource() {
1306
1307
  value: screenPreset,
1307
1308
  });
1308
1309
  }
1310
+ // A preset that fixes the clock (timers.mode 'fixed') is what the Node
1311
+ // capture runtime asks Playwright for. A live frame installs the same clock
1312
+ // here, before application modules run, so the running screen shows the
1313
+ // date and time its capture shows instead of the designer's wall clock.
1314
+ // Timers keep running, as they do under Playwright's fixed time.
1315
+ const timers = screenPreset ? plainRecord(screenPreset.timers) : null;
1316
+ const fixedNow = timers && timers.mode === 'fixed' ? timers.now : null;
1317
+ const fixedTime =
1318
+ typeof fixedNow === 'string' || typeof fixedNow === 'number'
1319
+ ? new Date(fixedNow).getTime()
1320
+ : NaN;
1321
+ if (Number.isFinite(fixedTime)) {
1322
+ const RealDate = globalThis.Date ?? Date;
1323
+ const FixedDate = function Date(...args) {
1324
+ if (!new.target) return new RealDate(fixedTime).toString();
1325
+ return Reflect.construct(
1326
+ RealDate,
1327
+ args.length === 0 ? [fixedTime] : args,
1328
+ new.target === FixedDate ? RealDate : new.target,
1329
+ );
1330
+ };
1331
+ Object.setPrototypeOf(FixedDate, RealDate);
1332
+ FixedDate.prototype = RealDate.prototype;
1333
+ FixedDate.now = () => fixedTime;
1334
+ Object.defineProperty(globalThis, 'Date', {
1335
+ configurable: true,
1336
+ writable: true,
1337
+ value: FixedDate,
1338
+ });
1339
+ }
1309
1340
  // 'sandbox' virtualizes the whole store per document: reads of undeclared
1310
1341
  // keys see a pristine profile and writes never reach the shared origin
1311
1342
  // storage. Hidden preview instances run with it so a walk that persists UI
@@ -1699,12 +1730,8 @@ export function sourceUsageMiddleware(root, { sourceDirectory = 'src' } = {}) {
1699
1730
  };
1700
1731
  }
1701
1732
 
1702
- function sendJson(response, statusCode, body) {
1703
- response.statusCode = statusCode;
1704
- response.setHeader('content-type', 'application/json; charset=utf-8');
1705
- response.setHeader('cache-control', 'no-store');
1706
- response.end(JSON.stringify(body));
1707
- }
1733
+ // One envelope for every endpoint; see envelope.mjs.
1734
+ const sendJson = sendJsonEnvelope;
1708
1735
 
1709
1736
  export function pygmalionStoryboardPlugin(options) {
1710
1737
  const root = path.resolve(options.root);
package/node/vite.mjs CHANGED
@@ -344,6 +344,7 @@ export function createPygmalionVitePlugins(config) {
344
344
  ),
345
345
  legacyEndpoints: project.inspect.legacyEndpoints ?? [],
346
346
  normalizeValue: project.inspect.normalizeValue,
347
+ catalogFile: revisionCatalogFile,
347
348
  sourceDirectory: project.sourceDirectory,
348
349
  }),
349
350
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pygmalionjs/pygmalion",
3
- "version": "0.7.4",
3
+ "version": "0.8.0",
4
4
  "description": "Code-backed DOM design sandbox and visual QA editor",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -52,6 +52,7 @@
52
52
  },
53
53
  "files": [
54
54
  "dist-lib",
55
+ "docs/coverage-contracts.md",
55
56
  "docs/screen-state-contract.md",
56
57
  "node/contract.mjs",
57
58
  "node/contract.d.mts",
@@ -59,8 +60,10 @@
59
60
  "node/automatic-motion-runtime.mjs",
60
61
  "node/automatic-pseudo-runtime.mjs",
61
62
  "node/design-session.mjs",
63
+ "node/envelope.mjs",
62
64
  "node/dev-mirror.mjs",
63
65
  "node/dev-view.vite.mjs",
66
+ "node/impact-frames.mjs",
64
67
  "node/inspect-plugin.mjs",
65
68
  "node/inspect.mjs",
66
69
  "node/inspect-writeback.mjs",
@@ -99,9 +102,11 @@
99
102
  "lint": "eslint .",
100
103
  "lint:language": "node scripts/lint-language.mjs",
101
104
  "typecheck": "tsc --noEmit",
102
- "prepublishOnly": "npm run lint && npm run lint:language && npm test && npm run typecheck && npm run build:lib",
105
+ "typecheck:node": "tsc -p tsconfig.node.json",
106
+ "prepublishOnly": "npm run lint && npm run lint:language && npm test && npm run typecheck && npm run typecheck:node && npm run build:lib",
103
107
  "preview": "vite preview",
104
108
  "build:types": "node scripts/emit-types.mjs",
109
+ "smoke:consumer": "node scripts/smoke-consumer.mjs",
105
110
  "release": "node scripts/release.mjs"
106
111
  },
107
112
  "peerDependencies": {