@onlook/storybook-plugin 0.4.0-beta.12 → 0.4.0-beta.14

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.
package/dist/index.d.ts CHANGED
@@ -53,6 +53,20 @@ type OnlookContainedPayload = {
53
53
  message: string;
54
54
  /** Present when the failure is attributed to a contained module (U4). */
55
55
  module?: ContainedModuleMarker;
56
+ /**
57
+ * React component stack from `onCaughtError`'s `errorInfo` (ONL-1505,
58
+ * additive). Present only on the `via: 'onCaughtError'` path — the legacy
59
+ * boundary decorator has no component stack to offer.
60
+ */
61
+ componentStack?: string;
62
+ /**
63
+ * Which catch path emitted this (ONL-1505, additive). `'onCaughtError'` =
64
+ * the React root hook registered via `parameters.react.rootOptions` (catches
65
+ * component render-throws, incl. those caught by Storybook's own
66
+ * renderToCanvas ErrorBoundary); `'decorator'` = the legacy
67
+ * OnlookContainmentBoundary decorator. Absent ⟹ legacy decorator (pre-1505).
68
+ */
69
+ via?: 'onCaughtError' | 'decorator';
56
70
  };
57
71
  type OnlookRenderedPayload = {
58
72
  storyId: string;
@@ -2,9 +2,13 @@ import { O as OnlookPluginOptions } from '../storybook-onlook-plugin-B9Eo_OIq.js
2
2
  import 'vite';
3
3
 
4
4
  declare function pluginOptionsFromPresetOptions(options: Record<string, unknown> | undefined): OnlookPluginOptions;
5
+ type ChannelLike = {
6
+ on(eventName: string, listener: (...args: unknown[]) => void): unknown;
7
+ };
8
+ declare function experimental_serverChannel(channel: ChannelLike): Promise<ChannelLike>;
5
9
  type ViteConfigLike = {
6
10
  plugins?: unknown[];
7
11
  } & Record<string, unknown>;
8
12
  declare function viteFinal(config: ViteConfigLike, options?: Record<string, unknown>): Promise<ViteConfigLike>;
9
13
 
10
- export { pluginOptionsFromPresetOptions, viteFinal };
14
+ export { experimental_serverChannel, pluginOptionsFromPresetOptions, viteFinal };
@@ -13,6 +13,15 @@ import { promisify } from 'util';
13
13
  import { chromium } from 'playwright';
14
14
 
15
15
  globalThis.require = createRequire(import.meta.url);
16
+
17
+ // src/containment-contract/containment-contract.ts
18
+ var ONLOOK_CONTAINED_MODULES_GLOBAL = "__ONLOOK_CONTAINED_MODULES__";
19
+ var ONLOOK_CONTAINED_PROP = "__onlookContained";
20
+ var ONLOOK_CONTAINED_EVENT = "onlook/contained";
21
+ var ONLOOK_RENDERED_EVENT = "onlook/rendered";
22
+ var ONLOOK_UNCONTAINED_ERROR_EVENT = "onlook/uncontained-error";
23
+ var ONLOOK_RENDER_STATE_ATTR = "data-onlook-render-state";
24
+ var ONLOOK_CONTAINED_CARD_ATTR = "data-onlook-contained-card";
16
25
  function componentLocPlugin(options = {}) {
17
26
  const include = options.include ?? /\.(jsx|tsx)$/;
18
27
  const traverse = traverseModule.default ?? traverseModule;
@@ -73,14 +82,6 @@ function componentLocPlugin(options = {}) {
73
82
  }
74
83
  };
75
84
  }
76
-
77
- // src/containment-contract/containment-contract.ts
78
- var ONLOOK_CONTAINED_MODULES_GLOBAL = "__ONLOOK_CONTAINED_MODULES__";
79
- var ONLOOK_CONTAINED_PROP = "__onlookContained";
80
- var ONLOOK_RENDER_STATE_ATTR = "data-onlook-render-state";
81
- var ONLOOK_CONTAINED_CARD_ATTR = "data-onlook-contained-card";
82
-
83
- // src/env-containment/env-containment.ts
84
85
  var ENV_CONTAINMENT_PLUGIN_NAME = "onlook-env-containment";
85
86
  var ENV_CONTAINMENT_VIRTUAL_PREFIX = "\0onlook-env-containment:";
