@proveanything/smartlinks 2.0.15 → 2.0.17
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 +3 -1
- package/dist/docs/app-records-pattern.md +1 -1
- package/dist/docs/deploying-apps.md +25 -7
- package/dist/docs/theme-tokens.md +239 -0
- package/dist/docs/ui-utils.md +121 -104
- package/dist/openapi.yaml +699 -183
- package/dist/theme-boot.js +78 -0
- package/dist/theme.css +55 -0
- package/dist/types/appManifest.d.ts +12 -0
- package/docs/API_SUMMARY.md +3 -1
- package/docs/app-records-pattern.md +1 -1
- package/docs/deploying-apps.md +25 -7
- package/docs/theme-lookbook.v1.json +120 -0
- package/docs/theme-tokens.md +239 -0
- package/docs/ui-utils.md +121 -104
- package/openapi.yaml +699 -183
- package/package.json +5 -1
- package/scripts/doctor.mjs +49 -1
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.17 | Generated: 2026-09-24T09:42:39.649Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -2183,6 +2183,8 @@ interface AppManifest {
|
|
|
2183
2183
|
moduleFormat?: 'umd' | 'esm' | 'dual';
|
|
2184
2184
|
sharedDependencies?: string;
|
|
2185
2185
|
cssBaseline?: string;
|
|
2186
|
+
respectsHostTheme?: boolean;
|
|
2187
|
+
themeTokens?: string;
|
|
2186
2188
|
globals?: Record<string, string>;
|
|
2187
2189
|
seo?: {
|
|
2188
2190
|
strategy?: 'executor' | string;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
> Status: **standard**. New apps MUST follow this contract; existing apps SHOULD migrate.
|
|
8
8
|
>
|
|
9
9
|
> SDK: `@proveanything/smartlinks` ≥ **2.0** (R5).
|
|
10
|
-
> Admin shell (React only): `@proveanything/smartlinks-utils-ui` ≥ **0
|
|
10
|
+
> Admin shell (React only): `@proveanything/smartlinks-utils-ui` ≥ **2.0** — required for the admin side if using the React shell; not needed in public widgets.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -34,14 +34,24 @@ bundle. Two small additions to your build, both non-secret:
|
|
|
34
34
|
// app.manifest.json
|
|
35
35
|
"build": { "hash": "a1b2c3d4", "at": "2026-09-20T10:00:00Z" }
|
|
36
36
|
```
|
|
37
|
-
2. **Ping us on publish** (a `postbuild` step —
|
|
38
|
-
|
|
37
|
+
2. **Ping us on publish** (a `postbuild` step — the app id is an *identifier, not a secret*, so it's
|
|
38
|
+
fine in the repo). **Prefer the tiny node wrapper** the example app ships
|
|
39
|
+
(`scripts/smartlinks-deploy.mjs` in `smartlinks-app-example` — copy it): its
|
|
40
|
+
no-key path does exactly this ping and, crucially, **resolves the app id from
|
|
41
|
+
`SMARTLINKS_APP_ID` *or* the manifest's `meta.appId`**, so it works even when no env var is set:
|
|
42
|
+
```js
|
|
43
|
+
// the id-resolution the wrapper uses — env var first, manifest fallback
|
|
44
|
+
const appId = process.env.SMARTLINKS_APP_ID
|
|
45
|
+
|| JSON.parse(fs.readFileSync('public/app.manifest.json')).meta.appId
|
|
46
|
+
```
|
|
47
|
+
```jsonc
|
|
48
|
+
// package.json — wrapper (recommended)
|
|
49
|
+
"scripts": { "postbuild": "node scripts/smartlinks-deploy.mjs" }
|
|
50
|
+
```
|
|
51
|
+
A bare `curl` works too, **but only if `$SMARTLINKS_APP_ID` is actually set in the build env** —
|
|
52
|
+
it has no manifest fallback, so an unset var makes the ping a silent no-op (a common miss):
|
|
39
53
|
```jsonc
|
|
40
|
-
|
|
41
|
-
"scripts": {
|
|
42
|
-
"build": "vite build && … && node scripts/hash-bundles.mjs", // your existing hashing step
|
|
43
|
-
"postbuild": "curl -fsS -X POST \"$SMARTLINKS_API/api/v1/apps/$SMARTLINKS_APP_ID/refresh-dev\" -H 'content-type: application/json' -d \"{\\\"hash\\\":\\\"$BUILD_HASH\\\"}\""
|
|
44
|
-
}
|
|
54
|
+
"postbuild": "curl -fsS -X POST \"$SMARTLINKS_API/api/v1/apps/$SMARTLINKS_APP_ID/refresh-dev\" -H 'content-type: application/json' -d \"{\\\"hash\\\":\\\"$BUILD_HASH\\\"}\""
|
|
45
55
|
```
|
|
46
56
|
|
|
47
57
|
That's it — no deploy key. When the ping arrives, SmartLinks looks up your app's published URL
|
|
@@ -51,6 +61,14 @@ and registers the dev release (the hash becomes the version). `POST /apps/{appId
|
|
|
51
61
|
authorises nothing on its own — worst case it re-fetches your app's own public bundle — so it needs
|
|
52
62
|
no secret; it's rate-limited and de-duped per app.
|
|
53
63
|
|
|
64
|
+
**Where your app learns its own id:** either `SMARTLINKS_APP_ID` (a build-env var) or the manifest's
|
|
65
|
+
`meta.appId`. **Whichever you use must equal your authoritative platform id** — the one in the
|
|
66
|
+
catalog (the `appModules` handle the CDN + every collection's config bind to). On the keyed
|
|
67
|
+
`/releases` path `meta.appId` is *ignored* in favour of the URL id, but for `refresh-dev` the id you
|
|
68
|
+
ping with **is** the lookup key — so if `meta.appId` is blank or has drifted from your platform id,
|
|
69
|
+
the ping resolves the wrong app (or `NO_DEV_URL`). **Stamp `meta.appId` with your real platform id**
|
|
70
|
+
(don't invent one) and the manifest fallback is reliable.
|
|
71
|
+
|
|
54
72
|
**One-time setup:** the app must exist in the catalog with its **id** and its **Lovable URL**
|
|
55
73
|
recorded (that's how we know what to fetch, and the `id` you ping with must match). Ask the platform
|
|
56
74
|
owner to register the app once; after that, every Publish auto-updates dev.
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# SmartLinks Theme Tokens — host theming contract
|
|
2
|
+
|
|
3
|
+
Status: **v1 (draft) — FORTHCOMING, not yet live host-side.**
|
|
4
|
+
> ⚠️ **Do not build apps against this yet.** The host (Hub/Portal) does not yet set `--sl-*` tokens
|
|
5
|
+
> or send `smartlinks:root-state`. Until it does, apps follow the **current** theming — see
|
|
6
|
+
> [`theme.system.md`](./theme.system.md) (`useSmartLinksTheme()` + shadcn vars `--primary`/`--background`,
|
|
7
|
+
> the `?theme=` base64 payload). This document is the target model that will supersede it once the host
|
|
8
|
+
> serves it; it exists so the SDK preset (`theme.css`), the iframe bootstrap (`theme-boot.js`) and the
|
|
9
|
+
> Hub theme engine can be built against a fixed contract.
|
|
10
|
+
|
|
11
|
+
Applies to: `@proveanything/smartlinks` ^2.0 (R5), container + widget embeds
|
|
12
|
+
Companion to: [`css-baseline.md`](./css-baseline.md) (mechanics, live) — this doc is **brand** (colour, shape, type)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## The one idea
|
|
17
|
+
|
|
18
|
+
`sl-baseline` gives apps guaranteed **mechanics** (layout, spacing, type scale) with no colour or brand.
|
|
19
|
+
Theme tokens give apps the host's **brand** — colour, corner shape, fonts — as a small set of
|
|
20
|
+
**semantic CSS custom properties** the host sets and the app reads. An app that binds to these
|
|
21
|
+
follows any host theme automatically, forever, with no code change.
|
|
22
|
+
|
|
23
|
+
The durable thing here is the **token contract** (names + meanings), not any theme. A *theme* is just
|
|
24
|
+
a set of token *values* — disposable data, hand-authored or AI-generated. The contract is small,
|
|
25
|
+
semantic, and versioned; themes churn freely on top of it.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
host sets --sl-color-accent: #b4531f (a theme value)
|
|
29
|
+
│
|
|
30
|
+
▼
|
|
31
|
+
SDK theme.css maps it onto Tailwind's token → bg-primary, text-primary, …
|
|
32
|
+
│
|
|
33
|
+
▼
|
|
34
|
+
app renders in the host's brand — no per-app work
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 1. Design rules (why it lasts)
|
|
40
|
+
|
|
41
|
+
1. **Semantic, never literal.** Tokens name a *role* (`--sl-color-accent`, `--sl-color-surface`),
|
|
42
|
+
never a colour (`--sl-blue-600`). Roles survive redesigns; literals rot.
|
|
43
|
+
2. **Small guaranteed core.** The app-facing contract is ~12 tokens. Richer, Hub-specific styling
|
|
44
|
+
(button weight, elevation, density, imagery) lives in **host-internal** component tokens and is
|
|
45
|
+
NOT part of this contract — so it can evolve without breaking apps or binding other surfaces.
|
|
46
|
+
3. **Additive, versioned, retained.** New tokens are added within a version with sane fallbacks; a
|
|
47
|
+
removal or rename is a new version, old retained. Same discipline as the shared-dependency
|
|
48
|
+
contract — never hard-remove.
|
|
49
|
+
4. **Each surface declares the subset it honours.** The token set is a **superset**; Hub honours all
|
|
50
|
+
of it, Portal a subset. Apps read defensively (every token has a fallback), so one brand renders
|
|
51
|
+
consistently across surfaces and missing tokens degrade gracefully.
|
|
52
|
+
5. **Tokens only — never templated CSS.** Themes are declarative values, always previewable and
|
|
53
|
+
validatable. (Liquid/templating belongs to content, not styling.)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 2. The v1 token set
|
|
58
|
+
|
|
59
|
+
All tokens are CSS custom properties read from the **nearest scoping element** (the app's mount
|
|
60
|
+
root), so different embeds on one page can theme differently. Every token has a fallback, so an app
|
|
61
|
+
renders correctly even against a host that sets none of them.
|
|
62
|
+
|
|
63
|
+
### Core (v1 — guaranteed; every host honours these)
|
|
64
|
+
|
|
65
|
+
| Token | Role | Example value |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `--sl-color-bg` | app / page background | `#ffffff` |
|
|
68
|
+
| `--sl-color-surface` | card / panel background | `#f7f7f8` |
|
|
69
|
+
| `--sl-color-fg` | primary text (on bg/surface) | `#18181b` |
|
|
70
|
+
| `--sl-color-muted` | secondary / muted text | `#71717a` |
|
|
71
|
+
| `--sl-color-border` | borders, dividers, input outlines | `#e4e4e7` |
|
|
72
|
+
| `--sl-color-accent` | brand / primary action | `#4f46e5` |
|
|
73
|
+
| `--sl-color-on-accent` | text / icon on an accent fill | `#ffffff` |
|
|
74
|
+
| `--sl-radius-sm` | small corner radius | `4px` |
|
|
75
|
+
| `--sl-radius-md` | default corner radius | `8px` |
|
|
76
|
+
| `--sl-radius-lg` | large corner radius | `16px` |
|
|
77
|
+
| `--sl-font-heading` | heading font stack | `'Inter', sans-serif` |
|
|
78
|
+
| `--sl-font-body` | body font stack | `'Inter', sans-serif` |
|
|
79
|
+
|
|
80
|
+
### Extended (v1 — optional; Hub may set, Portal may not; read defensively)
|
|
81
|
+
|
|
82
|
+
| Token | Role | Fallback |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `--sl-color-surface-raised` | elevated surface (popover, modal) | `--sl-color-surface` |
|
|
85
|
+
| `--sl-color-accent-soft` | soft accent fill (badges, hovers) | derived from `--sl-color-accent` |
|
|
86
|
+
| `--sl-color-success` | positive status | `#16a34a` |
|
|
87
|
+
| `--sl-color-danger` | negative / destructive status | `#dc2626` |
|
|
88
|
+
| `--sl-shadow-sm` / `--sl-shadow-md` | elevation | none |
|
|
89
|
+
|
|
90
|
+
> Everything richer than this — button weight (solid/soft/outline), density/compactness, image
|
|
91
|
+
> treatment, elevation scale — is a **host-internal component token**, driven by Hub's theme engine.
|
|
92
|
+
> It is deliberately NOT in this contract, so Hub can be as skinnable as it likes without it becoming
|
|
93
|
+
> a forever-obligation on every app and surface.
|
|
94
|
+
|
|
95
|
+
Dark mode is a *theme* (a different value-set), not a separate token set — the host swaps the values.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 3. Theme value-sets (what the AI emits, what the lookbook stores)
|
|
100
|
+
|
|
101
|
+
A theme is data: a token version + a flat map of values. The host expands each `values` key to
|
|
102
|
+
`--sl-<key>` on the mount root.
|
|
103
|
+
|
|
104
|
+
```jsonc
|
|
105
|
+
{
|
|
106
|
+
"tokensVersion": "v1",
|
|
107
|
+
"name": "Warm Editorial",
|
|
108
|
+
"values": {
|
|
109
|
+
"color-bg": "#faf7f2",
|
|
110
|
+
"color-surface": "#ffffff",
|
|
111
|
+
"color-fg": "#1a1a1a",
|
|
112
|
+
"color-muted": "#6b6b6b",
|
|
113
|
+
"color-border": "#e6e0d8",
|
|
114
|
+
"color-accent": "#b4531f",
|
|
115
|
+
"color-on-accent": "#ffffff",
|
|
116
|
+
"radius-sm": "4px", "radius-md": "10px", "radius-lg": "18px",
|
|
117
|
+
"font-heading": "'Fraunces', serif",
|
|
118
|
+
"font-body": "'Inter', sans-serif"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
- **Lookbook** = a gallery of these value-sets shipped as starting points; AI can generate more.
|
|
124
|
+
- **Refine** = AI edits `values` conversationally, previewed live in the real host, always inside the
|
|
125
|
+
contract → always safe, previewable, validatable.
|
|
126
|
+
- **Brand on-ramp** = ingest a site/brand guide → AI emits a `values` set → refine.
|
|
127
|
+
- **Validation** = a value-set must pass contrast (WCAG AA) for `fg`/`bg`, `muted`/`bg`,
|
|
128
|
+
`on-accent`/`accent` before it is offered or saved. AI output is checked, not trusted.
|
|
129
|
+
|
|
130
|
+
Store per collection as `{ tokensVersion, name, values }` — portable, diffable, exportable.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 4. Host obligations vs app obligations
|
|
135
|
+
|
|
136
|
+
**Host (Hub / Portal):**
|
|
137
|
+
- Applies the active theme's `--sl-*` tokens where the embed will read them — `:root`/`<body>` for the
|
|
138
|
+
simple one-brand page (container/widget apps inherit for free), or the app's mount root when embeds
|
|
139
|
+
must theme independently or you want leak isolation for untrusted apps.
|
|
140
|
+
- For **iframe** apps, hands the initial state in via the URL fragment and posts live viewer-pref
|
|
141
|
+
updates as `smartlinks:root-state` (§6.2). Brand-theme changes = reload the embed.
|
|
142
|
+
- Declares the token version it serves and which subset (Hub = full, Portal = core).
|
|
143
|
+
|
|
144
|
+
**App:**
|
|
145
|
+
- `@import "@proveanything/smartlinks/theme.css";` after Tailwind (see §5).
|
|
146
|
+
- Uses semantic utilities (`bg-primary`, `text-foreground`, `border-border`, `rounded-md`,
|
|
147
|
+
`font-heading`) — never hardcoded palette utilities (`bg-blue-600`, `#rrggbb`).
|
|
148
|
+
- Authors CSS against the viewer-pref hooks (§6.1): the `.dark` class, `[data-contrast="high"]`,
|
|
149
|
+
`--sl-font-scale`, and the native `@media (prefers-contrast|prefers-reduced-motion)` queries.
|
|
150
|
+
**Never writes a message handler** — the SDK bootstrap flips the hooks.
|
|
151
|
+
- Iframe/standalone entries only: include `@proveanything/smartlinks/theme-boot.js` in `<head>`
|
|
152
|
+
(§6.2). Container/widget apps don't need it.
|
|
153
|
+
- Declares intent in the manifest:
|
|
154
|
+
|
|
155
|
+
```jsonc
|
|
156
|
+
{ "meta": { "respectsHostTheme": true, "themeTokens": "v1" } }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`smartlinks-doctor` warns (not errors) on hardcoded colour utilities / hex in component source when
|
|
160
|
+
`respectsHostTheme` is `true`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 5. The SDK preset (`theme.css`)
|
|
165
|
+
|
|
166
|
+
Ships from the SDK. A Tailwind 4 `@theme` block that maps Tailwind's tokens onto the `--sl-*`
|
|
167
|
+
contract, with fallbacks so it is safe even where the host sets nothing:
|
|
168
|
+
|
|
169
|
+
```css
|
|
170
|
+
@import "tailwindcss";
|
|
171
|
+
@import "@proveanything/smartlinks/theme.css";
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Requires Tailwind 4 (`@theme`). Apps still on Tailwind 3 stay as they are until they migrate
|
|
175
|
+
(step 16); the preset is opt-in and additive.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 6. Viewer preferences, accessibility & iframe delivery
|
|
180
|
+
|
|
181
|
+
Two different kinds of "theme-ish" state, handled differently:
|
|
182
|
+
|
|
183
|
+
| | Brand theme | Viewer / accessibility prefs |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| What | accent, fonts, radius (the `--sl-*` tokens) | light/dark, contrast, font size, reduced-motion, language |
|
|
186
|
+
| Set by | the operator (rarely, in an editor) | the **viewer** (any time, mid-session) |
|
|
187
|
+
| Change model | **reload** the embed with new values | **live**, no reload (a reload on an a11y toggle *is* an a11y failure) |
|
|
188
|
+
|
|
189
|
+
**The app never writes a message handler.** It authors **CSS** against a small, fixed set of root
|
|
190
|
+
hooks; a generic SDK bootstrap (§6.2) flips those hooks. That's the whole obligation.
|
|
191
|
+
|
|
192
|
+
### 6.1 The viewer-pref hooks (fixed vocabulary)
|
|
193
|
+
|
|
194
|
+
- **Dark mode** — the `.dark` class (Tailwind/shadcn convention) *and* `[data-theme="dark"|"light"]`.
|
|
195
|
+
- **Contrast** — `[data-contrast="high"]`. Also honour `@media (prefers-contrast: more)` — the browser
|
|
196
|
+
propagates the OS setting into iframes natively, so you get that slice for free.
|
|
197
|
+
- **Reduced motion** — honour `@media (prefers-reduced-motion: reduce)` (native, free).
|
|
198
|
+
- **Font size** — `--sl-font-scale` (a number, e.g. `1.25`); scale your base/rem type off it.
|
|
199
|
+
- **Language** — the `lang` attribute. (A language change is *content*, not just CSS — re-render/
|
|
200
|
+
re-fetch is the app's own i18n job; the hook just flags it. A heavy content swap may reload.)
|
|
201
|
+
|
|
202
|
+
Most of accessibility is therefore **free**: the OS-level `prefers-contrast` / `prefers-reduced-motion`
|
|
203
|
+
/ browser zoom reach the iframe with no passing at all — just respect the standard media queries.
|
|
204
|
+
|
|
205
|
+
### 6.2 Iframe delivery (`theme-boot.js`)
|
|
206
|
+
|
|
207
|
+
Container/widget apps inherit the host `:root` and need none of this. **Iframe/standalone** apps
|
|
208
|
+
(their own document — the security boundary for untrusted apps) include the SDK bootstrap
|
|
209
|
+
`@proveanything/smartlinks/theme-boot.js` in `<head>` (inline is best — zero flash):
|
|
210
|
+
|
|
211
|
+
- **Boot (before first paint):** reads the initial state from the URL fragment
|
|
212
|
+
`#slt=<base64url(JSON)>` (`{ tokensVersion, values, theme, contrast, fontScale, lang }`), plus
|
|
213
|
+
convenience params `?theme=&contrast=&fontScale=&lang=`, and applies it to `:root` synchronously.
|
|
214
|
+
This *replaces* the legacy base64-17-keys scheme — same idea (correct on first paint), but a
|
|
215
|
+
structured, versioned payload.
|
|
216
|
+
- **Live:** applies generic root-state updates the host posts as
|
|
217
|
+
`{ type: 'smartlinks:root-state', values?, theme?, contrast?, fontScale?, lang?, attrs? }`. This is
|
|
218
|
+
what makes accessibility toggles instant. The app carries none of this logic — the bootstrap is
|
|
219
|
+
generic and SDK-owned; the app only wrote CSS.
|
|
220
|
+
|
|
221
|
+
Payload stays small (the reason the fixed vocabulary matters): the token set + a handful of mode keys
|
|
222
|
+
fit comfortably in a URL fragment, no compression needed.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 7. Versioning
|
|
227
|
+
|
|
228
|
+
- `themeTokens: "vN"` — the version an app targets.
|
|
229
|
+
- Within a version: additive only (new tokens get fallbacks). Never remove a token in-version.
|
|
230
|
+
- A removal/rename → `v(N+1)`, `vN` retained; hosts may serve several; apps declare which they target.
|
|
231
|
+
- Surfaces declare their honoured subset; the "mapping" between Hub and Portal is *which keys each
|
|
232
|
+
honours*, not a translation layer.
|
|
233
|
+
|
|
234
|
+
## 8. Change log
|
|
235
|
+
|
|
236
|
+
- **v1 (draft, 2026-09-23):** initial semantic core (12) + extended (optional) set; value-set schema;
|
|
237
|
+
host mount-root scoping; manifest declaration + doctor warn; viewer-pref/accessibility hooks
|
|
238
|
+
(`.dark`/`data-theme`, `data-contrast`, `--sl-font-scale`, `lang`, native `prefers-*`); iframe
|
|
239
|
+
`theme-boot.js` (URL-boot + generic live root-state); brand change = reload, prefs = live.
|
package/dist/docs/ui-utils.md
CHANGED
|
@@ -1,104 +1,121 @@
|
|
|
1
|
-
# UI Utils — `@proveanything/smartlinks-utils-ui`
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **
|
|
10
|
-
- **Per-component reference:**
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
import
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
# UI Utils — `@proveanything/smartlinks-utils-ui`
|
|
2
|
+
|
|
3
|
+
The standardised, **admin-only** React toolkit for SmartLinks microapps. This page
|
|
4
|
+
is self-contained: the table and examples below are enough to build with. The
|
|
5
|
+
exhaustive per-component reference (props, slots, hooks, design tokens) ships
|
|
6
|
+
**inside the package** — read it from `node_modules/@proveanything/smartlinks-utils-ui/docs/`
|
|
7
|
+
after install. Nothing here depends on an external site.
|
|
8
|
+
|
|
9
|
+
- **NPM:** [`@proveanything/smartlinks-utils-ui`](https://www.npmjs.com/package/@proveanything/smartlinks-utils-ui)
|
|
10
|
+
- **Per-component reference:** `node_modules/@proveanything/smartlinks-utils-ui/docs/*.md`
|
|
11
|
+
(`records-admin-shell.md`, `admin-page-header.md`, `asset-picker.md`, `conditions-editor.md`,
|
|
12
|
+
`facet-rule-editor.md`, `link-picker.md`, `records-admin-hooks.md`, `public-consumption.md`, …)
|
|
13
|
+
- **Tracks:** `@proveanything/smartlinks ≥ 2.0` (peer dep)
|
|
14
|
+
|
|
15
|
+
## Admin is standardised; the consumer surface is bespoke
|
|
16
|
+
|
|
17
|
+
Two different design mandates, and the split is deliberate:
|
|
18
|
+
|
|
19
|
+
- **Admin surfaces** (the desktop `admin.html` a brand operator uses) are a
|
|
20
|
+
**product family** — like Salesforce: consistent, predictable chrome across every
|
|
21
|
+
app an operator opens. Do **not** hand-roll admin tables, editors or page headers.
|
|
22
|
+
Mount `RecordsAdminShell` for anything record-shaped; otherwise wrap the page in
|
|
23
|
+
`AdminPageHeader` and use the pickers. That consistency *is* the product.
|
|
24
|
+
- **Consumer / public surfaces** (the container, widgets, public pages the
|
|
25
|
+
end-customer sees) are **bespoke and brand-led** — entirely your design. utils-ui
|
|
26
|
+
belongs here only as read-side hooks (`useResolvedRecord`, `useResolveAllRecords`),
|
|
27
|
+
never as UI.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @proveanything/smartlinks-utils-ui
|
|
33
|
+
# Peer deps you probably already have:
|
|
34
|
+
npm install react react-dom @proveanything/smartlinks @tanstack/react-query
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Import the styles **once** in your admin entry (typically `src/admin-main.tsx`):
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import '@proveanything/smartlinks-utils-ui/styles.css';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Components inherit your shadcn-compatible CSS variables (`--primary`,
|
|
44
|
+
`--background`, `--border`, …) — no extra theming required.
|
|
45
|
+
|
|
46
|
+
> **Admin-only.** Every component in this package calls the SDK with
|
|
47
|
+
> `admin: true` somewhere (save, upload, etc.). Never import it from a
|
|
48
|
+
> public widget or from `MobileAdminContainer`.
|
|
49
|
+
|
|
50
|
+
## What's in the box
|
|
51
|
+
|
|
52
|
+
| Module | Subpath import | Use it for |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| **RecordsAdminShell** | `/records-admin` | Full admin UI for the `app.records` pattern. Top-level scopes: **Global** (`'collection'`), **Rule** (`'rule'`, AND-of-OR over facets), and **Product** — with automatic variant & batch drill-down where the collection enables them. Browser pane, editor pane with sticky save/discard/delete, optimistic save, deep-linking, lifecycle hooks, conflict handling. Decide **cardinality** up front: `'singleton'` (one winning record per scope — warranty, nutrition) vs `'list'` (many records per scope — auction items, FAQs, gallery). See `docs/records-admin-shell.md`. |
|
|
55
|
+
| **Records hooks** | `/records-admin` | `useResolvedRecord`, `useMergedRecord`, `useCollectedRecords`, `useResolveAllRecords`, `useRulePreview`, … for the **public widget** side. See `docs/records-admin-hooks.md`. |
|
|
56
|
+
| **AdminPageHeader** | (root) | Standardised title / subtitle / icon / help / actions header. **Required** for any admin app that doesn't mount `RecordsAdminShell` (which embeds it). No bespoke admin chrome. |
|
|
57
|
+
| **AssetPicker** | `/asset-picker` | Pick / upload / paste / URL-import / AI-generate / stock-search / crop media assets. MIME filtering, scope-aware, tag editor. |
|
|
58
|
+
| **IconPicker** | `/icon-picker` | Searchable Font Awesome 7 Pro picker (requires the FA kit script on the host page). |
|
|
59
|
+
| **FontPicker** | `/font-picker` | Google Fonts catalogue + custom uploaded font families with live previews. |
|
|
60
|
+
| **ConditionsEditor** | `/conditions-editor` | Recursive AND/OR rule builder (12 condition types, facet-aware). |
|
|
61
|
+
| **FacetRuleEditor** | `/facet-rule-editor` | Author server-side facet rules (AND-of-OR) for record targeting. |
|
|
62
|
+
| **LinkPicker** | `/link-picker` | Universal navigation picker: external URL, an installed app, or a deep link inside an app. Stores a `LinkTarget` discriminated union — never a resolved URL. Ships with `resolveLink` and `useLinkTargets`. |
|
|
63
|
+
| **Liquid editors** | `/liquid-editor` | Author Liquid templates with a code or rich (TipTap) editor, variable autocomplete. |
|
|
64
|
+
| **Hints** | (root) | `useHintsPreference`, `useIntroState`, `HintsPreferenceToggle` — global "show/hide intro hints" preference shared across admin apps. |
|
|
65
|
+
|
|
66
|
+
Each module has a per-subpath export so bundlers tree-shake the rest:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { RecordsAdminShell } from '@proveanything/smartlinks-utils-ui/records-admin';
|
|
70
|
+
import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Minimal examples
|
|
74
|
+
|
|
75
|
+
### Records admin (most apps need this)
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { RecordsAdminShell } from '@proveanything/smartlinks-utils-ui/records-admin';
|
|
79
|
+
import * as SL from '@proveanything/smartlinks';
|
|
80
|
+
|
|
81
|
+
<RecordsAdminShell
|
|
82
|
+
SL={SL}
|
|
83
|
+
collectionId={collectionId}
|
|
84
|
+
appId={appId}
|
|
85
|
+
recordType="nutrition"
|
|
86
|
+
label="Nutrition info"
|
|
87
|
+
scopes={['collection', 'rule', 'product']} // canonical top-level tabs
|
|
88
|
+
// items={{ cardinality: 'list' }} // ← opt in for multi-item apps
|
|
89
|
+
defaultData={() => ({})}
|
|
90
|
+
renderEditor={(ctx) => <NutritionForm ctx={ctx} />}
|
|
91
|
+
/>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Public widget — read the resolved value
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { useResolvedRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
|
|
98
|
+
|
|
99
|
+
const { data, source } = useResolvedRecord({
|
|
100
|
+
SL, appId, recordType: 'nutrition',
|
|
101
|
+
collectionId, productId, variantId, batchId,
|
|
102
|
+
});
|
|
103
|
+
// source: 'proof' | 'batch' | 'variant' | 'product' | 'rule' | 'collection' | null
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For multi-item (`cardinality: 'list'`) apps, use `useResolveAllRecords` /
|
|
107
|
+
`app.records.resolveAll()` instead — it returns every applicable record, not
|
|
108
|
+
just the winner.
|
|
109
|
+
|
|
110
|
+
For everything else (props, slots, cardinality decisions, deep-link adapters,
|
|
111
|
+
lifecycle hooks, conflict handling, design tokens) → **read the matching file in
|
|
112
|
+
`node_modules/@proveanything/smartlinks-utils-ui/docs/`**.
|
|
113
|
+
|
|
114
|
+
## Prerequisites
|
|
115
|
+
|
|
116
|
+
All components assume `SL.initializeApi()` has already run.
|
|
117
|
+
|
|
118
|
+
## Where to go next
|
|
119
|
+
|
|
120
|
+
- Per-component reference (shipped with the package): `node_modules/@proveanything/smartlinks-utils-ui/docs/`
|
|
121
|
+
- Records pattern (concepts, scope inheritance, resolution order): [`app-records-pattern.md`](./app-records-pattern.md)
|