@persona/layout-editor-remote-module 0.5.0 → 0.5.1

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
@@ -1,8 +1,8 @@
1
1
  # @persona/layout-editor-remote-module
2
2
 
3
3
  Shared manifest format and Vite plugin for remotely-defined dashboard layout modules — used by
4
- app-builder Apps and `sa-*` subapps to declare a layout module directly in their own
5
- `vite.config.ts`, without editing persona-web.
4
+ Persona dashboard apps to declare a layout module directly in a typical app's `vite.config.ts`,
5
+ without editing the host.
6
6
 
7
7
  ## Install
8
8
 
@@ -81,6 +81,23 @@ import { RemoteModuleFrame } from '@persona/layout-editor-remote-module/bridge-h
81
81
 
82
82
  The required `sandbox` value is chosen by the host.
83
83
 
84
+ By default, the bridge accepts host origins matching `*.withpersona.com`,
85
+ `*.withpersona-staging.com`, `localhost`, and `127.0.0.1`. Add development or preview origins with
86
+ `allowedHostOrigins`, optionally composing the exported `personaDashboardHostOrigins` helper:
87
+
88
+ ```tsx
89
+ import {
90
+ LayoutModuleBridge,
91
+ personaDashboardHostOrigins,
92
+ } from '@persona/layout-editor-remote-module/bridge-frame';
93
+
94
+ <LayoutModuleBridge
95
+ allowedHostOrigins={(origin) => personaDashboardHostOrigins(origin) || myDevHost(origin)}
96
+ >
97
+ {({ data }) => <RiskSummary data={data} />}
98
+ </LayoutModuleBridge>;
99
+ ```
100
+
84
101
  The document entry uses `LayoutModuleBridge` as a render-prop component:
85
102
 
