@proveanything/smartlinks 1.8.4 → 1.8.6

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.
@@ -2,6 +2,27 @@
2
2
 
3
3
  The SmartLinks platform provides two distinct types of data storage for apps:
4
4
 
5
+ ## Choose the Right Model First
6
+
7
+ There are now two different families of flexible app data in SmartLinks, and they solve different problems:
8
+
9
+ | Need | Best Fit | Why |
10
+ |------|----------|-----|
11
+ | One settings blob per scope | `appConfiguration.getConfig` / `setConfig` | Simple app setup and feature flags |
12
+ | A few keyed scoped documents by ID | `appConfiguration.getData` / `setDataItem` | Lightweight, direct, minimal overhead |
13
+ | Rich app-owned entities with filtering, visibility, ownership, or lifecycle | `app.records` | Better default for real domain objects |
14
+ | Workflow items that move through resolution | `app.cases` | Status, priority, assignment, history |
15
+ | Discussions, comments, reply chains | `app.threads` | Built around replies and conversation flow |
16
+
17
+ ### Rule of Thumb
18
+
19
+ - Use `appConfiguration` when the thing you are storing is basically config or a small keyed document.
20
+ - Use `app.records` when the thing you are storing is a real object in your app.
21
+ - Use `app.cases` when the object represents work to resolve.
22
+ - Use `app.threads` when the object is conversational.
23
+
24
+ This matters for AI-generated apps too: if the documentation only mentions `setDataItem`, builders will overuse it. In most non-trivial app flows, `app.records` is usually the stronger default.
25
+
5
26
  ## 1. User-Specific Data (Global per User+App)
6
27
 
7
28
  **Use the `userAppData` namespace for all user-specific data.**
@@ -77,16 +98,19 @@ await userAppData.remove('garden-planner', 'bed-1');
77
98
 
78
99
  ## 2. Collection/Product-Scoped Data (Admin Configuration)
79
100
 
80
- **Use the `appConfiguration` namespace for collection/product-scoped data.**
101
+ **Use the `appConfiguration` namespace for collection/product-scoped config and simple keyed documents.**
81
102
 
82
103
  This data is scoped to specific collections, products, variants, or batches. It's typically configured by collection admins/owners and applies to all users viewing that collection/product.
83
104
 
105
+ If you need richer app-owned entities with querying, lifecycle, access zones, parent-child relationships, or workflow semantics, use [app-objects.md](app-objects.md) instead of forcing everything through `setDataItem`.
106
+
84
107
  ### Use Cases
85
108
  - App-specific settings for a collection
86
109
  - Product-level configuration
87
110
  - Feature flags and toggles
88
111
  - Theme and branding settings
89
112
  - Public content that all users see
113
+ - Small keyed content entries such as FAQs, menu items, or quick lookup documents
90
114
 
91
115
  ### API Endpoints
