@proveanything/smartlinks 2.0.6 → 2.0.9

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.
Files changed (177) hide show
  1. package/dist/api/ai.d.ts +1 -1
  2. package/dist/api/ai.js +1 -1
  3. package/dist/api/analytics.d.ts +1 -1
  4. package/dist/api/analytics.js +1 -1
  5. package/dist/api/appConfiguration.d.ts +3 -3
  6. package/dist/api/appConfiguration.js +3 -3
  7. package/dist/api/appObjects.d.ts +1 -1
  8. package/dist/api/appObjects.js +1 -1
  9. package/dist/api/asset.d.ts +1 -1
  10. package/dist/api/asset.js +2 -2
  11. package/dist/api/async.d.ts +1 -1
  12. package/dist/api/async.js +1 -1
  13. package/dist/api/attestation.d.ts +1 -1
  14. package/dist/api/attestation.js +1 -1
  15. package/dist/api/attestations.d.ts +1 -1
  16. package/dist/api/attestations.js +1 -1
  17. package/dist/api/auth.d.ts +2 -2
  18. package/dist/api/auth.js +2 -2
  19. package/dist/api/authKit.d.ts +1 -1
  20. package/dist/api/authKit.js +1 -1
  21. package/dist/api/batch.d.ts +1 -1
  22. package/dist/api/batch.js +1 -1
  23. package/dist/api/broadcasts.d.ts +2 -2
  24. package/dist/api/broadcasts.js +1 -1
  25. package/dist/api/claimSet.d.ts +1 -1
  26. package/dist/api/claimSet.js +1 -1
  27. package/dist/api/collection.d.ts +1 -1
  28. package/dist/api/collection.js +1 -1
  29. package/dist/api/comms.d.ts +15 -15
  30. package/dist/api/comms.js +1 -1
  31. package/dist/api/config.d.ts +1 -1
  32. package/dist/api/config.js +1 -1
  33. package/dist/api/contact.d.ts +1 -1
  34. package/dist/api/contact.js +1 -1
  35. package/dist/api/containers.d.ts +1 -1
  36. package/dist/api/containers.js +1 -1
  37. package/dist/api/crate.d.ts +1 -1
  38. package/dist/api/crate.js +1 -1
  39. package/dist/api/facets.d.ts +1 -1
  40. package/dist/api/facets.js +1 -1
  41. package/dist/api/form.js +1 -1
  42. package/dist/api/http.js +1 -1
  43. package/dist/api/index.d.ts +46 -46
  44. package/dist/api/index.js +46 -46
  45. package/dist/api/integrations.d.ts +1 -1
  46. package/dist/api/integrations.js +1 -1
  47. package/dist/api/interactions.d.ts +1 -1
  48. package/dist/api/interactions.js +1 -1
  49. package/dist/api/jobs.d.ts +1 -1
  50. package/dist/api/jobs.js +1 -1
  51. package/dist/api/journeys.d.ts +1 -1
  52. package/dist/api/journeys.js +1 -1
  53. package/dist/api/journeysAnalytics.d.ts +1 -1
  54. package/dist/api/journeysAnalytics.js +1 -1
  55. package/dist/api/location.d.ts +1 -1
  56. package/dist/api/location.js +1 -1
  57. package/dist/api/lots.d.ts +1 -1
  58. package/dist/api/lots.js +1 -1
  59. package/dist/api/loyalty.d.ts +1 -1
  60. package/dist/api/loyalty.js +1 -1
  61. package/dist/api/navigation.d.ts +1 -1
  62. package/dist/api/navigation.js +1 -1
  63. package/dist/api/nfc.d.ts +1 -1
  64. package/dist/api/nfc.js +1 -1
  65. package/dist/api/order.d.ts +1 -1
  66. package/dist/api/order.js +1 -1
  67. package/dist/api/product.d.ts +1 -1
  68. package/dist/api/product.js +1 -1
  69. package/dist/api/products.d.ts +1 -1
  70. package/dist/api/products.js +1 -1
  71. package/dist/api/proof.d.ts +1 -1
  72. package/dist/api/proof.js +1 -1
  73. package/dist/api/qr.d.ts +1 -1
  74. package/dist/api/qr.js +1 -1
  75. package/dist/api/realtime.d.ts +1 -1
  76. package/dist/api/realtime.js +1 -1
  77. package/dist/api/research.d.ts +1 -1
  78. package/dist/api/research.js +1 -1
  79. package/dist/api/secrets.d.ts +1 -1
  80. package/dist/api/secrets.js +1 -1
  81. package/dist/api/segments.d.ts +1 -1
  82. package/dist/api/segments.js +1 -1
  83. package/dist/api/sequence.js +1 -1
  84. package/dist/api/tags.d.ts +1 -1
  85. package/dist/api/tags.js +1 -1
  86. package/dist/api/template.d.ts +1 -1
  87. package/dist/api/template.js +1 -1
  88. package/dist/api/translations.d.ts +1 -1
  89. package/dist/api/translations.js +2 -2
  90. package/dist/api/variant.d.ts +1 -1
  91. package/dist/api/variant.js +1 -1
  92. package/dist/containers/types.d.ts +1 -1
  93. package/dist/docs/API_SUMMARY.md +7 -7
  94. package/dist/docs/ai.md +14 -520
  95. package/dist/docs/analytics.md +41 -2
  96. package/dist/docs/app-data-storage.md +0 -38
  97. package/dist/docs/app-manifest.md +104 -7
  98. package/dist/docs/app-objects.md +0 -148
  99. package/dist/docs/app-records-pattern.md +2 -2
  100. package/dist/docs/building-react-components.md +6 -14
  101. package/dist/docs/caching.md +20 -21
  102. package/dist/docs/container-tracking.md +2 -0
  103. package/dist/docs/containers.md +14 -66
  104. package/dist/docs/executor.md +4 -4
  105. package/dist/docs/host-dependency-contract.md +27 -0
  106. package/dist/docs/iframe-responder.md +308 -0
  107. package/dist/docs/item-context.md +0 -2
  108. package/dist/docs/manifests.md +3 -3
  109. package/dist/docs/mobile-admin-container.md +4 -4
  110. package/dist/docs/mpa.md +5 -5
  111. package/dist/docs/native-facade.md +1 -1
  112. package/dist/docs/overview.md +34 -15
  113. package/dist/docs/portal-back-button.md +2 -3
  114. package/dist/docs/widgets.md +11 -69
  115. package/dist/http.d.ts +24 -8
  116. package/dist/http.js +32 -14
  117. package/dist/iframe.d.ts +2 -2
  118. package/dist/iframe.js +1 -1
  119. package/dist/iframeResponder.d.ts +7 -1
  120. package/dist/iframeResponder.js +45 -4
  121. package/dist/index.d.ts +30 -27
  122. package/dist/index.js +10 -8
  123. package/dist/mobile-admin/errors.d.ts +1 -1
  124. package/dist/mobile-admin/types.d.ts +2 -2
  125. package/dist/openapi.yaml +12 -0
  126. package/dist/shared-dependencies.d.ts +37 -0
  127. package/dist/shared-dependencies.js +79 -0
  128. package/dist/testing/index.d.ts +1 -1
  129. package/dist/translationCache.d.ts +1 -1
  130. package/dist/types/appManifest.d.ts +23 -0
  131. package/dist/types/broadcasts.d.ts +1 -1
  132. package/dist/types/collection.d.ts +2 -2
  133. package/dist/types/comms.d.ts +5 -5
  134. package/dist/types/contact.d.ts +1 -1
  135. package/dist/types/facets.d.ts +1 -1
  136. package/dist/types/iframeResponder.d.ts +3 -3
  137. package/dist/types/index.d.ts +44 -44
  138. package/dist/types/index.js +44 -44
  139. package/dist/types/interaction.d.ts +1 -1
  140. package/dist/types/itemContext.d.ts +1 -1
  141. package/dist/types/journeysAnalytics.d.ts +1 -1
  142. package/dist/types/navigation.d.ts +1 -1
  143. package/dist/types/product.d.ts +1 -1
  144. package/dist/types/proof.d.ts +1 -1
  145. package/dist/types/segments.d.ts +1 -1
  146. package/dist/types/widgets.d.ts +2 -2
  147. package/dist/utils/conditions.d.ts +1 -1
  148. package/dist/utils/index.d.ts +3 -3
  149. package/dist/utils/index.js +3 -3
  150. package/dist/utils/paths.d.ts +4 -4
  151. package/docs/API_SUMMARY.md +7 -7
  152. package/docs/ai.md +14 -520
  153. package/docs/analytics.md +41 -2
  154. package/docs/app-data-storage.md +0 -38
  155. package/docs/app-manifest.md +104 -7
  156. package/docs/app-objects.md +0 -148
  157. package/docs/app-records-pattern.md +2 -2
  158. package/docs/building-react-components.md +6 -14
  159. package/docs/caching.md +20 -21
  160. package/docs/container-tracking.md +2 -0
  161. package/docs/containers.md +14 -66
  162. package/docs/executor.md +4 -4
  163. package/docs/host-dependency-contract.md +27 -0
  164. package/docs/iframe-responder.md +308 -0
  165. package/docs/item-context.md +0 -2
  166. package/docs/mobile-admin-container.md +4 -4
  167. package/docs/mpa.md +5 -5
  168. package/docs/native-facade.md +1 -1
  169. package/docs/overview.md +34 -15
  170. package/docs/portal-back-button.md +2 -3
  171. package/docs/widgets.md +11 -69
  172. package/openapi.yaml +12 -0
  173. package/package.json +17 -6
  174. package/scripts/doctor.mjs +171 -0
  175. package/docs/analytics-metadata-conventions.md +0 -88
  176. package/docs/iframe-streaming-parent-changes.md +0 -308
  177. package/docs/manifests.md +0 -204