86
103
  ```tsx
@@ -108,11 +125,11 @@ cannot derive itself:
108
125
  iframe it arrives through `ready()`, `getContext()`, and the `context` render prop (all
109
126
  `RemoteModuleContext | null` until init).
110
127
 
111
- The host still appends `access_token`, `environment_id`, and `locale` as query parameters. The
112
- builder app or `sa-*` subapp hosting the module consumes and strips those parameters itself, so by
113
- the time `LayoutModuleBridge` runs they have already been handled; the bridge neither reads nor
114
- strips them. Keep the host iframe's `referrerpolicy` at its default `no-referrer` (or use
115
- `strict-origin`) so the URL parameters cannot leak through subsequent requests.
128
+ The host still appends `access_token`, `environment_id`, and `locale` as query parameters. The app
129
+ hosting the module consumes and strips those parameters itself, so by the time `LayoutModuleBridge`
130
+ runs they have already been handled; the bridge neither reads nor strips them. Keep the host
131
+ iframe's `referrerpolicy` at its default `no-referrer` (or use `strict-origin`) so the URL
132
+ parameters cannot leak through subsequent requests.
116
133
 
117
134
  The bridge's security model is intentionally narrow:
118
135
 
@@ -1,6 +1,9 @@
1
1
  import { n as Json, t as RemoteModuleContext } from "./protocol-edoRdXpF.mjs";
2
2
  import { ReactNode } from "react";
3
3
 
4
+ //#region src/bridge/origin.d.ts
5
+ declare function personaDashboardHostOrigins(origin: string): boolean;
6
+ //#endregion
4
7
  //#region src/bridge/iframe/createModuleBridge.d.ts
5
8
  type ModuleBridgeInitPayload = {
6
9
  data: Json;
@@ -42,4 +45,4 @@ declare function LayoutModuleBridge({
42
45
  children
43
46
  }: LayoutModuleBridgeProps): ReactNode;
44
47
  //#endregion
45
- export { type Json, LayoutModuleBridge, type LayoutModuleBridgeProps, type LayoutModuleBridgeRenderProps, type RemoteModuleContext };
48
+ export { type Json, LayoutModuleBridge, type LayoutModuleBridgeProps, type LayoutModuleBridgeRenderProps, type RemoteModuleContext, personaDashboardHostOrigins };
@@ -1,4 +1,4 @@
1
- import { c as resolveAllowedHostOrigins, n as SOURCE, o as candidateParentOrigin, r as parseHostToModuleMessage, s as personaDashboardHostOrigins, t as PROTOCOL_VERSION } from "./protocol-DMsysRFd.mjs";
1
+ import { c as resolveAllowedHostOrigins, n as SOURCE, o as candidateParentOrigin, r as parseHostToModuleMessage, s as personaDashboardHostOrigins, t as PROTOCOL_VERSION } from "./protocol-Dz3ZX59L.mjs";
2
2
  import { Component, useEffect, useRef, useState } from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
 
@@ -347,4 +347,4 @@ function LayoutModuleBridge({ allowedHostOrigins, resolveTimeoutMs, observeHeigh
347
347
  }
348
348
 
349
349
  //#endregion
350
- export { LayoutModuleBridge };
350
+ export { LayoutModuleBridge, personaDashboardHostOrigins };
@@ -1,4 +1,4 @@
1
- import { a as assertNotSelfOrigin, i as parseModuleToHostMessage, n as SOURCE, t as PROTOCOL_VERSION } from "./protocol-DMsysRFd.mjs";
1
+ import { a as assertNotSelfOrigin, i as parseModuleToHostMessage, n as SOURCE, t as PROTOCOL_VERSION } from "./protocol-Dz3ZX59L.mjs";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
 
@@ -79,31 +79,20 @@ type RemoteModuleRegistrationManifestV1Schema = typeof remoteModuleRegistrationM
79
79
  type Json = boolean | JsonObject | JsonValue[] | null | number | string;
80
80
  /**
81
81
  * Loose JSON Schema object. Deliberately not a full JSON Schema type system —
82
- * this package only needs to carry what `z.toJSONSchema()` emits from one side
83
- * to whatever a consumer (e.g. persona-web's own `JsonSchemaObject`) does with
84
- * it on the other.
82
+ * this package only needs to carry what `z.toJSONSchema()` emits for consumers
83
+ * to validate or render.
85
84
  */
86
85
  type JsonSchemaObject = Record<string, unknown>;
87
- /**
88
- * Mirrors persona-web's `LayoutType`
89
- * (front-end/dashboard/modules/lib/types.ts).
90
- */
86
+ /** Mirrors the host's supported layout values. */
91
87
  type LayoutType = z.infer<typeof layoutTypeSchema>;
92
- /**
93
- * Mirrors persona-web's `ModuleBadge`
94
- * (front-end/dashboard/modules/lib/config-constants.tsx).
95
- */
88
+ /** Mirrors the host's supported module badges. */
96
89
  type ModuleBadge = z.infer<typeof moduleBadgeSchema>;
97
- /**
98
- * Mirrors persona-web's `ModuleCategory` values
99
- * (front-end/dashboard/modules/lib/module-library-config.ts).
100
- */
90
+ /** Mirrors the host's module-library categories. */
101
91
  type ModuleCategory = z.infer<typeof moduleCategorySchema>;
102
92
  /**
103
- * Options passed to a remote module's `defaultConfig` export. Deliberately
104
- * narrower than persona-web's own `ModuleDefaultConfigOptions` — the migration
105
- * flags on that type (e.g. `unifiedDataDisplayEnabled`) are host-internal
106
- * scaffolding a remote module has no business branching on.
93
+ * Options passed to a remote module's `defaultConfig` export. Narrower than the
94
+ * host's default-config options; host-internal flags are not exposed to remote
95
+ * modules.
107
96
  */
108
97
  interface RemoteModuleDefaultConfigOptions {
109
98
  layoutType: LayoutType;
@@ -119,7 +108,7 @@ type RemoteModuleDelivery = z.infer<typeof remoteDeliverySchema>;
119
108
  * optional and feature-detected by name — see the "one URL, conventional
120
109
  * named exports" design decision. Rendering/sandboxing `render` is out of
121
110
  * scope for this package; only `defaultConfig` has a concrete consumer here
122
- * (persona-web's default-config loader). Not applicable to a `document`
111
+ * (the host's default-config loader). Not applicable to a `document`
123
112
  * delivery — nothing is ever imported for those, so none of these exports are
124
113
  * reachable.
125
114
  */
@@ -133,10 +122,8 @@ interface RemoteModuleExports {
133
122
  * Wire format for a remotely-defined layout module. Must stay
134
123
  * JSON-serializable: no functions, no ReactNode, no zod.
135
124
  *
136
- * Producers: `defineLayoutModule()` + the Vite plugin (this package), or
137
- * app-builder's own build pipeline using the same shape.
138
- * Consumers: persona-web's `manifestToRegistration()`
139
- * (front-end/dashboard/modules/lib/remote/adapt.ts).
125
+ * Producers: `defineLayoutModule()` and this package's Vite plugin.
126
+ * Consumers: the host application.
140
127
  */
141
128
  type RemoteModuleRegistrationManifestV1 = z.infer<typeof remoteModuleRegistrationManifestV1Schema>;
142
129
  type JsonObject = {
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as JsonSchemaObject, c as ModuleCategory, d as RemoteModuleExports, f as RemoteModuleRegistrationManifestV1, i as Json, l as RemoteModuleDefaultConfigOptions, m as remoteModuleRegistrationManifestV1Schema, n as LayoutModuleEntry, o as LayoutType, p as RemoteModuleRegistrationManifestV1Schema, r as defineLayoutModule, s as ModuleBadge, t as LayoutModuleDefinitionInput, u as RemoteModuleDelivery } from "./define-Dt8sbrVO.mjs";
1
+ import { a as JsonSchemaObject, c as ModuleCategory, d as RemoteModuleExports, f as RemoteModuleRegistrationManifestV1, i as Json, l as RemoteModuleDefaultConfigOptions, m as remoteModuleRegistrationManifestV1Schema, n as LayoutModuleEntry, o as LayoutType, p as RemoteModuleRegistrationManifestV1Schema, r as defineLayoutModule, s as ModuleBadge, t as LayoutModuleDefinitionInput, u as RemoteModuleDelivery } from "./define-C-3P4CFz.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/manifest-path.d.ts
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as DEFAULT_MANIFEST_PATH, r as defineLayoutModule, t as remoteModuleRegistrationManifestV1Schema } from "./schema-BeI8GdXS.mjs";
1
+ import { n as DEFAULT_MANIFEST_PATH, r as defineLayoutModule, t as remoteModuleRegistrationManifestV1Schema } from "./schema-Bc3ZwB8y.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/parse.ts
@@ -2,11 +2,9 @@ import { z } from "zod";
2
2
 
3
3
  //#region src/bridge/origin.ts
4
4
  const dashboardHostOriginPattern = /^https:\/\/([a-z0-9-]+\.)*withpersona(-staging)?\.com$/;
5
- const pagesHostOriginPattern = /^https:\/\/[a-z0-9-]+\.persona-web\.pages\.dev$/;
6
- const coderHostOriginPattern = /^https:\/\/[a-z0-9-]+\.coder\.development\.personerds\.com$/;
7
5
  const localHostOriginPattern = /^http:\/\/(?:localhost|127\.0\.0\.1):\d+$/;
8
6
  function personaDashboardHostOrigins(origin) {
9
- return dashboardHostOriginPattern.test(origin) || pagesHostOriginPattern.test(origin) || coderHostOriginPattern.test(origin) || localHostOriginPattern.test(origin);
7
+ return dashboardHostOriginPattern.test(origin) || localHostOriginPattern.test(origin);
10
8
  }
11
9
  function resolveAllowedHostOrigins(allowedHostOrigins) {
12
10
  if (allowedHostOrigins === void 0) return personaDashboardHostOrigins;
@@ -32,10 +30,8 @@ function assertNotSelfOrigin(moduleOrigin) {
32
30
  //#endregion
33
31
  //#region src/schema.ts
34
32
  /**
35
- * Mirrors persona-web's `LAYOUT_COLUMN_TOTAL`
36
- * (front-end/dashboard/modules/lib/constants.ts). The enum schemas below
37
- * mirror persona-web's corresponding enum values and are kept in sync by hand;
38
- * this package has no dependency on persona-web.
33
+ * Mirrors the host's layout grid column count; the enum schemas below must
34
+ * match the host's values.
39
35
  */
40
36
  const MAX_GRID_COLUMNS = 12;
41
37
  const layoutTypeSchema = z.enum([
@@ -28,10 +28,8 @@ const DEFAULT_MANIFEST_PATH = "/_layout_modules_manifest.json";
28
28
  //#endregion
29
29
  //#region src/schema.ts
30
30
  /**
31
- * Mirrors persona-web's `LAYOUT_COLUMN_TOTAL`
32
- * (front-end/dashboard/modules/lib/constants.ts). The enum schemas below
33
- * mirror persona-web's corresponding enum values and are kept in sync by hand;
34
- * this package has no dependency on persona-web.
31
+ * Mirrors the host's layout grid column count; the enum schemas below must
32
+ * match the host's values.
35
33
  */
36
34
  const MAX_GRID_COLUMNS = 12;
37
35
  const layoutTypeSchema = z.enum([
@@ -1,4 +1,4 @@
1
- import { n as LayoutModuleEntry, r as defineLayoutModule, t as LayoutModuleDefinitionInput } from "./define-Dt8sbrVO.mjs";
1
+ import { n as LayoutModuleEntry, r as defineLayoutModule, t as LayoutModuleDefinitionInput } from "./define-C-3P4CFz.mjs";
2
2
  import { Plugin } from "vite";
3
3
 
4
4
  //#region src/vite-plugin.d.ts
@@ -33,7 +33,7 @@ interface PersonaLayoutModulesOptions {
33
33
  * wires every `code`-delivered entry into the build as an addressable ESM
34
34
  * chunk, ensures every `document`-delivered (`.html`) entry is actually
35
35
  * built/served, and emits a `RemoteModuleRegistrationManifestV1[]` document
36
- * persona-web can intake.
36
+ * the host can consume.
37
37
  *
38
38
  * Modules are configured directly here, not discovered by globbing the
39
39
  * source tree — a module IS whatever's in `options.modules`, full stop.
@@ -1,4 +1,4 @@
1
- import { n as DEFAULT_MANIFEST_PATH, r as defineLayoutModule, t as remoteModuleRegistrationManifestV1Schema } from "./schema-BeI8GdXS.mjs";
1
+ import { n as DEFAULT_MANIFEST_PATH, r as defineLayoutModule, t as remoteModuleRegistrationManifestV1Schema } from "./schema-Bc3ZwB8y.mjs";
2
2
  import { z } from "zod";
3
3
  import { build } from "esbuild";
4
4
  import { existsSync } from "node:fs";
@@ -123,7 +123,7 @@ function ensureRemoteShimFiles() {
123
123
  * wires every `code`-delivered entry into the build as an addressable ESM
124
124
  * chunk, ensures every `document`-delivered (`.html`) entry is actually
125
125
  * built/served, and emits a `RemoteModuleRegistrationManifestV1[]` document
126
- * persona-web can intake.
126
+ * the host can consume.
127
127
  *
128
128
  * Modules are configured directly here, not discovered by globbing the
129
129
  * source tree — a module IS whatever's in `options.modules`, full stop.
@@ -366,8 +366,7 @@ function matchDevBundleRequest(url, definitions) {
366
366
  * array) into an array of `{ find, replacement }`. esbuild's own `alias`
367
367
  * option only accepts exact string keys (no RegExp, no partial-path
368
368
  * matching) — a real limitation this doesn't try to hide, just the common
369
- * case: a plain string-to-string alias map, which is what every `sa-*`
370
- * subapp's `vite.config.ts` actually uses.
369
+ * case: a plain string-to-string alias map, which many apps use.
371
370
  */
372
371
  function resolveAliasForEsbuild(resolvedConfig) {
373
372
  const alias = {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@persona/layout-editor-remote-module",
3
- "version": "0.5.0",
4
- "description": "Shared manifest format, Vite plugin, and host/iframe bridge for remotely-defined dashboard layout modules (app-builder Apps and sa-* subapps).",
3
+ "version": "0.5.1",
4
+ "description": "Shared manifest format, Vite plugin, and host/iframe bridge for remotely-defined dashboard layout modules in Persona dashboard apps.",
5
5
  "license": "SEE LICENSE",
6
6
  "type": "module",
7
7
  "types": "./dist/index.d.mts",
@@ -1,37 +1,26 @@
1
1
  ---
2
2
  name: getting-started
3
3
  description: >
4
- Wire up @persona/layout-editor-remote-module in a sa-* subapp or app-builder app: register
5
- personaLayoutModules in vite.config.ts, author modules with defineLayoutModule using a document
6
- (.html) render delivery, add a zod configSchema and an RJSF uiConfigSchema, and talk to the host
7
- from the iframed document with LayoutModuleBridge from the bridge-frame entry (layout data, host
8
- context such as layoutType / layoutObjectId / isReadOnly, resolveDataPath, and height). Activate
9
- when adding a new layout module to an app, integrating this package for the first time, wiring a
10
- module document to the dashboard host, or debugging a manifest that fails to build or validate
11
- (duplicate key, missing defaultSize entry, render path not found) or a module that never receives
12
- data from the host.
4
+ Wire up @persona/layout-editor-remote-module in a Persona dashboard app: register
5
+ personaLayoutModules in a typical app's vite.config.ts, author modules with defineLayoutModule
6
+ using a document (.html) render delivery, add a zod configSchema and an RJSF uiConfigSchema, and
7
+ talk to the host from the iframed document with LayoutModuleBridge from the bridge-frame entry
8
+ (layout data, host context such as layoutType / layoutObjectId / isReadOnly, resolveDataPath, and
9
+ height). Activate when adding a new layout module to an app, integrating this package for the
10
+ first time, wiring a module document to the dashboard host, or debugging a manifest that fails to
11
+ build or validate (duplicate key, missing defaultSize entry, render path not found) or a module
12
+ that never receives data from the host.
13
13
  metadata:
14
14
  type: core
15
15
  library: '@persona/layout-editor-remote-module'
16
- library_version: '0.4.0'
17
- sources:
18
- - 'persona-id/persona-layout-editor:packages/remote-module/src/vite-plugin.ts'
19
- - 'persona-id/persona-layout-editor:packages/remote-module/src/define.ts'
20
- - 'persona-id/persona-layout-editor:packages/remote-module/src/schema.ts'
21
- - 'persona-id/persona-layout-editor:packages/remote-module/src/manifest-path.ts'
22
- - 'persona-id/persona-layout-editor:packages/remote-module/src/bridge/iframe/index.ts'
23
- - 'persona-id/persona-layout-editor:packages/remote-module/src/bridge/iframe/LayoutModuleBridge.tsx'
24
- - 'persona-id/persona-layout-editor:packages/remote-module/src/bridge/iframe/createModuleBridge.ts'
25
- - 'persona-id/persona-layout-editor:packages/remote-module/src/bridge/protocol.ts'
26
- - 'persona-id/persona-layout-editor:packages/remote-module/README.md'
27
16
  ---
28
17
 
29
18
  # Getting started with @persona/layout-editor-remote-module
30
19
 
31
- Shared manifest format and Vite plugin for remotely-defined dashboard layout modules — lets a `sa-*`
32
- subapp or app-builder App declare a layout module directly in its own `vite.config.ts`, without
33
- editing persona-web. The plugin builds each module and emits a
34
- `{ modules: RemoteModuleRegistrationManifestV1[] }` JSON document persona-web fetches and intakes.
20
+ Shared manifest format and Vite plugin for remotely-defined dashboard layout modules — lets Persona
21
+ dashboard apps declare a layout module directly in a typical app's `vite.config.ts`, without editing
22
+ the host. The plugin builds each module and emits a
23
+ `{ modules: RemoteModuleRegistrationManifestV1[] }` JSON document the host fetches and validates.
35
24
 
36
25
  This package is publicly available from the npm registry.
37
26
 
@@ -92,11 +81,11 @@ not relative to `vite.config.ts`'s own directory when `root` is overridden elsew
92
81
 
93
82
  ### Document delivery — iframed page
94
83
 
95
- A module's `render.entry` must end in `.html`: persona-web iframes it rather than importing
96
- anything. Any other extension (`.ts`/`.tsx`/`.js`) builds as a `code` delivery, which the plugin and
97
- manifest still accept but persona-web does not render yet — the module shows up in the library and
98
- renders nothing. Use `path` for a client-routed SPA whose `index.html` already renders the whole app
99
- — it names the logical route the SPA's own router should land on, not the file's build output path:
84
+ A module's `render.entry` must end in `.html`: the host iframes it rather than importing anything.
85
+ Any other extension (`.ts`/`.tsx`/`.js`) builds as a `code` delivery, which the plugin and manifest
86
+ still accept but the host does not render yet — the module shows up in the library and renders
87
+ nothing. Use `path` for a client-routed SPA whose `index.html` already renders the whole app — it
88
+ names the logical route the SPA's own router should land on, not the file's build output path:
100
89
 
101
90
  ```ts
