@proveanything/smartlinks 2.0.6 → 2.0.10

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 (179) 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 +26 -14
  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 +106 -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/executor.md +4 -4
  106. package/dist/docs/host-dependency-contract.md +27 -0
  107. package/dist/docs/iframe-responder.md +308 -0
  108. package/dist/docs/item-context.md +0 -2
  109. package/dist/docs/manifests.md +3 -3
  110. package/dist/docs/mobile-admin-container.md +4 -4
  111. package/dist/docs/mpa.md +5 -5
  112. package/dist/docs/native-facade.md +1 -1
  113. package/dist/docs/overview.md +35 -16
  114. package/dist/docs/portal-back-button.md +2 -3
  115. package/dist/docs/widgets.md +11 -69
  116. package/dist/http.d.ts +24 -8
  117. package/dist/http.js +32 -14
  118. package/dist/iframe.d.ts +2 -2
  119. package/dist/iframe.js +1 -1
  120. package/dist/iframeResponder.d.ts +7 -1
  121. package/dist/iframeResponder.js +45 -4
  122. package/dist/index.d.ts +30 -27
  123. package/dist/index.js +10 -8
  124. package/dist/mobile-admin/errors.d.ts +1 -1
  125. package/dist/mobile-admin/types.d.ts +2 -2
  126. package/dist/openapi.yaml +12 -0
  127. package/dist/shared-dependencies.d.ts +37 -0
  128. package/dist/shared-dependencies.js +79 -0
  129. package/dist/testing/index.d.ts +1 -1
  130. package/dist/translationCache.d.ts +1 -1
  131. package/dist/types/appManifest.d.ts +23 -0
  132. package/dist/types/broadcasts.d.ts +1 -1
  133. package/dist/types/collection.d.ts +2 -2
  134. package/dist/types/comms.d.ts +5 -5
  135. package/dist/types/contact.d.ts +1 -1
  136. package/dist/types/facets.d.ts +1 -1
  137. package/dist/types/iframeResponder.d.ts +3 -3
  138. package/dist/types/index.d.ts +44 -44
  139. package/dist/types/index.js +44 -44
  140. package/dist/types/interaction.d.ts +1 -1
  141. package/dist/types/itemContext.d.ts +1 -1
  142. package/dist/types/journeysAnalytics.d.ts +1 -1
  143. package/dist/types/navigation.d.ts +1 -1
  144. package/dist/types/product.d.ts +1 -1
  145. package/dist/types/proof.d.ts +1 -1
  146. package/dist/types/segments.d.ts +1 -1
  147. package/dist/types/widgets.d.ts +2 -2
  148. package/dist/utils/conditions.d.ts +1 -1
  149. package/dist/utils/index.d.ts +3 -3
  150. package/dist/utils/index.js +3 -3
  151. package/dist/utils/paths.d.ts +4 -4
  152. package/docs/API_SUMMARY.md +7 -7
  153. package/docs/agent-tools.md +26 -14
  154. package/docs/ai.md +14 -520
  155. package/docs/analytics.md +41 -2
  156. package/docs/app-data-storage.md +0 -38
  157. package/docs/app-manifest.md +106 -7
  158. package/docs/app-objects.md +0 -148
  159. package/docs/app-records-pattern.md +2 -2
  160. package/docs/building-react-components.md +6 -14
  161. package/docs/caching.md +20 -21
  162. package/docs/container-tracking.md +2 -0
  163. package/docs/containers.md +14 -66
  164. package/docs/executor.md +4 -4
  165. package/docs/host-dependency-contract.md +27 -0
  166. package/docs/iframe-responder.md +308 -0
  167. package/docs/item-context.md +0 -2
  168. package/docs/mobile-admin-container.md +4 -4
  169. package/docs/mpa.md +5 -5
  170. package/docs/native-facade.md +1 -1
  171. package/docs/overview.md +35 -16
  172. package/docs/portal-back-button.md +2 -3
  173. package/docs/widgets.md +11 -69
  174. package/openapi.yaml +12 -0
  175. package/package.json +17 -6
  176. package/scripts/doctor.mjs +171 -0
  177. package/docs/analytics-metadata-conventions.md +0 -88
  178. package/docs/iframe-streaming-parent-changes.md +0 -308
  179. package/docs/manifests.md +0 -204
@@ -40,7 +40,9 @@ The manifest is loaded automatically by the platform for every collection page.
40
40
  "name": "My App",