92
116
  ```
@@ -146,15 +170,37 @@ await appConfiguration.setDataItem({
146
170
 
147
171
  ## Comparison Table
148
172
 
149
- | Feature | User Data | Collection/Product Data |
150
- |---------|-----------|------------------------|
151
- | **Namespace** | `userAppData` | `appConfiguration` |
152
- | **Scope** | User + App (global) | Collection/Product/Variant/Batch |
153
- | **Set by** | Individual users | Collection admins/owners |
154
- | **Shared across collections?** | ✅ Yes | ❌ No |
155
- | **Requires auth?** | ✅ Yes (user token) | ✅ Yes (admin token for write) |
156
- | **Function signature** | Simple: `set(appId, data)` | Options object: `setDataItem({ appId, collectionId, data })` |
157
- | **Admin write required?** | No | Yes (for write operations) |
173
+ | Feature | User Data | Scoped Config/Data | App Objects |
174
+ |---------|-----------|--------------------|-------------|
175
+ | **Namespace** | `userAppData` | `appConfiguration` | `app.records` / `app.cases` / `app.threads` |
176
+ | **Scope** | User + App (global) | Collection/Product/Variant/Batch | Collection + App |
177
+ | **Best for** | Personal preferences and user-owned items | Config blobs and small keyed documents | Rich entities and workflows |
178
+ | **Shared across collections?** | ✅ Yes | ❌ No | ❌ No |
179
+ | **Requires auth?** | ✅ Yes (user token) | ✅ Yes (admin token for write) | Depends on public/admin endpoint and visibility |
180
+ | **Querying / filtering** | Minimal | Minimal | Strong |
181
+ | **Access control zones** | User-scoped only | Scope only | `data` / `owner` / `admin` zones |
182
+ | **Admin write required?** | ❌ No | ✅ Yes (for write operations) | Only for admin endpoints / admin fields |
183
+
184
+ ---
185
+
186
+ ## When `setDataItem` Is Still the Right Answer
187
+
188
+ `setDataItem` is still valuable and should not be treated as deprecated. It is the right fit when:
189
+
190
+ - You need a handful of documents attached to a collection or product
191
+ - Each item has a stable known ID
192
+ - You mostly fetch by exact ID or list all items
193
+ - You do not need rich lifecycle fields, reply chains, assignment, or advanced querying
194
+
195
+ Examples:
196
+
197
+ - Product FAQs
198
+ - Menu definitions
199
+ - Marketing content blocks
200
+ - Widget registry entries
201
+ - Static lookup tables for an app
202
+
203
+ If the object starts needing richer semantics, migrate that use case to `app.records`, `app.cases`, or `app.threads` rather than stretching scoped data items too far.
158
204
 
159
205
  ---
160
206
 
@@ -46,6 +46,8 @@ The manifest is loaded automatically by the platform for every collection page.
46
46
  "admin": "app.admin.json",
47
47
 
48
48
  "widgets": {
49
+ "instanceResolution": true,
50
+ "instanceParam": "widgetId",
49
51
  "files": {
50
52
  "js": {
51
53
  "umd": "dist/widgets.umd.js",
@@ -127,8 +129,36 @@ Declares the widget bundle. Omit if the app has no widget component.
127
129
  | `files.js.umd` | UMD bundle path — used for dynamic `<script>` loading |
128
130
  | `files.js.esm` | ESM bundle path — used for `import()` / native ES modules (optional but recommended) |
129
131
  | `files.css` | CSS bundle path — omit if the widget ships no styles |
132
+ | `instanceResolution` | Optional boolean. When `true`, this app supports resolving configured widget instances by ID from app config |
133
+ | `instanceParam` | Optional string. Query/hash param used for instance lookup. Defaults to `"widgetId"` |
130
134
  | `components[]` | One entry per exported widget component (see below) |
131
135
 
136
+ **Widget instance resolution**
137
+
138
+ Apps such as widget toolkits often store reusable widget instances in collection-scoped app config, for example under `config.widgets.launch-countdown`. When your widget bundle can self-configure from one of those stored instances, declare that capability in the manifest:
139
+
140
+ ```json
141
+ "widgets": {
142
+ "instanceResolution": true,
143
+ "instanceParam": "widgetId",
144
+ "files": {
145
+ "js": {
146
+ "umd": "dist/widgets.umd.js",
147
+ "esm": "dist/widgets.es.js"
148
+ },
149
+ "css": null
150
+ },
151
+ "components": [
152
+ {
153
+ "name": "WidgetToolkitResolver",
154
+ "description": "Resolves and renders a configured widget instance by ID."
155
+ }
156
+ ]
157
+ }
158
+ ```
159
+
160
+ This tells the platform and other apps that they can deep-link into a stored widget instance using a URL or embed context such as `?appId=widget-toolkit&widgetId=launch-countdown`.
161
+
132
162
  **Component fields:**
133
163
 
134
164
  | Field | Type | Description |
@@ -281,6 +311,8 @@ Fetched only by the admin UI and AI-assisted setup flows — never loaded on the
281
311
  }
282
312
  ```
283
313
 
314
+ > `dynamic-select` widget pickers are a reasonable future extension for admin schemas, but they are not a built-in question type in the SDK today. For now, treat widget-instance selection as an app-level UI convention powered by `appConfiguration.listWidgetInstances()`.
315
+
284
316
  ### Field Reference
285
317
 
286
318
  #### `aiGuide`
@@ -35,6 +35,8 @@ Deep-linkable states come from **two sources** depending on their nature:
35
35
  | **App manifest** (`app.manifest.json`) | Fixed routes built into the app | Only when the app itself is updated |
36
36
  | **App config** (`appConfig.linkable`) | Content-driven entries that vary by collection | When admins create, remove, or rename content |
37
37
 
38
+ Widget-oriented apps can also use the same deep-link model with `widgetId` rather than `pageId`: the app config stores reusable widget instances, and the URL or embed context selects a specific instance to render.
39
+
38
40
  Consumers merge both sources to get the full set of navigable states for an app.
39
41
 
