@proveanything/smartlinks 2.0.5 → 2.0.9

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 (185) hide show
  1. package/dist/api/ai.d.ts +1 -1
  2. package/dist/api/ai.js +1 -1
  3. package/dist/api/analytics.d.ts +1 -1
  4. package/dist/api/analytics.js +1 -1
  5. package/dist/api/appConfiguration.d.ts +3 -3
  6. package/dist/api/appConfiguration.js +3 -3
  7. package/dist/api/appObjects.d.ts +1 -1
  8. package/dist/api/appObjects.js +1 -1
  9. package/dist/api/asset.d.ts +1 -1
  10. package/dist/api/asset.js +2 -2
  11. package/dist/api/async.d.ts +1 -1
  12. package/dist/api/async.js +1 -1
  13. package/dist/api/attestation.d.ts +1 -1
  14. package/dist/api/attestation.js +1 -1
  15. package/dist/api/attestations.d.ts +1 -1
  16. package/dist/api/attestations.js +1 -1
  17. package/dist/api/auth.d.ts +2 -2
  18. package/dist/api/auth.js +2 -2
  19. package/dist/api/authKit.d.ts +1 -1
  20. package/dist/api/authKit.js +1 -1
  21. package/dist/api/batch.d.ts +1 -1
  22. package/dist/api/batch.js +1 -1
  23. package/dist/api/broadcasts.d.ts +2 -2
  24. package/dist/api/broadcasts.js +1 -1
  25. package/dist/api/claimSet.d.ts +1 -1
  26. package/dist/api/claimSet.js +1 -1
  27. package/dist/api/collection.d.ts +1 -1
  28. package/dist/api/collection.js +1 -1
  29. package/dist/api/comms.d.ts +15 -15
  30. package/dist/api/comms.js +1 -1
  31. package/dist/api/config.d.ts +1 -1
  32. package/dist/api/config.js +1 -1
  33. package/dist/api/contact.d.ts +1 -1
  34. package/dist/api/contact.js +1 -1
  35. package/dist/api/containers.d.ts +1 -1
  36. package/dist/api/containers.js +1 -1
  37. package/dist/api/crate.d.ts +1 -1
  38. package/dist/api/crate.js +1 -1
  39. package/dist/api/facets.d.ts +1 -1
  40. package/dist/api/facets.js +1 -1
  41. package/dist/api/form.js +1 -1
  42. package/dist/api/http.js +1 -1
  43. package/dist/api/index.d.ts +46 -46
  44. package/dist/api/index.js +46 -46
  45. package/dist/api/integrations.d.ts +1 -1
  46. package/dist/api/integrations.js +1 -1
  47. package/dist/api/interactions.d.ts +1 -1
  48. package/dist/api/interactions.js +1 -1
  49. package/dist/api/jobs.d.ts +1 -1
  50. package/dist/api/jobs.js +1 -1
  51. package/dist/api/journeys.d.ts +1 -1
  52. package/dist/api/journeys.js +1 -1
  53. package/dist/api/journeysAnalytics.d.ts +1 -1
  54. package/dist/api/journeysAnalytics.js +1 -1
  55. package/dist/api/location.d.ts +1 -1
  56. package/dist/api/location.js +1 -1
  57. package/dist/api/lots.d.ts +1 -1
  58. package/dist/api/lots.js +1 -1
  59. package/dist/api/loyalty.d.ts +1 -1
  60. package/dist/api/loyalty.js +1 -1
  61. package/dist/api/navigation.d.ts +1 -1
  62. package/dist/api/navigation.js +1 -1
  63. package/dist/api/nfc.d.ts +1 -1
  64. package/dist/api/nfc.js +1 -1
  65. package/dist/api/order.d.ts +1 -1
  66. package/dist/api/order.js +1 -1
  67. package/dist/api/product.d.ts +1 -1
  68. package/dist/api/product.js +1 -1
  69. package/dist/api/products.d.ts +1 -1
  70. package/dist/api/products.js +1 -1
  71. package/dist/api/proof.d.ts +1 -1
  72. package/dist/api/proof.js +1 -1
  73. package/dist/api/qr.d.ts +1 -1
  74. package/dist/api/qr.js +1 -1
  75. package/dist/api/realtime.d.ts +1 -1
  76. package/dist/api/realtime.js +1 -1
  77. package/dist/api/research.d.ts +1 -1
  78. package/dist/api/research.js +1 -1
  79. package/dist/api/secrets.d.ts +1 -1
  80. package/dist/api/secrets.js +1 -1
  81. package/dist/api/segments.d.ts +1 -1
  82. package/dist/api/segments.js +1 -1
  83. package/dist/api/sequence.js +1 -1
  84. package/dist/api/tags.d.ts +1 -1
  85. package/dist/api/tags.js +1 -1
  86. package/dist/api/template.d.ts +1 -1
  87. package/dist/api/template.js +1 -1
  88. package/dist/api/translations.d.ts +1 -1
  89. package/dist/api/translations.js +2 -2
  90. package/dist/api/variant.d.ts +1 -1
  91. package/dist/api/variant.js +1 -1
  92. package/dist/containers/types.d.ts +1 -1
  93. package/dist/docs/API_SUMMARY.md +7 -7
  94. package/dist/docs/agent-tools.md +111 -0
  95. package/dist/docs/ai.md +14 -520
  96. package/dist/docs/analytics.md +41 -2
  97. package/dist/docs/app-data-storage.md +0 -38
  98. package/dist/docs/app-manifest.md +104 -7
  99. package/dist/docs/app-objects.md +0 -148
  100. package/dist/docs/app-records-pattern.md +2 -2
  101. package/dist/docs/building-react-components.md +6 -14
  102. package/dist/docs/caching.md +20 -21
  103. package/dist/docs/container-tracking.md +2 -0
  104. package/dist/docs/containers.md +14 -66
  105. package/dist/docs/deploying-apps.md +8 -3
  106. package/dist/docs/executor.md +4 -4
  107. package/dist/docs/host-dependency-contract.md +159 -0
  108. package/dist/docs/iframe-responder.md +308 -0
  109. package/dist/docs/item-context.md +0 -2
  110. package/dist/docs/manifests.md +3 -3
  111. package/dist/docs/mobile-admin-container.md +4 -4
  112. package/dist/docs/mpa.md +5 -5
  113. package/dist/docs/native-facade.md +1 -1
  114. package/dist/docs/overview.md +36 -15
  115. package/dist/docs/portal-back-button.md +2 -3
  116. package/dist/docs/sequences.md +1 -1
  117. package/dist/docs/server-functions.md +2 -3
  118. package/dist/docs/widgets.md +11 -69
  119. package/dist/http.d.ts +24 -8
  120. package/dist/http.js +32 -14
  121. package/dist/iframe.d.ts +2 -2
  122. package/dist/iframe.js +1 -1
  123. package/dist/iframeResponder.d.ts +7 -1
  124. package/dist/iframeResponder.js +45 -4
  125. package/dist/index.d.ts +30 -27
  126. package/dist/index.js +10 -8
  127. package/dist/mobile-admin/errors.d.ts +1 -1
  128. package/dist/mobile-admin/types.d.ts +2 -2
  129. package/dist/openapi.yaml +12 -0
  130. package/dist/shared-dependencies.d.ts +37 -0
  131. package/dist/shared-dependencies.js +79 -0
  132. package/dist/testing/index.d.ts +1 -1
  133. package/dist/translationCache.d.ts +1 -1
  134. package/dist/types/appManifest.d.ts +23 -0
  135. package/dist/types/broadcasts.d.ts +1 -1
  136. package/dist/types/collection.d.ts +2 -2
  137. package/dist/types/comms.d.ts +5 -5
  138. package/dist/types/contact.d.ts +1 -1
  139. package/dist/types/facets.d.ts +1 -1
  140. package/dist/types/iframeResponder.d.ts +3 -3
  141. package/dist/types/index.d.ts +44 -44
  142. package/dist/types/index.js +44 -44
  143. package/dist/types/interaction.d.ts +1 -1
  144. package/dist/types/itemContext.d.ts +1 -1
  145. package/dist/types/journeysAnalytics.d.ts +1 -1
  146. package/dist/types/navigation.d.ts +1 -1
  147. package/dist/types/product.d.ts +1 -1
  148. package/dist/types/proof.d.ts +1 -1
  149. package/dist/types/segments.d.ts +1 -1
  150. package/dist/types/widgets.d.ts +2 -2
  151. package/dist/utils/conditions.d.ts +1 -1
  152. package/dist/utils/index.d.ts +3 -3
  153. package/dist/utils/index.js +3 -3
  154. package/dist/utils/paths.d.ts +4 -4
  155. package/docs/API_SUMMARY.md +7 -7
  156. package/docs/agent-tools.md +111 -0
  157. package/docs/ai.md +14 -520
  158. package/docs/analytics.md +41 -2
  159. package/docs/app-data-storage.md +0 -38
  160. package/docs/app-manifest.md +104 -7
  161. package/docs/app-objects.md +0 -148
  162. package/docs/app-records-pattern.md +2 -2
  163. package/docs/building-react-components.md +6 -14
  164. package/docs/caching.md +20 -21
  165. package/docs/container-tracking.md +2 -0
  166. package/docs/containers.md +14 -66
  167. package/docs/deploying-apps.md +8 -3
  168. package/docs/executor.md +4 -4
  169. package/docs/host-dependency-contract.md +159 -0
  170. package/docs/iframe-responder.md +308 -0
  171. package/docs/item-context.md +0 -2
  172. package/docs/mobile-admin-container.md +4 -4
  173. package/docs/mpa.md +5 -5
  174. package/docs/native-facade.md +1 -1
  175. package/docs/overview.md +36 -15
  176. package/docs/portal-back-button.md +2 -3
  177. package/docs/sequences.md +1 -1
  178. package/docs/server-functions.md +2 -3
  179. package/docs/widgets.md +11 -69
  180. package/openapi.yaml +12 -0
  181. package/package.json +17 -6
  182. package/scripts/doctor.mjs +171 -0
  183. package/docs/analytics-metadata-conventions.md +0 -88
  184. package/docs/iframe-streaming-parent-changes.md +0 -308
  185. package/docs/manifests.md +0 -204