@@ -468,3 +468,311 @@ import type {
468
468
  ### Portal back exits the app too early
469
469
  - Set `state.parentPath` on route changes for screens that should navigate "up" inside the app.
470
470
  - Make sure the receiving app listens for `smartlinks-navigate` if the SDK version in use does not already handle it.
471
+
472
+ ---
473
+
474
+ ## Streaming in a hand-rolled parent (without IframeResponder)
475
+
476
+ The following applies **only if you build your own parent proxy** instead of using the `IframeResponder` class above — which already handles AI streaming for you. It is the wire protocol for forwarding SSE/streaming responses to a child app in proxy mode.
477
+ ### Goal
478
+
479
+ Keep the existing architecture:
480
+
481
+ - local mode: child calls API directly
482
+ - iframe proxy mode: child never owns auth state and streams through the parent
483
+
484
+ This keeps user/session authority in the parent while making AI streaming behave like the rest of the SDK transport.
485
+
486
+ ### What changed
487
+
488
+ Previously, proxy mode only supported one-shot request/response messages:
489
+
490
+ - `_smartlinksProxyRequest`
491
+ - `_smartlinksProxyResponse`
492
+
493
+ Streaming now adds a second protocol for long-lived responses:
494
+
495
+ - `_smartlinksProxyStreamRequest`
496
+ - `_smartlinksProxyStream`
497
+ - `_smartlinksProxyStreamAbort`
498
+
499
+ ### New parent message handling
500
+
501
+ #### 1. Listen for stream requests
502
+
503
+ The iframe child may now send this message:
504
+
505
+ ```ts
506
+ {
507
+ _smartlinksProxyStreamRequest: true,
508
+ id: string,
509
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
510
+ path: string,
511
+ body?: any,
512
+ headers?: Record<string, string>
513
+ }
514
+ ```
515
+
516
+ Parent behavior:
517
+
518
+ - treat this like a proxied API request
519
+ - build the real API URL from your configured base URL plus `path`
520
+ - send the request using the parent's current auth/session context
521
+ - expect an SSE / streaming response body
522
+ - keep the request open until the stream ends or is aborted
523
+
524
+ #### 2. Forward stream lifecycle messages back to the child
525
+
526
+ The parent should send messages back to the iframe using this envelope:
527
+
528
+ ```ts
529
+ {
530
+ _smartlinksProxyStream: true,
531
+ id: string,
532
+ phase: 'open' | 'event' | 'end' | 'error',
533
+ data?: any,
534
+ error?: string,
535
+ status?: number
536
+ }
537
+ ```
538
+
539
+ Phases:
540
+
541
+ - `open`
542
+ - optional but recommended
543
+ - indicates the upstream streaming request was accepted and a body exists
544
+ - `event`
545
+ - contains one parsed JSON event from an SSE `data:` frame
546
+ - send one message per logical event payload
547
+ - `end`
548
+ - sent once when the stream finishes normally
549
+ - `error`
550
+ - sent if the upstream request fails before or during streaming
551
+
552
+ #### 3. Support abort from the child
553
+
554
+ The child may stop reading early and send:
555
+
556
+ ```ts
557
+ {
558
+ _smartlinksProxyStreamAbort: true,
559
+ id: string
560
+ }
561
+ ```
562
+
563
+ Parent behavior:
564
+
565
+ - look up the active stream by `id`
566
+ - abort the underlying fetch / reader
567
+ - clean up any local state for that stream
568
+ - do not keep streaming after abort
569
+
570
+ ### SSE forwarding rules
571
+
572
+ The upstream AI endpoints return SSE-like frames. The parent should:
573
+
574
+ - read the response body as a stream
575
+ - buffer text until line boundaries
576
+ - collect `data:` lines for a single event
577
+ - join multi-line `data:` payloads with `\n`
578
+ - ignore blank events
579
+ - stop on `data: [DONE]`
580
+ - JSON-parse each event payload
581
+ - forward parsed payloads to the iframe as `_smartlinksProxyStream` with `phase: 'event'`
582
+
583
+ Minimal parsing behavior:
584
+
585
+ 1. accumulate bytes into text
586
+ 2. split on `\r?\n`
587
+ 3. collect each `data:` line
588
+ 4. on blank line, finalize the event
589
+ 5. if payload is `[DONE]`, finish
590
+ 6. otherwise `JSON.parse(payload)` and forward
591
+
592
+ ### Auth and session expectations
593
+
594
+ The parent remains the source of truth for auth.
595
+
596
+ That means the parent stream handler should:
597
+
598
+ - use the same auth headers/token source as normal proxied requests
599
+ - not require the iframe to know the bearer token or API key
600
+ - naturally pick up the current logged-in user when the stream starts
601
+ - cancel active streams if your app invalidates session state on logout or account switch
602
+
603
+ In practice, the stream request should use the same header-building logic as your normal parent proxy transport.
604
+
605
+ ### Error handling expectations
606
+
607
+ If the upstream fetch returns a non-2xx status:
608
+
609
+ - try to read the JSON error body
610
+ - derive a useful message
611
+ - send one `_smartlinksProxyStream` message with `phase: 'error'`
612
+ - include `status` when available
613
+ - do not send `end` afterward
614
+
615
+ If the stream body is missing unexpectedly:
616
+
617
+ - send `phase: 'error'`
618
+
619
+ If JSON parsing fails for a single event chunk:
620
+
621
+ - safest behavior is to ignore that malformed chunk and continue
622
+
623
+ ### State the parent should keep
624
+
625
+ Track active streams in a map keyed by `id`:
626
+
627
+ ```ts
628
+ Map<string, AbortController>
629
+ ```
630
+
631
+ Recommended cleanup points:
632
+
633
+ - on normal stream end
634
+ - on error
635
+ - on child abort
636
+ - on iframe detach/unmount
637
+ - on parent auth reset/logout if you want all in-flight streams cancelled immediately
638
+
639
+ ### Parent implementation outline
640
+
641
+ ```ts
642
+ const activeStreams = new Map<string, AbortController>()
643
+
644
+ window.addEventListener('message', async (event) => {
645
+ const msg = event.data
646
+
647
+ if (msg?._smartlinksProxyStreamAbort && msg.id) {
648
+ activeStreams.get(msg.id)?.abort()
649
+ activeStreams.delete(msg.id)
650
+ return
651
+ }
652
+
653
+ if (msg?._smartlinksProxyStreamRequest && msg.id) {
654
+ const controller = new AbortController()
655
+ activeStreams.set(msg.id, controller)
656
+
657
+ try {
658
+ const response = await fetch(buildUrl(msg.path), {
659
+ method: msg.method,
660
+ headers: msg.headers,
661
+ body: msg.body ? JSON.stringify(msg.body) : undefined,
662
+ signal: controller.signal,
663
+ })
664
+
665
+ if (!response.ok || !response.body) {
666
+ postError(...)
667
+ return
668
+ }
669
+
670
+ postOpen(...)
671
+ await forwardSse(response.body, parsed => postEvent(...parsed))
672
+ postEnd(...)
673
+ } catch (err) {
674
+ if (err?.name !== 'AbortError') postError(...)
675
+ } finally {
676
+ activeStreams.delete(msg.id)
677
+ }
678
+ }
679
+ })
680
+ ```
681
+
682
+ ### Exact protocol summary
683
+
684
+ #### Child → parent
685
+
686
+ Standard stream request:
687
+
688
+ ```ts
689
+ {
690
+ _smartlinksProxyStreamRequest: true,
691
+ id,
692
+ method,
693
+ path,
694
+ body,
695
+ headers
696
+ }
697
+ ```
698
+
699
+ Abort request:
700
+
701
+ ```ts
702
+ {
703
+ _smartlinksProxyStreamAbort: true,
704
+ id
705
+ }
706
+ ```
707
+
708
+ #### Parent → child
709
+
710
+ Open:
711
+
712
+ ```ts
713
+ {
714
+ _smartlinksProxyStream: true,
715
+ id,
716
+ phase: 'open'
717
+ }
718
+ ```
719
+
720
+ Event:
721
+
722
+ ```ts
723
+ {
724
+ _smartlinksProxyStream: true,
725
+ id,
726
+ phase: 'event',
727
+ data: parsedJsonEvent
728
+ }
729
+ ```
730
+
731
+ End:
732
+
733
+ ```ts
734
+ {
735
+ _smartlinksProxyStream: true,
736
+ id,
737
+ phase: 'end'
738
+ }
739
+ ```
740
+
741
+ Error:
742
+
743
+ ```ts
744
+ {
745
+ _smartlinksProxyStream: true,
746
+ id,
747
+ phase: 'error',
748
+ error: 'message',
749
+ status?: number
750
+ }
751
+ ```
752
+
753
+ ### What does not change
754
+
755
+ These parts of the parent iframe integration stay the same:
756
+
757
+ - normal `_smartlinksProxyRequest` request/response flow
758
+ - upload proxy flow
759
+ - auth login/logout postMessage handling
760
+ - route/deep-link handling
761
+ - resize handling
762
+
763
+ This is an additive protocol, not a replacement.
764
+
765
+ ### Current SDK reference
766
+
767
+ The SDK implementation lives in:
768
+
769
+ - [src/http.ts](src/http.ts)
770
+ - [src/iframeResponder.ts](src/iframeResponder.ts)
771
+ - [src/types/iframeResponder.ts](src/types/iframeResponder.ts)
772
+ - [src/api/ai.ts](src/api/ai.ts)
773
+
774
+ ### Practical recommendation
775
+
776
+ If your parent already uses `IframeResponder`, prefer upgrading to the SDK version with these changes instead of re-implementing the protocol manually.
777
+
778
+ If your parent has a custom iframe bridge, implement exactly the three new message types above and reuse your existing auth/header logic from normal proxied requests.
@@ -1,7 +1,5 @@
1
1
  # Item Context (container prop)
2
2
 
3
- > **Copy this file into `node_modules/@proveanything/smartlinks/docs/item-context.md`** in the published SDK package.
4
-
5
3
  When the URL points at a specific item — either a **serial proof URL** or an
6
4
  **NFC tap** — the portal derives an `ItemContext` describing what it found
7
5
  and hands it to the container as the **`itemContext`** prop.
@@ -1,6 +1,6 @@
1
1
  # Mobile Admin Container SDK
2
2
 
3
- > **Version:** 1.0 · **Platform:** SmartLinks R4 · **Last updated:** 2026-04-30
3
+ > **Version:** 1.0 · **Platform:** SmartLinks R5 · **Last updated:** 2026-09-21
4
4
 
5
5
  This document describes how to build a **Mobile Admin Container** — a SmartLinks microapp that provides an in-the-field operator/admin surface optimised for mobile devices. These containers ship as a **separate `mobileAdmin` bundle** (not inside the `containers` bundle) so that Capacitor plugins, offline helpers, and operator-only code never reach the public consumer bundle.
6
6
 
@@ -271,7 +271,7 @@ Declare the bundle under the top-level `mobileAdmin` key in `app.manifest.json`.
271
271
  "meta": { "appId": "my-app", "name": "My App", "version": "1.0.0" },
272
272
 
273
273
  "containers": {
274
- "files": { "js": { "umd": "dist/containers.umd.js", "esm": "dist/containers.es.js" }, "css": "dist/containers.css" },
274
+ "files": { "js": { "umd": "dist/containers.umd.js", "esm": "dist/containers.esm.js" }, "css": "dist/containers.css" },
275
275
  "components": [
276
276
  { "name": "PublicContainer", "description": "Default consumer experience" }
277
277
  ]
@@ -281,7 +281,7 @@ Declare the bundle under the top-level `mobileAdmin` key in `app.manifest.json`.
281
281
  "files": {
282
282
  "js": {
283
283
  "umd": "dist/mobile-admin.umd.js",
284
- "esm": "dist/mobile-admin.es.js"
284
+ "esm": "dist/mobile-admin.esm.js"
285
285
  },
286
286
  "css": null
287
287
  },
@@ -416,7 +416,7 @@ The mobile admin bundle has its own Vite config: `vite.config.mobile-admin.ts`.
416
416
 
417
417
  ```
418
418
  dist/mobile-admin.umd.js
419
- dist/mobile-admin.es.js
419
+ dist/mobile-admin.esm.js
420
420
  dist/mobile-admin.css (if needed)
421
421
  ```
422
422
 
package/docs/mpa.md CHANGED
@@ -56,9 +56,9 @@ vite build
56
56
  | Step | Config / Script | Gate env var | Output |
57
57
  |------|----------------|-------------|--------|
58
58
  | 1 | `vite.config.ts` | Always runs | `index.html`, `admin.html`, `assets/*` |
59
- | 2 | `vite.config.widget.ts` | `VITE_ENABLE_WIDGETS=true` | `widgets.umd.js`, `widgets.es.js`, `widgets.css` |
60
- | 3 | `vite.config.container.ts` | `VITE_ENABLE_CONTAINERS=true` | `containers.umd.js`, `containers.es.js`, `containers.css` |
61
- | 4 | `vite.config.executor.ts` | `VITE_ENABLE_EXECUTOR!=false` | `executor.umd.js`, `executor.es.js` |
59
+ | 2 | `vite.config.widget.ts` | `VITE_ENABLE_WIDGETS=true` | `widgets.umd.js`, `widgets.esm.js`, `widgets.css` |
60
+ | 3 | `vite.config.container.ts` | `VITE_ENABLE_CONTAINERS=true` | `containers.umd.js`, `containers.esm.js`, `containers.css` |
61
+ | 4 | `vite.config.executor.ts` | `VITE_ENABLE_EXECUTOR!=false` | `executor.umd.js`, `executor.esm.js` |
62
62
  | 5 | `scripts/hash-bundles.mjs` | Always runs | Renames bundles with content hashes; patches `dist/app.manifest.json` |
63
63
 
64
64
  Steps 2–4 produce a harmless stub file when their gate env var is not set. Step 5 detects and skips stub files automatically.
@@ -107,7 +107,7 @@ dist/
107
107
  └── executor-[hash].es.js ← Executor bundle (ESM)
108
108
  ```
109
109
 
110
- > Widget and container CSS files are only present when the bundle ships custom styles. Most apps set `"css": null` in the manifest because they rely entirely on Tailwind/shadcn from the parent. See the [AI-Native App Manifests](manifests.md) guide for the CSS null warning.
110
+ > Widget and container CSS files are only present when the bundle ships custom styles. Most apps set `"css": null` in the manifest because they rely entirely on Tailwind/shadcn from the parent. See the [App Configuration Files](app-manifest.md) for the CSS null warning.
111
111
 
112
112
  ---
113
113
 
@@ -133,6 +133,6 @@ If the embedded app exposes nested screens, document its "up" navigation path wi
133
133
  | [Widgets](widgets.md) | Widget bundle: components, props, settings |
134
134
  | [Containers](containers.md) | Container bundle: full-app embeds |
135
135
  | [Executor Model](executor.md) | Executor bundle: SEO, LLM content, config mutations |
136
- | [AI-Native App Manifests](manifests.md) | How manifests wire all bundles together for AI discovery |
136
+ | [App Configuration Files](app-manifest.md) | Manifest + admin reference; widget settings schema; AI workflows |
137
137
  | [iframe Responder](iframe-responder.md) | Reading context params inside the iframe |
138
138
  | [Portal Back Button](portal-back-button.md) | Hierarchy-aware back navigation for embedded apps |
@@ -1,6 +1,6 @@
1
1
  # Native Capability Facade (`host.native` / `SL.native`)
2
2
 
3
- > **Version:** 1.12 · **Platform:** SmartLinks R4 · **Last updated:** 2026-04-30
3
+ > **Version:** 1.12 · **Platform:** SmartLinks R5 · **Last updated:** 2026-09-21
4
4
 
5
5
  The `NativeFacade` is a thin contract layer between microapps and the device capabilities available on the current host shell (Kotlin, Capacitor iOS/Android, PWA, or browser). It lets a microapp call `host.native.share.share({...})` without knowing whether it's running over `window.SmartlinksScanner`, a Capacitor plugin, or `navigator.share`.
6
6
 
package/docs/overview.md CHANGED
@@ -1,25 +1,21 @@
1
1
  # SmartLinks Microapp Development Guide
2
2
 
3
- > **Platform revision:** R5 · **SDK:** `@proveanything/smartlinks@^2` (current `latest`, 2.0.5) · React 19 · Vite 8 · Tailwind 4
4
- > **Last updated:** 2026-03-03
3
+ > **Platform revision:** R5 · **SDK:** `@proveanything/smartlinks@^2` · React 19 · Vite 8 · Router 7 · Tailwind 4 · TS 6.
4
+ > The canonical version/host stack lives in [host-dependency-contract.md](host-dependency-contract.md).
5
5
 
6
6
  ---
7
7
 
8
8
  ## What Is a SmartLinks Microapp?
9
9
 
10
- SmartLinks microapps are **modular, embeddable React applications** that extend the SmartLinks digital twin platform. They provide specialised functionality — product information displays, warranty registration, competitions, pamphlet generators, and more — while inheriting context, authentication, and theming from the parent platform.
10
+ SmartLinks connects **physical products to digital experiences**: each item has a digital identity — a **proof** — that a consumer reaches by scanning a QR / NFC tag, then claims and enriches over time. A **microapp** is a focused, embeddable React experience a brand adds to that product's digital life: product info, warranty registration, authenticity checks, manuals, competitions, loyalty, post-purchase support, and more.
11
11
 
12
- ### The Digital Twin Ecosystem
12
+ Rather than baking every feature into the core platform, functionality is distributed across purpose-built apps, so a brand adds exactly the experiences its product needs. Each app:
13
13
 
14
- SmartLinks is a **digital twin platform** that connects physical products to digital experiences. Each physical item (a wine bottle, a luxury handbag, a piece of equipment) has a corresponding digital identity — a "proof" — that can be scanned, claimed, and enriched with data over time.
15
-
16
- Microapps are the **extensibility layer** of this ecosystem. Rather than building monolithic features into the core platform, functionality is distributed across purpose-built apps that:
17
-
18
- - **Embed seamlessly** via iframes in the SmartLinks Portal (public) and Admin Console (management)
19
- - **Share context** through URL parameters (collection, product, or proof being viewed)
20
- - **Inherit identity** from the parent platform's authentication system
21
- - **Adapt visually** to the brand's theme configuration
22
- - **Communicate bidirectionally** with the parent via postMessage for deep linking and navigation
14
+ - **Embeds** in the SmartLinks Portal (public) and Admin Console (management)
15
+ - **Shares context** through URL parameters (collection, product, proof)
16
+ - **Inherits identity** from the parent platform's auth
17
+ - **Adapts** to the brand's theme
18
+ - **Communicates** with the parent via postMessage for deep linking and navigation
23
19
 
24
20
  ### Deployment Modes
25
21
 
@@ -45,7 +41,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
45
41
 
46
42
  > Product endpoints: use `products` (plural) for new integrations. The older `product` (singular) namespace remains for backward compatibility and is deprecated.
47
43
 
48
- > **Minimum SDK version: `1.4.1`** — Ensure `@proveanything/smartlinks` is at least this version. If not, update with `npm install @proveanything/smartlinks@latest`.
44
+ > **SDK baseline: `@proveanything/smartlinks@^2.0`** (R5). Install/update with `npm install @proveanything/smartlinks@^2.0`. Individual features may need a higher minor — see the relevant doc. The single source of truth for the R5 host/version stack is [`host-dependency-contract.md`](host-dependency-contract.md).
49
45
 
50
46
  | Topic | File | When to Use |
51
47
  |-------|------|-------------|
@@ -71,7 +67,6 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
71
67
  | **Portal Back Button** | `docs/portal-back-button.md` | Hierarchy-aware "up" navigation inside embedded apps |
72
68
  | **Portal Request Action** | `docs/portal-request-action.md` | Triggering portal built-in actions (__qrScanner, __share, __logout, etc.) from sub-apps |
73
69
  | **Interactions** | `docs/interactions.md` | Business events, outcomes, voting, competitions, and journey triggers |
74
- | **AI-Native Manifests** | `docs/manifests.md` | `app.manifest.json`, `app.admin.json`, `ai-guide.md` structure |
75
70
  | **App Config Files** | `docs/app-manifest.md` | Full field-by-field reference for both JSON config files |
76
71
  | **Real-time Messaging** | `docs/realtime.md` | Adding Ably real-time features (chat, live updates) |
77
72
  | **Liquid Templates** | `docs/liquid-templates.md` | Dynamic content rendering with LiquidJS |
@@ -89,6 +84,21 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
89
84
  | **Proof Share Grants** | `docs/proof-share-grants.md` | Delegated, scoped, revocable bearer access to a single proof (read/comment/verify-owner) |
90
85
  | **Proof Ownership Transfer** | `docs/proof-ownership-transfer.md` | Moving a proof's single owner (directed transfer / open release), accept/cancel, and the state machine |
91
86
  | **appConfig / Feature Flags** | `docs/appConfig.md` | `appConfig` settings contract — installed apps, `system.features`/`entitledAppGroups`/`meters`, `isFeatureEnabled()` helper |
87
+ | **App Objects** | `docs/app-objects.md` | Queryable domain objects — `app.records` / `app.cases` / `app.threads`: JSONB zones, visibility, policies, aggregations |
88
+ | **App Data Storage** | `docs/app-data-storage.md` | Decision guide: pick between `appConfiguration` / `userAppData` / `app.records` |
89
+ | **Attestations** | `docs/attestations.md` | Postgres attestations API (proof-level user/admin data; replaces the legacy Firestore path) |
90
+ | **Caching** | `docs/caching.md` | SDK GET cache tiers + TTLs, `invalidateCache({ exact })`, and `force`/push guidance |
91
+ | **Comms & Broadcasts** | `docs/comms.md` | Transactional comms, campaigns, consent, push |
92
+ | **Container Tracking** | `docs/container-tracking.md` | Physical/logical **item** container groupings — *not* app containers (see containers.md) |
93
+ | **Lots** | `docs/lots.md` | Cross-SKU production "Lot" entity |
94
+ | **Loyalty** | `docs/loyalty.md` | Points, members, earning rules |
95
+ | **Sequences** | `docs/sequences.md` | Atomic monotonic number allocation (raffle / queue / edition) |
96
+ | **Integrations** | `docs/integrations.md` | Inbound/outbound external-system integration flows |
97
+ | **Item Context** | `docs/item-context.md` | The `itemContext` container prop (serial / NFC authenticity context) |
98
+ | **Native Facade** | `docs/native-facade.md` | `host.native` / `SL.native` device-capability facade |
99
+ | **Product Facets** | `docs/PRODUCT_FACETS_SDK.md` | Facet API reference (admin + public endpoints, types) |
100
+ | **AI Tools & Skills** | `docs/ai-tools-and-skills.md` | Platform AI capability registry (web research, extraction, images) |
101
+ | **Proof Comms Triggers** | `docs/proof-comms-triggers.md` | Transactional comms fired as a side-effect of proof actions |
92
102
 
93
103
  ---
94
104
 
@@ -99,6 +109,15 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
99
109
  - **Context via URL params** — All contextual data is passed through URL parameters
100
110
  - **SmartLinks NPM module** — All data access and platform interaction goes through `@proveanything/smartlinks`
101
111
  - **No standalone auth** — Authentication is handled by the parent SmartLinks platform
112
+
113
+ > **Use the host-provided `SL`, don't instantiate your own.** When your app runs in a
114
+ > container/iframe, the host injects an already-initialized, authenticated SDK instance (the
115
+ > `SL` prop / the externalized `@proveanything/smartlinks` singleton). Import and use *that*.
116
+ > Calling `initializeApi()` yourself to spin up a **second** SDK instance is a common source
117
+ > of bugs: it won't share the host's auth (so it looks signed-out), won't share the host's
118
+ > cache (duplicate requests, stale data), and in proxy mode it bypasses the host's request
119
+ > routing. Rule of thumb: **host-provided `SL` for anything the platform owns** (auth, config,
120
+ > data, cache); only app-owned state is yours.
102
121
  - **Multi-page build** — Separate bundles for public and admin — see `docs/mpa.md`
103
122
  - **Embedded back navigation** — Use `docs/portal-back-button.md` when a sub-app has a real content hierarchy
104
123
 
@@ -1,8 +1,7 @@
1
1
  # Portal Back Button — "Up" Navigation Inside Sub-Apps
2
2
 
3
- > **For sub-app authors.** Drop this file into the SmartLinks SDK docs (e.g.
4
- > `docs/portal-back-button.md`) and link it from `routing.md` / `mpa.md` so
5
- > microapp authors discover it.
3
+ > **For sub-app authors.** How to cooperate with the portal shell's top-level
4
+ > back/up control. See also [`mpa.md`](mpa.md).
6
5
 
7
6
  When a sub-app is embedded by a portal shell, the shell renders a top-level
8
7
  back button. By default that button **exits the app entirely** when tapped —
package/docs/widgets.md CHANGED
@@ -14,7 +14,7 @@ Widgets are self-contained React components that:
14
14
 
15
15
  ```text
16
16
  ┌─────────────────────────────────────────────────────────────────┐
17
- │ Parent SmartLinks Portal (React 18) │
17
+ │ Parent SmartLinks Portal (React 19) │
18
18
  │ │
19
19
  │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
20
20
  │ │ Competition │ │ Music App │ │ Warranty │ │
@@ -53,53 +53,7 @@ Widgets are typically single-view components and don't need internal routing. If
53
53
 
54
54
  ### The `useAppContext()` Pattern
55
55
 
56
- To write widgets that work identically in both modes, use this abstraction pattern:
57
-
58
- ```tsx
59
- // src/hooks/useAppContext.ts
60
- import { useContext, createContext, useMemo } from 'react';
61
- import { useSearchParams } from 'react-router-dom';
62
-
63
- export interface AppContextValue {
64
- collectionId: string;
65
- appId: string;
66
- productId?: string;
67
- proofId?: string;
68
- pageId?: string;
69
- lang?: string;
70
- user?: { id: string; email: string; name?: string };
71
- SL: typeof import('@proveanything/smartlinks');
72
- onNavigate?: (request: any) => void;
73
- }
74
-
75
- export const AppContext = createContext<AppContextValue | null>(null);
76
-
77
- /**
78
- * Returns app context regardless of rendering mode.
79
- * - Direct component mode: reads from AppContext (props)
80
- * - Iframe mode: reads from URL search params
81
- */
82
- export function useAppContext(): AppContextValue {
83
- const ctx = useContext(AppContext);
84
-
85
- // If context exists, we're in direct-component mode
86
- if (ctx) return ctx;
87
-
88
- // Otherwise, we're in iframe mode — read from URL params
89
- const [searchParams] = useSearchParams();
90
- const SL = (window as any).SL ?? require('@proveanything/smartlinks');
91
-
92
- return useMemo(() => ({
93
- collectionId: searchParams.get('collectionId') ?? '',
94
- appId: searchParams.get('appId') ?? '',
95
- productId: searchParams.get('productId') ?? undefined,
96
- proofId: searchParams.get('proofId') ?? undefined,
97
- pageId: searchParams.get('pageId') ?? undefined,
98
- lang: searchParams.get('lang') ?? undefined,
99
- SL,
100
- }), [searchParams, SL]);
101
- }
102
- ```
56
+ Widgets read their context through the shared **`useAppContext()`** hook so the same code works in both direct-component and iframe modes. The hook and `AppContext` provider are defined once — see **[building-react-components.md](building-react-components.md#the-useappcontext-pattern)** for the full implementation (don't re-define it per app).
103
57
 
104
58
  **Usage in your widget:**
105
59
 
@@ -344,7 +298,7 @@ Declare support in `app.manifest.json`:
344
298
  "files": {
345
299
  "js": {
346
300
  "umd": "dist/widgets.umd.js",
347
- "esm": "dist/widgets.es.js"
301
+ "esm": "dist/widgets.esm.js"
348
302
  },
349
303
  "css": null
350
304
  },
@@ -511,7 +465,7 @@ export { MyWidget } from './MyWidget';
511
465
  // Update the manifest
512
466
  export const WIDGET_MANIFEST = {
513
467
  version: '1.0.0',
514
- reactVersion: '18.x',
468
+ reactVersion: '19.x',
515
469
  widgets: [
516
470
  // ... existing widgets
517
471
  {
@@ -570,7 +524,7 @@ The project includes a separate Vite config for building widgets:
570
524
  # Build widgets only
571
525
  vite build --config vite.config.widget.ts
572
526
 
573
- # Output: dist/widgets.es.js
527
+ # Output: dist/widgets.esm.js
574
528
  ```
575
529
 
576
530
  ### Build Configuration
@@ -582,23 +536,11 @@ The widget build:
582
536
  - Minifies with esbuild for production
583
537
  - Outputs to `/dist` alongside the main app (not a separate folder)
584
538
 
585
- ### Externalized Dependencies (Peer Dependencies)
586
-
587
- The widget bundle does **not** include these libraries—the parent app must provide them:
539
+ ### Externalized dependencies
588
540
 
589
- | Package | Why Externalized |
590
- |---------|------------------|
591
- | `react`, `react-dom` | Parent's React context |
592
- | `@proveanything/smartlinks` | Passed via props as `SL` |
593
- | `@proveanything/smartlinks-auth-ui` | Auth UI components (also available globally as `window.SmartlinksAuthUI`) |
594
- | `tailwind-merge` | Utility for merging Tailwind classes |
595
- | `clsx` | Utility for conditional class names |
596
- | `class-variance-authority` | Utility for component variants |
541
+ The widget bundle does **not** include the host's shared libraries (React, the SDK, Radix, LiquidJS, …) — it **externalizes** them and resolves them from the host at runtime, so the bundle stays tiny and there's exactly one shared instance.
597
542
 
598
- These are standard packages that any modern React + Tailwind app will have. Externalizing them:
599
- 1. Reduces bundle size significantly
600
- 2. Removes JSDoc comments that inflate the bundle
601
- 3. Ensures consistent behavior with parent's versions
543
+ **The canonical, versioned list is the shared-dependency contract — don't hand-maintain your own here.** Import it from the SDK (`SHARED_DEPENDENCY_SPECIFIERS`) for your build's `external` list, and see **[host-dependency-contract.md](host-dependency-contract.md)** for the full table, the Vite `external`/`globals` config, and the *never bundle your own React* rule. `@proveanything/smartlinks-auth-ui` is externalized too (host global `window.SmartlinksAuthUI`) — do not ship a second copy.
602
544
 
603
545
  ### Enabling Widget Builds
604
546
 
@@ -626,7 +568,7 @@ import * as SL from '@proveanything/smartlinks';
626
568
 
627
569
  // Dynamic import from app's CDN
628
570
  const CompetitionWidget = lazy(() =>
629
- import('https://competition-app.example.com/widgets.es.js')
571
+ import('https://competition-app.example.com/widgets.esm.js')
630
572
  .then(m => ({ default: m.CompetitionWidget }))
631
573
  );
632
574
 
@@ -670,7 +612,7 @@ import { WidgetWrapper, CompetitionWidget } from 'competition-app/widgets';
670
612
  import { WIDGET_MANIFEST } from 'competition-app/widgets';
671
613
 
672
614
  // Verify React version compatibility
673
- if (!WIDGET_MANIFEST.reactVersion.startsWith('18')) {
615
+ if (!WIDGET_MANIFEST.reactVersion.startsWith('19')) {
674
616
  console.warn('Widget React version mismatch');
675
617
  }
676
618
 
@@ -741,7 +683,7 @@ Each app exports a `WIDGET_MANIFEST` for discovery:
741
683
  ```typescript
742
684
  export const WIDGET_MANIFEST = {
743
685
  version: '1.0.0', // Widget bundle version
744
- reactVersion: '18.x', // Required React version
686
+ reactVersion: '19.x', // Required React version
745
687
  widgets: [
746
688
  {
747
689
  name: 'ExampleWidget',
package/openapi.yaml CHANGED
@@ -17471,6 +17471,18 @@ components:
17471
17471
  type: string
17472
17472
  appId:
17473
17473
  type: string
17474
+ moduleFormat:
17475
+ type: string
17476
+ enum:
17477
+ - umd
17478
+ - esm
17479
+ - dual
17480
+ sharedDependencies:
17481
+ type: string
17482
+ globals:
17483
+ type: object
17484
+ additionalProperties:
17485
+ type: string
17474
17486
  seo:
17475
17487
  type: object
17476
17488
  additionalProperties: true