@storypane/sdk 0.1.0-rc.2 → 0.1.0-rc.3

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/README.md CHANGED
@@ -12,15 +12,24 @@ Start with the [Next.js integration guide](https://github.com/lkuich/storypane/b
12
12
  server-side fetching, typed block components, and a separate live preview route.
13
13
  An [Astro example](https://github.com/lkuich/storypane/blob/main/apps/example-astro/README.md) is also included.
14
14
 
15
- The current version is **0.1.0-rc.2**. Use the SDK tarball attached to the
16
- [matching GitHub prerelease](https://github.com/lkuich/storypane/releases), or build
17
- from this monorepo with `pnpm --filter @storypane/sdk build` and pack/link
18
- `packages/sdk`. npm publication is separately gated on publisher setup; when
19
- available, select the explicit RC version or `@storypane/sdk@rc`. See the
20
- [release guide](https://storypane.dev/docs/releases-and-versioning/). Install the release artifact without waiting for npm publication:
15
+ The current version is **0.1.0-rc.3**, published on npm as a release candidate:
21
16
 
22
17
  ```sh
23
- npm install https://github.com/lkuich/storypane/releases/download/v0.1.0-rc.2/storypane-sdk-0.1.0-rc.2.tgz
18
+ npm install @storypane/sdk@0.1.0-rc.3
19
+ # Or follow the candidate channel:
20
+ npm install @storypane/sdk@rc
21
+ ```
22
+
23
+ Pin the explicit version for reproducible builds. npm also assigned `latest` to
24
+ this first publication; that tag does not make it stable. See the
25
+ [release guide](https://storypane.dev/docs/releases-and-versioning/) for status,
26
+ upgrades, and known limits.
27
+
28
+ You can also build from this monorepo with `pnpm --filter @storypane/sdk build`
29
+ and pack/link `packages/sdk`, or install the verified GitHub archive:
30
+
31
+ ```sh
32
+ npm install https://github.com/lkuich/storypane/releases/download/v0.1.0-rc.3/storypane-sdk-0.1.0-rc.3.tgz
24
33
  ```
25
34
 
26
35
  Entrypoints:
@@ -42,6 +51,12 @@ The root locale contributes an empty path segment; the editor removes doubled
42
51
  slashes. `{slug}` is the selected locale's URL slug. Query placeholders also work:
43
52
  `https://site.example/preview?slug={slug}&locale={locale}`.
44
53
 
54
+ Admins set the **Space default** preview URL. Each member can save **My default
55
+ preview** in the same settings screen, or choose **Use space default**. This
56
+ preference follows the user across browsers in that space. Editor and component
57
+ previews use it unless another URL is selected in the editor. Server-created
58
+ preview sessions continue to use the space default unless `previewId` is provided.
59
+
45
60
  The default preview parameter is `storypane_preview=1`. You can change its name in
46
61
  settings; use the same name in your SDK options. Your app must permit the CMS
47
62
  origin in its CSP `frame-ancestors` policy and must not send a conflicting
@@ -174,6 +189,61 @@ The [complete Next.js example](https://github.com/lkuich/storypane/blob/main/app
174
189
  It keeps published pages on the server and reuses the renderer inside `/preview`.
175
190
  Client preview components must not import server-only fetch modules.
176
191
 
192
+ ### Block hover previews
193
+
194
+ Component previews are available starting with `@storypane/sdk@0.1.0-rc.3`.
195
+
196
+ The editor can preview saved blocks and the add-block picker's sample content in
197
+ one temporary iframe after a short hover or keyboard focus. It uses the selected
198
+ preview URL and the same authenticated, read-only flow as the full preview.
199
+ Escape dismisses the preview. Nothing is inserted or saved by hovering.
200
+
201
+ Opt in by passing a stable `onComponent` callback to `useStorypanePreview`,
202
+ `useStorypaneStory`, or `connectPreview`. Render that block alone with your existing
203
+ component renderer, without page navigation or preview status chrome:
204
+
205
+ ```tsx
206
+ const [component, setComponent] = useState<StorypaneComponent>();
207
+ const { story } = useStorypanePreview<StorypaneComponent>({
208
+ cmsUrl,
209
+ spaceId,
210
+ story: { slug, locale },
211
+ onComponent: setComponent,
212
+ });
213
+ // Keep passing the story's references and other renderer context as usual.
214
+ if (story) return <Block block={component ?? story.content} />;
215
+ ```
216
+
217
+ Use the generated `StorypaneComponent` union here: picker previews include nestable
218
+ blocks as well as content types. Both example apps demonstrate this integration.
219
+ Picker previews are opt-in per definition: set **Config → Picker preview data (JSON)**
220
+ and deploy the schema migration. This stores an optional `previewData` object with
221
+ sample field values; nested blocks use `{ _uid, component, ...fields }`. Use real
222
+ representative content, including the nested items your renderer needs. A definition
223
+ without sample data has no picker preview. Samples override defaults for this iframe
224
+ only; inserting a block still uses its normal field defaults.
225
+
226
+ For example, a feature grid can have this preview data:
227
+
228
+ ```json
229
+ {
230
+ "title": "A fresh perspective",
231
+ "items": [
232
+ { "_uid": "preview-feature", "component": "feature", "title": "Start with a good question." }
233
+ ]
234
+ }
235
+ ```
236
+
237
+ Samples are validated against field types and allowed nested blocks, limited to
238
+ 100 KB per definition, and included in full schema migrations. Referenced original
239
+ media follows the existing clone/sync/promotion dependency review. They are excluded
240
+ from delivery schema metadata and generated component types. Both examples keep
241
+ editable fixtures in `storypane/preview-data.json`; the Astro setup script installs
242
+ them into newly seeded spaces. For an existing space, copy each fixture into that
243
+ block's Config tab and deploy the migration.
244
+
245
+ Existing integrations without `onComponent` keep full-page preview and show a setup message for hover previews.
246
+
177
247
  If you prefer preview on an existing published route, `useStorypaneStory(initial,
178
248
  options)` remains available. It starts with the publication and activates drafts
179
249
  only when its `mode` matches. For Next.js Draft Mode, pass the server's `isEnabled`
@@ -642,7 +712,15 @@ authenticated `getAsset` integration for static export when public media is disa
642
712
 
643
713
  ## MCP
644
714
 
645
- Run `storypane mcp` (Node 20+) with `STORYPANE_URL`, `STORYPANE_SPACE_ID`, and a
646
- private `STORYPANE_MANAGEMENT_TOKEN`. The stdio adapter exposes read and write
715
+ Launch the pinned package directly (Node 20+), with `STORYPANE_URL`,
716
+ `STORYPANE_SPACE_ID`, and a private `STORYPANE_MANAGEMENT_TOKEN` configured in
717
+ your MCP client's environment:
718
+
719
+ ```sh
720
+ npx --yes --package=@storypane/sdk@0.1.0-rc.3 storypane mcp
721
+ ```
722
+
723
+ If installed in your project, `pnpm exec storypane mcp` also works from its root.
724
+ The stdio adapter exposes read and write
647
725
  tools over the management API; the same scopes, issuer permissions and revocation
648
726
  apply. See [MCP configuration and contracts](https://github.com/lkuich/storypane/blob/main/docs/management-api.md#mcp-adapter).
package/dist/preview.d.ts CHANGED
@@ -9,6 +9,8 @@ export interface PreviewOptions<T = StorypaneStory['content']> {
9
9
  story: StoryReference;
10
10
  mode?: PreviewMode;
11
11
  onChange: (story: StorypaneStory<T>) => void;
12
+ /** Opt in to isolated block previews using the same component renderer as your site. */
13
+ onComponent?: (block: T | undefined) => void;
12
14
  onStatus?: (status: PreviewStatus, error?: Error) => void;
13
15
  /** For standalone preview, fetch a short-lived session from your authenticated app server. */
14
16
  getSession?: () => Promise<PreviewSession>;
package/dist/preview.js CHANGED
@@ -13,6 +13,8 @@ export function connectPreview(options) {
13
13
  let reconnect, renew;
14
14
  let ready, retry = 0, revision = -1, selected = '';
15
15
  let referencesRevision;
16
+ let content;
17
+ let component;
16
18
  const embedded = window.parent !== window;
17
19
  const send = (message) => {
18
20
  if (embedded)
@@ -39,6 +41,31 @@ export function connectPreview(options) {
39
41
  element.removeAttribute('data-storypane-selected');
40
42
  }
41
43
  }
44
+ function renderComponent() {
45
+ if (!component ||
46
+ !options.onComponent ||
47
+ !session ||
48
+ Date.parse(session.expiresAt) <= Date.now() ||
49
+ content === undefined)
50
+ return;
51
+ function find(value, id) {
52
+ if (!value || typeof value !== 'object')
53
+ return;
54
+ if ('_uid' in value &&
55
+ value._uid === id &&
56
+ 'component' in value &&
57
+ typeof value.component === 'string')
58
+ return value;
59
+ for (const child of Object.values(value)) {
60
+ const found = find(child, id);
61
+ if (found)
62
+ return found;
63
+ }
64
+ }
65
+ const block = 'block' in component ? component.block : find(content, component.blockId);
66
+ options.onComponent(block);
67
+ send({ type: 'storypane:component-rendered', found: !!block });
68
+ }
42
69
  function open(value) {
43
70
  if (stopped || !matches(value))
44
71
  return;
@@ -77,6 +104,8 @@ export function connectPreview(options) {
77
104
  revision = message.story.revision;
78
105
  referencesRevision = message.story.referencesRevision;
79
106
  options.onChange(message.story);
107
+ content = message.story.content;
108
+ renderComponent();
80
109
  highlight();
81
110
  }
82
111
  else if (message.type === 'error')
@@ -114,12 +143,14 @@ export function connectPreview(options) {
114
143
  else if (embedded) {
115
144
  send({
116
145
  type: 'storypane:ready',
146
+ componentPreview: !!options.onComponent,
117
147
  spaceId: options.spaceId,
118
148
  ...options.story,
119
149
  });
120
150
  if (!ready)
121
151
  ready = setInterval(() => send({
122
152
  type: 'storypane:ready',
153
+ componentPreview: !!options.onComponent,
123
154
  spaceId: options.spaceId,
124
155
  ...options.story,
125
156
  }), 3000);
@@ -137,6 +168,16 @@ export function connectPreview(options) {
137
168
  return;
138
169
  if (event.data?.type === 'storypane:init')
139
170
  open(event.data.session);
171
+ if (event.data?.type === 'storypane:component' && options.onComponent) {
172
+ const { blockId, block } = event.data;
173
+ if (typeof blockId === 'string')
174
+ component = { blockId };
175
+ else if (block && typeof block._uid === 'string' && typeof block.component === 'string')
176
+ component = { block };
177
+ else
178
+ return;
179
+ renderComponent();
180
+ }
140
181
  if (event.data?.type === 'storypane:select' && typeof event.data.blockId === 'string') {
141
182
  selected = event.data.blockId;
142
183
  highlight();
@@ -186,6 +227,8 @@ export function connectPreview(options) {
186
227
  void authorize();
187
228
  return () => {
188
229
  stopped = true;
230
+ if (component)
231
+ options.onComponent?.(undefined);
189
232
  if (socket) {
190
233
  socket.onclose = null;
191
234
  socket.close();
package/dist/react.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type PreviewOptions, type PreviewStatus } from './preview.js';
2
2
  import type { StorypaneStory, StoryReference } from './types.js';
3
3
  export { editable } from './types.js';
4
4
  /** Preview a story that may never have been published. The editor supplies its short-lived session. */
5
- export declare function useStorypanePreview<T = StorypaneStory['content']>({ story: reference, cmsUrl, spaceId, migrationVersion, getSession, }: Omit<PreviewOptions<T>, 'story' | 'mode' | 'onChange' | 'onStatus'> & {
5
+ export declare function useStorypanePreview<T = StorypaneStory['content']>({ story: reference, cmsUrl, spaceId, migrationVersion, getSession, onComponent, }: Omit<PreviewOptions<T>, 'story' | 'mode' | 'onChange' | 'onStatus'> & {
6
6
  story?: StoryReference;
7
7
  }): {
8
8
  story: StorypaneStory<T> | undefined;
package/dist/react.js CHANGED
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react';
3
3
  import { connectPreview } from './preview.js';
4
4
  export { editable } from './types.js';
5
5
  /** Preview a story that may never have been published. The editor supplies its short-lived session. */
6
- export function useStorypanePreview({ story: reference, cmsUrl, spaceId, migrationVersion, getSession, }) {
6
+ export function useStorypanePreview({ story: reference, cmsUrl, spaceId, migrationVersion, getSession, onComponent, }) {
7
7
  const id = reference?.id;
8
8
  const slug = reference?.slug;
9
9
  const locale = reference?.locale;
@@ -20,12 +20,13 @@ export function useStorypanePreview({ story: reference, cmsUrl, spaceId, migrati
20
20
  spaceId,
21
21
  migrationVersion,
22
22
  getSession,
23
+ onComponent,
23
24
  story: id ? { id, locale } : { slug: slug, locale },
24
25
  mode: { draft: true },
25
26
  onChange: (story) => setResult((current) => ({ ...current, key, story })),
26
27
  onStatus: (status, error) => setResult((current) => ({ ...current, key, status, error })),
27
28
  });
28
- }, [cmsUrl, spaceId, id, slug, locale, migrationVersion, getSession, key]);
29
+ }, [cmsUrl, spaceId, id, slug, locale, migrationVersion, getSession, onComponent, key]);
29
30
  // Never display the previous story while a new route is authorizing.
30
31
  const current = result.key === key
31
32
  ? result
@@ -48,6 +49,7 @@ export function useStorypaneStory(initial, options) {
48
49
  story: { id: initial.id, locale: initial.locale },
49
50
  mode: JSON.parse(mode),
50
51
  getSession: options.getSession,
52
+ onComponent: options.onComponent,
51
53
  onChange: setStory,
52
54
  onStatus: (value, cause) => {
53
55
  setStatus(value);
@@ -60,6 +62,7 @@ export function useStorypaneStory(initial, options) {
60
62
  options.spaceId,
61
63
  options.migrationVersion,
62
64
  options.getSession,
65
+ options.onComponent,
63
66
  mode,
64
67
  ]);
65
68
  return { story, status, error };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storypane/sdk",
3
- "version": "0.1.0-rc.2",
3
+ "version": "0.1.0-rc.3",
4
4
  "type": "module",
5
5
  "description": "Published content delivery and live visual preview for Storypane",
6
6
  "files": [