@proveanything/smartlinks 1.11.0 → 1.11.2
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/docs/API_SUMMARY.md +1 -1
- package/dist/docs/app-manifest.md +30 -22
- package/dist/docs/app-objects.md +17 -4
- package/dist/docs/mobile-admin-container.md +83 -20
- package/dist/docs/records-admin-pattern.md +305 -334
- package/dist/docs/ui-utils.md +257 -41
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/mobile-admin/errors.d.ts +65 -0
- package/dist/mobile-admin/errors.js +72 -0
- package/dist/mobile-admin/types.d.ts +199 -0
- package/dist/mobile-admin/types.js +1 -0
- package/docs/API_SUMMARY.md +1 -1
- package/docs/app-manifest.md +30 -22
- package/docs/app-objects.md +17 -4
- package/docs/mobile-admin-container.md +83 -20
- package/docs/records-admin-pattern.md +305 -334
- package/docs/ui-utils.md +257 -41
- package/package.json +1 -1
- package/docs/scanner-container.md +0 -556
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -103,8 +103,8 @@ The manifest is loaded automatically by the platform for every collection page.
|
|
|
103
103
|
{
|
|
104
104
|
"name": "WarehousePickContainer",
|
|
105
105
|
"description": "In-field operator admin surface.",
|
|
106
|
-
"
|
|
107
|
-
"
|
|
106
|
+
"capabilities": ["nfc", "qr"],
|
|
107
|
+
"offline": true
|
|
108
108
|
}
|
|
109
109
|
]
|
|
110
110
|
},
|
|
@@ -117,14 +117,18 @@ The manifest is loaded automatically by the platform for every collection page.
|
|
|
117
117
|
|
|
118
118
|
"records": {
|
|
119
119
|
"nutrition": {
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
120
|
+
"label": "Nutrition info",
|
|
121
|
+
"cardinality": "singleton",
|
|
122
|
+
"allowFacetRules": true,
|
|
123
|
+
"scopes": ["collection", "rule", "product", "facet", "batch"],
|
|
124
|
+
"defaultScope": "product"
|
|
123
125
|
},
|
|
124
126
|
"cooking_steps": {
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
127
|
+
"label": "Cooking steps",
|
|
128
|
+
"cardinality": "singleton",
|
|
129
|
+
"allowFacetRules": false,
|
|
130
|
+
"scopes": ["collection", "product"],
|
|
131
|
+
"defaultScope": "product"
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
}
|
|
@@ -236,8 +240,8 @@ See [mobile-admin-container.md](mobile-admin-container.md) for the `AdminMobileH
|
|
|
236
240
|
{
|
|
237
241
|
"name": "WarehousePickContainer",
|
|
238
242
|
"description": "Pick orders by scanning NFC tags",
|
|
239
|
-
"
|
|
240
|
-
"
|
|
243
|
+
"capabilities": ["nfc", "qr"],
|
|
244
|
+
"offline": true
|
|
241
245
|
}
|
|
242
246
|
]
|
|
243
247
|
}
|
|
@@ -250,8 +254,8 @@ See [mobile-admin-container.md](mobile-admin-container.md) for the `AdminMobileH
|
|
|
250
254
|
| `files.css` | CSS bundle path — set to `null` if no styles |
|
|
251
255
|
| `components[].name` | Exported component name (must match the UMD bundle export) |
|
|
252
256
|
| `components[].description` | Shown in the mobile launcher's app picker |
|
|
253
|
-
| `components[].
|
|
254
|
-
| `components[].
|
|
257
|
+
| `components[].capabilities` | Hardware capabilities this component needs or can use. See [capability list](mobile-admin-container.md#hardware-capabilities--the-capability-matrix). |
|
|
258
|
+
| `components[].offline` | Set to `true` if this component queues writes locally and needs offline sync support. |
|
|
255
259
|
#### `linkable`
|
|
256
260
|
|
|
257
261
|
Static deep-linkable states built into the app — fixed routes that exist regardless of per-collection content. Declared once at build time.
|
|
@@ -268,25 +272,29 @@ See the [Deep Link Discovery guide](deep-link-discovery.md) for the full dual-so
|
|
|
268
272
|
|
|
269
273
|
Declares which `app.records` record types the app stores, and which scopes each type supports. Required for any app that follows the [Records-Based Admin Pattern](records-admin-pattern.md). Omit if the app does not use scoped records.
|
|
270
274
|
|
|
271
|
-
The platform and the `<RecordsAdminShell>` from `@proveanything/
|
|
275
|
+
The platform and the `<RecordsAdminShell>` from `@proveanything/smartlinks-utils-ui` read this block to render the right scope tabs, rule editor, and cardinality-appropriate right pane.
|
|
272
276
|
|
|
273
277
|
```json
|
|
274
278
|
"records": {
|
|
275
279
|
"<recordType>": {
|
|
276
|
-
"
|
|
277
|
-
"
|
|
278
|
-
"
|
|
280
|
+
"label": "Human-readable label",
|
|
281
|
+
"cardinality": "singleton",
|
|
282
|
+
"allowFacetRules": false,
|
|
283
|
+
"scopes": ["collection", "product", "variant", "batch", "facet"],
|
|
284
|
+
"defaultScope": "product"
|
|
279
285
|
}
|
|
280
286
|
}
|
|
281
287
|
```
|
|
282
288
|
|
|
283
|
-
| Field
|
|
284
|
-
|
|
285
|
-
| `
|
|
286
|
-
| `
|
|
287
|
-
| `
|
|
289
|
+
| Field | Type | Default | Description |
|
|
290
|
+
|-------------------|----------|---------|-------------|
|
|
291
|
+
| `label` | string | — | Human-readable label for the record type, used in headings and tabs. |
|
|
292
|
+
| `cardinality` | string | `'singleton'` | `'singleton'` — one record wins per scope (e.g. ingredients, nutrition). `'collection'` — every matching record is returned in resolution order (e.g. FAQs, recipes). Drives which hook to use on the public side (`useResolvedRecord` vs `useCollectedRecords`) and how the shell lays out the right pane. |
|
|
293
|
+
| `allowFacetRules` | boolean | `false` | When `true`, the shell renders a **Rule** scope tab and embeds `<FacetRuleEditor>`. Add `'rule'` to `scopes` when setting this. |
|
|
294
|
+
| `scopes` | string[] | — | Allowed scope kinds in resolution order. Valid values: `"collection"`, `"product"`, `"variant"`, `"batch"`, `"facet"`, `"proof"`, `"rule"`. `'rule'` is a synthetic scope holding `facetRule`-targeted records. `'collection'` replaces the legacy empty-ref catch-all — **there is no `'global'` scope**. |
|
|
295
|
+
| `defaultScope` | string | — | The scope the "Create new" button targets in the admin shell. Must be one of the declared `scopes`. |
|
|
288
296
|
|
|
289
|
-
An app may declare multiple record types under different keys (e.g. `"nutrition"` and `"cooking_steps"`).
|
|
297
|
+
An app may declare multiple record types under different keys (e.g. `"nutrition"` and `"cooking_steps"`). See [records-admin-pattern.md](records-admin-pattern.md) for the full admin + public pattern.
|
|
290
298
|
|
|
291
299
|
#### `executor`
|
|
292
300
|
|
package/dist/docs/app-objects.md
CHANGED
|
@@ -495,7 +495,7 @@ The `ref` field is derived automatically from anchor fields when omitted:
|
|
|
495
495
|
```
|
|
496
496
|
productId: 'prod_abc' → ref: 'product:prod_abc'
|
|
497
497
|
productId: 'prod_abc', variantId: 'var_x' → ref: 'product:prod_abc/variant:var_x'
|
|
498
|
-
(no anchor fields) → ref: '' (
|
|
498
|
+
(no anchor fields) → ref: '' (collection-level catch-all)
|
|
499
499
|
facetRule: { ... } → ref: 'rule:<ulid>'
|
|
500
500
|
```
|
|
501
501
|
|
|
@@ -513,6 +513,20 @@ When multiple scoped records match a context, they are ordered by `specificity`.
|
|
|
513
513
|
| Per `anyOf` value | +1 |
|
|
514
514
|
| No anchors / no rule | 0 |
|
|
515
515
|
|
|
516
|
+
### Resolution order
|
|
517
|
+
|
|
518
|
+
When the public side of a records-based app needs "the data that applies to this product context", the platform walks a canonical chain from most-specific to least-specific:
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
proof → batch → variant → product → rule(*) → facet(*) → collection
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
- `rule(*)` — `facetRule`-targeted records are scored by **specificity** (number of clauses + number of constrained values). The most specific rule wins at its tier.
|
|
525
|
+
- `facet(*)` — legacy single-facet anchors, walked alphabetically. Prefer `facetRule` for new work.
|
|
526
|
+
- `collection` — the top of the chain and the catch-all for any record with no anchor fields. **There is no `'global'` tier above collection.**
|
|
527
|
+
|
|
528
|
+
For a **singleton** record type (one answer wins), use `useResolvedRecord` — it performs this walk server-side and returns the first match plus a `matchedAt` tag. For a **collection** record type (every match is shown), use `useCollectedRecords`. See [records-admin-pattern.md §2](records-admin-pattern.md#2-resolution-order-one-canonical-chain) for the full guide.
|
|
529
|
+
|
|
516
530
|
### Singleton Cardinality
|
|
517
531
|
|
|
518
532
|
By default, `create` always inserts a new row — calling it twice produces two records with identical anchor fields. **Singleton cardinality** changes that: pass `singletonPer` on creation and the server will **upsert** instead, ensuring at most one record of a given `recordType` exists per scope boundary.
|
|
@@ -592,12 +606,11 @@ for (const entry of data) {
|
|
|
592
606
|
case 'product': /* "Inherited from product" */ break;
|
|
593
607
|
case 'facet': /* "Tier-specific" */ break;
|
|
594
608
|
case 'collection': /* "Collection default" */ break;
|
|
595
|
-
case 'universal': /* "Default" */ break;
|
|
596
609
|
}
|
|
597
610
|
}
|
|
598
611
|
```
|
|
599
612
|
|
|
600
|
-
Precedence follows: `
|
|
613
|
+
Precedence follows: `proof > batch > variant > product > rule > facet > collection`. There is no scope above `collection` — a record with no anchor fields is a collection-level catch-all.
|
|
601
614
|
|
|
602
615
|
#### React — `useResolvedRecord`
|
|
603
616
|
|
|
@@ -806,7 +819,7 @@ Examples:
|
|
|
806
819
|
| `productId: 'prod_abc', variantId: 'var_500ml'` | `product:prod_abc/variant:var_500ml` |
|
|
807
820
|
| `batchId: 'batch_q1'` | `batch:batch_q1` |
|
|
808
821
|
| `facetRule: { ... }` | `rule:<ulid>` |
|
|
809
|
-
| *(no anchor fields)* | `''` (
|
|
822
|
+
| *(no anchor fields)* | `''` (collection-level catch-all) |
|
|
810
823
|
|
|
811
824
|
`parseRef` / `buildRef` in `data/refs.ts` should be used for **display and URL round-tripping only**, never as upsert keys. For ETL use cases, set an explicit `ref` using a stable external key (see [External ID / ETL Workflow](#external-id--etl-workflow)).
|
|
812
825
|
|
|
@@ -16,12 +16,13 @@ This document describes how to build a **Mobile Admin Container** — a SmartLin
|
|
|
16
16
|
4. [Hardware Capabilities & the Capability Matrix](#hardware-capabilities--the-capability-matrix)
|
|
17
17
|
5. [Capacitor Plugin Baseline](#capacitor-plugin-baseline)
|
|
18
18
|
6. [Manifest Declaration](#manifest-declaration)
|
|
19
|
-
7. [
|
|
20
|
-
8. [
|
|
21
|
-
9. [
|
|
22
|
-
10. [
|
|
23
|
-
11. [
|
|
24
|
-
12. [
|
|
19
|
+
7. [Launcher Discovery](#launcher-discovery)
|
|
20
|
+
8. [Event Stream](#event-stream)
|
|
21
|
+
9. [Error Handling](#error-handling)
|
|
22
|
+
10. [Lifecycle](#lifecycle)
|
|
23
|
+
11. [Build & Bundle Requirements](#build--bundle-requirements)
|
|
24
|
+
12. [Example: Minimal Mobile Admin Container](#example-minimal-mobile-admin-container)
|
|
25
|
+
13. [Best Practices](#best-practices)
|
|
25
26
|
|
|
26
27
|
---
|
|
27
28
|
|
|
@@ -57,6 +58,8 @@ Your container **never** detects the host directly. It receives a `host` prop fr
|
|
|
57
58
|
|
|
58
59
|
Every container mounted by the SmartLinks Mobile launcher receives a single `host` prop — `AdminMobileHostContext`. Do not reach for `window.SmartlinksScanner` or `window.Capacitor` directly; both are wrapped here.
|
|
59
60
|
|
|
61
|
+
> **SDK export** — `AdminMobileHostContext`, `AdminMobileCapability`, `AdminMobileHostId`, `AdminMobileEvent`, `ScannerEventSubscriber`, `MobileAdminComponentManifest`, and `MobileAdminBundleManifest` are all exported from `@proveanything/smartlinks`. Import via `import type { AdminMobileHostContext } from '@proveanything/smartlinks'` — no local mirror needed.
|
|
62
|
+
|
|
60
63
|
```typescript
|
|
61
64
|
interface AdminMobileHostContext {
|
|
62
65
|
// Identity
|
|
@@ -106,7 +109,8 @@ interface AdminMobileHostContext {
|
|
|
106
109
|
network: { isOnline: () => boolean }
|
|
107
110
|
device: { info: () => Promise<{ model: string; platform: string }> }
|
|
108
111
|
|
|
109
|
-
//
|
|
112
|
+
// Informational host version — use for logging/diagnostics, not feature detection.
|
|
113
|
+
// For feature detection prefer existence checks: 'requestNfcTap' in host.actions
|
|
110
114
|
_version: number
|
|
111
115
|
}
|
|
112
116
|
```
|
|
@@ -114,7 +118,15 @@ interface AdminMobileHostContext {
|
|
|
114
118
|
### Contract rules
|
|
115
119
|
|
|
116
120
|
1. **Check `host.hardware.X` before calling `host.actions.X`.** Calls to unavailable capabilities reject with `HostCapabilityUnavailableError`.
|
|
117
|
-
2. **Do not call `initializeApi
|
|
121
|
+
2. **Do not call `initializeApi`, and do not use top-level SDK imports for API calls.** `host.SL` is already configured with the right `baseURL`, auth, and logger. Code that does `import * as SL from "@proveanything/smartlinks"` and calls `SL.attestation.create(...)` will silently hit the wrong base URL — the top-level import is uninitialised in the launcher's runtime and produces no loud error. Always go through `host.SL`:
|
|
122
|
+
```typescript
|
|
123
|
+
// ❌ Silent footgun — uninitialised SDK, wrong baseURL
|
|
124
|
+
import * as SL from '@proveanything/smartlinks'
|
|
125
|
+
await SL.attestation.create(...)
|
|
126
|
+
|
|
127
|
+
// ✅ Correct
|
|
128
|
+
await host.SL.attestation.create(...)
|
|
129
|
+
```
|
|
118
130
|
3. **Do not access `window.SmartlinksScanner` or `window.Capacitor` directly.** The host wraps both; direct access produces containers that only work on one shell.
|
|
119
131
|
4. **Externalise all shared deps** (React, ReactDOM, SL, Radix, etc.) — the launcher provides them as globals. See [Build & Bundle Requirements](#build--bundle-requirements).
|
|
120
132
|
|
|
@@ -129,6 +141,36 @@ if (host.hardware.nfc) {
|
|
|
129
141
|
}
|
|
130
142
|
```
|
|
131
143
|
|
|
144
|
+
### `host.ui` — native helpers vs. your own components
|
|
145
|
+
|
|
146
|
+
`host.ui.setHeaderTitle()` and `host.ui.navigateBack()` are **host-only** — there is no in-container equivalent. Call them via `host.ui` or omit them.
|
|
147
|
+
|
|
148
|
+
`host.ui.toast()` and `host.ui.haptic()` are **optional conveniences**. Use them when you want native system feedback. When rendering in Storybook, unit tests, or a plain browser tab, your own `<Toaster />` is a perfectly valid substitute — you do not need to stub the entire `host.ui` surface just to get toast notifications.
|
|
149
|
+
|
|
150
|
+
**Stub pattern for testing and Storybook:**
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const stubHost: Partial<AdminMobileHostContext> = {
|
|
154
|
+
hardware: { nfc: false, rfid: false, qr: true, camera: true, keyboard: false },
|
|
155
|
+
actions: {
|
|
156
|
+
requestQrScan: async () => 'STUB_QR_CODE',
|
|
157
|
+
requestNfcTap: async () => ({ uid: 'STUB_UID' }),
|
|
158
|
+
requestCameraPhoto: async () => new Blob(),
|
|
159
|
+
share: async () => {},
|
|
160
|
+
clipboard: { read: async () => '', write: async () => {} },
|
|
161
|
+
},
|
|
162
|
+
ui: {
|
|
163
|
+
toast: (opts) => console.log('[stub toast]', opts.title),
|
|
164
|
+
haptic: () => {},
|
|
165
|
+
setHeaderTitle: (t) => { if (t) document.title = t },
|
|
166
|
+
navigateBack: () => history.back(),
|
|
167
|
+
},
|
|
168
|
+
network: { isOnline: () => true },
|
|
169
|
+
user: { isAdmin: true, displayName: 'Test User', email: 'test@example.com' },
|
|
170
|
+
SL: undefined as any, // replace with your test SL instance
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
132
174
|
---
|
|
133
175
|
|
|
134
176
|
## Hardware Capabilities & the Capability Matrix
|
|
@@ -148,7 +190,6 @@ Containers declare which capabilities they need (or can use) in the manifest `ca
|
|
|
148
190
|
| `"camera"` | Photo capture, gallery picker |
|
|
149
191
|
| `"keyboard"` | Physical hardware trigger/action buttons |
|
|
150
192
|
| `"geolocation"` | GPS coordinates |
|
|
151
|
-
| `"offline-queue"` | Container needs local write queuing + sync |
|
|
152
193
|
| `"push"` | Remote push notifications (FCM/APNs) |
|
|
153
194
|
|
|
154
195
|
The full hardware capability matrix by host:
|
|
@@ -196,7 +237,7 @@ The custom Kotlin shell and both Capacitor shells ship the same baseline plugin
|
|
|
196
237
|
| `@capgo/capacitor-nfc` | `"nfc"` |
|
|
197
238
|
| `@capacitor/geolocation` | `"geolocation"` |
|
|
198
239
|
| `@capacitor/push-notifications` | `"push"` |
|
|
199
|
-
| `@capacitor/filesystem` | *(
|
|
240
|
+
| `@capacitor/filesystem` | *(used when `offline: true`; bundle it in)* |
|
|
200
241
|
|
|
201
242
|
### Tier 3 — custom-android only (not Capacitor)
|
|
202
243
|
|
|
@@ -231,8 +272,8 @@ Declare the bundle under the top-level `mobileAdmin` key in `app.manifest.json`.
|
|
|
231
272
|
{
|
|
232
273
|
"name": "WarehousePickContainer",
|
|
233
274
|
"description": "Pick orders by scanning NFC tags",
|
|
234
|
-
"
|
|
235
|
-
"
|
|
275
|
+
"capabilities": ["nfc", "qr"],
|
|
276
|
+
"offline": true
|
|
236
277
|
}
|
|
237
278
|
]
|
|
238
279
|
}
|
|
@@ -247,8 +288,29 @@ A `mobileAdmin` bundle can export multiple components targeting different operat
|
|
|
247
288
|
|-------|------|-------------|
|
|
248
289
|
| `name` | string | Exported component name (must match the UMD bundle export) |
|
|
249
290
|
| `description` | string | Shown in the mobile launcher's app picker |
|
|
250
|
-
| `
|
|
251
|
-
| `
|
|
291
|
+
| `capabilities` | string[] | Hardware capabilities this component needs or can use. See table above. |
|
|
292
|
+
| `offline` | boolean | Set to `true` if this component queues writes locally and needs offline sync support. |
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Launcher Discovery
|
|
297
|
+
|
|
298
|
+
The SmartLinks Mobile launcher loads your `mobileAdmin` bundle through the platform's app registry — you do not reference the manifest URL directly.
|
|
299
|
+
|
|
300
|
+
**How it works:**
|
|
301
|
+
|
|
302
|
+
1. A collection admin enables the app for a collection in the SmartLinks admin console.
|
|
303
|
+
2. On startup (and periodically), the launcher fetches `app.manifest.json` for every enabled app in that collection via the SmartLinks platform API. These requests are authenticated with the launcher's operator session.
|
|
304
|
+
3. The launcher reads `mobileAdmin.components[]`, evaluates each component's `capabilities` against the current device's hardware flags, and shows matching components in the app picker.
|
|
305
|
+
4. When the operator opens a component, the launcher resolves `mobileAdmin.files.js.umd` against the app's CDN base, fetches the bundle, and mounts the component with its `host` prop.
|
|
306
|
+
|
|
307
|
+
**File URL resolution:** `files.js.umd` / `files.js.esm` are paths relative to your app's CDN root (set when the app version is published). Provide relative paths — the launcher resolves them; do not hardcode absolute URLs.
|
|
308
|
+
|
|
309
|
+
**Auth:** Manifest and bundle fetches use the launcher's session token. Your container is already authenticated via `host.user` and `host.SL` — do not add additional auth headers.
|
|
310
|
+
|
|
311
|
+
**On load failure:** If the bundle returns a non-2xx response, the launcher shows a user-visible error and logs it — it does not crash the full launcher. If `app.manifest.json` itself cannot be fetched, the app is silently excluded from the picker for that session.
|
|
312
|
+
|
|
313
|
+
> To debug a missing or stale manifest, confirm in the SmartLinks admin console that the app version is published and that the `mobileAdmin` key is present in the uploaded manifest.
|
|
252
314
|
|
|
253
315
|
---
|
|
254
316
|
|
|
@@ -318,7 +380,7 @@ try {
|
|
|
318
380
|
|-------|------|------------|
|
|
319
381
|
| mount | User opens your container | Subscribe to events, start readers |
|
|
320
382
|
| unmount | User backs out / app backgrounded > 30s | Unsubscribe; persist in-flight state |
|
|
321
|
-
| `lifecycle: 'offline'` | Network lost | Switch to offline
|
|
383
|
+
| `lifecycle: 'offline'` | Network lost | Switch to offline mode; queue writes locally |
|
|
322
384
|
| `lifecycle: 'online'` | Network restored | Flush queued writes |
|
|
323
385
|
| `lifecycle: 'pause'` | App backgrounded | Pause readers to save battery |
|
|
324
386
|
| `lifecycle: 'resume'` | App foregrounded | Resubscribe; refresh stale data |
|
|
@@ -400,7 +462,7 @@ vite build --config vite.config.mobile-admin.ts
|
|
|
400
462
|
```typescript
|
|
401
463
|
// src/mobile-admin/WarehousePickContainer.tsx
|
|
402
464
|
import { useEffect, useState } from 'react'
|
|
403
|
-
import type { AdminMobileHostContext } from '
|
|
465
|
+
import type { AdminMobileHostContext } from '@proveanything/smartlinks'
|
|
404
466
|
|
|
405
467
|
interface Props {
|
|
406
468
|
host: AdminMobileHostContext
|
|
@@ -459,9 +521,10 @@ export const MOBILE_ADMIN_MANIFEST = {
|
|
|
459
521
|
|
|
460
522
|
- **Always check `host.hardware.X` before calling `host.actions.X`** — never assume a capability is available.
|
|
461
523
|
- **Always wrap action calls in try/catch** — handle `HostPermissionDeniedError`, `HostTimeoutError`, and `HostCapabilityUnavailableError`.
|
|
462
|
-
- **Use `host.ui.
|
|
524
|
+
- **Use `host.ui.setHeaderTitle` and `host.ui.navigateBack`** for header integration — these are host-only and have no in-container equivalent.
|
|
525
|
+
- **`host.ui.toast` and `host.ui.haptic` are optional** — use them for native feedback, or fall back to your own `<Toaster />` when testing in isolation.
|
|
463
526
|
- **Use `host.events.subscribe` for lifecycle events** — `'offline'`/`'online'`/`'pause'`/`'resume'` fire consistently on every host.
|
|
464
|
-
- **Never call `initializeApi
|
|
527
|
+
- **Never call `initializeApi`, never use top-level SDK imports for API calls** — `host.SL` is already configured. `SL.method()` instead of `host.SL.method()` silently uses the wrong baseURL.
|
|
465
528
|
- **Bundle Capacitor plugins in** (do not externalise) — so the component degrades gracefully on PWA/browser without crashing.
|
|
466
|
-
- **Declare `offline
|
|
467
|
-
- **Use `host.
|
|
529
|
+
- **Declare `offline: true`** on a component if it queues writes locally — this signals the launcher to provision offline sync support.
|
|
530
|
+
- **Use `'methodName' in host.actions`** to feature-detect new host capabilities rather than comparing `host._version`. The version is informational only.
|