@proveanything/smartlinks 1.14.10 → 1.14.12

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,20 +1,36 @@
1
- # Portal Back Button
1
+ # Portal Back Button — "Up" Navigation Inside Sub-Apps
2
2
 
3
- > **For sub-app authors.** This guide explains how to make the portal's top back button behave like hierarchy-aware "up" navigation inside an embedded app.
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.
4
6
 
5
- When a SmartLinks app is embedded inside the portal shell, the shell can render a top-level back button. By default that button exits the app entirely. That is correct for flat screens, but wrong for apps with internal hierarchy such as lists, detail pages, wizards, or nested checkout flows.
7
+ When a sub-app is embedded by a portal shell, the shell renders a top-level
8
+ back button. By default that button **exits the app entirely** when tapped —
9
+ which is wrong for any app with internal hierarchy (lists → detail, wizards,
10
+ etc.).
6
11
 
7
- This document describes the contract for telling the portal where "up" goes from the current screen so the shell can keep the app mounted and route to the correct parent screen instead of leaving the embed.
12
+ This document describes the contract for telling the portal where "up" goes
13
+ from the current screen, so the top back button performs **hierarchy-aware
14
+ "up" navigation** inside your app instead.
8
15
 
9
- ## Why "up", not browser back
16
+ ---
10
17
 
11
- Browser back replays history. If a user navigates `list -> item A -> list -> item B`, browser back can yo-yo between detail and list screens in a way that feels wrong for an app chrome back button.
18
+ ## Why "up", not "back"
12
19
 
13
- "Up" navigates the content hierarchy instead: `item B -> list -> exit app`. It is single-direction and idempotent. Only your app's router knows that hierarchy, so the portal cannot infer it on its own.
20
+ Browser back replays history. If a user navigates **list item A list
21
+ item B**, browser back yo-yos: `B → list → A → list → ...`. That is not what
22
+ users expect from an "up arrow" in app chrome.
14
23
 
15
- ## The contract
24
+ "Up" navigates the **content hierarchy**: `B → list → exit app`. It is
25
+ single-direction and idempotent. Only your app's router knows the hierarchy,
26
+ so the portal can't infer it — your app must declare it.
16
27
 
17
- When your app posts `smartlinks-route-change` messages, include a `parentPath` field inside `state` for the current screen:
28
+ ---
29
+
30
+ ## The Contract
31
+
32
+ With every `smartlinks-route-change` your app already posts (via
33
+ `useDeepLinkSync` or directly), include a `parentPath` field inside `state`:
18
34
 
19
35
  ```ts
20
36
  window.parent.postMessage({
@@ -22,7 +38,8 @@ window.parent.postMessage({
22
38
  path: '/items/123',
23
39
  context: { collectionId: 'auction-2026', appId: 'auction' },
24
40
  state: {
25
- parentPath: '/items',
41
+ parentPath: '/items', // ← where "up" goes from /items/123
42
+ // ... your other app state
26
43
  },
27
44
  appId: 'auction',
28
45
  }, '*');
@@ -30,43 +47,68 @@ window.parent.postMessage({
30
47
 
31
48
  | `state.parentPath` value | Portal back-button behaviour |
32
49
  | --- | --- |
33
- | `'/items'` (or any string) | The portal posts `smartlinks-navigate` to the iframe with that path and keeps the app mounted. |
34
- | `''`, missing, or omitted | The portal falls back to the default behaviour and exits the app. |
50
+ | `'/items'` (or any string) | Posts `smartlinks-navigate` to the iframe with that path. App stays mounted. |
51
+ | `''` / missing / omitted | Default: clears the active app and returns to the portal homepage. |
52
+
53
+ When the portal sends the up-navigation message, your app must respond by
54
+ navigating to that path. If you use `useDeepLinkSync` with an SDK release
55
+ that includes the inbound `smartlinks-navigate` listener, this is automatic —
56
+ otherwise add a listener (see "Receiving the up-message" below).
35
57
 
36
- The portal only uses `parentPath` when it is present on the current route. Query-string changes, tab switches, and modal state should keep the same parent path.
58
+ ---
37
59
 
38
- ## Recommended pattern
60
+ ## Recommended sub-app pattern (React Router)
39
61
 
40
- Keep the hierarchy mapping close to your route definitions:
62
+ Keep "what's above this route" co-located with your route definitions:
41
63
 
42
64
  ```ts