102
91
  defineLayoutModule({
@@ -115,7 +104,7 @@ plugin detects that and doesn't inject it again.
115
104
 
116
105
  ### `configSchema` — validated module configuration
117
106
 
118
- Author config as zod; the plugin converts it to JSON Schema (`z.toJSONSchema()`) for persona-web's
107
+ Author config as zod; the plugin converts it to JSON Schema (`z.toJSONSchema()`) for the host's
119
108
  config panel:
120
109
 
121
110
  ```ts
@@ -134,7 +123,7 @@ as static JSON Schema, not re-executed as zod on the consuming side.
134
123
 
135
124
  ### `uiConfigSchema` — config panel layout
136
125
 
137
- persona-web renders the config panel with RJSF. Pass an RJSF `uiSchema` (widget choice, field order,
126
+ The host renders the config panel with RJSF. Pass an RJSF `uiSchema` (widget choice, field order,
138
127
  labels) as `uiConfigSchema`; it is already plain data, so the plugin copies it into the manifest
139
128
  unchanged:
140
129
 
@@ -154,9 +143,9 @@ defineLayoutModule({
154
143
 
155
144
  ### `bridge-frame` — talking to the host from the iframed document
156
145
 
157
- The host (persona-web / layout editor) renders a document-delivered module in an iframe and speaks
158
- to it over `postMessage`. Wrap the module's React root in `LayoutModuleBridge` from the
159
- `bridge-frame` entry; it owns the whole handshake and hands the module a render prop:
146
+ The host renders a document-delivered module in an iframe and speaks to it over `postMessage`. Wrap
147
+ the module's React root in `LayoutModuleBridge` from the `bridge-frame` entry; it owns the whole
148
+ handshake and hands the module a render prop:
160
149
 
161
150
  ```tsx
162
151
  import { LayoutModuleBridge } from '@persona/layout-editor-remote-module/bridge-frame';
@@ -179,7 +168,7 @@ export function App() {
179
168
  - `context` (`RemoteModuleContext`) is where the module is rendered — host-owned facts the module
180
169
  cannot derive itself. It arrives with the initial data and re-renders whenever it changes (see
181
170
  below).
182
- - `resolveDataPath(dataPath, variableMap?)` returns `Promise<Json>`; the host (persona-web) owns the
171
+ - `resolveDataPath(dataPath, variableMap?)` returns `Promise<Json>`; the host owns the
183
172
  implementation. It rejects if the host reports an error or does not answer within
184
173
  `resolveTimeoutMs` (default 30s). Outside an iframe it rejects immediately, so a module opened
185
174
  directly in a tab still renders.
@@ -189,9 +178,23 @@ export function App() {
189
178
  - Mount exactly one `LayoutModuleBridge` per document.
190
179
 
191
180
  Props: `fallback?: ReactNode`, `observeHeight?: boolean` (default `true`),
192
- `resolveTimeoutMs?: number`, `allowedHostOrigins?` (only needed to embed from a host origin outside
193
- the built-in Persona allow-list — `*.withpersona.com`, staging, `*.persona-web.pages.dev`, Coder,
194
- `localhost`).
181
+ `resolveTimeoutMs?: number`, `allowedHostOrigins?` (to add host origins outside the built-in
182
+ allow-list). Built-in origins include `*.withpersona.com`, `*.withpersona-staging.com`, `localhost`,
183
+ and `127.0.0.1`. Dev and preview dashboards must be added explicitly; compose the built-in predicate
184
+ with an app-specific check:
185
+
186
+ ```tsx
187
+ import {
188
+ LayoutModuleBridge,
189
+ personaDashboardHostOrigins,
190
+ } from '@persona/layout-editor-remote-module/bridge-frame';
191
+
192
+ <LayoutModuleBridge
193
+ allowedHostOrigins={(origin) => personaDashboardHostOrigins(origin) || myDevHost(origin)}
194
+ >
195
+ {({ data }) => <RiskSummary data={data} />}
196
+ </LayoutModuleBridge>;
197
+ ```
195
198
 
196
199
  #### Host context
197
200
 
@@ -223,11 +226,11 @@ function RiskSummary({ context }: { context: RemoteModuleContext | null }) {
223
226
 
224
227
  #### Access token and environment id
225
228
 
226
- The host still appends `access_token`, `environment_id`, and `locale` to the iframe `src`. The
227
- builder app or `sa-*` subapp hosting the module consumes and strips those parameters itself, so by
228
- the time `LayoutModuleBridge` runs they have already been handled; the bridge neither reads nor
229
- strips them. Keep the host iframe's `referrerpolicy` at its default `no-referrer` (or use
230
- `strict-origin`) so the URL parameters cannot leak through subsequent requests.
229
+ The host still appends `access_token`, `environment_id`, and `locale` to the iframe `src`. The app
230
+ hosting the module consumes and strips those parameters itself, so by the time `LayoutModuleBridge`
231
+ runs they have already been handled; the bridge neither reads nor strips them. Keep the host
232
+ iframe's `referrerpolicy` at its default `no-referrer` (or use `strict-origin`) so the URL
233
+ parameters cannot leak through subsequent requests.
231
234
 
232
235
  ## Common Mistakes
233
236