@proveanything/smartlinks 1.8.4 → 1.8.5

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.
@@ -1,4 +1,5 @@
1
1
  import { CollectionWidgetsResponse, GetCollectionWidgetsOptions } from "../types/appManifest";
2
+ import type { GetWidgetInstanceOptions, WidgetInstance, WidgetInstanceSummary } from "../types/appConfiguration";
2
3
  /**
3
4
  * Options for collection/product-scoped app configuration.
4
5
  * This data is set by admins and applies to all users within the scope.
@@ -193,6 +194,39 @@ export declare namespace appConfiguration {
193
194
  * ```
194
195
  */
195
196
  function getConfig(opts: AppConfigOptions): Promise<any>;
197
+ /**
198
+ * Resolve a configured widget instance by ID from an app's stored config.
199
+ * This is a thin convenience wrapper over `getConfig()` that reads `config.widgets[widgetId]`.
200
+ *
201
+ * @param opts - Scope options plus the widget instance ID
202
+ * @returns The configured widget instance
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const widget = await appConfiguration.getWidgetInstance({
207
+ * collectionId: 'my-collection',
208
+ * appId: 'widget-toolkit',
209
+ * widgetId: 'launch-countdown'
210
+ * })
211
+ * ```
212
+ */
213
+ function getWidgetInstance<TWidget = any>(opts: GetWidgetInstanceOptions): Promise<WidgetInstance<TWidget>>;
214
+ /**
215
+ * List configured widget instances for an app.
216
+ * Useful for picker UIs, setup schemas, and widget-to-widget references.
217
+ *
218
+ * @param opts - App config scope options
219
+ * @returns Array of widget instance summaries
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * const widgets = await appConfiguration.listWidgetInstances({
224
+ * collectionId: 'my-collection',
225
+ * appId: 'widget-toolkit'
226
+ * })
227
+ * ```
228
+ */
229
+ function listWidgetInstances(opts: Omit<GetWidgetInstanceOptions, 'widgetId'>): Promise<WidgetInstanceSummary[]>;
196
230
  /**
197
231
  * Set app configuration for a collection/product scope.
198
232
  * Requires admin authentication.
@@ -1,5 +1,24 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  // src/api/appConfiguration.ts
2
13
  import { request, post, del } from "../http";
14
+ function getWidgetsMap(config) {
15
+ if (!config || typeof config !== 'object' || Array.isArray(config))
16
+ return {};
17
+ const widgets = config.widgets;
18
+ if (!widgets || typeof widgets !== 'object' || Array.isArray(widgets))
19
+ return {};
20
+ return widgets;
21
+ }
3
22
  function buildAppPath(opts, type) {
4
23
  const base = opts.admin ? "admin" : "public";
5
24
  let path = `/${base}`;
@@ -228,6 +247,67 @@ export var appConfiguration;
228
247
  return request(path);
229
248
  }
230
249
  appConfiguration.getConfig = getConfig;
250
+ /**
251
+ * Resolve a configured widget instance by ID from an app's stored config.
252
+ * This is a thin convenience wrapper over `getConfig()` that reads `config.widgets[widgetId]`.
253
+ *
254
+ * @param opts - Scope options plus the widget instance ID
255
+ * @returns The configured widget instance
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * const widget = await appConfiguration.getWidgetInstance({
260
+ * collectionId: 'my-collection',
261
+ * appId: 'widget-toolkit',
262
+ * widgetId: 'launch-countdown'
263
+ * })
264
+ * ```
265
+ */
266
+ async function getWidgetInstance(opts) {
267
+ const { widgetId } = opts, configOpts = __rest(opts, ["widgetId"]);
268
+ const config = await getConfig(configOpts);
269
+ const widgets = getWidgetsMap(config);
270
+ const instance = widgets[widgetId];
271
+ if (!instance || typeof instance !== 'object' || Array.isArray(instance)) {
272
+ throw new Error(`Widget instance \"${widgetId}\" not found for app \"${opts.appId}\"`);
273
+ }
274
+ return instance;
275
+ }
276
+ appConfiguration.getWidgetInstance = getWidgetInstance;
277
+ /**
278
+ * List configured widget instances for an app.
279
+ * Useful for picker UIs, setup schemas, and widget-to-widget references.
280
+ *
281
+ * @param opts - App config scope options
282
+ * @returns Array of widget instance summaries
283
+ *
284
+ * @example
285
+ * ```typescript
286
+ * const widgets = await appConfiguration.listWidgetInstances({
287
+ * collectionId: 'my-collection',
288
+ * appId: 'widget-toolkit'
289
+ * })
290
+ * ```
291
+ */
292
+ async function listWidgetInstances(opts) {
293
+ const config = await getConfig(opts);
294
+ const widgets = getWidgetsMap(config);
295
+ return Object.entries(widgets).map(([id, instance]) => {
296
+ var _a;
297
+ const widgetInstance = instance && typeof instance === 'object' && !Array.isArray(instance)
298
+ ? instance
299
+ : {};
300
+ const resolvedId = typeof widgetInstance.id === 'string' && widgetInstance.id.trim()
301
+ ? widgetInstance.id
302
+ : id;
303
+ return Object.assign(Object.assign({}, widgetInstance), { id: resolvedId, name: typeof widgetInstance.name === 'string' && widgetInstance.name.trim()
304
+ ? widgetInstance.name
305
+ : resolvedId, type: typeof ((_a = widgetInstance.widget) === null || _a === void 0 ? void 0 : _a.type) === 'string'
306
+ ? widgetInstance.widget.type
307
+ : undefined });
308
+ });
309
+ }
310
+ appConfiguration.listWidgetInstances = listWidgetInstances;
231
311
  /**
232
312
  * Set app configuration for a collection/product scope.
233
313
  * Requires admin authentication.
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.8.4 | Generated: 2026-03-14T10:47:38.203Z
3
+ Version: 1.8.5 | Generated: 2026-03-14T14:43:55.897Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -1283,6 +1283,39 @@ interface AppConfigurationResponse {
1283
1283
  }
1284
1284
  ```
1285
1285
 
1286
+ **GetWidgetInstanceOptions** (interface)
1287
+ ```typescript
1288
+ interface GetWidgetInstanceOptions {
1289
+ appId: string
1290
+ collectionId?: string
1291
+ productId?: string
1292
+ variantId?: string
1293
+ batchId?: string
1294
+ admin?: boolean
1295
+ widgetId: string
1296
+ }
1297
+ ```
1298
+
1299
+ **WidgetInstance<TWidget = any>** (interface)
1300
+ ```typescript
1301
+ interface WidgetInstance<TWidget = any> {
1302
+ id: string
1303
+ name?: string
1304
+ widget?: TWidget
1305
+ [key: string]: any
1306
+ }
1307
+ ```
1308
+
1309
+ **WidgetInstanceSummary** (interface)
1310
+ ```typescript
1311
+ interface WidgetInstanceSummary {
1312
+ id: string
1313
+ name: string
1314
+ type?: string
1315
+ [key: string]: any
1316
+ }
1317
+ ```
1318
+
1286
1319
  ### appManifest
1287
1320
 
1288
1321
  **AppBundle** (interface)
@@ -1324,6 +1357,16 @@ interface AppWidgetComponent {
1324
1357
  }
1325
1358
  ```
1326
1359
 
1360
+ **AppManifestWidgets** (interface)
1361
+ ```typescript
1362
+ interface AppManifestWidgets {
1363
+ files: AppManifestFiles;
1364
+ components: AppWidgetComponent[];
1365
+ instanceResolution?: boolean;
1366
+ instanceParam?: string;
1367
+ }
1368
+ ```
1369
+
1327
1370
  **AppContainerComponent** (interface)
1328
1371
  ```typescript
1329
1372
  interface AppContainerComponent {
@@ -1525,10 +1568,7 @@ interface AppManifest {
1525
1568
  * (setup questions, import schema, tunable fields, metrics definitions).
1526
1569
  * Absent when the app has no admin UI.
1527
1570
  admin?: string;
1528
- widgets?: {
1529
- files: AppManifestFiles;
1530
- components: AppWidgetComponent[];
1531
- };
1571
+ widgets?: AppManifestWidgets;
1532
1572
  containers?: {
1533
1573
  files: AppManifestFiles;
1534
1574
  components: AppContainerComponent[];
@@ -6016,6 +6056,9 @@ Get related threads and records for a case (admin only) GET /cases/:caseId/relat
6016
6056
  **getConfig**(opts: AppConfigOptions) → `Promise<any>`
6017
6057
  Get app configuration for a collection/product scope. ```typescript const config = await appConfiguration.getConfig({ appId: 'warranty-portal', collectionId: 'my-collection' }); ```
6018
6058
 
6059
+ **listWidgetInstances**(opts: Omit<GetWidgetInstanceOptions, 'widgetId'>) → `Promise<WidgetInstanceSummary[]>`
6060
+ List configured widget instances for an app. Useful for picker UIs, setup schemas, and widget-to-widget references. ```typescript const widgets = await appConfiguration.listWidgetInstances({ collectionId: 'my-collection', appId: 'widget-toolkit' }) ```
6061
+
6019
6062
  **setConfig**(opts: AppConfigOptions) → `Promise<any>`
6020
6063
  Set app configuration for a collection/product scope. Requires admin authentication. ```typescript await appConfiguration.setConfig({ appId: 'warranty-portal', collectionId: 'my-collection', admin: true, config: { warrantyPeriod: 24, supportEmail: 'support@example.com' } }); ```
6021
6064
 
@@ -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
@@ -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
  ---
@@ -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/dist/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:
@@ -9,3 +9,27 @@ export interface AppConfigurationResponse {
9
9
  /** Key-value pairs representing configuration settings */