65
+ // routes.ts
43
66
  const PARENTS: Record<string, (params: any) => string | null> = {
44
- '/': () => null,
45
- '/items': () => null,
46
- '/items/:id': () => '/items',
47
- '/items/:id/bid': ({ id }) => `/items/${id}`,
48
- '/checkout': () => '/items',
67
+ '/': () => null, // home — back exits the app
68
+ '/items': () => null, // list — back exits the app
69
+ '/items/:id': () => '/items', // detail — back goes to list
70
+ '/items/:id/bid': ({ id }) => `/items/${id}`,
71
+ '/checkout': () => '/items',
49
72
  };
50
73
 
51
74
  export function getParentPath(pathname: string): string | null {
52
- // Use your router's path matching here and return the parent route.
53
- return null;
75
+ // matchPath against PARENTS keys return parent or null
76
+ // (use react-router's matchPath in your real implementation)
54
77
  }
55
78
  ```
56
79
 
57
- Then emit `parentPath` whenever the route changes:
80
+ ```tsx
81
+ // App.tsx
82
+ import { useDeepLinkSync } from '@/hooks/useDeepLinkSync';
83
+ import { getParentPath } from './routes';
84
+
85
+ function App() {
86
+ useDeepLinkSync({
87
+ getParentPath: ({ pathname }) => getParentPath(pathname),
88
+ });
89
+ // ...
90
+ }
91
+ ```
92
+
93
+ If your app does not use `useDeepLinkSync`, post the message yourself:
58
94
 
59
95
  ```ts
60
- useDeepLinkSync({
61
- getParentPath: ({ pathname }) => getParentPath(pathname),
62
- });
96
+ useEffect(() => {
97
+ if (window.parent === window) return;
98
+ window.parent.postMessage({
99
+ type: 'smartlinks-route-change',
100
+ path: location.pathname,
101
+ context: { /* ... */ },
102
+ state: { parentPath: getParentPath(location.pathname) ?? '' },
103
+ }, '*');
104
+ }, [location.pathname]);
63
105
  ```
64
106
 
65
- If you do not use `useDeepLinkSync`, post the message yourself and include `parentPath` in `state`.
107
+ ---
66
108
 
67
- ## Receiving the up-message
109
+ ## Receiving the up-message (`smartlinks-navigate`)
68
110
 
69
- When the portal consumes `parentPath`, it posts `smartlinks-navigate` back to the iframe:
111
+ When the portal back button consumes a `parentPath`, it posts:
70
112
 
71
113
  ```ts
72
114
  {
@@ -78,18 +120,68 @@ When the portal consumes `parentPath`, it posts `smartlinks-navigate` back to th
78
120
  }
