@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
@@ -46,7 +46,7 @@ There are two analytics domains:
46
46
 
47
47
  The backend stores custom analytics dimensions in `metadata`, but promoted analytics fields now belong at top level and are queried from real columns.
48
48
 
49
- See [docs/analytics-metadata-conventions.md](analytics-metadata-conventions.md) for the recommended key set.
49
+ See [Metadata conventions](#metadata-conventions) below for the recommended key set.
50
50
 
51
51
  ### A note on server-written events
52
52
 
@@ -663,7 +663,7 @@ Analytics metadata filtering currently works best with top-level scalar keys suc
663
663
  - `utmCampaign`
664
664
  - `pagePath`
665
665
 
666
- See [docs/analytics-metadata-conventions.md](analytics-metadata-conventions.md) for the recommended shared vocabulary.
666
+ See [Metadata conventions](#metadata-conventions) for the recommended shared vocabulary.
667
667
 
668
668
  ### 4. Prefer generic endpoints for custom dashboards
669
669
 
@@ -719,3 +719,42 @@ async function loadScanDashboard(collectionId: string) {
719
719
  }
720
720
  }
721
721
  ```
722
+
723
+ ---
724
+
725
+ ## Metadata conventions
726
+
727
+ Recommended standard analytics keys — a shared vocabulary so teams don't invent divergent names. Some are promoted top-level fields; others are good `metadata` keys for custom dimensions.
728
+
729
+ ### Promoted top-level fields
730
+
731
+ Send these at the **top level**, not inside `metadata`: `visitorId`, `referrerHost`, `entryType`, `pageId`, `scanMethod`, `source` (collection/web-events only — free-form client id like `'portal'`/`'hub'`; not on tag events), `redirectMode` (tag-events only; usually server-written).
732
+
733
+ ### Metadata-friendly keys
734
+
735
+ `referrer`, `utmSource`, `utmMedium`, `utmCampaign`, `utmContent`, `utmTerm`, `group`, `tag`, `campaign`, `placement`, `linkGroup`, `linkPlacement`, `linkPosition`, `linkTitle`, `destinationDomain`, `pagePath`, `qrCodeId`.
736
+
737
+ ### Guidance
738
+
739
+ - Treat these as reserved standard keys; prefer them before inventing alternatives.
740
+ - Keep values flat and scalar so they're easy to filter/break-down later.
741
+ - Promote a field to a first-class backend column only when it becomes a hot platform-wide dimension.
742
+ - `source` (the event column) and the query-time `source` parameter (`'events' | 'tag'`, which table to query) are unrelated fields that share a name — filter the column via the **plural** `sources` array (there is no singular `source` filter).
743
+
744
+ ```typescript
745
+ analytics.collection.track({
746
+ sessionId: 1234567890,
747
+ eventType: 'click_link',
748
+ collectionId: 'demo-collection',
749
+ visitorId: 'visitor_123',
750
+ linkId: 'hero-cta',
751
+ href: 'https://example.com/buy',
752
+ referrerHost: 'instagram.com',
753
+ placement: 'hero',
754
+ campaign: 'summer-launch',
755
+ utmSource: 'email',
756
+ pageId: 'QR123',
757
+ source: 'portal',
758
+ metadata: { pagePath: '/c/demo-collection' },
759
+ })
760
+ ```
@@ -249,44 +249,6 @@ If the object starts needing richer semantics, migrate that use case to `app.rec
249
249
 
250
250
  ---
251
251
 
252
- ## Migration from Old SDK
253
-
254
- ### Old SDK → New SDK
255
-
256
- ```typescript
257
- // OLD: Get user config
258
- RemoteApi.get({ path: `public/auth/app/${appId}` })
259
- // NEW:
260
- userAppData.getConfig(appId)
261
-
262
- // OLD: Set user config
263
- RemoteApi.post({ path: `public/auth/app/${appId}`, data })
264
- // NEW:
265
- userAppData.setConfig(appId, data)
266
-
267
- // OLD: Get user data items
268
- RemoteApi.get({ path: `public/auth/app/${appId}/data` })
269
- // NEW:
270
- userAppData.list(appId)
271
-
272
- // OLD: Get user data item
273
- RemoteApi.get({ path: `public/auth/app/${appId}/data/${itemId}` })
274
- // NEW:
275
- userAppData.get(appId, itemId)
276
-
277
- // OLD: Set user data item
278
- RemoteApi.post({ path: `public/auth/app/${appId}/data`, data: item })
279
- // NEW:
280
- userAppData.set(appId, item)
281
-
282
- // OLD: Delete user data item
283
- RemoteApi.delete({ path: `public/auth/app/${appId}/data/${itemId}` })
284
- // NEW:
285
- userAppData.remove(appId, itemId)
286
- ```
287
-
288
- ---
289
-
290
252
  ## Complete API Reference
291
253
 
292
254
  ### `userAppData` (User-Specific Data)
@@ -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,54 @@ 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
+
552
649
  ## Reading the Files at Runtime
553
650
 
554
651
  ### 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)
@@ -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