86
87
  var VALID_IDENT_RE = /^[A-Za-z_$][\w$]*$/;
@@ -1431,12 +1432,12 @@ function storybookOnlookPlugin(options = {}) {
1431
1432
  "manifest.json"
1432
1433
  );
1433
1434
  refreshManifest(cachedManifestPath);
1434
- let pending2;
1435
+ let pending3;
1435
1436
  const scheduleRefresh = (file) => {
1436
1437
  if (file !== cachedManifestPath) return;
1437
- if (pending2) clearTimeout(pending2);
1438
- pending2 = setTimeout(() => {
1439
- pending2 = void 0;
1438
+ if (pending3) clearTimeout(pending3);
1439
+ pending3 = setTimeout(() => {
1440
+ pending3 = void 0;
1440
1441
  refreshManifest(cachedManifestPath);
1441
1442
  }, 100);
1442
1443
  };
@@ -1521,6 +1522,68 @@ function storybookOnlookPlugin(options = {}) {
1521
1522
  );
1522
1523
  }
1523
1524
 
1525
+ // src/utils/notifyStoryError/notifyStoryError.ts
1526
+ var DEBOUNCE_MS3 = 300;
1527
+ var REQUEST_TIMEOUT_MS2 = 5e3;
1528
+ var pending2 = null;
1529
+ var inFlight2 = false;
1530
+ var trailing = false;
1531
+ var latest = null;
1532
+ function notifyStoryError(payload) {
1533
+ enqueue({ type: "STORY_ERROR", ...payload });
1534
+ }
1535
+ function notifyStoryResolved(storyId) {
1536
+ enqueue({ type: "STORY_RESOLVED", storyId });
1537
+ }
1538
+ function enqueue(event) {
1539
+ const proxyToken = process.env.ONBOOK_PROXY_TOKEN;
1540
+ const broadcastUrl = process.env.ONBOOK_BROADCAST_URL;
1541
+ if (!proxyToken || !broadcastUrl) return;
1542
+ latest = event;
1543
+ if (pending2) clearTimeout(pending2);
1544
+ pending2 = setTimeout(() => {
1545
+ pending2 = null;
1546
+ if (inFlight2) {
1547
+ trailing = true;
1548
+ return;
1549
+ }
1550
+ void fire2(proxyToken, broadcastUrl);
1551
+ }, DEBOUNCE_MS3);
1552
+ }
1553
+ async function fire2(proxyToken, broadcastUrl) {
1554
+ const event = latest;
1555
+ if (!event) return;
1556
+ inFlight2 = true;
1557
+ try {
1558
+ const res = await fetch(broadcastUrl, {
1559
+ method: "POST",
1560
+ headers: {
1561
+ Authorization: `Bearer ${proxyToken}`,
1562
+ "Content-Type": "application/json"
1563
+ },
1564
+ body: JSON.stringify({ event }),
1565
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
1566
+ });
1567
+ if (!res.ok) {
1568
+ console.warn("[STORYBOOK_PLUGIN] story-event broadcast non-2xx", {
1569
+ status: res.status,
1570
+ statusText: res.statusText
1571
+ });
1572
+ }
1573
+ } catch (err) {
1574
+ console.warn(
1575
+ "[STORYBOOK_PLUGIN] story-event broadcast failed",
1576
+ err instanceof Error ? err.message : String(err)
1577
+ );
1578
+ } finally {
1579
+ inFlight2 = false;
1580
+ if (trailing) {
1581
+ trailing = false;
1582
+ void fire2(proxyToken, broadcastUrl);
1583
+ }
1584
+ }
1585
+ }
1586
+
1524
1587
  // src/preset/preset.ts
1525
1588
  var OPTION_KEY_MAP = {
1526
1589
  port: true,
@@ -1542,10 +1605,56 @@ function pluginOptionsFromPresetOptions(options) {
1542
1605
  }
1543
1606
  return picked;
1544
1607
  }
1608
+ var erroredStories = /* @__PURE__ */ new Map();
1609
+ var RENDER_RESOLVE_SUPPRESS_MS = 1500;
1610
+ async function experimental_serverChannel(channel) {
1611
+ try {
1612
+ channel.on(ONLOOK_CONTAINED_EVENT, (...args) => {
1613
+ const payload = args[0];
1614
+ if (!payload) return;
1615
+ console.error(`[ONBOOK_STORY_ERROR] contained story=${payload.storyId || "?"}`);
1616
+ if (payload.storyId) erroredStories.set(payload.storyId, Date.now());
1617
+ notifyStoryError({
1618
+ storyId: payload.storyId ?? "",
1619
+ message: payload.message ?? "Unknown story error",
1620
+ source: "contained",
1621
+ ...payload.componentStack ? { componentStack: payload.componentStack } : {}
1622
+ });
1623
+ });
1624
+ channel.on(ONLOOK_UNCONTAINED_ERROR_EVENT, (...args) => {
1625
+ const payload = args[0];
1626
+ if (!payload) return;
1627
+ console.error(`[ONBOOK_STORY_ERROR] uncontained story=${payload.storyId || "?"}`);
1628
+ if (payload.storyId) erroredStories.set(payload.storyId, Date.now());
1629
+ notifyStoryError({
1630
+ storyId: payload.storyId ?? "",
1631
+ message: payload.message ?? "Unknown story error",
1632
+ source: "uncontained"
1633
+ });
1634
+ });
1635
+ channel.on(ONLOOK_RENDERED_EVENT, (...args) => {
1636
+ const payload = args[0];
1637
+ if (!payload?.storyId) return;
1638
+ const erroredAt = erroredStories.get(payload.storyId);
1639
+ if (erroredAt === void 0) return;
1640
+ if (Date.now() - erroredAt < RENDER_RESOLVE_SUPPRESS_MS) return;
1641
+ erroredStories.delete(payload.storyId);
1642
+ console.error(`[ONBOOK_STORY_ERROR] resolved story=${payload.storyId}`);
1643
+ notifyStoryResolved(payload.storyId);
1644
+ });
1645
+ console.error("[ONBOOK_STORY_ERROR] server-channel error + recovery tap attached");
1646
+ } catch (err) {
1647
+ console.error(
1648
+ "[ONBOOK_STORY_ERROR] failed to attach server-channel tap",
1649
+ err instanceof Error ? err.message : String(err)
1650
+ );
1651
+ }
1652
+ return channel;
1653
+ }
1545
1654
  async function viteFinal(config, options = {}) {
1546
1655
  const plugins = storybookOnlookPlugin(pluginOptionsFromPresetOptions(options));
1547
1656
  if (plugins.length === 0) return config;
1548
1657
  return { ...config, plugins: [...config.plugins ?? [], ...plugins] };
1549
1658
  }
1550
1659
 
1551
- export { pluginOptionsFromPresetOptions, viteFinal };
1660
+ export { experimental_serverChannel, pluginOptionsFromPresetOptions, viteFinal };
@@ -44,6 +44,20 @@ type OnlookContainedPayload = {
44
44
  message: string;
45
45
  /** Present when the failure is attributed to a contained module (U4). */
46
46
  module?: ContainedModuleMarker;
47
+ /**
48
+ * React component stack from `onCaughtError`'s `errorInfo` (ONL-1505,
49
+ * additive). Present only on the `via: 'onCaughtError'` path — the legacy
50
+ * boundary decorator has no component stack to offer.
51
+ */
52
+ componentStack?: string;
53
+ /**
54
+ * Which catch path emitted this (ONL-1505, additive). `'onCaughtError'` =
55
+ * the React root hook registered via `parameters.react.rootOptions` (catches
56
+ * component render-throws, incl. those caught by Storybook's own
57
+ * renderToCanvas ErrorBoundary); `'decorator'` = the legacy
58
+ * OnlookContainmentBoundary decorator. Absent ⟹ legacy decorator (pre-1505).
59
+ */
60
+ via?: 'onCaughtError' | 'decorator';
47
61
  };
48
62
  type OnlookRenderedPayload = {
49
63
  storyId: string;
@@ -92,7 +106,7 @@ declare class OnlookContainmentBoundary extends React.Component<BoundaryProps, B
92
106
  private renderedSignalSent;
93
107
  constructor(props: BoundaryProps);
94
108
  static getDerivedStateFromError(error: unknown): BoundaryState;
95
- componentDidCatch(error: unknown): void;
109
+ componentDidCatch(): void;
96
110
  componentDidMount(): void;
97
111
  componentDidUpdate(): void;
98
112
  componentWillUnmount(): void;
@@ -134,6 +148,27 @@ declare function getPreviewChannel(): ChannelLike | null;
134
148
  * stay byte-stable.
135
149
  */
136
150
  declare function isSnapshotEnvironment(): boolean;
151
+ /** Trim a React component stack to a payload-sized summary. */
152
+ declare function summarizeComponentStack(stack: unknown, maxLength?: number): string | undefined;
153
+ /**
154
+ * React root `onCaughtError` handler (ONL-1505). Registered via the preview
155
+ * annotations' `parameters.react.rootOptions.onCaughtError`, which
156
+ * `@storybook/react`'s `renderToCanvas` threads verbatim into `createRoot`.
157
+ *
158
+ * React 19 calls this for ANY error caught by an error boundary in the story
159
+ * tree — crucially including Storybook's own `renderToCanvas` ErrorBoundary,
160
+ * which is what catches a component render-throw. This is the ONLY node-
161
+ * reachable signal for render-crashes: `window.onerror` fires only for
162
+ * UNCAUGHT errors, and the channel `storyThrewException` / `storyErrored`
163
+ * events don't fire for boundary-caught render-throws. We re-emit as the
164
+ * existing `onlook/contained` event (tagged `via: 'onCaughtError'`) so the
165
+ * node-side server-channel tap needs no change.
166
+ *
167
+ * Registering `onCaughtError` replaces React's default (which logs caught
168
+ * errors to `console.error`), so we re-log for dev parity — keeping the
169
+ * iframe console behavior unchanged and giving the live tap a second marker.
170
+ */
171
+ declare function handleRootCaughtError(error: unknown, componentStack?: unknown): void;
137
172
  /**
138
173
  * Attribute an error to a contained module (U4's env-containment stand-ins).
139
174
  * Primary: the marker the stand-in attaches at `error.__onlookContained`.
@@ -152,5 +187,14 @@ declare function onlookBeforeAll(): void;
152
187
 
153
188
  declare const decorators: (typeof onlookContainmentDecorator)[];
154
189
  declare const beforeAll: typeof onlookBeforeAll;
190
+ declare const parameters: {
191
+ react: {
192
+ rootOptions: {
193
+ onCaughtError: (error: unknown, errorInfo?: {
194
+ componentStack?: string;
195
+ }) => void;
196
+ };
197
+ };
198
+ };
155
199
 
156
- export { ContainmentCard, ONLOOK_CONTAINED_CARD_ATTR, ONLOOK_CONTAINED_EVENT, ONLOOK_CONTAINMENT_CONTRACT_GLOBAL, ONLOOK_CONTAINMENT_CONTRACT_VERSION, ONLOOK_RENDERED_EVENT, ONLOOK_RENDER_STATE_ATTR, ONLOOK_RENDER_STORY_ATTR, ONLOOK_UNCONTAINED_ERROR_EVENT, type OnlookContainedPayload, OnlookContainmentBoundary, type OnlookContainmentContract, type OnlookRenderState, type OnlookRenderedPayload, type OnlookUncontainedErrorPayload, attributeErrorToModule, beforeAll, decorators, getPreviewChannel, installUncontainedErrorRelay, isSnapshotEnvironment, onlookBeforeAll, onlookContainmentDecorator };
200
+ export { ContainmentCard, ONLOOK_CONTAINED_CARD_ATTR, ONLOOK_CONTAINED_EVENT, ONLOOK_CONTAINMENT_CONTRACT_GLOBAL, ONLOOK_CONTAINMENT_CONTRACT_VERSION, ONLOOK_RENDERED_EVENT, ONLOOK_RENDER_STATE_ATTR, ONLOOK_RENDER_STORY_ATTR, ONLOOK_UNCONTAINED_ERROR_EVENT, type OnlookContainedPayload, OnlookContainmentBoundary, type OnlookContainmentContract, type OnlookRenderState, type OnlookRenderedPayload, type OnlookUncontainedErrorPayload, attributeErrorToModule, beforeAll, decorators, getPreviewChannel, handleRootCaughtError, installUncontainedErrorRelay, isSnapshotEnvironment, onlookBeforeAll, onlookContainmentDecorator, parameters, summarizeComponentStack };
@@ -85,6 +85,31 @@ function emitRendered(storyId) {
85
85
  contractVersion: ONLOOK_CONTAINMENT_CONTRACT_VERSION
86
86
  });
87
87
  }
88
+ function summarizeComponentStack(stack, maxLength = 2e3) {
89
+ if (typeof stack !== "string") return void 0;
90
+ const trimmed = stack.trim();
91
+ if (!trimmed) return void 0;
92
+ return trimmed.length > maxLength ? `${trimmed.slice(0, maxLength - 1)}\u2026` : trimmed;
93
+ }
94
+ function handleRootCaughtError(error, componentStack) {
95
+ try {
96
+ console.error(error);
97
+ } catch {
98
+ }
99
+ const storyId = currentStoryId();
100
+ const stack = summarizeComponentStack(componentStack);
101
+ const module = attributeErrorToModule(error);
102
+ emitContained({
103
+ storyId: storyId ?? "",
104
+ contractVersion: ONLOOK_CONTAINMENT_CONTRACT_VERSION,
105
+ message: summarizeError(error),
106
+ ...module ? { module } : {},
107
+ ...stack ? { componentStack: stack } : {}
108
+ });
109
+ if (storyId) {
110
+ queueMicrotask(() => setRenderState("contained", storyId));
111
+ }
112
+ }
88
113
  function summarizeError(error, maxLength = 400) {
89
114
  const raw = error instanceof Error ? error.message : typeof error === "string" ? error : String(error);
90
115
  const oneline = raw.trim();
@@ -242,15 +267,8 @@ var OnlookContainmentBoundary = class extends React.Component {
242
267
  static getDerivedStateFromError(error) {
243
268
  return { error, hasError: true };
244
269
  }
245
- componentDidCatch(error) {
246
- const module = attributeErrorToModule(error);
270
+ componentDidCatch() {
247
271
  setRenderState("contained", this.props.storyId);
248
- emitContained({
249
- storyId: this.props.storyId,
250
- contractVersion: ONLOOK_CONTAINMENT_CONTRACT_VERSION,
251
- message: summarizeError(error),
252
- ...module ? { module } : {}
253
- });
254
272
  }
255
273
  componentDidMount() {
256
274
  exposeContract();
@@ -298,5 +316,12 @@ function onlookContainmentDecorator(Story, context) {
298
316
  // src/preview/index.ts
299
317
  var decorators = [onlookContainmentDecorator];
300
318
  var beforeAll = onlookBeforeAll;
319
+ var parameters = {
320
+ react: {
321
+ rootOptions: {
322
+ onCaughtError: (error, errorInfo) => handleRootCaughtError(error, errorInfo?.componentStack)
323
+ }
324
+ }
325
+ };
301
326
 
302
- export { ContainmentCard, ONLOOK_CONTAINED_CARD_ATTR, ONLOOK_CONTAINED_EVENT, ONLOOK_CONTAINMENT_CONTRACT_GLOBAL, ONLOOK_CONTAINMENT_CONTRACT_VERSION, ONLOOK_RENDERED_EVENT, ONLOOK_RENDER_STATE_ATTR, ONLOOK_RENDER_STORY_ATTR, ONLOOK_UNCONTAINED_ERROR_EVENT, OnlookContainmentBoundary, attributeErrorToModule, beforeAll, decorators, getPreviewChannel, installUncontainedErrorRelay, isSnapshotEnvironment, onlookBeforeAll, onlookContainmentDecorator };
327
+ export { ContainmentCard, ONLOOK_CONTAINED_CARD_ATTR, ONLOOK_CONTAINED_EVENT, ONLOOK_CONTAINMENT_CONTRACT_GLOBAL, ONLOOK_CONTAINMENT_CONTRACT_VERSION, ONLOOK_RENDERED_EVENT, ONLOOK_RENDER_STATE_ATTR, ONLOOK_RENDER_STORY_ATTR, ONLOOK_UNCONTAINED_ERROR_EVENT, OnlookContainmentBoundary, attributeErrorToModule, beforeAll, decorators, getPreviewChannel, handleRootCaughtError, installUncontainedErrorRelay, isSnapshotEnvironment, onlookBeforeAll, onlookContainmentDecorator, parameters, summarizeComponentStack };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlook/storybook-plugin",
3
- "version": "0.4.0-beta.12",
3
+ "version": "0.4.0-beta.14",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "onlook-storybook": "./dist/cli/index.js"
@@ -10,6 +10,7 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
12
  },
13
+ "./package.json": "./package.json",
13
14
  "./preset": {
14
15
  "types": "./dist/preset/index.d.ts",
15
16
  "default": "./dist/preset/index.js"