@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.
- package/dist/api/ai.d.ts +1 -1
- package/dist/api/ai.js +1 -1
- package/dist/api/analytics.d.ts +1 -1
- package/dist/api/analytics.js +1 -1
- package/dist/api/appConfiguration.d.ts +3 -3
- package/dist/api/appConfiguration.js +3 -3
- package/dist/api/appObjects.d.ts +1 -1
- package/dist/api/appObjects.js +1 -1
- package/dist/api/asset.d.ts +1 -1
- package/dist/api/asset.js +2 -2
- package/dist/api/async.d.ts +1 -1
- package/dist/api/async.js +1 -1
- package/dist/api/attestation.d.ts +1 -1
- package/dist/api/attestation.js +1 -1
- package/dist/api/attestations.d.ts +1 -1
- package/dist/api/attestations.js +1 -1
- package/dist/api/auth.d.ts +2 -2
- package/dist/api/auth.js +2 -2
- package/dist/api/authKit.d.ts +1 -1
- package/dist/api/authKit.js +1 -1
- package/dist/api/batch.d.ts +1 -1
- package/dist/api/batch.js +1 -1
- package/dist/api/broadcasts.d.ts +2 -2
- package/dist/api/broadcasts.js +1 -1
- package/dist/api/claimSet.d.ts +1 -1
- package/dist/api/claimSet.js +1 -1
- package/dist/api/collection.d.ts +1 -1
- package/dist/api/collection.js +1 -1
- package/dist/api/comms.d.ts +15 -15
- package/dist/api/comms.js +1 -1
- package/dist/api/config.d.ts +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/contact.d.ts +1 -1
- package/dist/api/contact.js +1 -1
- package/dist/api/containers.d.ts +1 -1
- package/dist/api/containers.js +1 -1
- package/dist/api/crate.d.ts +1 -1
- package/dist/api/crate.js +1 -1
- package/dist/api/facets.d.ts +1 -1
- package/dist/api/facets.js +1 -1
- package/dist/api/form.js +1 -1
- package/dist/api/http.js +1 -1
- package/dist/api/index.d.ts +46 -46
- package/dist/api/index.js +46 -46
- package/dist/api/integrations.d.ts +1 -1
- package/dist/api/integrations.js +1 -1
- package/dist/api/interactions.d.ts +1 -1
- package/dist/api/interactions.js +1 -1
- package/dist/api/jobs.d.ts +1 -1
- package/dist/api/jobs.js +1 -1
- package/dist/api/journeys.d.ts +1 -1
- package/dist/api/journeys.js +1 -1
- package/dist/api/journeysAnalytics.d.ts +1 -1
- package/dist/api/journeysAnalytics.js +1 -1
- package/dist/api/location.d.ts +1 -1
- package/dist/api/location.js +1 -1
- package/dist/api/lots.d.ts +1 -1
- package/dist/api/lots.js +1 -1
- package/dist/api/loyalty.d.ts +1 -1
- package/dist/api/loyalty.js +1 -1
- package/dist/api/navigation.d.ts +1 -1
- package/dist/api/navigation.js +1 -1
- package/dist/api/nfc.d.ts +1 -1
- package/dist/api/nfc.js +1 -1
- package/dist/api/order.d.ts +1 -1
- package/dist/api/order.js +1 -1
- package/dist/api/product.d.ts +1 -1
- package/dist/api/product.js +1 -1
- package/dist/api/products.d.ts +1 -1
- package/dist/api/products.js +1 -1
- package/dist/api/proof.d.ts +1 -1
- package/dist/api/proof.js +1 -1
- package/dist/api/qr.d.ts +1 -1
- package/dist/api/qr.js +1 -1
- package/dist/api/realtime.d.ts +1 -1
- package/dist/api/realtime.js +1 -1
- package/dist/api/research.d.ts +1 -1
- package/dist/api/research.js +1 -1
- package/dist/api/secrets.d.ts +1 -1
- package/dist/api/secrets.js +1 -1
- package/dist/api/segments.d.ts +1 -1
- package/dist/api/segments.js +1 -1
- package/dist/api/sequence.js +1 -1
- package/dist/api/tags.d.ts +1 -1
- package/dist/api/tags.js +1 -1
- package/dist/api/template.d.ts +1 -1
- package/dist/api/template.js +1 -1
- package/dist/api/translations.d.ts +1 -1
- package/dist/api/translations.js +2 -2
- package/dist/api/variant.d.ts +1 -1
- package/dist/api/variant.js +1 -1
- package/dist/containers/types.d.ts +1 -1
- package/dist/docs/API_SUMMARY.md +7 -7
- package/dist/docs/agent-tools.md +26 -14
- package/dist/docs/ai.md +14 -520
- package/dist/docs/analytics.md +41 -2
- package/dist/docs/app-data-storage.md +0 -38
- package/dist/docs/app-manifest.md +106 -7
- package/dist/docs/app-objects.md +0 -148
- package/dist/docs/app-records-pattern.md +2 -2
- package/dist/docs/building-react-components.md +6 -14
- package/dist/docs/caching.md +20 -21
- package/dist/docs/container-tracking.md +2 -0
- package/dist/docs/containers.md +14 -66
- package/dist/docs/executor.md +4 -4
- package/dist/docs/host-dependency-contract.md +27 -0
- package/dist/docs/iframe-responder.md +308 -0
- package/dist/docs/item-context.md +0 -2
- package/dist/docs/manifests.md +3 -3
- package/dist/docs/mobile-admin-container.md +4 -4
- package/dist/docs/mpa.md +5 -5
- package/dist/docs/native-facade.md +1 -1
- package/dist/docs/overview.md +35 -16
- package/dist/docs/portal-back-button.md +2 -3
- package/dist/docs/widgets.md +11 -69
- package/dist/http.d.ts +24 -8
- package/dist/http.js +32 -14
- package/dist/iframe.d.ts +2 -2
- package/dist/iframe.js +1 -1
- package/dist/iframeResponder.d.ts +7 -1
- package/dist/iframeResponder.js +45 -4
- package/dist/index.d.ts +30 -27
- package/dist/index.js +10 -8
- package/dist/mobile-admin/errors.d.ts +1 -1
- package/dist/mobile-admin/types.d.ts +2 -2
- package/dist/openapi.yaml +12 -0
- package/dist/shared-dependencies.d.ts +37 -0
- package/dist/shared-dependencies.js +79 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/translationCache.d.ts +1 -1
- package/dist/types/appManifest.d.ts +23 -0
- package/dist/types/broadcasts.d.ts +1 -1
- package/dist/types/collection.d.ts +2 -2
- package/dist/types/comms.d.ts +5 -5
- package/dist/types/contact.d.ts +1 -1
- package/dist/types/facets.d.ts +1 -1
- package/dist/types/iframeResponder.d.ts +3 -3
- package/dist/types/index.d.ts +44 -44
- package/dist/types/index.js +44 -44
- package/dist/types/interaction.d.ts +1 -1
- package/dist/types/itemContext.d.ts +1 -1
- package/dist/types/journeysAnalytics.d.ts +1 -1
- package/dist/types/navigation.d.ts +1 -1
- package/dist/types/product.d.ts +1 -1
- package/dist/types/proof.d.ts +1 -1
- package/dist/types/segments.d.ts +1 -1
- package/dist/types/widgets.d.ts +2 -2
- package/dist/utils/conditions.d.ts +1 -1
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +3 -3
- package/dist/utils/paths.d.ts +4 -4
- package/docs/API_SUMMARY.md +7 -7
- package/docs/agent-tools.md +26 -14
- package/docs/ai.md +14 -520
- package/docs/analytics.md +41 -2
- package/docs/app-data-storage.md +0 -38
- package/docs/app-manifest.md +106 -7
- package/docs/app-objects.md +0 -148
- package/docs/app-records-pattern.md +2 -2
- package/docs/building-react-components.md +6 -14
- package/docs/caching.md +20 -21
- package/docs/container-tracking.md +2 -0
- package/docs/containers.md +14 -66
- package/docs/executor.md +4 -4
- package/docs/host-dependency-contract.md +27 -0
- package/docs/iframe-responder.md +308 -0
- package/docs/item-context.md +0 -2
- package/docs/mobile-admin-container.md +4 -4
- package/docs/mpa.md +5 -5
- package/docs/native-facade.md +1 -1
- package/docs/overview.md +35 -16
- package/docs/portal-back-button.md +2 -3
- package/docs/widgets.md +11 -69
- package/openapi.yaml +12 -0
- package/package.json +17 -6
- package/scripts/doctor.mjs +171 -0
- package/docs/analytics-metadata-conventions.md +0 -88
- package/docs/iframe-streaming-parent-changes.md +0 -308
- package/docs/manifests.md +0 -204
package/docs/app-manifest.md
CHANGED
|
@@ -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": "
|
|
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.
|
|
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.
|
|
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.
|
|
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 | ❌ |
|
|
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.
|
|
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.
|
|
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
|
package/docs/app-objects.md
CHANGED
|
@@ -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` ≥ **
|
|
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@^
|
|
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:**
|
|
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
|
-
- [
|
|
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
|
-
//
|
|
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
|
|
package/docs/containers.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SmartLinks Containers
|
|
2
2
|
|
|
3
|
-
> **
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
349
|
-
//
|
|
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
|
|
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
|
-
|
|
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`?**
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|