41
41
  "description": "A short human-readable description of what this app does.",
42
42
  "version": "1.2.0",
43
- "platformRevision": "2026-01-01"
43
+ "platformRevision": "R5",
44
+ "moduleFormat": "dual",
45
+ "sharedDependencies": "v5"
44
46
  },
45
47
 
46
48
  "admin": "app.admin.json",
@@ -51,7 +53,7 @@ The manifest is loaded automatically by the platform for every collection page.
51
53
  "files": {
52
54
  "js": {
53
55
  "umd": "dist/widgets.umd.js",
54
- "esm": "dist/widgets.es.js"
56
+ "esm": "dist/widgets.esm.js"
55
57
  },
56
58
  "css": "dist/widgets.css"
57
59
  },
@@ -75,7 +77,7 @@ The manifest is loaded automatically by the platform for every collection page.
75
77
  "files": {
76
78
  "js": {
77
79
  "umd": "dist/containers.umd.js",
78
- "esm": "dist/containers.es.js"
80
+ "esm": "dist/containers.esm.js"
79
81
  },
80
82
  "css": "dist/containers.css"
81
83
  },
@@ -95,7 +97,7 @@ The manifest is loaded automatically by the platform for every collection page.
95
97
  "files": {
96
98
  "js": {
97
99
  "umd": "dist/mobile-admin.umd.js",
98
- "esm": "dist/mobile-admin.es.js"
100
+ "esm": "dist/mobile-admin.esm.js"
99
101
  },
100
102
  "css": null
101
103
  },
@@ -144,9 +146,56 @@ The manifest is loaded automatically by the platform for every collection page.
144
146
  | `name` | string | ✅ | Human-readable display name |
145
147
  | `description` | string | ❌ | Short description shown in app directories and AI context |
146
148
  | `version` | string | ✅ | SemVer string, e.g. `"1.2.0"` |
147
- | `platformRevision` | string | ❌ | ISO date string marking the platform API revision this build targets |
149
+ | `platformRevision` | string | ❌ | Platform revision tag this build targets, e.g. `"R5"` (see [host-dependency-contract.md](host-dependency-contract.md)) |
150
+ | `moduleFormat` | `"umd"` \| `"esm"` \| `"dual"` | ❌ | How the host loads this app's bundles. Absent = `"umd"`. See [Module format](#module-format-umd-vs-esm) below. |
151
+ | `sharedDependencies` | string | ❌ | Shared-dependency contract version the bundle was built against, e.g. `"v5"`. Used by the host to pick a compatible ESM import map. |
152
+ | `globals` | object | ❌ | Per-app namespaced UMD globals (R4.7+), e.g. `{ "widgets": "MyAppWidgets" }`. UMD-only; ESM bundles don't need it. |
148
153
  | `seo.priority` | number | ❌ | Controls which app's `title`/`description`/`ogImage` wins when multiple apps are on the same page. Default `0`; higher wins. See the [Executor guide](executor.md). |
149
154
 
155
+ #### Module format (UMD vs ESM)
156
+
157
+ The host provides shared libraries (React, Radix, the SmartLinks SDK, LiquidJS…) as **singletons**
158
+ so apps never bundle their own. Two delivery mechanisms exist, and `meta.moduleFormat` tells the host
159
+ which to use:
160
+
161
+ | `moduleFormat` | Host behaviour |
162
+ |---|---|
163
+ | `"umd"` *(default)* | Loads `files.js.umd` via the CommonJS `require` shim; shared deps resolve from **window globals**. Every existing app works unchanged. |
164
+ | `"dual"` | Prefers `files.js.esm` when the host has an **import map** for the declared `sharedDependencies` version; **falls back to UMD** otherwise. The safe transition setting. |
165
+ | `"esm"` | Loads `files.js.esm` **natively**; if the host has no matching import map it fails with an actionable error rather than a bare-specifier crash. Use only once you know your hosts are on the contract. |
166
+
167
+ The ESM bundle is declared in the **same** `files.js` block as `esm` (there is no separate `jsEsm`
168
+ field):
169
+
170
+ ```jsonc
171
+ "widgets": {
172
+ "files": {
173
+ "js": { "umd": "dist/widgets.umd.js", "esm": "dist/widgets.esm.js" },
174
+ "css": "dist/widgets.css"
175
+ }
176
+ }
177
+ ```
178
+
179
+ An ESM bundle **must externalize exactly the shared-dependency contract** — read it from the SDK
180
+ (`SHARED_DEPENDENCY_SPECIFIERS`) rather than hard-coding it, and stamp the version you built against
181
+ into `meta.sharedDependencies`. See [host-dependency-contract.md](host-dependency-contract.md).
182
+
183
+ ##### Validate before you ship: `smartlinks doctor`
184
+
185
+ Run the checker (shipped with the SDK) against your built app — it reads the same contract the host
186
+ serves, so the two can't drift:
187
+
188
+ ```bash
189
+ npx smartlinks-doctor # in the app dir, after building
190
+ ```
191
+
192
+ It reads your manifest, and for every ESM surface confirms **every bare import in the bundle is a
193
+ contract entry**. A correctly-externalized ESM bundle inlines everything except the host singletons,
194
+ so anything else left as a bare import will either fail to resolve through the import map or silently
195
+ double-load (the duplicate-React class of bug). It also warns when a UMD app still declares stale
196
+ `*.es.js`/`*.esm.js` bundles the host will never load. Exit code is non-zero on violations, so it
197
+ drops straight into CI.
198
+
150
199
  #### `build`