@@ -1,6 +1,6 @@
1
1
  # SmartLinks Containers
2
2
 
3
- > **Copy this file into `node_modules/@proveanything/smartlinks/docs/containers.md`** in the published SDK package.
3
+ > **Not [container-tracking.md](container-tracking.md).** This doc is the embeddable full-app **bundle**; that one is about grouping physical/logical *items*.
4
4
 
5
5
  Containers are the **full public app experience** packaged as an embeddable React component. Unlike widgets (lightweight previews/cards), containers render the complete public interface — all pages, routing, and features — inside a parent React application.
6
6
 
@@ -15,7 +15,7 @@ Containers are the **full public app experience** packaged as an embeddable Reac
15
15
  | **Loading** | Loaded immediately with page | Lazy-loaded on demand |
16
16
  | **Routing** | None (single component) | MemoryRouter (parent owns URL bar) |
17
17
  | **Use case** | Cards, thumbnails, quick glance | "Open full view", embedded experiences |
18
- | **Build output**| `widgets.umd.js` / `widgets.es.js` | `containers.umd.js` / `containers.es.js` |
18
+ | **Build output**| `widgets.umd.js` / `widgets.esm.js` | `containers.umd.js` / `containers.esm.js` |
19
19
 
20
20
  ### Why Separate Bundles?