10
10
  settings?: Record<string, any>;
11
11
  }
12
+ /** Options for resolving a configured widget instance from app config. */
13
+ export interface GetWidgetInstanceOptions {
14
+ appId: string;
15
+ collectionId?: string;
16
+ productId?: string;
17
+ variantId?: string;
18
+ batchId?: string;
19
+ admin?: boolean;
20
+ widgetId: string;
21
+ }
22
+ /** A configured widget instance stored in `appConfig.widgets`. */
23
+ export interface WidgetInstance<TWidget = any> {
24
+ id: string;
25
+ name?: string;
26
+ widget?: TWidget;
27
+ [key: string]: any;
28
+ }
29
+ /** Minimal summary shape for picker UIs and instance selectors. */
30
+ export interface WidgetInstanceSummary {
31
+ id: string;
32
+ name: string;
33
+ type?: string;
34
+ [key: string]: any;
35
+ }
@@ -51,6 +51,15 @@ export interface AppWidgetComponent {
51
51
  /** JSON-Schema-style settings the widget accepts */
52
52
  settings?: Record<string, any>;
53
53
  }
54
+ /** Widget bundle declaration in `app.manifest.json`. */
55
+ export interface AppManifestWidgets {
56
+ files: AppManifestFiles;
57
+ components: AppWidgetComponent[];
58
+ /** Whether this app supports resolving configured widget instances by ID. */
59
+ instanceResolution?: boolean;
60
+ /** Query/hash parameter name used for instance resolution. Defaults to `widgetId`. */
61
+ instanceParam?: string;
62
+ }
54
63
  /** A single container component defined in the manifest */