40
42
  ```text
@@ -232,6 +234,7 @@ The parent platform constructs the final navigation URL from an entry by combini
232
234
  | Entry | Resolved URL |
233
235
  |-------|--------------|
234
236
  | `{ title: "About Us", params: { pageId: "about-us" } }` | `https://app.example.com/#/?collectionId=abc&appId=my-app&pageId=about-us` |
237
+ | `{ title: "Launch Countdown", params: { widgetId: "launch-countdown" } }` | `https://app.example.com/#/?collectionId=abc&appId=widget-toolkit&widgetId=launch-countdown` |
235
238
  | `{ title: "Advanced Settings", path: "/settings", params: { tab: "advanced" } }` | `https://app.example.com/admin.html#/settings?collectionId=abc&appId=my-app&tab=advanced` |
236
239
  | `{ title: "Gallery", path: "/gallery" }` | `https://app.example.com/#/gallery?collectionId=abc&appId=my-app` |
237
240
  | `{ title: "Home" }` | `https://app.example.com/#/?collectionId=abc&appId=my-app` |
@@ -259,6 +262,31 @@ This section only applies to **dynamic links stored in `appConfig.linkable`**. S
259
262
 
260
263
  Apps **MUST** update `appConfig.linkable` whenever the set of dynamic navigable states changes:
261
264
 
265
+ For widget toolkits, that usually means syncing entries from your stored widget instance map:
266
+
267
+ ```typescript
268
+ const widgets = await SL.appConfiguration.listWidgetInstances({
269
+ collectionId,
270
+ appId: 'widget-toolkit',
271
+ admin: true,
272
+ })
273
+
274
+ const linkable = widgets.map(widget => ({
275
+ title: widget.name,
276
+ params: { widgetId: widget.id },
277
+ }))
278
+
279
+ const current = await SL.appConfiguration.getConfig({ collectionId, appId: 'widget-toolkit', admin: true }) ?? {}
280
+ await SL.appConfiguration.setConfig({
281
+ collectionId,
282
+ appId: 'widget-toolkit',
283
+ admin: true,
284
+ config: { ...current, linkable },
285
+ })
286
+ ```
287
+
288
+ This gives the portal and AI orchestrators the same discoverability for widget instances that page-driven apps already get for `pageId` content.
289
+
262
290
  - A page is created, deleted, published, or unpublished
263
291
  - A page's `deepLinkable` flag is toggled
264
292
  - A page title changes