21
21
 
@@ -104,54 +104,7 @@ export const PublicContainer = (props: Record<string, any>) => {
104
104
 
105
105
  ### The `useAppContext()` Pattern
106
106
 
107
- To write containers that work identically in both modes, use this abstraction pattern:
108
-
109
- ```tsx
110
- // src/hooks/useAppContext.ts
111
- import { useContext, createContext, useMemo } from 'react';
112
- import { useSearchParams } from 'react-router-dom';
113
-
114
- export interface AppContextValue {
115
- collectionId: string;
116
- appId: string;
117
- productId?: string;
118
- proofId?: string;
119
- pageId?: string;
120
- initialPath?: string;
121
- lang?: string;
122
- user?: { id: string; email: string; name?: string };
123
- SL: typeof import('@proveanything/smartlinks');
124
- onNavigate?: (request: any) => void;
125
- }
126
-
127
- export const AppContext = createContext<AppContextValue | null>(null);
128
-
129
- /**
130
- * Returns app context regardless of rendering mode.
131
- * - Direct component mode: reads from AppContext (props)
132
- * - Iframe mode: reads from URL search params
133
- */
134
- export function useAppContext(): AppContextValue {
135
- const ctx = useContext(AppContext);
136
-
137
- // If context exists, we're in direct-component mode
138
- if (ctx) return ctx;
139
-
140
- // Otherwise, we're in iframe mode — read from URL params
141
- const [searchParams] = useSearchParams();
142
- const SL = (window as any).SL ?? require('@proveanything/smartlinks');
143
-
144
- return useMemo(() => ({
145
- collectionId: searchParams.get('collectionId') ?? '',
146
- appId: searchParams.get('appId') ?? '',
147
- productId: searchParams.get('productId') ?? undefined,
148
- proofId: searchParams.get('proofId') ?? undefined,
149
- pageId: searchParams.get('pageId') ?? undefined,
150
- lang: searchParams.get('lang') ?? undefined,
151
- SL,
152
- }), [searchParams, SL]);
153
- }
154
- ```
107
+ Containers read their context through the same shared **`useAppContext()`** hook as widgets, so one codebase works in both direct-component and iframe modes. See **[building-react-components.md](building-react-components.md#the-useappcontext-pattern)** for the canonical hook + `AppContext` provider — don't re-define it. Containers additionally carry an **`initialPath?`** field on the context (the entry route the host asked for).
155
108
 
156
109
  **Usage in your container:**
157
110
 
@@ -323,7 +276,7 @@ Parent App (owns URL bar, provides globals)
323
276
 
324
277
  ```typescript
325
278
  // Lazy-load the container only when needed
326
- const { PublicContainer } = await import('https://my-app.com/containers.es.js');
279
+ const { PublicContainer } = await import('https://my-app.com/containers.esm.js');
327
280
 
328
281
  <PublicContainer
329
282
  collectionId="abc"
@@ -345,27 +298,22 @@ const { PublicContainer } = await import('https://my-app.com/containers.es.js');
345
298
  <!-- Ensure shared globals are set up first (see Shared Dependencies Contract) -->
346
299
  <script src="https://my-app.com/containers.umd.js"></script>
347
300
  <script>
348
- const { PublicContainer } = window.SmartLinksContainers;
349
- // Render with React
301
+ // The UMD global is PER-APP NAMESPACED — read the name from the manifest
302
+ // (manifest.meta.globals.containers), e.g. "SmartLinksContainers__myApp".
303
+ // Do NOT use a bare `window.SmartLinksContainers` — it collides across apps.
304
+ const globalName = manifest.meta.globals.containers;
305
+ const { PublicContainer } = window[globalName];
306
+ // Render with React (or prefer the ESM path: import from containers.esm.js)
350
307
  </script>
351
308
  ```
352
309
 
353
310
  ---
354
311
 
355
- ## Shared Dependencies Contract
356
-
357
- Containers use the **exact same Shared Dependencies Contract** as widgets. No additional globals are needed. The parent app must expose these globals before loading container bundles:
358
-
359
- - React, ReactDOM, jsxRuntime
360
- - SL (SmartLinks SDK)
361
- - CVA (class-variance-authority) — **uppercase to avoid `cva.cva` collision**
362
- - ReactRouterDOM, ReactQuery
363
- - LucideReact, dateFns, LiquidJS
364
- - 12 Radix UI primitives (Slot, Dialog, Popover, Tooltip, Tabs, Accordion, Select, ScrollArea, Label, Toast, Progress, Avatar)
312
+ ## Shared dependencies
365
313
 
366
- See `widgets.md` for the complete table with globals and version expectations.
314
+ Containers externalize the **exact same shared-dependency contract** as widgets — resolved from host globals (UMD) or the import map (ESM); no extra globals are needed. **The full versioned list, window-global names, and Vite `external`/`globals` config are canonical in [host-dependency-contract.md](host-dependency-contract.md)** — externalize exactly that set (from the SDK's `SHARED_DEPENDENCY_SPECIFIERS`) and never bundle your own React.
367
315
 
368
- > **Why `CVA` not `cva`?** The `class-variance-authority` package exports a named function called `cva`. If the UMD global is also `cva`, the wrapper resolves it as `window.cva.cva` — a double-nesting bug. Using uppercase `CVA` avoids this collision.
316
+ > **Why `CVA` not `cva`?** `class-variance-authority` exports a function named `cva`; if the UMD global were also `cva`, the wrapper resolves it as `window.cva.cva` — a double-nesting bug. The contract uses uppercase `CVA` to avoid the collision.
369
317
 
370
318
  ---
371
319
 
@@ -396,7 +344,7 @@ vite build --config vite.config.container.ts
396
344
  ```text
397
345
  dist/
398
346
  ├── containers.umd.js # Full app container (UMD)
399
- ├── containers.es.js # Full app container (ESM)
347
+ ├── containers.esm.js # Full app container (ESM)
400
348
  └── containers.css # Container styles
401
349
  ```
402
350
 
@@ -1,8 +1,7 @@
1
1
  # Deploying & registering an app
2
2
 
3
- > **Preview — SmartLinks SDK 2.0.0-alpha.** Part of the installable-app platform being built
4
- > toward 2.0.0 stable. These APIs may change before then. Published under the npm `next` tag;
5
- > `latest` remains 1.x.
3
+ > **SmartLinks SDK 2.x** (current `latest`). Part of the installable-app platform. Install
4
+ > `@proveanything/smartlinks@^2`.
6
5
 
7
6
  A SmartLinks app is a bundle (widgets, containers, and — new — [server functions](server-functions.md))
8
7
  described by an `app.manifest.json`. Deploying an app has two halves:
@@ -56,6 +55,12 @@ no secret; it's rate-limited and de-duped per app.
56
55
  recorded (that's how we know what to fetch, and the `id` you ping with must match). Ask the platform
57
56
  owner to register the app once; after that, every Publish auto-updates dev.
58
57
 
58
+ **If a publish doesn't appear:** because the ping is fire-and-forget, a failure (e.g. the build
59
+ hash never went live, or an invalid manifest) happens in the background — it won't show in your
60
+ build output. Every attempt (success *and* failure, with the reason) is recorded, and the platform
61
+ owner can see it in the console at **Admin → App Registry → Recent activity**. That's the place to
62
+ look for "I hit Publish but dev didn't change."
63
+
59
64
  ### B. From local / CI / Claude — `smartlinks-publish`
60
65
 
61
66
  If you build somewhere you control (so you can hold a dev key locally — never committed), push the
@@ -1,12 +1,12 @@
1
1
  # SmartLinks Executor Model
2
2
 
3
- > **SDK minimum:** `@proveanything/smartlinks@1.4.1`
3
+ > **SDK:** `@proveanything/smartlinks@^2.0` (R5 baseline; build against 2.0.7+)
4
4
 
5
5
  ---
6
6
 
7
7
  ## What Is an Executor?
8
8
 
9
- An executor is a **standalone JavaScript library** (`executor.umd.js` / `executor.es.js`) that a SmartLinks app ships alongside its widget and container bundles. It exposes programmatic functions that external systems — AI orchestrators, the Hub server, setup wizards — can call **without rendering the app's UI**.
9
+ An executor is a **standalone JavaScript library** (`executor.umd.js` / `executor.esm.js`) that a SmartLinks app ships alongside its widget and container bundles. It exposes programmatic functions that external systems — AI orchestrators, the Hub server, setup wizards — can call **without rendering the app's UI**.
10
10
 
11
11
  Every app can optionally ship an executor. The executor pattern solves three problems that iframe-based apps can't address on their own:
12
12
 
@@ -38,7 +38,7 @@ Every executor is declared in `app.manifest.json` so the platform can discover a
38
38
  },
39
39
  "executor": {
40
40
  "files": {
41
- "js": { "umd": "dist/executor.umd.js", "esm": "dist/executor.es.js" }
41
+ "js": { "umd": "dist/executor.umd.js", "esm": "dist/executor.esm.js" }
42
42
  },
43
43
  "factory": "createMyAppExecutor",
44
44
  "exports": ["createMyAppExecutor", "getSEO", "getLLMContent"],
@@ -61,7 +61,7 @@ Every executor is declared in `app.manifest.json` so the platform can discover a
61
61
 
62
62
  ```typescript
63
63
  // ESM (modern bundlers, Deno, Node 18+)
64
- const { createMyAppExecutor } = await import('https://my-app.smartlinks.app/dist/executor.es.js');
64
+ const { createMyAppExecutor } = await import('https://my-app.smartlinks.app/dist/executor.esm.js');
65
65
 
66
66
  // UMD (script tag, legacy environments)
67
67
  // After loading executor.umd.js:
@@ -0,0 +1,159 @@
1
+ # Host dependency contract (R5)
2
+
3
+ > **SmartLinks SDK 2.x** (current `latest`). The portal host (R5) provides a fixed set of runtime
4
+ > libraries as window globals. Micro-apps **externalise** these and resolve them from the host —
5
+ > they must **not** bundle their own copies. This keeps one instance of React (and friends) on the
6
+ > page and keeps bundles small.
7
+
8
+ ## The one rule that matters: externalise, never bundle
9
+
10
+ A container/widget/executor bundle **must externalise `react`, `react-dom`, and every shared
11
+ dependency below**, resolving them from the host globals. **Bundling your own React is the one
12
+ hard failure** — two React instances on the page → hooks break → crash. (Bundling `liquidjs` or
13
+ another shared lib is wasteful and can double-load, but React is the fatal one.)
14
+
15
+ React-18-built bundles keep working on the R5 host: they externalise React and run against the
16
+ host's **React 19** runtime. A bundle compiled against React 18 *typings* runs fine on the 19
17
+ *runtime* — see backwards-compatibility below.
18
+
19
+ ## Vite / Rollup config
20
+
21
+ Externalise the shared deps and map each to its window global:
22
+
23
+ ```ts
24
+ // vite.config.ts (library build for a container/widget/executor)
25
+ import { defineConfig } from 'vite'
26
+ export default defineConfig({
27
+ build: {
28
+ lib: { entry: 'src/index.tsx', formats: ['umd'], name: 'MyApp', fileName: () => 'widgets.umd.js' },
29
+ rollupOptions: {
30
+ // Everything the host provides — do NOT bundle these.
31
+ external: [
32
+ 'react', 'react-dom', 'react/jsx-runtime',
33
+ '@proveanything/smartlinks',
34
+ 'react-router-dom', '@tanstack/react-query',
35
+ 'lucide-react', 'date-fns', 'liquidjs', 'class-variance-authority',
36
+ '@radix-ui/react-slot', '@radix-ui/react-dialog', '@radix-ui/react-popover',
37
+ '@radix-ui/react-tooltip', '@radix-ui/react-tabs', '@radix-ui/react-accordion',
38
+ '@radix-ui/react-select', '@radix-ui/react-scroll-area', '@radix-ui/react-label',
39
+ '@radix-ui/react-toast', '@radix-ui/react-progress', '@radix-ui/react-avatar',
40
+ ],
41
+ output: {
42
+ globals: {
43
+ 'react': 'React', 'react-dom': 'ReactDOM', 'react/jsx-runtime': 'jsxRuntime',
44
+ '@proveanything/smartlinks': 'SL',
45
+ 'react-router-dom': 'ReactRouterDOM', '@tanstack/react-query': 'ReactQuery',
46
+ 'lucide-react': 'LucideReact', 'date-fns': 'dateFns', 'liquidjs': 'LiquidJS',
47
+ 'class-variance-authority': 'CVA',
48
+ '@radix-ui/react-slot': 'RadixSlot', '@radix-ui/react-dialog': 'RadixDialog',
49
+ '@radix-ui/react-popover': 'RadixPopover', '@radix-ui/react-tooltip': 'RadixTooltip',
50
+ '@radix-ui/react-tabs': 'RadixTabs', '@radix-ui/react-accordion': 'RadixAccordion',
51
+ '@radix-ui/react-select': 'RadixSelect', '@radix-ui/react-scroll-area': 'RadixScrollArea',
52
+ '@radix-ui/react-label': 'RadixLabel', '@radix-ui/react-toast': 'RadixToast',
53
+ '@radix-ui/react-progress': 'RadixProgress', '@radix-ui/react-avatar': 'RadixAvatar',
54
+ },
55
+ },
56
+ },
57
+ },
58
+ })
59
+ ```
60
+
61
+ ## Host-provided globals (build against versions ≤ these)
62
+
63
+ | Import | Window global | Host provides (R5) |
64
+ |---|---|---|
65
+ | `react` | `React` | 19.3 (accepts 18.3 builds) |
66
+ | `react-dom` | `ReactDOM` | 19.3 (accepts 18.3 builds) |
67
+ | `react/jsx-runtime` | `jsxRuntime` | 19.3 |
68
+ | `@proveanything/smartlinks` | `SL` | 2.0.5 |
69
+ | `react-router-dom` | `ReactRouterDOM` | 7.18 (accepts 6.x builds) |
70
+ | `@tanstack/react-query` | `ReactQuery` | 5.103 |
71
+ | `lucide-react` | `LucideReact` | 1.47 |
72
+ | `date-fns` | `dateFns` | 4.4 |
73
+ | `liquidjs` | `LiquidJS` | 10.27+ |
74
+ | `class-variance-authority` | `CVA` | 0.7 |
75
+ | `@radix-ui/react-slot` | `RadixSlot` | 1.2.4 |
76
+ | `@radix-ui/react-dialog` | `RadixDialog` | 1.1.23 |
77
+ | `@radix-ui/react-popover` | `RadixPopover` | 1.1.1 |
78
+ | `@radix-ui/react-tooltip` | `RadixTooltip` | 1.2.8 |
79
+ | `@radix-ui/react-tabs` | `RadixTabs` | 1.1.13 |
80
+ | `@radix-ui/react-accordion` | `RadixAccordion` | 1.2.12 |
81
+ | `@radix-ui/react-select` | `RadixSelect` | 2.3.7 |
82
+ | `@radix-ui/react-scroll-area` | `RadixScrollArea` | 1.2.10 |
83
+ | `@radix-ui/react-label` | `RadixLabel` | 2.1.8 |
84
+ | `@radix-ui/react-toast` | `RadixToast` | 1.2.15 |
85
+ | `@radix-ui/react-progress` | `RadixProgress` | 1.1.8 |
86
+ | `@radix-ui/react-avatar` | `RadixAvatar` | 1.1.11 |
87
+
88
+ `liquidjs` is **host-provided** — externalise it, don't ship a second copy (frequently missed).
89
+
90
+ ## Backwards compatibility
91
+
92
+ React-18-built containers and widgets keep working unchanged on R5. A pre-existing bundle only
93
+ breaks if it:
94
+
95
+ - calls `ReactDOM.render` / `hydrate` / `unmountComponentAtNode` (self-mounting — containers are
96
+ mounted by the host, so this only affects apps that mount themselves);
97
+ - relies on `defaultProps` / `propTypes` on **function** components (React 19 silently ignores
98
+ these → missing defaults, not a crash);
99
+ - uses string refs, `findDOMNode`, or legacy context;
100
+ - **bundles its own React** instead of externalising it → two instances → crash (the one hard
101
+ failure);
102
+ - imports a `lucide-react` icon renamed/removed in the 0.x → 1.x move.
103
+
104
+ ## Tailwind is *not* part of the contract
105
+
106
+ Bundles ship their own compiled CSS, so the host's Tailwind version is irrelevant to them. A
107
+ micro-app can stay on **Tailwind 3 indefinitely**, or adopt Tailwind 4 — its choice. (The starter
108
+ app ships the Tailwind 4 CSS-first layout as the default; see its README.)
109
+
110
+ ## The R5 host stack (reference)
111
+
112
+ React **19.3** · Vite **8.3** · react-router-dom **7.18** · Tailwind **4.3** (CSS-first) ·
113
+ TypeScript **6.0** · ESLint **10.11** · `@proveanything/smartlinks` **2.0.5** ·
114
+ `@proveanything/smartlinks-utils-ui` **1.16.4** · liquidjs **10.29**.
115
+
116
+ > **TypeScript 7** (the native/Go compiler) is **deliberately deferred** — tooling hasn't settled.
117
+ > Target **TS 6** for R5; it compiles existing code with no source changes.
118
+
119
+ ## Security floor
120
+
121
+ An R5 app must ship with **`npm audit` reporting zero vulnerabilities**. Run it against the real
122
+ registry — `npm audit --registry=https://registry.npmjs.org` — because the sandbox mirror doesn't
123
+ implement the audit endpoint. Two high-severity advisories are already pinned out in the R5 set and
124
+ must stay pinned:
125
+
126
+ - **`react-router` 7.12.0–7.18.1** — RSC-mode CSRF bypass. Build against **7.18.4+** (the host
127
+ provides ≥7.18.4). Never ship a router below 7.18.4.
128
+ - **`browserslist` ≤4.28.6** — unbounded memory growth / prototype write. It's a transitive build
129
+ dependency, so pin it with an `overrides` entry (`"overrides": { "browserslist": "^4.29" }`), not
130
+ a direct dependency.
131
+
132
+ Both are compile-time/build-time concerns for the app's own toolchain; neither is a host global.
133
+
134
+ ## Reading the contract programmatically (one source of truth)
135
+
136
+ Don't hard-code the externalized list — import it from the SDK, so hosts and apps
137
+ never drift:
138
+
139
+ ```ts
140
+ import {
141
+ SHARED_DEPENDENCY_CONTRACT_VERSION, // 'v5'
142
+ SHARED_DEPENDENCIES, // [{ specifier, globalName, minVersion, importMapPath }, …] (25)
143
+ SHARED_DEPENDENCY_SPECIFIERS, // bare specifiers — drop straight into a bundler `external` list
144
+ getHostSharedDependencies, // what the live host advertises at runtime, or null
145
+ } from '@proveanything/smartlinks'
146
+
147
+ // Build config: externalize exactly the contract.
148
+ export const external = [...SHARED_DEPENDENCY_SPECIFIERS]
149
+
150
+ // Runtime: degrade gracefully on an older host that lacks the import map.
151
+ const host = getHostSharedDependencies()
152
+ if (host && host.version !== SHARED_DEPENDENCY_CONTRACT_VERSION) {
153
+ console.warn(`Built against ${SHARED_DEPENDENCY_CONTRACT_VERSION}, host serves ${host.version}`)
154
+ }
155
+ ```
156
+
157
+ The host publishes its live contract on `window.__SMARTLINKS_SHARED__` (`{ version, specifiers }`),
158
+ which `getHostSharedDependencies()` reads. Import-map shim paths follow `importMapPathFor(specifier)`
159
+ (`/sl-shared/<version>/<slug>.js`), so both the portal and the SDK generate identical paths.
@@ -468,3 +468,311 @@ import type {
468
468
  ### Portal back exits the app too early
469
469
  - Set `state.parentPath` on route changes for screens that should navigate "up" inside the app.
470
470
  - Make sure the receiving app listens for `smartlinks-navigate` if the SDK version in use does not already handle it.
471
+
472
+ ---
473
+
474
+ ## Streaming in a hand-rolled parent (without IframeResponder)
475
+
476
+ The following applies **only if you build your own parent proxy** instead of using the `IframeResponder` class above — which already handles AI streaming for you. It is the wire protocol for forwarding SSE/streaming responses to a child app in proxy mode.
477
+ ### Goal
478
+
479
+ Keep the existing architecture:
480
+
481
+ - local mode: child calls API directly
482
+ - iframe proxy mode: child never owns auth state and streams through the parent
483
+
484
+ This keeps user/session authority in the parent while making AI streaming behave like the rest of the SDK transport.
485
+
486
+ ### What changed
487
+
488
+ Previously, proxy mode only supported one-shot request/response messages:
489
+
490
+ - `_smartlinksProxyRequest`
491
+ - `_smartlinksProxyResponse`
492
+
493
+ Streaming now adds a second protocol for long-lived responses:
494
+
495
+ - `_smartlinksProxyStreamRequest`
496
+ - `_smartlinksProxyStream`
497
+ - `_smartlinksProxyStreamAbort`
498
+
499
+ ### New parent message handling
500
+
501
+ #### 1. Listen for stream requests
502
+
503
+ The iframe child may now send this message:
504
+
505
+ ```ts
506
+ {
507
+ _smartlinksProxyStreamRequest: true,
508
+ id: string,
509
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
510
+ path: string,
511
+ body?: any,
512
+ headers?: Record<string, string>
513
+ }
514
+ ```
515
+
516
+ Parent behavior:
517
+
518
+ - treat this like a proxied API request
519
+ - build the real API URL from your configured base URL plus `path`
520
+ - send the request using the parent's current auth/session context
521
+ - expect an SSE / streaming response body
522
+ - keep the request open until the stream ends or is aborted
523
+
524
+ #### 2. Forward stream lifecycle messages back to the child
525
+
526
+ The parent should send messages back to the iframe using this envelope:
527
+
528
+ ```ts
529
+ {
530
+ _smartlinksProxyStream: true,
531
+ id: string,
532
+ phase: 'open' | 'event' | 'end' | 'error',
533
+ data?: any,
534
+ error?: string,
535
+ status?: number
536
+ }
537
+ ```
538
+
539
+ Phases:
540
+
541
+ - `open`
542
+ - optional but recommended
543
+ - indicates the upstream streaming request was accepted and a body exists
544
+ - `event`
545
+ - contains one parsed JSON event from an SSE `data:` frame
546
+ - send one message per logical event payload
547
+ - `end`
548
+ - sent once when the stream finishes normally
549
+ - `error`
550
+ - sent if the upstream request fails before or during streaming
551
+
552
+ #### 3. Support abort from the child
553
+
554
+ The child may stop reading early and send:
555
+
556
+ ```ts
557
+ {
558
+ _smartlinksProxyStreamAbort: true,
559
+ id: string
560
+ }
561
+ ```
562
+
563
+ Parent behavior:
564
+
565
+ - look up the active stream by `id`
566
+ - abort the underlying fetch / reader
567
+ - clean up any local state for that stream
568
+ - do not keep streaming after abort
569
+
570
+ ### SSE forwarding rules
571
+
572
+ The upstream AI endpoints return SSE-like frames. The parent should:
573
+
574
+ - read the response body as a stream
575
+ - buffer text until line boundaries
576
+ - collect `data:` lines for a single event
577
+ - join multi-line `data:` payloads with `\n`
578
+ - ignore blank events
579
+ - stop on `data: [DONE]`
580
+ - JSON-parse each event payload
581
+ - forward parsed payloads to the iframe as `_smartlinksProxyStream` with `phase: 'event'`
582
+
583
+ Minimal parsing behavior:
584
+
585
+ 1. accumulate bytes into text
586
+ 2. split on `\r?\n`
587
+ 3. collect each `data:` line
588
+ 4. on blank line, finalize the event
589
+ 5. if payload is `[DONE]`, finish
590
+ 6. otherwise `JSON.parse(payload)` and forward
591
+
592
+ ### Auth and session expectations
593
+
594
+ The parent remains the source of truth for auth.
595
+
596
+ That means the parent stream handler should:
597
+
598
+ - use the same auth headers/token source as normal proxied requests
599
+ - not require the iframe to know the bearer token or API key
600
+ - naturally pick up the current logged-in user when the stream starts
601
+ - cancel active streams if your app invalidates session state on logout or account switch
602
+
603
+ In practice, the stream request should use the same header-building logic as your normal parent proxy transport.
604
+
605
+ ### Error handling expectations
606
+
607
+ If the upstream fetch returns a non-2xx status:
608
+
609
+ - try to read the JSON error body
610
+ - derive a useful message
611
+ - send one `_smartlinksProxyStream` message with `phase: 'error'`
612
+ - include `status` when available
613
+ - do not send `end` afterward
614
+
615
+ If the stream body is missing unexpectedly:
616
+
617
+ - send `phase: 'error'`
618
+
619
+ If JSON parsing fails for a single event chunk:
620
+
621
+ - safest behavior is to ignore that malformed chunk and continue
622
+
623
+ ### State the parent should keep
624
+
625
+ Track active streams in a map keyed by `id`:
626
+
627
+ ```ts
628
+ Map<string, AbortController>
629
+ ```
630
+
631
+ Recommended cleanup points:
632
+
633
+ - on normal stream end
634
+ - on error
635
+ - on child abort
636
+ - on iframe detach/unmount
637
+ - on parent auth reset/logout if you want all in-flight streams cancelled immediately
638
+
639
+ ### Parent implementation outline
640
+
641
+ ```ts
642
+ const activeStreams = new Map<string, AbortController>()
643
+
644
+ window.addEventListener('message', async (event) => {
645
+ const msg = event.data
646
+
647
+ if (msg?._smartlinksProxyStreamAbort && msg.id) {
648
+ activeStreams.get(msg.id)?.abort()
649
+ activeStreams.delete(msg.id)
650
+ return
651
+ }
652
+
653
+ if (msg?._smartlinksProxyStreamRequest && msg.id) {
654
+ const controller = new AbortController()
655
+ activeStreams.set(msg.id, controller)
656
+
657
+ try {
658
+ const response = await fetch(buildUrl(msg.path), {
659
+ method: msg.method,
660
+ headers: msg.headers,
661
+ body: msg.body ? JSON.stringify(msg.body) : undefined,
662
+ signal: controller.signal,
663
+ })
664
+
665
+ if (!response.ok || !response.body) {
666
+ postError(...)
667
+ return
668
+ }
669
+
670
+ postOpen(...)
671
+ await forwardSse(response.body, parsed => postEvent(...parsed))
672
+ postEnd(...)
673
+ } catch (err) {
674
+ if (err?.name !== 'AbortError') postError(...)
675
+ } finally {
676
+ activeStreams.delete(msg.id)
677
+ }
678
+ }
679
+ })
680
+ ```
681
+
682
+ ### Exact protocol summary
683
+
684
+ #### Child → parent
685
+
686
+ Standard stream request:
687
+
688
+ ```ts
689
+ {
690
+ _smartlinksProxyStreamRequest: true,
691
+ id,
692
+ method,
693
+ path,
694
+ body,
695
+ headers
696
+ }
697
+ ```
698
+
699
+ Abort request:
700
+
701
+ ```ts
702
+ {
703
+ _smartlinksProxyStreamAbort: true,
704
+ id
705
+ }
706
+ ```
707
+
708
+ #### Parent → child
709
+
710
+ Open:
711
+
712
+ ```ts
713
+ {
714
+ _smartlinksProxyStream: true,
715
+ id,
716
+ phase: 'open'
717
+ }
718
+ ```
719
+
720
+ Event:
721
+
722
+ ```ts
723
+ {
724
+ _smartlinksProxyStream: true,
725
+ id,
726
+ phase: 'event',
727
+ data: parsedJsonEvent
728
+ }
729
+ ```
730
+
731
+ End:
732
+
733
+ ```ts
734
+ {
735
+ _smartlinksProxyStream: true,
736
+ id,
737
+ phase: 'end'
738
+ }
739
+ ```
740
+
741
+ Error:
742
+
743
+ ```ts
744
+ {
745
+ _smartlinksProxyStream: true,
746
+ id,
747
+ phase: 'error',
748
+ error: 'message',
749
+ status?: number
750
+ }
751
+ ```
752
+
753
+ ### What does not change
754
+
755
+ These parts of the parent iframe integration stay the same:
756
+
757
+ - normal `_smartlinksProxyRequest` request/response flow
758
+ - upload proxy flow
759
+ - auth login/logout postMessage handling
760
+ - route/deep-link handling
761
+ - resize handling
762
+
763
+ This is an additive protocol, not a replacement.
764
+
765
+ ### Current SDK reference
766
+
767
+ The SDK implementation lives in:
768
+
769
+ - [src/http.ts](src/http.ts)
770
+ - [src/iframeResponder.ts](src/iframeResponder.ts)
771
+ - [src/types/iframeResponder.ts](src/types/iframeResponder.ts)
772
+ - [src/api/ai.ts](src/api/ai.ts)
773
+
774
+ ### Practical recommendation
775
+
776
+ If your parent already uses `IframeResponder`, prefer upgrading to the SDK version with these changes instead of re-implementing the protocol manually.
777
+
778
+ If your parent has a custom iframe bridge, implement exactly the three new message types above and reuse your existing auth/header logic from normal proxied requests.
@@ -1,7 +1,5 @@
1
1
  # Item Context (container prop)
2
2
 
3
- > **Copy this file into `node_modules/@proveanything/smartlinks/docs/item-context.md`** in the published SDK package.
4
-
5
3
  When the URL points at a specific item — either a **serial proof URL** or an
6
4
  **NFC tap** — the portal derives an `ItemContext` describing what it found
7
5
  and hands it to the container as the **`itemContext`** prop.