151
200
 
152
201
  Optional build provenance. Recommended for the **Lovable dev publish** flow: stamp the content
@@ -194,7 +243,7 @@ Apps such as widget toolkits often store reusable widget instances in collection
194
243
  "files": {
195
244
  "js": {
196
245
  "umd": "dist/widgets.umd.js",
197
- "esm": "dist/widgets.es.js"
246
+ "esm": "dist/widgets.esm.js"
198
247
  },
199
248
  "css": null
200
249
  },
@@ -247,7 +296,7 @@ See [mobile-admin-container.md](mobile-admin-container.md) for the `AdminMobileH
247
296
  "files": {
248
297
  "js": {
249
298
  "umd": "dist/mobile-admin.umd.js",
250
- "esm": "dist/mobile-admin.es.js"
299
+ "esm": "dist/mobile-admin.esm.js"
251
300
  },
252
301
  "css": null
253
302
  },
@@ -549,6 +598,56 @@ Declares what interactions and KPIs the app reports. Used by the platform's anal
549
598
 
550
599
  ---
551
600
 
601
+ ## Widget settings schema (JSON Schema)
602
+
603
+ Each widget component's `settings` object uses **JSON Schema** to describe its configurable props, so schema-form renderers *and* AI orchestrators can auto-generate a config UI without per-widget code:
604
+
605
+ ```json
606
+ "components": [
607
+ {
608
+ "name": "MyWidget",
609
+ "description": "What this widget does",
610
+ "sizes": ["compact", "standard", "large"],
611
+ "settings": {
612
+ "type": "object",
613
+ "properties": {
614
+ "displayMode": {
615
+ "type": "string", "title": "Display Mode",
616
+ "description": "How the widget renders",
617
+ "enum": ["compact", "standard", "large"],
618
+ "enumLabels": { "compact": "Icons only", "standard": "With names", "large": "Full cards" },
619
+ "default": "standard", "order": 1
620
+ },
621
+ "showImage": { "type": "boolean", "title": "Show Product Image", "default": true, "order": 2 }
622
+ }
623
+ }
624
+ }
625
+ ]
626
+ ```
627
+
628
+ | Field | Purpose |
629
+ |-------|---------|
630
+ | `type`, `enum` | Standard JSON Schema validation |
631
+ | `title` | Human-readable form label |
632
+ | `description` | Help text shown alongside the field |
633
+ | `enumLabels` | Friendly display names for enum values (`{ value → label }`) |
634
+ | `default` | Pre-selected value when no config exists |
635
+ | `order` | Field display order (lower number = higher) |
636
+
637
+ The schema serves both AI orchestrators (understand what a widget accepts, configure it conversationally) and schema-form renderers (auto-generate settings UIs).
638
+
639
+ ## AI workflows the manifest enables
640
+
641
+ SmartLinks manifests are **AI-discoverable, -configurable, and -importable**: the structured manifest plus the prose `ai-guide.md` (start from [ai-guide-template.md](ai-guide-template.md)) let AI systems set up and populate apps without custom integration code. Three workflows read the manifest:
642
+
643
+ 1. **Widget Builder** — reads `widgets.components[]` and each `settings` schema to embed a widget with correct props and auto-render its configuration UI.
644
+ 2. **Setup Wizard** — reads `setup` from `app.admin.json`: walks `setup.questions[]`, validates against `setup.configSchema`, optionally auto-generates content via `SL.ai`, then saves per `setup.saveWith`.
645
+ 3. **Data Importer** — reads `import` from `app.admin.json`: builds a CSV template from `import.fields[]`, normalises rows, and calls `import.saveWith.method` per row (multi-app imports merge fields into one CSV).
646
+
647
+ When you change your config shape, keep all three in sync: `app.manifest.json` (widget `settings`, containers, executor, linkable), `app.admin.json` (setup / import / tunable), and `ai-guide.md` (prose guidance).
648
+
649
+ This declarative model is canonical and works today. An app may **optionally** layer agent tools on top — a function that reads the declaration and returns a *context-adapted* result (e.g. "which setup questions for this collection?") — without changing the schema. That's additive and opt-in; see [agent-tools.md](agent-tools.md).
650
+
552
651
  ## Reading the Files at Runtime
553
652
 
554
653
  ### Manifest — available from the widgets endpoint
@@ -291,32 +291,6 @@ const summary = await app.cases.summary(collectionId, appId, {
291
291
  // Returns: { total: 142, byStatus: { open: 12, resolved: 130 }, ... }
292
292
  ```