package/docs/manifests.md CHANGED
@@ -19,7 +19,7 @@ app.admin.json ← loaded on-demand (setup wizards, import, AI config flow
19
19
  |---------|---------|-------------|
20
20
  | `meta` | App identity, version, appId, SEO priority | All workflows |
21
21
  | `admin` | Pointer to `app.admin.json` | Admin orchestrators |
22
- | `widgets` | Bundle files + component definitions + settings schemas | Widget Builder |
22
+ | `widgets` | Bundle files + component definitions + settings schemas; can also declare widget-instance resolution via `widgetId` | Widget Builder |
23
23
  | `containers` | Bundle files + component definitions | Container Loader |
24
24
  | `executor` | Bundle files, factory name, exports list | Server / AI |
25
25
  | `linkable` | Static deep-link routes | Portal menus / AI nav |
@@ -29,6 +29,8 @@ app.admin.json ← loaded on-demand (setup wizards, import, AI config flow
29
29
  "meta": { "name": "My App", "appId": "my-app", "version": "1.0.0" },
30
30
  "admin": "app.admin.json",
31
31
  "widgets": {
32
+ "instanceResolution": true,
33
+ "instanceParam": "widgetId",
32
34
  "files": {
33
35
  "js": { "umd": "dist/widgets.umd.js", "esm": "dist/widgets.es.js" },
34
36
  "css": null
@@ -62,6 +64,8 @@ app.admin.json ← loaded on-demand (setup wizards, import, AI config flow
62
64
  }
63
65
  ```
64
66
 
67
+ When `instanceResolution` is enabled, the app is declaring that consumers can pass a widget instance identifier such as `?appId=widget-toolkit&widgetId=launch-countdown`, and the widget bundle will resolve its stored configuration from app config.
68
+
65
69
  > ⚠️ **`css` is `null` by default.** Most widgets and containers use Tailwind/shadcn classes inherited from the parent and produce **no CSS output file**. Set `"css": null` in the manifest. Only set it to a filename if your widget/container ships a custom CSS file that actually exists in `dist/`. The parent portal checks this value before injecting a `<link>` tag — a non-null value pointing to a missing file will cause a 404.
66
70
 
67
71
  ---
package/docs/overview.md CHANGED
@@ -146,9 +146,12 @@ This applies to all write operations: `setConfig`, `setDataItem`, `updateDataIte
146
146
  |-------------|---------|---------|
147
147
  | **Config** (`getConfig`/`setConfig`) | Single JSON document | App settings, feature flags, global options |
148
148
  | **Data** (`getData`/`setDataItem`) | Array of documents with IDs | Lists of items, records, entries |
149
+ | **App Objects** (`app.records` / `app.cases` / `app.threads`) | Queryable domain objects | Real app entities, workflows, conversations, richer access control |
149
150
 
150
151
  Both can be scoped to **collection level** or **product level** by including `productId`.
151
152
 
153
+ Prefer `app.records` over `setDataItem` when the data is becoming a real entity that needs lifecycle, ownership, visibility, relationships, or filtering. Keep `setDataItem` for simple keyed scoped documents and config-adjacent content.
154
+
152
155
  ### Attestations (Proof-level data)
153
156
 
154
157
  For data attached to specific proof instances, use `SL.attestation.create()` and `SL.attestation.list()`.
package/docs/widgets.md CHANGED
@@ -202,6 +202,141 @@ Widgets support two navigation patterns:
202
202
 
203
203
  ## Building a Widget
204
204
 
205
+ ---
206
+
207
+ ## Widget Instance Resolution
208
+
209
+ Some apps expose a **widget resolver** instead of a single hard-coded widget. The resolver receives a `widgetId`, looks up a stored instance in app config, and renders the correct widget with its saved settings.
210
+
211
+ This pattern is useful for widget toolkits, promo blocks, countdown libraries, CTA collections, and any app where admins create multiple reusable widget instances.
212
+
213
+ ### URL and embed convention
214
+
215
+ Use `widgetId` the same way page-oriented apps use `pageId`:
216
+
217
+ ```text
218
+ ?appId=widget-toolkit&widgetId=launch-countdown
219
+ ```
220
+
221
+ This works naturally in:
222
+
223
+ - direct container or iframe links
224
+ - widget-to-widget references
225
+ - content slots that need to embed a preconfigured widget instance
226
+ - platform deep-link routing when the manifest declares widget instance resolution
227
+
228
+ ### Manifest declaration
229
+
230
+ Declare support in `app.manifest.json`:
231
+
232
+ ```json
233
+ {
234
+ "widgets": {
235
+ "instanceResolution": true,
236
+ "instanceParam": "widgetId",
237
+ "files": {
238
+ "js": {
239
+ "umd": "dist/widgets.umd.js",
240
+ "esm": "dist/widgets.es.js"
241
+ },
242
+ "css": null
243
+ },
244
+ "components": [
245
+ {
246
+ "name": "WidgetToolkitResolver",
247
+ "description": "Resolves and renders widget instances by ID."
248
+ }
249
+ ]
250
+ }
251
+ }
252
+ ```
253
+
254
+ ### SDK helpers
255
+
256
+ The SDK now includes two thin helpers on `SL.appConfiguration`:
257
+
258
+ ```typescript
259
+ const widget = await SL.appConfiguration.getWidgetInstance({
260
+ collectionId,
261
+ appId: 'widget-toolkit',
262
+ widgetId: 'launch-countdown',
263
+ })
264
+
265
+ const widgets = await SL.appConfiguration.listWidgetInstances({
266
+ collectionId,
267
+ appId: 'widget-toolkit',
268
+ })
269
+ ```
270
+
271
+ `getWidgetInstance()` is intentionally just a wrapper over `getConfig()` that reads `config.widgets[widgetId]`. That keeps the integration simple today while giving the platform freedom to optimize the lookup later.
272
+
273
+ ### Stored config shape
274
+
275
+ The recommended storage shape is:
276
+
277
+ ```json
278
+ {
279
+ "widgets": {
280
+ "launch-countdown": {
281
+ "id": "launch-countdown",
282
+ "name": "Launch Countdown",
283
+ "widget": {
284
+ "type": "countdown",
285
+ "targetDate": "2026-06-01T00:00:00Z",
286
+ "label": "Launching in..."
287
+ }
288
+ }
289
+ }
290
+ }
291
+ ```
292
+
293
+ ### Resolver pattern
294
+
295
+ ```typescript
296
+ const WidgetToolkitResolver: React.FC<SmartLinksWidgetProps & { widgetId?: string }> = ({
297
+ collectionId,
298
+ appId,
299
+ widgetId,
300
+ SL,
301
+ ...props
302
+ }) => {
303
+ const [instance, setInstance] = React.useState<any | null>(null)
304
+
305
+ React.useEffect(() => {
306
+ if (!widgetId) return
307
+ SL.appConfiguration.getWidgetInstance({ collectionId, appId, widgetId })
308
+ .then(setInstance)
309
+ .catch(console.error)
310
+ }, [collectionId, appId, widgetId, SL])
311
+
312
+ if (!widgetId || !instance) return null
313
+
314
+ switch (instance.widget?.type) {
315
+ case 'countdown':
316
+ return <CountdownWidget {...props} {...instance.widget} />
317
+ case 'cta-button':
318
+ return <CtaButtonWidget {...props} {...instance.widget} />
319
+ default:
320
+ return null
321
+ }
322
+ }
323
+ ```
324
+
325
+ ### Listing widget instances
326
+
327
+ `listWidgetInstances()` is useful for picker UIs and cross-app references:
328
+
329
+ ```typescript
330
+ const options = await SL.appConfiguration.listWidgetInstances({
331
+ collectionId,
332
+ appId: 'widget-toolkit',
333
+ })
334
+
335
+ // [{ id: 'launch-countdown', name: 'Launch Countdown', type: 'countdown' }]
336
+ ```
337
+
338
+ This is a good foundation for future admin question types or dynamic selects, but the SDK does not currently define a built-in `dynamic-select` schema field.
339
+
205
340
  ### 1. Create the Widget Component
206
341
 
207
342
  ```typescript
package/openapi.yaml CHANGED
@@ -11433,6 +11433,49 @@ components:
11433
11433
  required:
11434
11434
  - id
11435
11435
  - name
11436
+ GetWidgetInstanceOptions:
11437
+ type: object
11438
+ properties:
11439
+ appId:
11440
+ type: string
11441
+ collectionId:
11442
+ type: string
11443
+ productId:
11444
+ type: string
11445
+ variantId:
11446
+ type: string
11447
+ batchId:
11448
+ type: string
11449
+ admin:
11450
+ type: boolean
11451
+ widgetId:
11452
+ type: string
11453
+ required:
11454
+ - appId
11455
+ - widgetId
11456
+ WidgetInstance:
11457
+ type: object
11458
+ properties:
11459
+ id:
11460
+ type: string
11461
+ name:
11462
+ type: string
11463
+ widget:
11464
+ $ref: "#/components/schemas/TWidget"
11465
+ required:
11466
+ - id
11467
+ WidgetInstanceSummary:
11468
+ type: object
11469
+ properties:
11470
+ id:
11471
+ type: string
11472
+ name:
11473
+ type: string
11474
+ type:
11475
+ type: string
11476
+ required:
11477
+ - id
11478
+ - name
11436
11479
  AppBundle:
11437
11480
  type: object
11438
11481
  properties:
@@ -11493,6 +11536,22 @@ components:
11493
11536
  additionalProperties: true
11494
11537
  required:
11495
11538
  - name
11539
+ AppManifestWidgets:
11540
+ type: object
11541
+ properties:
11542
+ files:
11543
+ $ref: "#/components/schemas/AppManifestFiles"
11544
+ components:
11545
+ type: array
11546
+ items:
11547
+ $ref: "#/components/schemas/AppWidgetComponent"
11548
+ instanceResolution:
11549
+ type: boolean
11550
+ instanceParam:
11551
+ type: string
11552
+ required:
11553
+ - files
11554
+ - components
11496
11555
  AppContainerComponent:
11497
11556
  type: object
11498
11557
  properties:
@@ -11785,6 +11844,8 @@ components:
11785
11844
  admin:
11786
11845
  type: string
11787
11846
  widgets:
11847
+ $ref: "#/components/schemas/AppManifestWidgets"
11848
+ containers:
11788
11849
  type: object
11789
11850
  additionalProperties: true
11790
11851
  files:
@@ -11793,9 +11854,6 @@ components:
11793
11854
  type: array
11794
11855
  items:
11795
11856
  $ref: "#/components/schemas/AppContainerComponent"
11796
- containers:
11797
- type: object
11798
- additionalProperties: true
11799
11857
  linkable:
11800
11858
  type: array
11801
11859
  items:
@@ -11809,8 +11867,6 @@ components:
11809
11867
  - function
11810
11868
  - files
11811
11869
  - components
11812
- - files
11813
- - components
11814
11870
  CollectionAppWidget:
11815
11871
  type: object
11816
11872
  properties:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",