55
64
  export interface AppContainerComponent {
56
65
  name: string;
@@ -301,10 +310,7 @@ export interface AppManifest {
301
310
  */
302
311
  admin?: string;
303
312
  /** Widget bundle definition. Presence means a widget bundle exists for this app. */
304
- widgets?: {
305
- files: AppManifestFiles;
306
- components: AppWidgetComponent[];
307
- };
313
+ widgets?: AppManifestWidgets;
308
314
  /** Container bundle definition. Presence means a container bundle exists. */
309
315
  containers?: {
310
316
  files: AppManifestFiles;
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.8.4 | Generated: 2026-03-14T10:47:38.203Z
3
+ Version: 1.8.5 | Generated: 2026-03-14T14:43:55.897Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -1283,6 +1283,39 @@ interface AppConfigurationResponse {
1283
1283
  }
1284
1284
  ```
1285
1285
 
1286
+ **GetWidgetInstanceOptions** (interface)
1287
+ ```typescript
1288
+ interface GetWidgetInstanceOptions {
1289
+ appId: string
1290
+ collectionId?: string
1291
+ productId?: string
1292
+ variantId?: string
1293
+ batchId?: string
1294
+ admin?: boolean
1295
+ widgetId: string
1296
+ }
1297
+ ```
1298
+
1299
+ **WidgetInstance<TWidget = any>** (interface)
1300
+ ```typescript
1301
+ interface WidgetInstance<TWidget = any> {
1302
+ id: string
1303
+ name?: string
1304
+ widget?: TWidget
1305
+ [key: string]: any
1306
+ }
1307
+ ```
1308
+
1309
+ **WidgetInstanceSummary** (interface)
1310
+ ```typescript
1311
+ interface WidgetInstanceSummary {
1312
+ id: string
1313
+ name: string
1314
+ type?: string
1315
+ [key: string]: any
1316
+ }
1317
+ ```
1318
+
1286
1319
  ### appManifest
1287
1320
 
1288
1321
  **AppBundle** (interface)
@@ -1324,6 +1357,16 @@ interface AppWidgetComponent {
1324
1357
  }
1325
1358
  ```
1326
1359
 
1360
+ **AppManifestWidgets** (interface)
1361
+ ```typescript
1362
+ interface AppManifestWidgets {
1363
+ files: AppManifestFiles;
1364
+ components: AppWidgetComponent[];
1365
+ instanceResolution?: boolean;
1366
+ instanceParam?: string;
1367
+ }
1368
+ ```
1369
+
1327
1370
  **AppContainerComponent** (interface)
1328
1371
  ```typescript
1329
1372
  interface AppContainerComponent {
@@ -1525,10 +1568,7 @@ interface AppManifest {
1525
1568
  * (setup questions, import schema, tunable fields, metrics definitions).
1526
1569
  * Absent when the app has no admin UI.
1527
1570
  admin?: string;
1528
- widgets?: {
1529
- files: AppManifestFiles;
1530
- components: AppWidgetComponent[];
1531
- };
1571
+ widgets?: AppManifestWidgets;
1532
1572
  containers?: {
1533
1573
  files: AppManifestFiles;
1534
1574
  components: AppContainerComponent[];
@@ -6016,6 +6056,9 @@ Get related threads and records for a case (admin only) GET /cases/:caseId/relat
6016
6056
  **getConfig**(opts: AppConfigOptions) → `Promise<any>`
6017
6057
  Get app configuration for a collection/product scope. ```typescript const config = await appConfiguration.getConfig({ appId: 'warranty-portal', collectionId: 'my-collection' }); ```
6018
6058
 
6059
+ **listWidgetInstances**(opts: Omit<GetWidgetInstanceOptions, 'widgetId'>) → `Promise<WidgetInstanceSummary[]>`
6060
+ List configured widget instances for an app. Useful for picker UIs, setup schemas, and widget-to-widget references. ```typescript const widgets = await appConfiguration.listWidgetInstances({ collectionId: 'my-collection', appId: 'widget-toolkit' }) ```
6061
+
6019
6062
  **setConfig**(opts: AppConfigOptions) → `Promise<any>`
6020
6063
  Set app configuration for a collection/product scope. Requires admin authentication. ```typescript await appConfiguration.setConfig({ appId: 'warranty-portal', collectionId: 'my-collection', admin: true, config: { warrantyPeriod: 24, supportEmail: 'support@example.com' } }); ```
6021
6064
 
@@ -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/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.5",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",