293
293
 
294
- ### Use Case: Support Dashboard
295
-
296
- Build a live support dashboard showing open cases by priority:
297
-
298
- ```typescript
299
- const openCases = await app.cases.list(collectionId, appId, {
300
- status: 'open',
301
- sort: 'priority:desc',
302
- limit: 50
303
- }, true);
304
-
305
- // Aggregate by category
306
- const stats = await app.cases.aggregate(collectionId, appId, {
307
- filters: { status: 'open' },
308
- groupBy: ['category', 'priority'],
309
- metrics: ['count']
310
- }, true);
311
-
312
- // Time series: cases created per week
313
- const trend = await app.cases.aggregate(collectionId, appId, {
314
- timeSeriesField: 'created_at',
315
- timeSeriesInterval: 'week',
316
- metrics: ['count']
317
- }, true);
318
- ```
319
-
320
294
  ---
321
295
 
322
296
  ## Threads
@@ -382,53 +356,6 @@ await app.threads.update(collectionId, appId, question.id, {
382
356
  }, true);
383
357
  ```
384
358
 
385
- ### Use Case: Forum-Style Discussions
386
-
387
- List recent discussions with reply counts:
388
-
389
- ```typescript
390
- // Get active threads
391
- const activeThreads = await app.threads.list(collectionId, appId, {
392
- status: 'open',
393
- sort: 'lastReplyAt:desc',
394
- limit: 20
395
- });
396
-
397
- // Filter by tag
398
- const cleaningThreads = await app.threads.list(collectionId, appId, {
399
- tag: 'cleaning'
400
- });
401
-
402
- // Aggregate: most active discussion topics
403
- const topicStats = await app.threads.aggregate(collectionId, appId, {
404
- groupBy: ['status'],
405
- metrics: ['count', 'reply_count']
406
- });
407
- ```
408
-
409
- ### Use Case: Product Comments
410
-
411
- Attach comments to a specific product:
412
-
413
- ```typescript
414
- // Create a comment thread for a product
415
- await app.threads.create(collectionId, appId, {
416
- visibility: 'public',
417
- parentType: 'product',
418
- parentId: product.id,
419
- authorId: user.contactId,
420
- body: { text: 'Love this product! Best purchase ever.' },
421
- tags: ['positive']
422
- });
423
-
424
- // List all comments for a product
425
- const productComments = await app.threads.list(collectionId, appId, {
426
- parentType: 'product',
427
- parentId: product.id,
428
- sort: 'createdAt:desc'
429
- });
430
- ```
431
-
432
359
  ### Anchoring to app entities and proofs
433
360
 
434
361
  Anchor a thread to your own entity with `parentType` + `parentId`. `parentId` is a
@@ -948,81 +875,6 @@ const expiringSoon = await app.records.list(collectionId, appId, {
948
875
  }, true);
949
876
  ```
950
877
 
