payload-live-preview 2.0.0 → 2.0.2

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 (92) hide show
  1. package/README.md +41 -34
  2. package/dist/adapters/astro/RichText.astro +1 -1
  3. package/dist/adapters/astro/index.d.ts +8 -8
  4. package/dist/adapters/astro/index.js +1 -1
  5. package/dist/adapters/astro/index.js.map +1 -1
  6. package/dist/adapters/astro/middleware-entry.d.ts +3 -3
  7. package/dist/adapters/astro/middleware-entry.js +1 -1
  8. package/dist/adapters/astro/middleware-entry.js.map +1 -1
  9. package/dist/adapters/nextjs/index.d.ts +2 -2
  10. package/dist/adapters/nextjs/index.js +1 -1
  11. package/dist/adapters/nextjs/index.js.map +1 -1
  12. package/dist/adapters/nuxt/index.d.ts +4 -4
  13. package/dist/adapters/nuxt/index.js +1 -1
  14. package/dist/adapters/nuxt/index.js.map +1 -1
  15. package/dist/adapters/nuxt/module.d.ts +13 -5
  16. package/dist/adapters/nuxt/module.js.map +1 -1
  17. package/dist/adapters/react/index.d.ts +8 -6
  18. package/dist/adapters/react/index.js.map +1 -1
  19. package/dist/adapters/sveltekit/index.d.ts +5 -5
  20. package/dist/adapters/sveltekit/index.js +1 -1
  21. package/dist/adapters/sveltekit/index.js.map +1 -1
  22. package/dist/adapters/vue/index.d.ts +3 -3
  23. package/dist/adapters/vue/index.js.map +1 -1
  24. package/dist/annotate.js +1 -1
  25. package/dist/annotate.js.map +1 -1
  26. package/dist/client.cjs +1 -1
  27. package/dist/client.cjs.map +1 -1
  28. package/dist/client.js +1 -1
  29. package/dist/client.js.map +1 -1
  30. package/dist/codegen-astro.d.ts +4 -1
  31. package/dist/codegen-astro.js.map +1 -1
  32. package/dist/codegen-cli.js +1 -1
  33. package/dist/codegen-cli.js.map +1 -1
  34. package/dist/codegen.cjs +1 -1
  35. package/dist/codegen.cjs.map +1 -1
  36. package/dist/codegen.js +1 -1
  37. package/dist/codegen.js.map +1 -1
  38. package/dist/core.cjs +1 -1
  39. package/dist/core.cjs.map +1 -1
  40. package/dist/core.d.cts +1 -1
  41. package/dist/core.d.ts +1 -1
  42. package/dist/core.js +1 -1
  43. package/dist/core.js.map +1 -1
  44. package/dist/doctor-cli.d.ts +1 -1
  45. package/dist/doctor-cli.js +1 -1
  46. package/dist/doctor-cli.js.map +1 -1
  47. package/dist/doctor.d.ts +4 -2
  48. package/dist/doctor.js +1 -1
  49. package/dist/doctor.js.map +1 -1
  50. package/dist/{fragment-endpoint-3Mlz7GDz.d.ts → fragment-endpoint-DATKU183.d.ts} +2 -2
  51. package/dist/fragment.cjs.map +1 -1
  52. package/dist/fragment.d.cts +6 -3
  53. package/dist/fragment.d.ts +6 -3
  54. package/dist/fragment.js.map +1 -1
  55. package/dist/index.cjs +1 -1
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.cts +10 -10
  58. package/dist/index.d.ts +10 -10
  59. package/dist/index.js +1 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/lean.cjs +1 -1
  62. package/dist/lean.cjs.map +1 -1
  63. package/dist/lean.d.cts +3 -3
  64. package/dist/lean.d.ts +3 -3
  65. package/dist/lean.js +1 -1
  66. package/dist/lean.js.map +1 -1
  67. package/dist/lexical.cjs +1 -1
  68. package/dist/lexical.cjs.map +1 -1
  69. package/dist/lexical.d.cts +11 -1
  70. package/dist/lexical.d.ts +11 -1
  71. package/dist/lexical.js +1 -1
  72. package/dist/lexical.js.map +1 -1
  73. package/dist/{locals-DBXDMxOA.d.ts → locals-CNUGYbA_.d.ts} +1 -1
  74. package/dist/{middleware-Bx9I7PXW.d.ts → middleware-CWObDO37.d.ts} +2 -2
  75. package/dist/migrate.d.ts +6 -0
  76. package/dist/migrate.js +1 -1
  77. package/dist/migrate.js.map +1 -1
  78. package/dist/{options-BW1eqXvc.d.ts → options-uHKrDM2U.d.ts} +2 -1
  79. package/dist/plugins.cjs +1 -1
  80. package/dist/plugins.cjs.map +1 -1
  81. package/dist/plugins.js +1 -1
  82. package/dist/plugins.js.map +1 -1
  83. package/dist/{probe-CELeUciC.d.ts → probe-DqaItcwX.d.ts} +15 -0
  84. package/dist/server.cjs.map +1 -1
  85. package/dist/server.d.cts +1 -1
  86. package/dist/server.d.ts +1 -1
  87. package/dist/server.js.map +1 -1
  88. package/dist/structural.cjs +2 -2
  89. package/dist/structural.cjs.map +1 -1
  90. package/dist/structural.js +2 -2
  91. package/dist/structural.js.map +1 -1
  92. package/package.json +6 -4