79
121
  ```
80
122
 
81
- Your app should listen for that message and route accordingly. If your SDK release already includes the inbound `smartlinks-navigate` listener, this can be automatic; otherwise add a small `message` listener and call your router manually.
123
+ Your app must listen for this and route accordingly. A minimal hook:
124
+
125
+ ```ts
126
+ import { useEffect } from 'react';
127
+ import { useNavigate } from 'react-router-dom';
128
+
129
+ export function useParentNavigation() {
130
+ const navigate = useNavigate();
131
+ useEffect(() => {
132
+ const onMessage = (e: MessageEvent) => {
133
+ if (e.data?.type !== 'smartlinks-navigate') return;
134
+ if (typeof e.data.path !== 'string') return;
135
+ navigate(e.data.path);
136
+ };
137
+ window.addEventListener('message', onMessage);
138
+ return () => window.removeEventListener('message', onMessage);
139
+ }, [navigate]);
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Reference: Updated `RouteChangeMessage`
146
+
147
+ `state` is an open `Record<string, string>` — `parentPath` is a recognised
148
+ optional convention; everything else flows through unchanged.
149
+
150
+ ```ts
151
+ interface RouteChangeMessage {
152
+ type: 'smartlinks-route-change';
153
+ path: string;
154
+ context: Record<string, string>;
155
+ state: Record<string, string> & {
156
+ /**
157
+ * Optional. The "up" target from the current screen. When present, the
158
+ * portal's top back button posts smartlinks-navigate with this path
159
+ * instead of exiting the app. Empty / missing → exits the app.
160
+ */
161
+ parentPath?: string;
162
+ };
163
+ appId?: string;
164
+ }
165
+ ```
82
166
 
83
- ## Reference
167
+ No breaking change: existing apps that don't emit `parentPath` keep today's
168
+ behaviour (one tap of the portal back exits the app).
84
169
 
85
- `state` remains a general `Record<string, string>`. `parentPath` is a recognised optional convention, not a breaking protocol change.
170
+ ---
86
171
 
87
172
  ## FAQ
88
173
 
89
- **Do I need to push every query-param change as a navigation step?** No. `parentPath` is hierarchy, not history.
174
+ **Q: Do I need to push every query-param change as a navigation step?**
175
+ No. `parentPath` is hierarchy, not history. A filter toggle, a tab switch,
176
+ or a modal open on the same screen should keep the same `parentPath`.
90
177
 
91
- **What if my up target leaves the app?** Set `parentPath` to `''` or omit it.
178
+ **Q: What if my "up" leaves the app?**
179
+ Set `parentPath` to `''` (or omit it). The portal will exit the app on back.
92
180
 
93
- **Can I disable the portal back button on certain screens?** Not via this protocol. Use the existing `homeScope: 'entry'` portal behaviour if you need the shell back button suppressed.
181
+ **Q: Can I disable the portal back button on certain screens?**
182
+ Not directly via this protocol. Use the existing portal `homeScope: 'entry'`
183
+ mechanism if you need the entire portal back button suppressed.
94
184
 
95
- **What about an in-app back button?** Keep it if you want. The portal back is shell chrome; an in-content back is part of the app UI.
185
+ **Q: What about the in-app back button I already render?**
186
+ Keep it if you want — they're not mutually exclusive. The portal back is
187
+ chrome; an in-content back is content. Many apps will only need one.
@@ -0,0 +1,450 @@
1
+ Skip to main content
2
+
3
+ Core Portal Framework [r4.7]
4
+
5
+
6
+
7
+
8
+ Code
9
+
10
+
11
+
12
+ Code
13
+
14
+ 19 May at 09:05
15
+ Okay, I have a question for you. I have ended up with a real mess of confusions about logins, and part of that is because not everyone is implementing the login UI in the same way. What I've got is a situation. I've built out the login auth UI toolset, and it's great that I can use it in different apps. Like we have different apps. We have an app, a mobile app, we have a, a, a Hub app, et cetera, ...
16
+
17
+
18
+ Thought for 29s
19
+ Yes — that's very sensible, and it's actually the cleanest way out of the mess. Let me reflect back what you're describing and flag the design decisions, then we can build it.
20
+
21
+ The core idea
22
+ Sub-apps stop owning auth UI. They only ever declare intent:
23
+
24
+ "I need the user to be authenticated to continue."
25
+
26
+ The portal owns the entire flow: open modal → run AuthKit → persist session → broadcast back down → restore the sub-app exactly where it was. The sub-app just re-renders and sees isAuthenticated: true.
27
+
28
+ This is the right call. The current model (every app importing smartlinks-auth-ui and calling login() itself) is exactly what causes the duplicate-React-context / desync bugs we already have a memory about.
29
+
30
+ Proposed contract
31
+ One new message from sub-apps:
32
+
33
+ window.parent.postMessage({
34
+ _smartlinksIframeMessage: true,
35
+ type: 'smartlinks:authkit:request-login',
36
+ payload: {
37
+ reason?: 'bid' | 'claim' | string, // optional, for analytics/copy
38
+ mode?: 'login' | 'signup' | 'either', // default 'either'
39
+ requireAuthenticated?: boolean, // if already logged in, resolve immediately
40
+ requestId: string, // so we can reply
41
+ },
42
+ }, '*');
43
+ One reply back:
44
+
45
+ { type: 'smartlinks:authkit:login-result',
46
+ payload: { requestId, status: 'success' | 'cancelled' | 'error', user?, token? } }
47
+ For container/widget apps (same React tree), expose the same thing as a hook so they don't need postMessage:
48
+
49
+ const { requestLogin } = useSafeAuth();
50
+ const result = await requestLogin({ reason: 'bid' });
51
+ if (result.status === 'success') { /* place bid */ }
52
+ State restoration — the important part
53
+ You're right that this is what makes or breaks the UX. Options, in increasing order of robustness:
54
+
55
+ Do nothing special — AuthKit flows that stay in-page (modal email/password, in-modal OTP) just work; the sub-app never unmounted, its URL/state is intact.
56
+ For redirect flows (magic link, WhatsApp deep-link, OAuth bounce) — portal snapshots the full URL (including the sub-app's hash/query) into sessionStorage keyed by requestId before redirecting, and restores it on return. The sub-app gets remounted at the exact same deep-link.
57
+ Resume callback — after restoration, portal re-posts the original request-login requestId to the sub-app as login-result: success so it can resume its specific action (e.g. auto-submit the bid the user was about to place).
58
+ I'd build all three; they're additive.
59
+
60
+ Config flexibility you mentioned
61
+ Per-collection (in layout config) or per-request:
62
+
63
+ allowSignup: boolean — hide the "create account" tab
64
+ signupProminence: 'primary' | 'secondary' | 'hidden' (we already have this type)
65
+ methods?: ('email'|'phone'|'oauth')[] — restrict
66
+ Copy override per reason (e.g. "Sign in to place a bid")
67
+ What we'd remove / deprecate
68
+ The current pattern where sub-apps import smartlinks-auth-ui and call login() themselves becomes the exception, only for genuinely custom auth (your bidder API example). Standard "I just need a logged-in user" goes through requestLogin.
69
+ The smartlinks:authkit:login / :logout broadcast messages stay (for the custom-flow case), but most apps stop using them.
70
+ Open questions before I build
71
+ Scope — implement on portal-framework side (modal + message handler + hook) and document the contract for sub-apps? Or do you also want me to update the two problem apps (auction, claim) in this repo? (I don't think they live here, but confirming.)
72
+ Redirect-flow state restoration — do any of your AuthKit methods currently redirect away from the page? (Magic link click-through, OAuth.) If yes, I'll wire the sessionStorage snapshot/restore. If everything is in-modal today, I'll stub it and we add it when needed.
73
+ requestLogin for container/widget apps — add it to useSafeAuth() return value, or a separate hook useRequestLogin()? I lean toward folding into useSafeAuth so there's one entry point.
74
+ If you confirm those three, I'll implement it.
75
+
76
+ 19 May at 09:09
77
+ Um, yes, we have to implement the portal side framework here, right? And, um... Yes, 100% the redirect session flow is, is part of the problem. And don't forget, the auth kit should handle quite a lot of that anyway, right? Um, so for example, a WhatsApp flow or a session flow, like you-- the, the login auth kit currently that you use should handle such things like the way you currently have it, r ...
78
+
79
+
80
+ Thought for 4s
81
+ Centralized auth modal flow
82
+ Done.
83
+
84
+ What's shipped:
85
+
86
+ useSafeAuth().requestLogin(opts?) — single entry point for container/widget apps. Returns { status, user?, token? }.
87
+ smartlinks:authkit:request-login postMessage with matching smartlinks:authkit:login-result reply (carrying requestId) for iframe apps.
88
+ Portal owns the modal (still the same AuthModal → SmartlinksAuthUI, so all your existing AuthKit redirect handling — WhatsApp, OAuth, magic link — works unchanged).
89
+ Header login button now goes through the same requestLogin path, so there's one code path for everything.
90
+ Already-authenticated callers resolve immediately without showing the modal.
91
+ Docs updated in packages/portal-framework/docs/authentication.md with the new recommended pattern and the legacy "publish your own session" pattern explicitly flagged as the exception.
92
+ Sub-app authors can now delete their bundled auth UI entirely; the auction/claim apps just await auth.requestLogin({ reason: 'bid' }).
93
+
94
+ 19 May at 09:26
95
+ Okay, so we need to document this. Um, we need to document it in the Smart Links SDK and, um, if you think there needs to be any additional document in the core workspace memory, let me know. Um, but basically, can you give me a-- If you can find the right documents, can you give me a new and updated version of any documents in the Smart Links SDK that'll help describe this?
96
+
97
+
98
+ Thought for 4s
99
+ Added portal-request-login doc
100
+ Two SDK docs are ready to drop into node_modules/@proveanything/smartlinks/docs/:
101
+
102
+ sdk-doc-additions/portal-request-login.md (new — the canonical guide) — Documents the requestLogin contract: container/widget hook (useSafeAuth().requestLogin), iframe postMessage request/reply (smartlinks:authkit:request-login / smartlinks:authkit:login-result), options, result statuses (success / already-authenticated / cancelled / unavailable), redirect-bounce behavior, and anti-patterns.
103
+ sdk-doc-additions/portal-auth-broadcast.md (updated) — Now opens with a pointer to portal-request-login.md and is explicitly scoped to the custom-auth case (sub-app runs its own flow and publishes the resulting session). The login/logout postMessage payloads are unchanged.
104
+ When you copy them into the SDK, also add two rows to API_SUMMARY.md under an "AuthKit / Portal Bridge" section (it doesn't exist yet):
105
+
106
+ Doc Purpose
107
+ portal-request-login.md Sub-app asks the portal to log the user in (recommended)
108
+ portal-auth-broadcast.md Sub-app publishes a session from its own custom auth flow
109
+ Workspace memory: I don't think a new memory is needed. The existing index already has entries for the auth singleton, safe-bridge pattern, and admin-proxy sync. The requestLogin contract is a sub-app authoring pattern that lives naturally in the SDK docs above — it's not a recurring constraint the agent has to remember to avoid violating. If you want, I can add a one-line core rule like "Sub-apps must use requestLogin for standard auth; never render their own SmartlinksAuthUI modal" — say the word and I'll add it.
110
+
111
+ 19 May at 09:27
112
+ And actually, I want to add to this. Is there any other things we should do? So for example, one of the apps I have is an app called Linktree, um, sorry, Linkpage. And the Linkpage often like links to other actions in the system. And one of the things it can do is has, it has essentially these ability to link to other pages as well as built-in apps as well as external things. And I wonder if we sh ...
113
+
114
+
115
+ Thought for 4s
116
+ Read
117
+ builtin-actions.ts
118
+ Yes — this is a very natural extension and worth doing. The plumbing already exists; you'd just be exposing it through a programmatic channel instead of only navigation zones.
119
+
120
+ What's already there
121
+ packages/portal-framework/src/orchestrator/builtin-actions.ts already defines a closed set of reserved action IDs that the framework owns and renders consistently:
122
+
123
+ __language __qrScanner __barcodeScanner __nfcScanner
124
+ __refresh __share __account __profile __settings
125
+ Today these are placed by config into headers/side menus/tabs. They are not callable from inside an app. Linkpage currently can only navigate (via LinkTarget) — it can't say "open the QR scanner" or "log out".
126
+
127
+ Proposed addition — requestAction
128
+ Mirror exactly what we just did for login. One verb, one contract, two transports.
129
+
130
+ Container/widget hook (folded into useSafeAuth's sibling on the portal context):
131
+
132
+ const portal = usePortalActions();
133
+ const result = await portal.requestAction('__qrScanner', { /* params */ });
134
+ // result: { status: 'completed' | 'cancelled' | 'unavailable' | 'error',
135
+ // value?: unknown, error?: string, requestId?: string }
136
+ Iframe postMessage:
137
+
138
+ window.parent.postMessage({
139
+ _smartlinksIframeMessage: true,
140
+ type: 'smartlinks:action:request',
141
+ payload: { requestId, actionId: '__qrScanner', params: {} },
142
+ }, '*');
143
+ // reply: { type: 'smartlinks:action:result',
144
+ // payload: { requestId, status, value?, error? } }
145
+ Standard return shapes per action — published in the SDK doc so Linkpage and others know what to expect:
146
+
147
+ Action value on completed
148
+ __qrScanner / __barcodeScanner { text, format, resolved?: { collectionId, productId?, proofId? } }
149
+ __nfcScanner { serial, payload? }
150
+ __share { method: 'native' | 'clipboard' }
151
+ __language { language }
152
+ __account (login) / __profile / __settings { user? }
153
+ __refresh {}
154
+ __logout (new) {}
155
+ __takePhoto (proposed) { dataUrl } or { assetId }
156
+ Why this is the right shape
157
+ It's the same primitive. requestLogin becomes literally requestAction('__account', { mode }) under the hood — you keep the dedicated requestLogin as a typed convenience and document it as such.
158
+ One layout-builder source of truth. BUILTIN_ACTION_META already drives the Linkpage-style picker. The same enum becomes the dropdown in any "pick an action" UI.
159
+ Capability negotiation comes free. getAvailableActions() (and a capabilities field in the iframe context sync) lets Linkpage hide "NFC scan" on iOS web, "take photo" without a camera, "logout" when not logged in, etc. — much cleaner than every app re-detecting.
160
+ Mobile shell maps onto the same enum. When the Capacitor host wants to override __qrScanner with the native camera, it registers the same action ID — sub-apps don't change.
161
+ What I'd add to the set
162
+ Genuinely missing today:
163
+
164
+ __logout — symmetric with login, currently no clean way to trigger.
165
+ __takePhoto — distinct from QR scan; returns image/asset.
166
+ __pickAsset — opens the same AssetPicker the admin UI uses (useful for any "let the user pick a saved image" flow).
167
+ __copyLink — distinct from __share; deterministic clipboard with toast.
168
+ __navigate — wraps SL.navigation.resolveLink(linkTarget).navigate(); lets iframes use the portal's link resolver without re-implementing it.
169
+ I'd not add: file download, generic toast, dialog. Those are app concerns, not portal capabilities.
170
+
171
+ What to be careful about
172
+ Keep the surface closed. Only __-prefixed IDs the framework ships. Don't let apps register their own — that's what apps and LinkTarget's 'app' / 'deep' kinds are for. The action registry is for host capabilities.
173
+ Permissioning. __logout from a random iframe should probably require user confirmation; __settings exposing account deletion definitely should. Decide per-action whether the portal prompts.
174
+ Linkpage UX vs programmatic. Linkpage's links should resolve through the same requestAction path so a link configured as "open QR scanner" behaves identically to a programmatic call. Don't fork two execution paths.
175
+ Don't replace LinkTarget. LinkTarget stays the persisted shape (it's what's saved in config). requestAction is the runtime API. Linkpage stores a LinkTarget of a new kind: { kind: 'action'; actionId; params? } — and the resolver dispatches that to requestAction.
176
+ Recommendation
177
+ Build it in two phases:
178
+
179
+ Phase 1 — programmatic API + iframe contract + LinkTarget 'action' kind. Wire requestAction to the existing built-in actions (no new actions yet). Update Linkpage to use it.
180
+ Phase 2 — add __logout, __takePhoto, __pickAsset, __copyLink, __navigate. Document return shapes in the SDK.
181
+ Want me to write up a plan for Phase 1, or go straight to implementing it?
182
+
183
+ 19 May at 09:46
184
+ Um, great. Let's go and add these. This is great
185
+
186
+
187
+ Thought for 7s
188
+ Added sync actions & doc
189
+ Add requestAction core API
190
+ Add new built-in actions
191
+ Extend LinkTarget with action kind
192
+ Document requestAction in SDK docs
193
+ Shipped the requestAction contract end-to-end:
194
+
195
+ ActionRequestProvider (packages/portal-framework/src/orchestrator/ActionRequestProvider.tsx) — usePortalActions().requestAction(actionId, params) + iframe postMessage bridge (smartlinks:action:request / :result) + capabilities probe (smartlinks:action:capabilities).
196
+ Synchronous built-ins executed inline: __refresh, __share, __copyLink, __navigate, __login, __logout, __account (adaptive). Overlay built-ins (__qrScanner, __profile, etc.) dispatch through SiteInjectionContext.triggerAction and resolve triggered for now.
197
+ BUILTIN_ACTION_META extended with LOGIN, LOGOUT, COPY_LINK, NAVIGATE entries so Linkpage-style pickers list them automatically.
198
+ Mounted inside SiteInjectionProvider in both OrchestratedPortal branches, exported from the framework root.
199
+ portal-request-action.md — full SDK doc covering hook + iframe contract, capability discovery, the LinkTarget 'action' kind, and anti-patterns.
200
+ Returning values from overlay actions (e.g. QR text back to the caller) is the natural next pass — the envelope already supports value; only the overlay components need a resolve() callback wired in.
201
+
202
+ 19 May at 11:37
203
+ Okay, so can you take a step back for a second? 'Cause I'm getting confused about all the docs. So I now have four different docs that all say SDK doc additions. Can you tell me which ones I actually still need? Which ones are already in the SDK? Which ones have been supplemented? Can we delete the ones we no longer need, look at the SDK and check everything's right, and give me a single summary o ...
204
+
205
+
206
+ Thought for 3s
207
+ Used 5 tools
208
+
209
+ Here's the audit:
210
+
211
+ What's where
212
+ File in sdk-doc-additions/ In installed SDK? Status
213
+ portal-auth-broadcast.md (158 lines) Yes, older 136-line version Supersedes — our copy adds the pointer to requestLogin and reframes this as the custom-auth-only path
214
+ portal-back-button.md (187 lines) Yes, older 95-line version Supersedes — our copy is the expanded version from the earlier back-button work
215
+ portal-request-login.md (240 lines) No New — missing from SDK
216
+ portal-request-action.md (194 lines) No New — missing from SDK
217
+ API_SUMMARY.md in the SDK has no "AuthKit / Portal Bridge" section yet — none of the four docs are linked from it.
218
+
219
+ Recommendation
220
+ Keep all four. None are redundant — the two that exist in the SDK are stale versions that need replacing, and the other two are net-new. Deleting any of them would lose content the SDK doesn't have yet.
221
+
222
+ What's actually missing from the SDK right now
223
+ Replace docs/portal-auth-broadcast.md with our 158-line revision (adds the "read requestLogin first" pointer and scopes this doc to custom-auth flows only).
224
+ Replace docs/portal-back-button.md with our 187-line revision.
225
+ Add docs/portal-request-login.md (canonical login contract).
226
+ Add docs/portal-request-action.md (general-purpose built-in actions contract).
227
+ Add to API_SUMMARY.md a new "AuthKit / Portal Bridge" section with rows for all four docs (currently no entries point to any of them).
228
+ Want me to also verify our portal-back-button.md revision against the implementation in OrchestratedPortal / useNavigationState, or leave it as-is?
229
+
230
+ Security
231
+ 2
232
+ Errors (outdated)
233
+ Code
234
+ Ask Lovable...
235
+
236
+
237
+ Add files
238
+ Drop any files here to add them to your message
239
+
240
+ Search code
241
+ .github/workflows
242
+ sync-package-lock.yml
243
+ .lovable
244
+ plan.md
245
+ deploy/portal
246
+ server
247
+ cloudbuild.yaml
248
+ Dockerfile
249
+ nginx.conf
250
+ package.json
251
+ README.md
252
+ tsconfig.server.json
253
+ vite.portal.config.ts
254
+ packages/portal-framework
255
+ docs
256
+ sdk-doc-additions
257
+ portal-auth-broadcast.md
258
+ portal-back-button.md
259
+ portal-request-action.md
260
+ portal-request-login.md
261
+ ai-assistant.md
262
+ ai-content-extraction.md
263
+ architecture.md
264
+ authentication.md
265
+ cross-app-communication.md
266
+ direct-component-rendering.md
267
+ embed-mode.md
268
+ routing.md
269
+ site-injection.md
270
+ src
271
+ GETTING_STARTED.md
272
+ package.json
273
+ README.md
274
+ rollup.config.js
275
+ tsconfig.build.json
276
+ tsconfig.json
277
+ public
278
+ app.admin.json
279
+ app.manifest.json
280
+ robots.txt
281
+ scripts
282
+ check-lockfile-sync.mjs
283
+ src
284
+ components
285
+ docs
286
+ hooks
287
+ i18n
288
+ integrations/supabase
289
+ lib
290
+ pages
291
+ types
292
+ utils
293
+ widgets
294
+ App.css
295
+ App.tsx
296
+ index.css
297
+ main.tsx
298
+ platform.ts
299
+ preview-main.tsx
300
+ PreviewApp.tsx
301
+ vite-env.d.ts
302
+ supabase
303
+ config.toml
304
+ .dockerignore
305
+ .env
306
+ .env.example
307
+ .gitignore
308
+ bun.lock
309
+ components.json
310
+ eslint.config.js
311
+ index.html
312
+ package.json
313
+ postcss.config.js
314
+ preview.html
315
+ tailwind.config.ts
316
+ tsconfig.app.json
317
+ tsconfig.json
318
+ tsconfig.node.json
319
+ vite.config.ts
320
+ vite.config.widget.ts
321
+ packages/portal-framework/docs/sdk-doc-additions/portal-auth-broadcast.md
322
+ packages/portal-framework/docs/sdk-doc-additions/portal-back-button.md
323
+ packages/portal-framework/docs/sdk-doc-additions/portal-request-login.md
324
+ packages/portal-framework/docs/sdk-doc-additions/portal-request-action.md
325
+ 1
326
+ 2
327
+ 3
328
+ 4
329
+ 5
330
+ 6
331
+ 7
332
+ 8
333
+ 9
334
+ 10
335
+ 11
336
+ 12
337
+ 13
338
+ 14
339
+ 15
340
+ 16
341
+ 17
342
+ 18
343
+ 19
344
+ 20
345
+ 21
346
+ 22
347
+ 23
348
+ 24
349
+ 25
350
+ 26
351
+ 27
352
+ 28
353
+ 29
354
+ 30
355
+ 31
356
+ 32
357
+ 33
358
+ 34
359
+ 35
360
+ 36
361
+ 37
362
+ 38
363
+ 39
364
+ 40
365
+ 41
366
+ 42
367
+ 43
368
+ 44
369
+ 45
370
+ 46
371
+ 47
372
+ 48
373
+ 49
374
+ 50
375
+ 51
376
+ 52
377
+ 53
378
+ 54
379
+ 55
380
+ 56
381
+ 57
382
+ 58
383
+ 59
384
+ 60
385
+ 61
386
+ 62
387
+ 63
388
+ # Requesting Built-in Portal Actions
389
+
390
+ > **Where to put this file:** copy into the SmartLinks SDK at
391
+ > `node_modules/@proveanything/smartlinks/docs/portal-request-action.md`
392
+ > and add a row to `API_SUMMARY.md`'s "AuthKit / Portal Bridge" section.
393
+
394
+ `requestAction` is the general-purpose sibling of
395
+ [`requestLogin`](./portal-request-login.md). It lets a sub-app
396
+ (container, widget, or iframe) trigger any of the portal's built-in
397
+ capabilities — open the QR scanner, log out, copy the current link,
398
+ navigate to another app — and await a result.
399
+
400
+ Use it whenever the answer to *"what do I want?"* is one of the
401
+ portal's standardized capabilities. The portal owns the implementation;
402
+ your sub-app declares intent.
403
+
404
+ ---
405
+
406
+ ## Why this exists
407
+
408
+ Linkpage-style apps, action grids and CTAs across the platform all need
409
+ the same primitives: scan a QR, take a photo, copy a link, jump to
410
+ another app, log the user out. Without a shared contract every app
411
+ re-implemented these against direct browser APIs, fragmenting UX and
412
+ breaking cross-iframe boundaries. `requestAction` is the contract.
413
+
414
+ ---
415
+
416
+ ## Container / Widget Apps (Same React Tree)
417
+
418
+ ```tsx
419
+ import { usePortalActions } from '@proveanything/portal-framework';
420
+
421
+ function ShareButton() {
422
+ const portal = usePortalActions();
423
+ async function onClick() {
424
+ const result = await portal?.requestAction('__share', {
425
+ title: 'Check this out',
426
+ url: window.location.href,
427
+ });
428
+ // result.status === 'success' | 'cancelled' | 'unavailable' | 'error' | 'triggered'
429
+ }
430
+ return <button onClick={onClick}>Share</button>;
431
+ }
432
+ ```
433
+
434
+ `usePortalActions()` returns `null` when no `ActionRequestProvider` is
435
+ mounted upstream — treat that the same as `status: 'unavailable'`.
436
+
437
+ ### Discovering available actions
438
+
439
+ ```ts
440
+ const ids = portal?.getAvailableActions() ?? [];
441
+ // → ['__refresh','__share','__copyLink','__navigate','__login','__logout',
442
+ // '__account','__qrScanner','__barcodeScanner','__nfcScanner', ...]
443
+ ```
444
+
445
+ Combine with `BUILTIN_ACTION_META` (also exported) for labels/icons in
446
+ your action pickers.
447
+
448
+ ---
449
+
450
+ ## Iframe Apps (Cross-Origin)