951
- ### Example: Appointment Booking
952
-
953
- ```typescript
954
- // Customer books a service appointment
955
- const booking = await app.records.create(collectionId, appId, {
956
- recordType: 'service_appointment',
957
- visibility: 'owner',
958
- contactId: user.contactId,
959
- startsAt: '2026-03-15T10:00:00Z',
960
- expiresAt: '2026-03-15T11:00:00Z', // 1-hour appointment
961
- data: {
962
- serviceType: 'installation',
963
- location: 'Customer site',
964
- technician: null // assigned later
965
- },
966
- owner: {
967
- address: '123 Main St',
968
- phone: '555-1234',
969
- notes: 'Call before arrival'
970
- }
971
- });
972
-
973
- // Admin assigns technician
974
- await app.records.update(collectionId, appId, booking.id, {
975
- data: {
976
- serviceType: 'installation',
977
- location: 'Customer site',
978
- technician: 'tech_john'
979
- },
980
- admin: {
981
- cost: 150.00,
982
- travelTime: 30
983
- }
984
- }, true);
985
-
986
- // List today's appointments
987
- const today = new Date().toISOString().split('T')[0];
988
- const todaysAppointments = await app.records.list(collectionId, appId, {
989
- recordType: 'service_appointment',
990
- startsAt: `gte:${today}T00:00:00Z`,
991
- sort: 'startsAt:asc'
992
- }, true);
993
- ```
994
-
995
- ### Example: Usage Tracking
996
-
997
- ```typescript
998
- // Log product usage (could be triggered by IoT device)
999
- await app.records.create(collectionId, appId, {
1000
- recordType: 'usage_log',
1001
- visibility: 'admin',
1002
- productId: product.id,
1003
- proofId: proof.id,
1004
- startsAt: new Date().toISOString(),
1005
- data: {
1006
- metric: 'power_on',
1007
- duration: 3600, // seconds
1008
- location: 'geo:37.7749,-122.4194'
1009
- }
1010
- }, true);
1011
-
1012
- // Aggregate usage metrics
1013
- const usageStats = await app.records.aggregate(collectionId, appId, {
1014
- filters: {
1015
- record_type: 'usage_log',
1016
- created_at: {
1017
- gte: '2026-02-01',
1018
- lte: '2026-02-28'
1019
- }
1020
- },
1021
- groupBy: ['product_id'],
1022
- metrics: ['count']
1023
- }, true);
1024
- ```
1025
-
1026
878
  ---
1027
879
 
1028
880
  ## Public Create Policies
@@ -6,7 +6,7 @@
6
6
  >
7
7
  > Status: **standard**. New apps MUST follow this contract; existing apps SHOULD migrate.
8
8
  >
9
- > SDK: `@proveanything/smartlinks` ≥ **1.11**.
9
+ > SDK: `@proveanything/smartlinks` ≥ **2.0** (R5).
10
10
  > Admin shell (React only): `@proveanything/smartlinks-utils-ui` ≥ **0.7.6** — required for the admin side if using the React shell; not needed in public widgets.
11
11
 
12
12
  ---