package/README.md CHANGED
@@ -6,55 +6,59 @@
6
6
  [![Payload 2.x / 3.x](https://img.shields.io/badge/Payload-2.x%20%2F%203.x-black)](https://payloadcms.com)
7
7
  [![npm](https://img.shields.io/npm/v/payload-live-preview?color=cb3837&logo=npm)](https://www.npmjs.com/package/payload-live-preview)
8
8
 
9
- > **Live preview for Payload CMS in Astro** — and any other server-rendered or static frontend (SvelteKit, Nuxt, Next.js, plain HTML).
9
+ > **Live preview for Payload CMS on server-rendered and static sites** — Astro, Next.js, SvelteKit, Nuxt and plain HTML.
10
10
 
11
- **The missing piece for Astro + Payload.** The official live-preview packages are React and Vue hooks: they re-render a hydrated component tree, so they cannot touch server-produced Astro markup. This package makes the admin's real-time preview work where no client framework owns the page. Add one line to `astro.config.mjs`, mark what should update — one attribute per component on a server-rendered page, per field on a static one — and edits stream into the preview iframe as the editor types. No rebuild, no reload, no React.
11
+ The official live-preview packages are React and Vue hooks: they re-render a hydrated component tree, so they cannot touch markup a server produced. This package makes the admin's real-time preview work where no client framework owns the page. Mark what should update — one attribute per component on a server-rendered page with a fragment endpoint, per field on a static one — and edits reach the preview iframe as the editor types: bound elements are patched in place, fragment boundaries are rendered again by your server. No rebuild, no reload. A client-rendered app gets React and Vue hooks with the same merge underneath. Strict by default, zero runtime dependencies.
12
12
 
13
- The runtime is framework-agnostic: one script drives Astro, SvelteKit, Nuxt, Next.js and plain HTML. Astro is the first-class, end-to-end-tested path.
13
+ One runtime serves every framework, and every framework adapter's example app runs end to end in Chromium, Firefox and WebKit on every push to `main` and on pull requests. Astro has the most coverage: the widest version matrix (4.x to 7.x), the only end-to-end tests against a real Payload admin, the `RichText` and `PreviewBoundary` components, and the build-time annotator.
14
14
 
15
15
  **New here? Start with the [Astro guide](docs/astro.md), or pick your framework in the [documentation index](docs/README.md).**
16
16
 
17
17
  ## Highlights
18
18
 
19
- - **One runtime, every frontend.** One TypeScript runtime compiled to a self-contained inline script of about 34 KB gzip — 26 KB with the lean artifact ([docs/options.md](docs/options.md)); the adapters for Astro, Next.js, SvelteKit and Nuxt only decide when to deliver it.
20
- - **Payload 3.x native.** `serverURL` re-fetches the populated document, like the official client, so relationship and upload fields render as content rather than as IDs — but only when an edit needs one: typing into a text field costs no request, and a burst on a relationship costs two rather than one per keystroke.
21
- - **Complete Lexical renderer.** 16 node types including `upload`, `relationship`, `block`, `autolink`, tabs, indent and RTL, with automatic rich-text detection: `data-payload-field` alone is enough.
22
- - **Authorization before anything privileged.** Draft reads, runtime injection, CSP changes and binding attributes follow one verified decision per request; the client-controlled intent signals never unlock anything.
19
+ - **One runtime, every frontend.** One TypeScript runtime compiled to a self-contained inline script of about 35 KB gzip — 28 KB with the lean artifact ([docs/options.md](docs/options.md)). The adapters for Astro, Next.js, SvelteKit and Nuxt deliver it; where they decide per request they also authorize the request, merge `frame-ancestors` into your CSP and mark the changed response `private, no-store`. They provide the fragment endpoint and, outside Astro, the route that serves the runtime asset.
20
+ - **Patch, fragment, route.** A binding is patched in place; a `data-payload-fragment` boundary is rendered again by your server from the unsaved form state; a binding in `<head>`, or one marked `data-payload-strategy="route"`, can refresh the whole route. A patch the runtime knows fell short, or a change nothing binds, is handed to the fragment render or a route refresh when the page has one ([below](#patch-fragment-route)).
21
+ - **Delivery.** Inline, or a bootstrap that fetches the content-hashed, SRI-verified runtime only inside a preview (`delivery: 'asset'`; Astro's static equivalent is `mode: 'loader'`). Where something decides per request — Astro's middleware, `<LivePreviewScript />` in Next.js, the SvelteKit handle, the Nuxt Nitro plugin — a public visitor receives no script at all ([docs/deployment.md](docs/deployment.md#what-a-public-visitor-pays)).
22
+ - **Payload 3.x native.** `serverURL` re-fetches the populated document, like the official client, so relationship and upload fields render as content rather than as IDs — but only when an edit needs one: in the DOM runtime, typing into a text field costs no request, and a burst on a relationship costs two rather than one per keystroke.
23
+ - **Lexical renderer.** 20 node types including `upload`, `relationship`, `block`, `autolink`, tabs, indent and RTL, with automatic rich-text detection: `data-payload-field` alone is enough. An unknown node renders only its children; a block with no registered renderer becomes an empty placeholder, and the live write keeps the markup your server rendered in its place when the two trees line up. The prebuilt inline runtime cannot register block renderers.
24
+ - **Hooks for client-rendered apps.** `useLivePreviewDocument()` from `payload-live-preview/react` or `/vue` hands the merged document to your component tree ([docs/react.md](docs/react.md), [docs/vue.md](docs/vue.md)).
25
+ - **Tooling.** `pll doctor <url>` requests a page as a visitor and as the admin's iframe and reports the difference; `pll migrate` rewrites 1.x APIs to their 2.0 names; `pll-codegen` generates types from the Payload config, and `pll-codegen annotate` finds the bindings a template can take unambiguously.
26
+ - **Authorization before anything privileged.** Where an adapter decides per request with `authorizePreview`, draft reads, runtime injection, CSP changes and binding attributes follow one verified decision, and the client-controlled intent signals unlock nothing. The setups that deliver at build time, render the script for every request or inject on intent alone are named under [Security model](#security-model).
23
27
  - **Strict by default.** Escape-by-default sanitizer, URL and `srcset` validation, policed attribute writes, prototype-pollution guards, per-instance clients, typed bindings with `pll-codegen`.
24
28
 
25
29
  ## Compatibility
26
30
 
27
- | | Payload 2.x | Payload 3.x |
28
- | ---------------------------------------------- | ----------------------------- | --------------------------------------------------------------------- |
29
- | Scalar field updates | ✅ | ✅ |
30
- | Rich text (Lexical) | ✅ | ✅ |
31
- | Relationship / upload population | ✅ (admin merges client-side) | ✅ with `serverURL` (REST merge) |
32
- | Schema-driven field typing (`fieldSchemaJSON`) | ✅ | — (3.x removed it; DOM heuristics + Lexical auto-detection take over) |
31
+ | | Payload 2.x | Payload 3.x |
32
+ | ---------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
33
+ | Scalar field updates | ✅ | ✅ |
34
+ | Rich text (Lexical) | ✅ | ✅ |
35
+ | Relationship / upload population | The admin's populated data, used as sent; not verified against a real 2.x admin | ✅ with `serverURL` (REST merge) |
36
+ | Schema-driven field typing (`fieldSchemaJSON`) | ✅ | — (3.x removed it; DOM heuristics + Lexical auto-detection take over) |
33
37
 
34
38
  <!-- compat-matrix:start -->
35
39
 
36
- | Framework | Supported | Tested in CI on every push (version, browsers) |
37
- | --------- | --------------------- | --------------------------------------------------------------------------------- |
38
- | Astro | >=4.0.0 <8.0.0 | 7.3.2 (chromium, firefox, webkit); 6.x (chromium); 5.x (chromium); 4.x (chromium) |
39
- | Next.js | App Router, 15 and 16 | 16.3.4 (chromium, firefox, webkit) |
40
- | SvelteKit | 2.x | 2.70.2 (chromium, firefox, webkit) |
41
- | Nuxt | 3.x | 3.21.11 (chromium, firefox, webkit) |
40
+ | Framework | Supported | Tested in CI on every push (version, browsers) |
41
+ | --------- | -------------- | --------------------------------------------------------------------------------- |
42
+ | Astro | >=4.0.0 <8.0.0 | 7.3.2 (chromium, firefox, webkit); 6.x (chromium); 5.x (chromium); 4.x (chromium) |
43
+ | Next.js | App Router, 16 | 16.3.4 (chromium, firefox, webkit) |
44
+ | SvelteKit | 2.x | 2.70.2 (chromium, firefox, webkit) |
45
+ | Nuxt | 3.x | 3.21.11 (chromium, firefox, webkit) |
42
46
 
43
47
  Node >=20.19.0; the unit and integration suites run on Node 20, 22, 24, 26. Every version in the table is what the fixture lockfile or the matrix job installs, checked by `npm run compat:check`.
44
48
 
45
49
  Vite 5 through 8: that is what the supported framework majors install (Astro 7 → 8, Astro 6 → 7, Astro 5 → 6, Astro 4 → 5, SvelteKit 2 → 5/6/7/8, Nuxt 3 → 7), measured 2026-09-06. `npm run compat:check` keeps the devDependency and the fixture lockfiles inside that span; `npm run compat:refresh` re-reads it from the registry.
46
50
 
47
- - Payload 2.x: captured-message integration tests and fieldSchemaJSON typing.
51
+ - Payload 2.32.3: wire corpus captured from a real admin in a one-off round (examples/ has no Payload 2 fixture), replayed in tests/integration/wire-corpus.test.ts, plus fieldSchemaJSON typing in tests/integration/schema-driven.test.ts; relationship and upload population is not verified against a real 2.x admin.
48
52
  - Payload 3.85.0: wire corpus captured from a real admin, replayed in tests/integration/wire-corpus.test.ts.
49
53
  - Payload 3.88.0: wire corpus captured from a real admin, replayed in tests/integration/wire-corpus.test.ts.
50
54
  - Payload 3.89.0: real admin E2E (examples/payload-backend) on every push, plus a wire corpus captured from it.
51
55
  - Payload 4.0.0-canary.33: wire corpus captured from a real Payload 4 admin in a one-off upgrade round, replayed in tests/integration/wire-corpus.test.ts; the fixture itself stays on 3.x.
52
- - Payload latest: weekly protocol watch executes @payloadcms/live-preview@latest against the corpus.
53
- - Payload 4.0 pre-releases: weekly protocol watch against @payloadcms/live-preview@canary, early warning only.
56
+ - Payload latest: daily protocol watch executes @payloadcms/live-preview@latest against the corpus.
57
+ - Payload 4.0 pre-releases: daily protocol watch against @payloadcms/live-preview@canary, early warning only.
54
58
 
55
59
  <!-- compat-matrix:end -->
56
60
 
57
- **A client-rendered app wants a hook.** `payload-live-preview/react` and `payload-live-preview/vue` are ones: `useLivePreviewDocument()` returns the merged document and re-renders your tree, with this package's merge underneath — one request at a time, the newer answer winning, an HTTP error refused instead of becoming the document, and a cache per call ([docs/react.md](docs/react.md), [docs/vue.md](docs/vue.md)). The official [`@payloadcms/live-preview-react`](https://payloadcms.com/docs/live-preview/client) / `-vue` hooks ship in lockstep with Payload and remain the safe default if that matters more to you; running either alongside the DOM runtime on one page: [docs/interop.md](docs/interop.md).
61
+ **A client-rendered app wants a hook.** `payload-live-preview/react` and `payload-live-preview/vue` are ones: `useLivePreviewDocument()` returns the merged document and re-renders your tree, with this package's merge underneath: every accepted update, a text edit too, sends one merge request, and a newer one aborts the request in flight; an HTTP error is refused instead of becoming the document; and each hook has a session of its own, so two previews on one page keep their own documents ([docs/react.md](docs/react.md), [docs/vue.md](docs/vue.md)). The official [`@payloadcms/live-preview-react`](https://payloadcms.com/docs/live-preview/client) / `-vue` hooks ship in lockstep with Payload and remain the safe default if that matters more to you; running either alongside the DOM runtime on one page: [docs/interop.md](docs/interop.md).
58
62
 
59
63
  ## Install
60
64
 
@@ -62,7 +66,7 @@ Vite 5 through 8: that is what the supported framework majors install (Astro 7
62
66
  npm install payload-live-preview
63
67
  ```
64
68
 
65
- Three entries cover most projects. The root `payload-live-preview` carries the client, the inline script generator, the renderers and the plugins. `payload-live-preview/astro`, `/nextjs`, `/sveltekit` and `/nuxt` hold one framework adapter each. `payload-live-preview/server` is the privileged surface for server code: `definePreview()`, `authorizePreviewRequest()`, `issuePreviewToken()` and `createPreviewBindings()`. The focused entries (`core`, `client`, `lexical`, `structural`, `plugins`, `fragment`, `payload`, `codegen`, `doctor`, `migrate`) are listed in [docs/options.md](docs/options.md).
69
+ Three entries cover most projects. The root `payload-live-preview` carries the client, the inline script generator, the renderers and four of the five built-in plugins; the unbound-fields overlay and `PluginManager` are on `payload-live-preview/plugins`. `payload-live-preview/astro`, `/nextjs`, `/sveltekit` and `/nuxt` hold one framework adapter each; `/nuxt-module` registers the Nuxt one from `nuxt.config.ts`, and since it cannot carry `authorizePreview` it runs with `defaults: 'v1'` ([docs/nuxt.md](docs/nuxt.md)). `payload-live-preview/server` is the privileged surface for server code: `definePreview()`, `authorizePreviewRequest()`, `issuePreviewToken()` and `createPreviewBindings()`. The focused entries (`core`, `client`, `lexical`, `structural`, `plugins`, `fragment`, `lean`, `payload`, `codegen`, `codegen/astro`, `annotate`, `doctor`, `migrate`) are listed in [docs/options.md](docs/options.md).
66
70
 
67
71
  ## Configure Payload
68
72
 
@@ -83,7 +87,7 @@ export default buildConfig({
83
87
  globals: {
84
88
  homepage: '/',
85
89
  },
86
- fallback: '/', // new drafts without a slug land here
90
+ fallback: '/', // unmapped documents, and resolvers that return '', land here
87
91
  }),
88
92
  breakpoints: [
89
93
  { label: 'Mobile', name: 'mobile', width: 375, height: 667 },
@@ -112,9 +116,10 @@ import { livePreview } from 'payload-live-preview/astro';
112
116
  export default defineConfig({
113
117
  integrations: [
114
118
  livePreview({
115
- allowedOrigins: [import.meta.env.PUBLIC_PAYLOAD_ADMIN_ORIGIN],
119
+ // process.env: import.meta.env carries no PUBLIC_ variables in this file.
120
+ allowedOrigins: [process.env.PUBLIC_PAYLOAD_ADMIN_ORIGIN],
116
121
  // Payload 3.x: populate relationship/upload fields via REST merge
117
- serverURL: import.meta.env.PUBLIC_PAYLOAD_ADMIN_ORIGIN,
122
+ serverURL: process.env.PUBLIC_PAYLOAD_ADMIN_ORIGIN,
118
123
  // Required alongside serverURL: the population depth, 0 for none.
119
124
  mergeDepth: 1,
120
125
  }),
@@ -130,6 +135,8 @@ Then mark what should update. On a server-rendered page that is one attribute pe
130
135
  </section>
131
136
  ```
132
137
 
138
+ The attribute needs a renderer behind it: a route exporting `createFragmentEndpoint()` with `authorizePreview` or `authorize`, served at request time (an SSR adapter, `prerender = false` on that route), and its path in the options above as `fragments: { endpoint: '/payload/fragment' }` ([docs/hybrid.md](docs/hybrid.md)). Without `fragments` the bindings inside the boundary are patched instead (`LP0806`), and a boundary with none inside does not update.
139
+
133
140
  On a static build there is no server to render it, so the fields are bound individually — and inside a boundary too, for the ones an editor types into while watching, because a patch keeps focus and the caret where a re-render would not:
134
141
 
135
142
  ```astro
@@ -138,20 +145,20 @@ On a static build there is no server to render it, so the fields are bound indiv
138
145
  <img data-payload-field="hero" alt={alt} src={url} />
139
146
  ```
140
147
 
141
- Which to reach for, and what each costs: [docs/bindings.md](docs/bindings.md#how-much-markup-this-actually-needs). `pll-codegen annotate` writes the unambiguous bindings for you and reports the rest.
148
+ Which to reach for, and what each costs: [docs/bindings.md](docs/bindings.md#how-much-markup-this-actually-needs). `pll-codegen annotate` adds the unambiguous bindings and names each one it refuses, with the reason; without `--write` it only reports, and exits with `3` when it found work.
142
149
 
143
150
  That is it: the inline script detects the admin's iframe and starts patching. Rich text is detected from the value shape; `data-payload-richtext` only forces it. Payload 3.x posts raw form values, so relationship and upload fields arrive as IDs until `serverURL` with `mergeDepth` re-fetches the populated document ([docs/options.md](docs/options.md)). Injection modes, request-time middleware, authorization and the initial draft read: [docs/astro.md](docs/astro.md).
144
151
 
145
152
  ### Other frameworks
146
153
 
147
- - **Next.js (App Router)** — the script in `app/layout.tsx`, the adapter middleware for headers: [docs/nextjs.md](docs/nextjs.md).
154
+ - **Next.js (App Router)** — `<LivePreviewScript />` in `app/layout.tsx`, the adapter middleware for headers: [docs/nextjs.md](docs/nextjs.md).
148
155
  - **SvelteKit** — `livePreviewHandle()` in `hooks.server.ts`: [docs/sveltekit.md](docs/sveltekit.md).
149
156
  - **Nuxt** — a Nitro plugin plus a server handler: [docs/nuxt.md](docs/nuxt.md).
150
157
  - **Plain HTML** — `generateInlineScript()` at build time: [docs/html.md](docs/html.md).
151
158
 
152
159
  ## Patch, fragment, route
153
160
 
154
- A binding is patched in place by default. A `data-payload-fragment` boundary is rendered by your server from the unsaved form state instead — conditional sections, derived values, custom blocks, the component's own logic — and morphed in with focus and visitor state intact; the runtime posts the fields to a same-origin endpoint built with `createFragmentEndpoint()`, which every adapter entry exports — Astro renders through its container API, Next.js through `react-dom/server`, SvelteKit through `svelte/server`, Nuxt through `vue/server-renderer` — and patches the boundary's own bindings when the server cannot render. A binding in `<head>`, or one marked `data-payload-strategy="route"`, refreshes the whole route once per revision with scroll and focus kept. Markup, endpoint, deployment requirements and the abuse model: [docs/hybrid.md](docs/hybrid.md).
161
+ A binding is patched in place by default. A `data-payload-fragment` boundary is rendered by your server from the unsaved form state instead — conditional sections, derived values, custom blocks, the component's own logic — and morphed in with focus and visitor state intact; the runtime posts the fields to the same-origin endpoint named in `fragments: { endpoint }`, built with `createFragmentEndpoint()`, which every adapter entry exports — Astro renders through its container API, Next.js through `react-dom/server`, SvelteKit through `svelte/server`, Nuxt through `vue/server-renderer` — and patches the boundary's own bindings when the server cannot render. With `routeStrategy: true` or `fragments` set, a binding in `<head>`, or one marked `data-payload-strategy="route"`, refreshes the whole route once per revision with scroll and focus kept; without either it is patched like any other. The route is matched by top-level field name, so a dotted binding such as `meta.title` refreshes it only with `data-payload-depends="meta"`. Markup, endpoint, deployment requirements and the abuse model: [docs/hybrid.md](docs/hybrid.md).
155
162
 
156
163
  ## Events and plugins
157
164
 
@@ -169,8 +176,8 @@ Every event, transforms, custom field renderers, the built-in plugins and the pl
169
176
 
170
177
  ## Security model
171
178
 
172
- - **Preview intent is not authorization.** The query parameter, the iframe destination and the referer are client-controlled; the adapters count the query alone by default. `allowedOrigins` governs browser `postMessage` senders and `shouldInject` filters routes; neither authenticates the request. `authorizePreviewRequest()` does, and that one result controls draft reads, `private, no-store` caching, CSP changes and runtime injection.
173
- - **Origin validation.** Every incoming message is checked against `allowedOrigins`; `document.referrer` is ignored by default, and after the first accepted update the runtime locks to that origin. The adapters merge a `frame-ancestors` policy for the admin origins without clobbering the rest of your CSP.
179
+ - **Preview intent is not authorization.** The query parameter, the iframe destination and the referer are client-controlled; the adapters count the query alone by default. `allowedOrigins` governs browser `postMessage` senders and `shouldInject` filters routes; neither authenticates the request. `authorizePreviewRequest()` does, and where an adapter decides per request with `authorizePreview` — `createLivePreviewMiddleware()`, `livePreviewHandle()`, `livePreviewNitroPlugin()`, `defineLivePreviewServerHandler()` — that one result controls draft reads, `private, no-store` caching, CSP changes and runtime injection, and `<LivePreviewScript />` gates the Next.js script on the same verdict. Other setups decide less. Astro's build-time `inline` and `loader` modes and the synchronous `livePreviewScriptProps()` and `renderLivePreviewScript()` put the runtime or its bootstrap into every page that renders them, where it does not start outside a preview frame. Astro's `mode: 'middleware'` and the Nuxt module cannot carry `authorizePreview`, so they need `defaults: 'v1'` (or `strict: false`) and inject on intent alone.
180
+ - **Origin validation.** Every incoming message is checked against `allowedOrigins`; `document.referrer` is ignored by default, and after the first accepted update the inline runtime and `LivePreviewClient` lock to that origin (the React and Vue hooks do not). The adapters merge a `frame-ancestors` policy for the admin origins without clobbering the rest of your CSP.
174
181
  - **Sanitization and URL validation.** Lexical and HTML writes pass a DOM sanitizer with a curated allow-list — `<script>`, `<form>`, `<iframe>`, `<svg>`, event handlers and `style` are rejected — and every `href`, `src`, `srcset` and `poster` must be `http(s)`, `mailto:`, `tel:` or relative; external links get `rel="noopener noreferrer"`.
175
182
  - **Binding attributes are disclosure.** `data-payload-field` names a CMS field and `data-payload-owner` a document. `createPreviewBindings({ authorization })` suppresses them, companions included, on public responses. Never key CSS off `data-payload-*`.
176
183
  - **Policed writes, no prototype pollution.** `data-payload-attribute` refuses event handlers, `style`, `srcdoc`, `formaction`, `id` and `name`; nested lookups refuse `__proto__`, `prototype` and `constructor`, and incoming data is never merged into existing objects.
@@ -179,12 +186,12 @@ Full details in [docs/security.md](docs/security.md). Report vulnerabilities per
179
186
 
180
187
  ## Troubleshooting
181
188
 
182
- - **Nothing updates.** Run `__livePreview.inspect()` in the preview iframe's console first; it names the cause in most cases. The common ones: the admin origin is missing from `allowedOrigins`, the page is not inside an iframe, the bound element does not exist (an empty field needs an anchor).
189
+ - **Nothing updates.** Run `__livePreview.inspect()` in the preview iframe's console first — the handle exists once the injected runtime has started inside a preview frame; a page that starts `LivePreviewClient` itself calls `client.inspect()`. It names the cause in most cases. The common ones: the admin origin is missing from `allowedOrigins`, the page is not inside an iframe, the bound element does not exist (an empty field needs an anchor).
183
190
  - **Relationship fields show IDs.** Set `serverURL` with `mergeDepth`; Payload 3.x posts unpopulated form values.
184
191
  - **The preview iframe refuses to load.** The host sets `X-Frame-Options` or a restrictive `frame-ancestors`. The adapters merge `frame-ancestors` on authorized preview responses; an `X-Frame-Options: DENY` from a proxy must go.
185
192
  - **The adapter refuses to start.** The strict default needs `authorizePreview`, a non-empty `allowedOrigins` (`https:` in production) and no referer trust; `defaults: 'v1'` stages a migration one row at a time.
186
193
 
187
- `inspect()` readings, `pll doctor` and every diagnostic code: [docs/troubleshooting.md](docs/troubleshooting.md).
194
+ `inspect()` readings, `pll doctor` and every diagnostic code: [docs/troubleshooting.md](docs/troubleshooting.md). `pll doctor` sends its preview request with `?preview=true`; a preview behind `authorizePreview` is audited with an editor's credential. A signed token goes in the URL as `?previewToken=…`, which the visitor request drops; `--header "x-preview-token: …"` works only where the token strategy sets `transport: { kind: 'header' }`; a Payload session is `--header "Cookie: payload-token=…"`.
188
195
 
189
196
  ## Documentation
190
197
 
@@ -33,7 +33,7 @@
33
33
  * every value they interpolate, but the sanitizer that would catch a
34
34
  * custom renderer's mistake needs a DOM, and there is none during SSR
35
35
  * unless the project supplied one with `setSanitizerDocument()`. See
36
- * docs/security.md §3.
36
+ * "HTML sanitization" in docs/security.md.
37
37
  */
38
38
  // Self-referencing import so the shipped copy in dist/ resolves
39
39
  // through the package exports map in consumer projects.
@@ -1,10 +1,10 @@
1
- import { L as LivePreviewAstroOptions } from '../../middleware-Bx9I7PXW.js';
2
- export { a as LivePreviewMiddleware, c as createLivePreviewMiddleware } from '../../middleware-Bx9I7PXW.js';
3
- import { h as hasPreviewIntent } from '../../options-BW1eqXvc.js';
4
- export { P as PreviewAdapterOptions, a as PreviewRequestLike, b as PreviewRequestOptions, c as PreviewSignal } from '../../options-BW1eqXvc.js';
5
- import { F as FragmentEndpointOptions$1, a as FragmentRegistry$1, b as FragmentRegistryEntry$1, c as FragmentRenderer$1 } from '../../fragment-endpoint-3Mlz7GDz.js';
6
- export { d as FragmentRenderInput } from '../../fragment-endpoint-3Mlz7GDz.js';
7
- export { A as AUTHORIZATION_LOCALS_KEY, a as AUTHORIZATION_OUTCOME_LOCALS_KEY, L as LivePreviewLocals, N as NONCE_LOCALS_KEY } from '../../locals-DBXDMxOA.js';
1
+ import { L as LivePreviewAstroOptions } from '../../middleware-CWObDO37.js';
2
+ export { a as LivePreviewMiddleware, c as createLivePreviewMiddleware } from '../../middleware-CWObDO37.js';
3
+ import { h as hasPreviewIntent } from '../../options-uHKrDM2U.js';
4
+ export { P as PreviewAdapterOptions, a as PreviewRequestLike, b as PreviewRequestOptions, c as PreviewSignal } from '../../options-uHKrDM2U.js';
5
+ import { F as FragmentEndpointOptions$1, a as FragmentRegistry$1, b as FragmentRegistryEntry$1, c as FragmentRenderer$1 } from '../../fragment-endpoint-DATKU183.js';
6
+ export { d as FragmentRenderInput } from '../../fragment-endpoint-DATKU183.js';
7
+ export { A as AUTHORIZATION_LOCALS_KEY, a as AUTHORIZATION_OUTCOME_LOCALS_KEY, L as LivePreviewLocals, N as NONCE_LOCALS_KEY } from '../../locals-CNUGYbA_.js';
8
8
 
9
9
  /**
10
10
  * The Astro integration for `astro.config.mjs`: `livePreview({ ... })`.
@@ -87,7 +87,7 @@ declare function renderLivePreviewScript(options?: RenderScriptOptions): string;
87
87
  *
88
88
  * Measured against the published 1.8.1 declarations, entry by entry, not
89
89
  * guessed: `isPreviewRequest` was exported from the root **and** from
90
- * `./astro`, so it is re-exported from both. The other seven names 2.0 dropped
90
+ * `./astro`, so it is re-exported from both. The six names 2.0 dropped
91
91
  * moved behind `definePreview()` or changed shape, where an alias would restore
92
92
  * the very defaults the move removed (ADR 0007, ledger rows 1 and 9).
93
93
  */