@proveanything/smartlinks 1.6.7 → 1.7.0

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.
@@ -0,0 +1,430 @@
1
+ # App Configuration Files: `app.manifest.json` & `app.admin.json`
2
+
3
+ Every SmartLinks app ships with two JSON configuration files that the platform reads to understand what the app is and how to configure it. They have clearly separated responsibilities:
4
+
5
+ | File | Role | Loaded by |
6
+ |------|------|-----------|
7
+ | `app.manifest.json` | **Definitional** — what the app *is*: its bundles, components, static routes | Platform on every page load; portals; AI orchestrators |
8
+ | `app.admin.json` | **Operational** — how to *set up and tune* the app: setup questions, import schemas, tunable fields, metrics | Admin UI, AI-assisted setup flows |
9
+
10
+ The manifest always references the admin config via its `admin` field. Consumers that only need to *render* the app work entirely from the manifest. Only admin/setup flows need to fetch `app.admin.json`.
11
+
12
+ ```text
13
+ ┌─────────────────────────────────────────────────────────────────┐
14
+ │ Platform boot sequence │
15
+ │ │
16
+ │ 1. GET /collection/:id/widgets │
17
+ │ └─→ CollectionWidgetsResponse { apps: [...] } │
18
+ │ each app has: manifest, widget bundle, container │
19
+ │ │
20
+ │ 2. manifest.admin ──→ "app.admin.json" (pointer only) │
21
+ │ │
22
+ │ 3. Admin UI fetches app.admin.json when setup/config needed │
23
+ └─────────────────────────────────────────────────────────────────┘
24
+ ```
25
+
26
+ ---
27
+
28
+ ## `app.manifest.json`
29
+
30
+ The manifest is loaded automatically by the platform for every collection page. Keep it lean — it is fetched on every widget render.
31
+
32
+ ### Full Schema
33
+
34
+ ```json
35
+ {
36
+ "$schema": "https://smartlinks.app/schemas/app-manifest-v1.json",
37
+
38
+ "meta": {
39
+ "appId": "my-app",
40
+ "name": "My App",
41
+ "description": "A short human-readable description of what this app does.",
42
+ "version": "1.2.0",
43
+ "platformRevision": "2026-01-01"
44
+ },
45
+
46
+ "admin": "app.admin.json",
47
+
48
+ "widgets": {
49
+ "files": {
50
+ "js": {
51
+ "umd": "dist/widgets.umd.js",
52
+ "esm": "dist/widgets.es.js"
53
+ },
54
+ "css": "dist/widgets.css"
55
+ },
56
+ "components": [
57
+ {
58
+ "name": "SummaryWidget",
59
+ "description": "Compact summary card for use on product pages.",
60
+ "sizes": ["compact", "standard"],
61
+ "props": {
62
+ "required": ["collectionId", "appId"],
63
+ "optional": ["productId", "proofId"]
64
+ },
65
+ "settings": {
66
+ "showImage": { "type": "boolean", "default": true }
67
+ }
68
+ }
69
+ ]
70
+ },
71
+
72
+ "containers": {
73
+ "files": {
74
+ "js": {
75
+ "umd": "dist/containers.umd.js",
76
+ "esm": "dist/containers.es.js"
77
+ },
78
+ "css": "dist/containers.css"
79
+ },
80
+ "components": [
81
+ {
82
+ "name": "FullApp",
83
+ "description": "Full public app experience with internal routing.",
84
+ "props": {
85
+ "required": ["collectionId", "appId"],
86
+ "optional": ["productId", "proofId", "className"]
87
+ }
88
+ }
89
+ ]
90
+ },
91
+
92
+ "linkable": [
93
+ { "title": "Home", "path": "/" },
94
+ { "title": "Gallery", "path": "/gallery" },
95
+ { "title": "Settings", "path": "/settings", "params": { "tab": "advanced" } }
96
+ ]
97
+ }
98
+ ```
99
+
100
+ ### Field Reference
101
+
102
+ #### `meta`
103
+
104
+ | Field | Type | Required | Description |
105
+ |-------|------|----------|-------------|
106
+ | `appId` | string | ✅ | Unique identifier for the app (slug-style, e.g. `"warranty-tracker"`) |
107
+ | `name` | string | ✅ | Human-readable display name |
108
+ | `description` | string | ❌ | Short description shown in app directories and AI context |
109
+ | `version` | string | ✅ | SemVer string, e.g. `"1.2.0"` |
110
+ | `platformRevision` | string | ❌ | ISO date string marking the platform API revision this build targets |
111
+ | `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). |
112
+
113
+ #### `admin`
114
+
115
+ A relative path (from the app's public root) to the `app.admin.json` file. Omit entirely if the app has no admin UI.
116
+
117
+ ```json
118
+ "admin": "app.admin.json"
119
+ ```
120
+
121
+ #### `widgets`
122
+
123
+ Declares the widget bundle. Omit if the app has no widget component.
124
+
125
+ | Field | Description |
126
+ |-------|-------------|
127
+ | `files.js.umd` | UMD bundle path — used for dynamic `<script>` loading |
128
+ | `files.js.esm` | ESM bundle path — used for `import()` / native ES modules (optional but recommended) |
129
+ | `files.css` | CSS bundle path — omit if the widget ships no styles |
130
+ | `components[]` | One entry per exported widget component (see below) |
131
+
132
+ **Component fields:**
133
+
134
+ | Field | Type | Description |
135
+ |-------|------|-------------|
136
+ | `name` | string | Exported component name (must match the bundle export) |
137
+ | `description` | string | Human-readable description for portals and AI |
138
+ | `sizes` | string[] | Supported size hints: `"compact"`, `"standard"`, `"large"` |
139
+ | `props.required` | string[] | Props that must be provided for the component to render |
140
+ | `props.optional` | string[] | Props the component can use if provided |
141
+ | `settings` | object | JSON-Schema-style settings the widget accepts from its host |
142
+
143
+ #### `containers`
144
+
145
+ Same structure as `widgets` but declares the full-app container bundle. Lazy-loaded on demand.
146
+
147
+ See the [Containers guide](containers.md) for details on the container component model.
148
+
149
+ #### `linkable`
150
+
151
+ Static deep-linkable states built into the app — fixed routes that exist regardless of per-collection content. Declared once at build time.
152
+
153
+ See the [Deep Link Discovery guide](deep-link-discovery.md) for the full dual-source pattern (static manifest routes + dynamic `appConfig.linkable`).
154
+
155
+ | Field | Type | Required | Description |
156
+ |-------|------|----------|-------------|
157
+ | `title` | string | ✅ | Human-readable label shown in menus and offered to AI agents |
158
+ | `path` | string | ❌ | Hash route within the app (defaults to `"/"` if omitted) |
159
+ | `params` | object | ❌ | App-specific query params appended to the URL — do **not** include platform params (`collectionId`, `productId`, etc.) |
160
+
161
+ #### `executor`
162
+
163
+ Declares the executor bundle — a standalone JS library for programmatic configuration, server-side SEO, and LLM content generation. Omit if the app has no executor.
164
+
165
+ See the **[Executor Model guide](executor.md)** for the full build setup, SEO contract, LLM content contract, and implementation patterns.
166
+
167
+ | Field | Type | Description |
168
+ |-------|------|-------------|
169
+ | `files.js.umd` | string | UMD bundle path |
170
+ | `files.js.esm` | string | ESM bundle path |
171
+ | `factory` | string | Name of the factory function that creates an executor instance |
172
+ | `exports` | string[] | All named exports — tells consumers what's available without loading the bundle |
173
+ | `description` | string | Human-readable summary for AI orchestrators |
174
+ | `llmContent.function` | string | Name of the `getLLMContent` export |
175
+ | `llmContent.timeout` | number | Timeout in ms (default 500) |
176
+
177
+ ---
178
+
179
+ ## `app.admin.json`
180
+
181
+ Fetched only by the admin UI and AI-assisted setup flows — never loaded on the public-facing page. Keep setup logic and configuration schemas here, not in the manifest.
182
+
183
+ ### Full Schema
184
+
185
+ ```json
186
+ {
187
+ "$schema": "https://smartlinks.app/schemas/app-admin-v1.json",
188
+
189
+ "aiGuide": "ai-guide.md",
190
+
191
+ "setup": {
192
+ "description": "Configure the app for this collection.",
193
+ "questions": [
194
+ {
195
+ "id": "brandName",
196
+ "prompt": "What is your brand name?",
197
+ "type": "text",
198
+ "required": true
199
+ },
200
+ {
201
+ "id": "primaryColor",
202
+ "prompt": "Choose a primary theme colour.",
203
+ "type": "select",
204
+ "options": [
205
+ { "value": "blue", "label": "Blue" },
206
+ { "value": "green", "label": "Green" },
207
+ { "value": "red", "label": "Red" }
208
+ ]
209
+ },
210
+ {
211
+ "id": "welcomeEnabled",
212
+ "prompt": "Show a welcome message to first-time visitors?",
213
+ "type": "boolean",
214
+ "default": true
215
+ }
216
+ ],
217
+ "configSchema": {
218
+ "brandName": { "type": "string" },
219
+ "primaryColor": { "type": "string" },
220
+ "welcomeEnabled": { "type": "boolean" }
221
+ },
222
+ "saveWith": {
223
+ "method": "appConfiguration.setConfig",
224
+ "scope": "collection",
225
+ "admin": true,
226
+ "note": "Saved under the collection scope; readable by all app users."
227
+ },
228
+ "contentHints": {
229
+ "welcomeMessage": {
230
+ "aiGenerate": true,
231
+ "prompt": "Write a short, friendly welcome message for a brand called {{brandName}}."
232
+ }
233
+ }
234
+ },
235
+
236
+ "import": {
237
+ "description": "Bulk-import items via CSV.",
238
+ "scope": "collection",
239
+ "fields": [
240
+ { "name": "title", "type": "string", "required": true },
241
+ { "name": "description", "type": "string" },
242
+ { "name": "imageUrl", "type": "string" },
243
+ { "name": "price", "type": "number", "default": 0 }
244
+ ],
245
+ "csvExample": "title,description,imageUrl,price\nWidget A,Our first widget,https://example.com/img.jpg,9.99",
246
+ "saveWith": {
247
+ "method": "appObjects.createRecord",
248
+ "scope": "collection",
249
+ "admin": true
250
+ }
251
+ },
252
+
253
+ "tunable": {
254
+ "description": "Adjust display options after initial setup.",
255
+ "fields": [
256
+ {
257
+ "name": "displayMode",
258
+ "description": "How items are laid out on the page.",
259
+ "type": "select",
260
+ "options": ["grid", "list", "carousel"]
261
+ },
262
+ {
263
+ "name": "itemsPerPage",
264
+ "description": "Number of items shown per page.",
265
+ "type": "number"
266
+ }
267
+ ]
268
+ },
269
+
270
+ "metrics": {
271
+ "interactions": [
272
+ { "id": "view", "description": "User viewed an item." },
273
+ { "id": "click", "description": "User clicked a link or CTA." },
274
+ { "id": "purchase", "description": "User completed a purchase." }
275
+ ],
276
+ "kpis": [
277
+ { "name": "Click-through Rate", "compute": "click / view" },
278
+ { "name": "Conversion Rate", "compute": "purchase / view" }
279
+ ]
280
+ }
281
+ }
282
+ ```
283
+
284
+ ### Field Reference
285
+
286
+ #### `aiGuide`
287
+
288
+ Path (relative to the app's public root) to a Markdown file providing natural-language context for AI-assisted configuration. See the [AI Guide Template](ai-guide-template.md).
289
+
290
+ ```json
291
+ "aiGuide": "ai-guide.md"
292
+ ```
293
+
294
+ ---
295
+
296
+ #### `setup`
297
+
298
+ Drives the initial configuration wizard shown to admins when they first install the app for a collection.
299
+
300
+ | Field | Type | Description |
301
+ |-------|------|-------------|
302
+ | `description` | string | Intro text shown at the top of the setup wizard |
303
+ | `questions` | array | Ordered list of questions to ask the admin (see below) |
304
+ | `configSchema` | object | JSON-Schema-style shape of the resulting config object |
305
+ | `saveWith` | object | Which SDK method and scope to use when persisting answers |
306
+ | `contentHints` | object | Keys that AI should auto-generate based on question answers |
307
+
308
+ **`questions[]` fields:**
309
+
310
+ | Field | Type | Required | Description |
311
+ |-------|------|----------|-------------|
312
+ | `id` | string | ✅ | Key used in the saved config and in `contentHints` references |
313
+ | `prompt` | string | ✅ | Question text displayed to the admin |
314
+ | `type` | string | ✅ | Input type: `"text"`, `"number"`, `"boolean"`, `"select"`, `"multiselect"`, `"textarea"` |
315
+ | `required` | boolean | ❌ | Whether an answer is mandatory (default `false`) |
316
+ | `default` | any | ❌ | Pre-filled default value |
317
+ | `options` | array | ❌ | For `select`/`multiselect`: `[{ "value": "...", "label": "..." }]` |
318
+
319
+ **`saveWith` fields:**
320
+
321
+ | Field | Type | Description |
322
+ |-------|------|-------------|
323
+ | `method` | string | SDK method to call, e.g. `"appConfiguration.setConfig"` |
324
+ | `scope` | string | Data scope: `"collection"`, `"product"`, or `"proof"` |
325
+ | `admin` | boolean | Whether the save call requires admin credentials |
326
+ | `note` | string | Human-readable note explaining the save behaviour |
327
+
328
+ **`contentHints`:**
329
+
330
+ A map from content key to AI generation instructions. The AI setup flow uses this to pre-fill content fields after the admin answers setup questions.
331
+
332
+ ```json
333
+ "contentHints": {
334
+ "welcomeMessage": {
335
+ "aiGenerate": true,
336
+ "prompt": "Write a short welcome message for a brand called {{brandName}}."
337
+ }
338
+ }
339
+ ```
340
+
341
+ ---
342
+
343
+ #### `import`
344
+
345
+ Defines a CSV bulk-import flow available in the admin UI.
346
+
347
+ | Field | Type | Description |
348
+ |-------|------|-------------|
349
+ | `description` | string | Explains what will be imported and how |
350
+ | `scope` | string | Data scope for the imported records |
351
+ | `fields` | array | Column definitions — `name`, `type`, `required`, `default`, `description` |
352
+ | `csvExample` | string | A sample CSV string (shown as a download template) |
353
+ | `saveWith` | object | SDK method used to persist each imported row (same shape as `setup.saveWith`) |
354
+
355
+ ---
356
+
357
+ #### `tunable`
358
+
359
+ Post-setup display options that admins can tweak without re-running the full setup wizard.
360
+
361
+ | Field | Type | Description |
362
+ |-------|------|-------------|
363
+ | `description` | string | Explains what these settings control |
364
+ | `fields` | array | Tunable parameters — `name`, `description`, `type`, `options[]` |
365
+
366
+ ---
367
+
368
+ #### `metrics`
369
+
370
+ Declares what interactions and KPIs the app reports. Used by the platform's analytics dashboard.
371
+
372
+ | Field | Type | Description |
373
+ |-------|------|-------------|
374
+ | `interactions` | array | Interaction event types: `{ id, description }` |
375
+ | `kpis` | array | Derived metrics: `{ name, compute }` — `compute` is a simple expression over interaction IDs |
376
+
377
+ ---
378
+
379
+ ## Reading the Files at Runtime
380
+
381
+ ### Manifest — available from the widgets endpoint
382
+
383
+ ```typescript
384
+ import { appObjects } from '@proveanything/smartlinks';
385
+ import type { AppManifest, AppAdminConfig } from '@proveanything/smartlinks';
386
+
387
+ // The manifest arrives inline in the widgets response
388
+ const { apps } = await SL.collection.getWidgets(collectionId);
389
+ const { manifest, widget, container, admin: adminUrl } = apps[0];
390
+
391
+ // manifest.meta.name, manifest.linkable, manifest.widgets.components, …
392
+ ```
393
+
394
+ ### Admin config — fetch separately, only when needed
395
+
396
+ ```typescript
397
+ // adminUrl is the fully-resolved URL from CollectionAppWidget.admin
398
+ if (adminUrl) {
399
+ const adminConfig: AppAdminConfig = await fetch(adminUrl).then(r => r.json());
400
+ // adminConfig.setup.questions, adminConfig.tunable.fields, …
401
+ }
402
+ ```
403
+
404
+ ---
405
+
406
+ ## TypeScript Types
407
+
408
+ ```typescript
409
+ import type {
410
+ AppManifest, // app.manifest.json
411
+ AppAdminConfig, // app.admin.json
412
+ DeepLinkEntry, // one entry in manifest.linkable or appConfig.linkable
413
+ AppWidgetComponent, // one entry in manifest.widgets.components
414
+ AppContainerComponent,
415
+ AppManifestExecutor, // executor block in app.manifest.json
416
+ AppBundle, // { js, css, source?, styles? }
417
+ AppManifestFiles, // { js: { umd, esm? }, css? }
418
+ CollectionAppWidget, // one app in the /widgets response
419
+ CollectionWidgetsResponse,
420
+ // Executor types
421
+ ExecutorContext, // { collectionId, appId, SL }
422
+ SEOInput,
423
+ SEOResult,
424
+ LLMContentInput,
425
+ LLMContentResult,
426
+ LLMContentSection,
427
+ } from '@proveanything/smartlinks';
428
+ ```
429
+
430
+ All types live in `src/types/appManifest.ts`.