@@ -347,7 +347,7 @@ interface EditorContext<TData> {
347
347
 
348
348
  ## 7. Migration checklist (existing apps)
349
349
 
350
- 1. **Update SDKs:** `@proveanything/smartlinks@^1.11`, `@proveanything/smartlinks-utils-ui@^0.7.6`.
350
+ 1. **Update SDKs:** `@proveanything/smartlinks@^2.0`, `@proveanything/smartlinks-utils-ui@^2.0`.
351
351
  2. **Add `cardinality` and `allowFacetRules`** to every entry under `records` in `app.admin.json`.
352
352
  3. **Add `'rule'` (and `'collection'` if missing) to `scopes`** wherever `allowFacetRules: true`.
353
353
  4. **Pass `cardinality`** to `<RecordsAdminShell>`.
@@ -212,26 +212,18 @@ In **direct-component mode**, there are no URL search params — context comes a
212
212
 
213
213
  **Cause:** Your bundle includes its own copy of React instead of using the parent's shared instance.
214
214
 
215
- **Fix:** Ensure your build configuration externalizes React, ReactDOM, and react/jsx-runtime:
215
+ **Fix:** Externalize the shared-dependency contract — read it from the SDK so it can't drift:
216
216
 
217
217
  ```ts
218
218
  // vite.config.container.ts or vite.config.widget.ts
219
+ import { SHARED_DEPENDENCY_SPECIFIERS } from '@proveanything/smartlinks'
219
220
  export default defineConfig({
220
- build: {
221
- rollupOptions: {
222
- external: [
223
- 'react',
224
- 'react-dom',
225
- 'react/jsx-runtime',
226
- 'react-router-dom',
227
- '@proveanything/smartlinks',
228
- // ... other shared dependencies
229
- ],
230
- },
231
- },
221
+ build: { rollupOptions: { external: [...SHARED_DEPENDENCY_SPECIFIERS] } },
232
222
  });
233
223
  ```
234
224
 
225
+ > The full list + `globals` map is canonical in [host-dependency-contract.md](host-dependency-contract.md). Never bundle your own React (two instances = a hard crash).
226
+
235
227
  ---
236
228
 
237
229
  ### ❌ Context values are undefined in direct-component mode
@@ -320,4 +312,4 @@ Now that you understand the core concepts, see the implementation guides:
320
312
  - [deep-link-discovery.md](./deep-link-discovery.md) — Deep linking patterns
321
313
  - [portal-back-button.md](./portal-back-button.md) — Portal shell back-button behavior for hierarchical embeds
322
314
  - [portal-auth-broadcast.md](./portal-auth-broadcast.md) — Custom authentication and session broadcast
323
- - [manifests.md](./manifests.md) — App manifest configuration
315
+ - [app-manifest.md](./app-manifest.md) — App configuration files (manifest + admin)
package/docs/caching.md CHANGED
@@ -119,13 +119,32 @@ Different API resources have different cache lifetimes:
119
119
  ```typescript
120
120
  import { invalidateCache } from '@smartlinks/sdk';
121
121
 
122
- // Clear cache for a specific collection
122
+ // Substring match (default): clears the collection AND everything under it
123
+ // (settings, widgets, products, proofs) — a prefix wipe.
123
124
  invalidateCache('/collection/abc123');
124
125
 
126
+ // Exact match: clears ONLY that entry, not its sub-resources. Use this to avoid
127
+ // accidental cascade wipes.
128
+ invalidateCache('/collection/abc123', { exact: true });
129
+
125
130
  // Clear all product caches
126
131
  invalidateCache('/product/');
127
132
  ```
128
133
 
134
+ ### Don't force-refresh from a container-hosted app
135
+
136
+ `getWidgets(collectionId, { force: true })` (and `getAppConfig(..., { force: true })`)
137
+ bypass the cache and re-fetch. Inside a **container/iframe app**, the host has usually
138
+ already fetched that data and shares its own cache — forcing a refetch duplicates the
139
+ request the host just made. `force: true` was a **polling-era workaround** for not being
140
+ able to tell when something changed; **prefer the default (cached) path** in hosted apps.
141
+
142
+ > **Direction:** the platform is moving to a **push-based** model — app/config updates are
143
+ > pushed centrally and invalidate the relevant caches immediately, so a normal GET returns
144
+ > fresh data (and, with conditional requests/ETags, returns "not modified" cheaply) without
145
+ > anyone polling or forcing. As that lands, `force: true` should disappear from app code
146
+ > entirely. Don't build new flows around it.
147
+
129
148
  ### Clear All Caches
130
149
 
131
150
  ```typescript
@@ -164,23 +183,3 @@ try {
164
183
  }
165
184
  ```
166
185
 
167
- ## Migration from Old Behavior
168
-
169
- If you previously relied on caches persisting across refreshes:
170
-
171
- ```typescript
172
- // Old (implicit) behavior:
173
- // - SessionStorage survived refreshes
174
- // - Apps might see stale data after F5
175
-
176
- // New (explicit) behavior:
177
- configureSdkCache({
178
- clearOnPageLoad: true, // default - fresh data on refresh
179
- });
180
-
181
- // If you REALLY need old behavior (not recommended):
182
- configureSdkCache({
183
- clearOnPageLoad: false,
184
- persistence: 'indexeddb', // move to IndexedDB instead
185
- });
186
- ```
@@ -1,6 +1,8 @@
1
1
  # Container Tracking
2
2
 
3
3
  > Physical or logical groupings with hierarchical nesting, item membership, and attestation history.
4
+ >
5
+ > **Not [containers.md](containers.md)** — that's the embeddable app *bundle*. This doc is about grouping physical/logical **items** (cases, pallets, shipments…).
4
6
 
5
7
  ---
6
8
 
@@ -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
 
package/docs/executor.md CHANGED
@@ -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:
@@ -130,3 +130,30 @@ must stay pinned:
130
130
  a direct dependency.
131
131
 
132
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.