@proveanything/smartlinks 1.9.21 → 1.9.22

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,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.9.21 | Generated: 2026-04-25T10:48:10.932Z
3
+ Version: 1.9.22 | Generated: 2026-04-25T11:11:00.344Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -67,7 +67,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
67
67
  | **Forms** | `docs/forms.md` | Form definitions, schema-driven rendering, submission patterns |
68
68
  | **Auth Kit** | `docs/auth-kit.md` | End-user sign-in: email/password, magic links, phone OTP, Google OAuth |
69
69
  | **Records Admin Pattern** | `docs/records-admin-pattern.md` | Standard pattern for per-product/facet/variant/batch admin UIs |
70
- | **UI Utils** | `docs/ui-utils.md` | `@proveanything/ui-utils` — React shells, hooks, and primitives for records-based apps |
70
+ | **UI Utils** | `docs/ui-utils.md` | `@proveanything/smartlinks-utils-ui` — React shells, hooks, and primitives for records-based apps |
71
71
 
72
72
  ---
73
73
 
@@ -79,7 +79,7 @@ Notes:
79
79
  - Variants and batches are **always nested under a product** in the SDK, so their refs include `productId`.
80
80
  - `facet:` refs are **collection-wide** — facets cross products by design.
81
81
  - `default` is a single record per (app, recordType) used as the global fallback.
82
- - Refs are opaque to the SDK. Apps parse them. A helper module (`@proveanything/ui-utils/records`) exports `parseRef`/`buildRef` so all apps agree on syntax. See Appendix A for the full implementation.
82
+ - Refs are opaque to the SDK. Apps parse them. A helper module (`@proveanything/smartlinks-utils-ui/records-admin`) exports `parseRef`/`buildRef` so all apps agree on syntax. See Appendix A for the full implementation.
83
83
 
84
84
  ### Adding scopes later
85
85
 
@@ -99,10 +99,10 @@ proof → batch → variant → product → facet(*) → default
99
99
 
100
100
  > **Rule:** Resolution is **first-match-wins, not merge**. If you need field-level merging, build it on top with explicit `inheritsFrom` markers in the payload — but the default for shared infra is whole-record replacement, because it is far easier to reason about.
101
101
 
102
- The canonical resolver lives in `@proveanything/ui-utils/records` so every app behaves identically:
102
+ The canonical resolver lives in `@proveanything/smartlinks-utils-ui/records-admin` so every app behaves identically:
103
103
 
104
104
  ```ts
105
- import { resolveRecord } from '@proveanything/ui-utils/records';
105
+ import { resolveRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
106
106
 
107
107
  const resolved = await resolveRecord({
108
108
  appId,
@@ -147,29 +147,24 @@ See [app-manifest.md](app-manifest.md) for the full schema reference.
147
147
 
148
148
  ## 6. Discovering whether variants / batches are in use
149
149
 
150
- The SDK does **not** expose a `collection.variantsEnabled` flag, by design. Whether a product has variants/batches is an **emergent** property: it has them iff someone created some.
151
-
152
- The standard probe pattern:
150
+ The `Collection` object exposes top-level `variants: boolean` and `batches: boolean` flags that indicate whether the collection has these features enabled. Read them directly rather than probing by listing:
153
151
 
154
152
  ```ts
155
- import { variant, batch } from '@proveanything/smartlinks';
153
+ import { appConfiguration } from '@proveanything/smartlinks';
156
154
 
157
- const [variants, batches] = await Promise.all([
158
- variant.list(collectionId, productId).catch(() => []),
159
- batch.list(collectionId, productId).catch(() => []),
160
- ]);
155
+ const collection = await appConfiguration.getCollection(collectionId);
161
156
 
162
- // scopeConfig is the parsed manifest records entry for this recordType
163
- // e.g. manifest.records?.nutrition → { scopes: ['product', 'facet', 'batch'], ... }
164
- const showVariantTab = variants.length > 0 || scopeConfig?.scopes.includes('variant') === true;
165
- const showBatchTab = batches.length > 0 || scopeConfig?.scopes.includes('batch') === true;
157
+ const showVariantTab = collection.variants && scopeConfig?.scopes.includes('variant') === true;
158
+ const showBatchTab = collection.batches && scopeConfig?.scopes.includes('batch') === true;
166
159
  ```
167
160
 
161
+ `scopeConfig` is the parsed manifest `records` entry for this `recordType` — e.g. `manifest.records?.nutrition`.
162
+
168
163
  Rules:
169
164
 
170
- 1. If the app **declares** support for the scope, always offer "Add variant" / "Add batch" affordances even when the list is empty.
171
- 2. If the app does **not** declare support, hide the tab entirely even if some exist (another app created them).
172
- 3. Cache list results per `(collectionId, productId)` with a short TTL the shell will hit them often.
165
+ 1. If the collection has the feature **and** the app **declares** support for the scope, show the tab and offer "Add variant" / "Add batch" affordances.
166
+ 2. If the app does **not** declare support, hide the tab entirely even if `collection.variants` is true (another app created them).
167
+ 3. If the collection does not have the feature enabled, hide the tab even if the app declares support.
173
168
 
174
169
  ---
175
170
 
@@ -181,7 +176,7 @@ Because resolution is first-match-wins, the admin UI must make inheritance **vis
181
176
  - Each field in the editor displays a small **↩ "Inherited"** marker when its value matches the parent and **● "Override"** when it differs.
182
177
  - A row-level "Reset to inherited" action removes the override (deletes the record at the current scope if all overrides are reset).
183
178
 
184
- Apps don't have to implement this themselves — the `<RecordEditor>` primitive in `@proveanything/ui-utils` does it given the resolved parent payload.
179
+ Apps don't have to implement this themselves — the `<RecordEditor>` primitive in `@proveanything/smartlinks-utils-ui` does it given the resolved parent payload.
185
180
 
186
181
  ---
187
182
 
@@ -195,23 +190,19 @@ Standard verbs every shell should expose:
195
190
  | **Copy from** | Pick a source scope, copy its payload to the current scope. |
196
191
  | **Clear** | Delete records at the current scope (children unaffected). |
197
192
 
198
- The SDK does not currently expose a bulk write endpoint for records. Implement these by fanning out individual `app.records.create` / `app.records.update` calls with bounded concurrency (e.g. 10 in-flight at a time):
193
+ Use the `bulkUpsert` / `bulkDelete` helpers from `@proveanything/smartlinks-utils-ui/records-admin`, which handle batching and error collection for you:
199
194
 
200
195
  ```ts
201
- import { app } from '@proveanything/smartlinks';
196
+ import { bulkUpsert, bulkDelete } from '@proveanything/smartlinks-utils-ui/records-admin';
202
197
 
203
- // fan-out with concurrency cap
204
- const chunks = chunkArray(targetRefs, 10);
205
- for (const chunk of chunks) {
206
- await Promise.all(
207
- chunk.map((ref) =>
208
- app.records.update(collectionId, appId, refToRecordId(ref), { data: payload }, true)
209
- )
210
- );
211
- }
198
+ // Apply current payload to many refs
199
+ await bulkUpsert({ SL, collectionId, appId, recordType, refs: targetRefs, data: payload });
200
+
201
+ // Clear records at selected refs
202
+ await bulkDelete({ SL, collectionId, appId, recordType, refs: targetRefs });
212
203
  ```
213
204
 
214
- If you are writing to **hundreds** of records at once, **flag it** to the platform teama batch endpoint should be added rather than saturating the API from the browser.
205
+ If you are using `<RecordsAdminShell>`, the bulk actions menu is included and wired automatically via the `onTelemetry` hookyou don't call these directly unless you're building a custom shell.
215
206
 
216
207
  ---
217
208
 
@@ -238,7 +229,7 @@ facet,bread_type/sourdough,240,11.0,...
238
229
  To keep widgets consistent across apps, expose one hook per record type (implemented in `@proveanything/ui-utils`):
239
230
 
240
231
  ```ts
241
- import { useResolvedRecord } from '@proveanything/ui-utils/records';
232
+ import { useResolvedRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
242
233
 
243
234
  const { data, source, isLoading } = useResolvedRecord({
244
235
  appId,
@@ -269,7 +260,7 @@ All admin shells emit these events. The `<RecordsAdminShell>` from `@proveanythi
269
260
  ## 12. Required reading for app authors
270
261
 
271
262
  1. This document.
272
- 2. The companion **[UI utils guide](ui-utils.md)** — explains the React primitives that implement this pattern.
263
+ 2. The companion **[UI utils guide](ui-utils.md)** — explains the React primitives (`<RecordsAdminShell>`, `useResolvedRecord`, etc.) that implement this pattern.
273
264
  3. [PRODUCT_FACETS_SDK.md](PRODUCT_FACETS_SDK.md) — facet model.
274
265
  4. [app-data-storage.md](app-data-storage.md) — `app.records` surface.
275
266
 
@@ -279,9 +270,9 @@ All admin shells emit these events. The `<RecordsAdminShell>` from `@proveanythi
279
270
  - [ ] Move to `app.records` with `recordType` + `ref`.
280
271
  - [ ] Adopt the `ref` syntax in §3.
281
272
  - [ ] Add a `records` block to `app.manifest.json`.
282
- - [ ] Replace bespoke admin browser with `<RecordsAdminShell>` from `@proveanything/ui-utils`.
273
+ - [ ] Replace bespoke admin browser with `<RecordsAdminShell>` from `@proveanything/smartlinks-utils-ui`.
283
274
  - [ ] Replace bespoke public hook with `useResolvedRecord`.
284
- - [ ] Remove any "is variants enabled?" config — probe instead (§6).
275
+ - [ ] Remove any "is variants enabled?" config — use `collection.variants` / `collection.batches` flags instead (§6).
285
276
 
286
277
  ---
287
278
 
@@ -1,67 +1,133 @@
1
- # SmartLinks UI Utils (`@proveanything/ui-utils`)
1
+ # SmartLinks UI Utils (`@proveanything/smartlinks-utils-ui`)
2
2
 
3
- > Companion module to the SmartLinks SDK. Provides React primitives, hooks, and admin shells for building consistent microapp UIs.
3
+ > Companion React component library for the SmartLinks SDK. Ships the heavy, opinionated admin UI pieces that almost every SmartLinks microapp ends up needing — built once, theme-able, tree-shakeable, and wired straight into the SmartLinks SDK.
4
4
  >
5
- > Package: `@proveanything/ui-utils`
6
- > Install: `npm install @proveanything/ui-utils`
5
+ > Package: `@proveanything/smartlinks-utils-ui`
6
+ > Tracks: `@proveanything/smartlinks 1.9`
7
7
 
8
8
  ---
9
9
 
10
10
  ## What is this module for?
11
11
 
12
- `@proveanything/ui-utils` sits on top of `@proveanything/smartlinks`. The core SDK handles data — authentication, records, configurations, interactions. This module handles **UI** — the shared React components, hooks, and shell primitives that translate SDK data into consistent admin and public interfaces.
12
+ `@proveanything/smartlinks-utils-ui` sits on top of `@proveanything/smartlinks`. The core SDK handles data — records, configurations, interactions. This module handles **UI** — the shared React components, hooks, and admin shells that translate SDK data into consistent admin interfaces.
13
13
 
14
14
  **When do you need it?**
15
15
 
16
16
  - You are building an admin UI for a records-based microapp (see [records-admin-pattern.md](records-admin-pattern.md))
17
+ - You need a media asset picker, icon picker, or font picker in an admin panel
18
+ - You need a recursive rule/conditions editor for targeting or audience logic
17
19
  - You want the standard inheritance/override editor for scoped records
18
20
  - You need the `useResolvedRecord` hook on the public widget side
19
- - You are using `parseRef` / `buildRef` for the standard `ref` convention
20
21
 
21
22
  You do **not** need it for apps that only use `appConfiguration`, basic widgets without scoped data, or executor bundles.
22
23
 
24
+ > **Admin-only**: all components call the SDK with `admin: true`. Do not render them in public-facing views.
25
+
23
26
  ---
24
27
 
25
- ## Key exports
28
+ ## Install
29
+
30
+ ```bash
31
+ npm install @proveanything/smartlinks-utils-ui
32
+ ```
33
+
34
+ Peer dependencies (you already have these in a SmartLinks app):
35
+
36
+ ```bash
37
+ npm install react react-dom @proveanything/smartlinks
38
+ # Recommended — enables caching and pagination in Records Admin Shell:
39
+ npm install @tanstack/react-query
40
+ ```
26
41
 
27
- ### Records admin shell
42
+ Import the compiled styles **once** in your app entry (e.g. `main.tsx`):
28
43
 
29
44
  ```tsx
30
- import { RecordsAdminShell } from '@proveanything/ui-utils';
45
+ import '@proveanything/smartlinks-utils-ui/styles.css';
46
+ ```
47
+
48
+ Components inherit your shadcn-compatible CSS variables (`--primary`, `--background`, `--border`, …) so they pick up your theme automatically.
49
+
50
+ ---
31
51
 
32
- // Drop-in admin interface for any records-based app.
33
- // Reads the app's manifest `records` block to determine tabs and scopes.
34
- <RecordsAdminShell
52
+ ## What's in the box
53
+
54
+ | Module | What it is | When to reach for it |
55
+ |--------|------------|----------------------|
56
+ | [Records Admin Shell](#records-admin-shell) | Full admin UI for `app.records` with scope inheritance | Per-product / per-variant / per-batch / per-facet config tools |
57
+ | [Asset Picker](#asset-picker) | Browse / upload / paste / URL-import images and files | Any time the admin needs to pick or upload media |
58
+ | [Icon Picker](#icon-picker) | Searchable Font Awesome 7 Pro picker | Configurable buttons, badges, menus, tiles |
59
+ | [Font Picker](#font-picker) | Google Fonts + custom uploaded fonts | Theme editors, brand customisation panels |
60
+ | [Conditions Editor](#conditions-editor) | Recursive AND/OR rule builder for 12 condition types | Targeting, gating, audience rules, segmentation |
61
+
62
+ ---
63
+
64
+ ## Records Admin Shell
65
+
66
+ The primary export. A complete admin UI for managing `app.records` — typed JSON blobs attached to facets, products, variants, and batches — with scope inheritance built in. You provide the form for one record; the shell handles everything else.
67
+
68
+ ```tsx
69
+ import {
70
+ RecordsAdminShell,
71
+ InheritanceMarker,
72
+ type EditorContext,
73
+ } from '@proveanything/smartlinks-utils-ui/records-admin';
74
+ import * as SL from '@proveanything/smartlinks';
75
+
76
+ <RecordsAdminShell<NutritionData>
77
+ SL={SL}
35
78
  collectionId={collectionId}
36
79
  appId={appId}
37
80
  recordType="nutrition"
38
- renderForm={(record, parentRecord) => <NutritionForm record={record} parent={parentRecord} />}
81
+ label="Nutrition info"
82
+ scopes={['facet', 'product', 'variant', 'batch']}
83
+ contextScope={{ productId, variantId, batchId }} // from iframe URL — optional
84
+ defaultData={() => ({})}
85
+ csvSchema={{ columns: [/* ... */] }}
86
+ renderEditor={(ctx) => <NutritionForm ctx={ctx} />}
87
+ renderPreview={({ resolved }) => <pre>{JSON.stringify(resolved, null, 2)}</pre>}
39
88
  />
40
89
  ```
41
90
 
42
- `RecordsAdminShell` handles: left-rail product/scope browsing, variant/batch tab discovery, the "New record" button, inheritance markers, bulk operations, CSV import/export, and telemetry events. Your app only provides the domain form.
91
+ **What it handles:**
92
+
93
+ - **Browser pane** with scope tabs (Facet / Product / Variant / Batch), search, and status filter pills (All / Configured / Partial / Empty)
94
+ - **Editor pane** with sticky save / discard / delete footer
95
+ - **Per-field `<InheritanceMarker>`** showing whether a value is the record's own or inherited from a parent scope, with one-click revert-to-inherited
96
+ - **Inheritance resolver** walks `batch → variant → product → facet` and returns both the resolved and parent values
97
+ - **Collection-aware tabs**: calls `collection.get` and hides Variants / Batches tabs unless `collection.variants` / `collection.batches` are true — no flicker
98
+ - **Server-side pagination** via `useInfiniteQuery` — handles thousands of products with a "Load more" button
99
+ - **Context-aware**: pass `contextScope` from your iframe URL (`productId` / `variantId` / `batchId`) and the browser is constrained to that subtree with the right tab auto-selected
100
+ - **CSV import / export** with a schema you define; failed rows come back as an annotated CSV
101
+ - **Bulk actions menu** (apply-to-many, copy-from, clear) via `bulkUpsert` / `bulkDelete`
102
+ - **Telemetry hook** (`onTelemetry`) emits typed events for save, delete, scope change, CSV import/export, bulk apply
103
+ - **i18n strings** fully overridable
104
+
105
+ > Requires a `<QueryClientProvider>` from `@tanstack/react-query` somewhere up the tree.
43
106
 
44
- ### Record editor primitive
107
+ ### Lower-level pieces (advanced use)
45
108
 
46
109
  ```tsx
47
- import { RecordEditor } from '@proveanything/ui-utils';
48
-
49
- // Lower-level editor with inheritance diffing. Use when you need
50
- // custom layout but still want the inherited/override markers.
51
- <RecordEditor
52
- record={variantRecord}
53
- parentRecord={productRecord}
54
- fields={nutritionFields}
55
- onSave={(data) => app.records.update(collectionId, appId, record.id, { data }, true)}
56
- />
110
+ import {
111
+ // Hooks
112
+ useRecordList, useRecordEditor, useResolvedRecord, useScopeProbe,
113
+ // Data helpers
114
+ parseRef, buildRef, resolutionChain,
115
+ listRecords, getRecordByRef, upsertRecord, deleteRecord,
116
+ bulkUpsert, bulkDelete,
117
+ exportCsv, importCsv, downloadBlob,
118
+ // UI pieces
119
+ RecordBrowser, RecordEditor, ScopeBreadcrumb,
120
+ InheritanceMarker, ResolvedPreview, BulkActionsMenu,
121
+ } from '@proveanything/smartlinks-utils-ui/records-admin';
57
122
  ```
58
123
 
59
124
  ### `useResolvedRecord` hook
60
125
 
61
126
  ```ts
62
- import { useResolvedRecord } from '@proveanything/ui-utils/records';
127
+ import { useResolvedRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
63
128
 
64
129
  const { data, source, isLoading } = useResolvedRecord({
130
+ SL,
65
131
  appId,
66
132
  recordType: 'nutrition',
67
133
  collectionId,
@@ -73,27 +139,12 @@ const { data, source, isLoading } = useResolvedRecord({
73
139
  // source: 'proof' | 'batch' | 'variant' | 'product' | 'facet' | 'default' | null
74
140
  ```
75
141
 
76
- Walks the resolution chain defined in [records-admin-pattern.md §4](records-admin-pattern.md#4-resolution-order-required) and returns the first matching record plus its source scope.
77
-
78
- ### `resolveRecord` function
79
-
80
- ```ts
81
- import { resolveRecord } from '@proveanything/ui-utils/records';
82
-
83
- // Async, non-hook version for executors and server-side logic.
84
- const resolved = await resolveRecord({
85
- appId,
86
- recordType: 'nutrition',
87
- scope: { collectionId, productId, variantId, batchId, proofId },
88
- supportedScopes: ['product', 'facet'], // optional — skip unsupported scopes
89
- });
90
- // → { record: AppRecord, source: string } | null
91
- ```
142
+ Walks the resolution chain defined in [records-admin-pattern.md §4](records-admin-pattern.md#4-resolution-order-required). Use this on the **public widget side** to read the correct value for a given context.
92
143
 
93
144
  ### `parseRef` / `buildRef` utilities
94
145
 
95
146
  ```ts
96
- import { parseRef, buildRef } from '@proveanything/ui-utils/records';
147
+ import { parseRef, buildRef } from '@proveanything/smartlinks-utils-ui/records-admin';
97
148
 
98
149
  const parsed = parseRef('variant:prod_abc:var_500ml');
99
150
  // → { kind: 'variant', productId: 'prod_abc', variantId: 'var_500ml' }
@@ -102,26 +153,142 @@ const ref = buildRef({ kind: 'facet', facetKey: 'bread_type', valueKey: 'sourdou
102
153
  // → 'facet:bread_type:sourdough'
103
154
  ```
104
155
 
105
- See [records-admin-pattern.md Appendix A](records-admin-pattern.md#appendix-a--ref-parser-reference) for the full `ParsedRef` type and standalone implementation.
156
+ See [records-admin-pattern.md Appendix A](records-admin-pattern.md#appendix-a--ref-parser-reference) for the full `ParsedRef` type.
157
+
158
+ ---
159
+
160
+ ## Asset Picker
161
+
162
+ ```tsx
163
+ import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
164
+
165
+ <AssetPicker
166
+ scope={{ type: 'collection', collectionId: 'abc123' }}
167
+ mode="dialog" // or "inline"
168
+ allowUpload
169
+ accept={['image/*']} // MIME filtering
170
+ onSelect={(asset) => setHeroUrl(asset.url)}
171
+ trigger={<Button>Choose image</Button>}
172
+ />
173
+ ```
174
+
175
+ **What it does:**
176
+ - Browses assets at **collection** or **product** scope (dual-scope tabs)
177
+ - Four ingest paths: file upload, URL import, clipboard paste (with rename preview), and selection from existing assets
178
+ - Inline mode for embedding in a panel; dialog mode for modal pickers
179
+ - Double-click a tile to confirm instantly
180
+
181
+ ---
182
+
183
+ ## Icon Picker
184
+
185
+ ```tsx
186
+ import { IconPicker } from '@proveanything/smartlinks-utils-ui/icon-picker';
187
+
188
+ <IconPicker
189
+ mode="dialog"
190
+ value="fa-solid fa-heart"
191
+ onSelect={(icon) => setIcon(icon.name)}
192
+ trigger={<Button>Pick icon</Button>}
193
+ />
194
+ ```
195
+
196
+ **What it does:**
197
+ - Searches **Font Awesome 7 Pro** (uses the shared kit `75493b59b3`)
198
+ - Family hierarchy: Classic (Solid / Regular / Light), Duotone, and Brands
199
+ - Search-first with a background catalogue crawler — first results appear instantly, the full index fills in behind
200
+ - Auto-switches families intelligently (e.g. brand searches surface brand icons even when "Classic" is selected)
201
+
202
+ > Requires the FA kit script to be present on the host page.
203
+
204
+ ---
205
+
206
+ ## Font Picker
207
+
208
+ ```tsx
209
+ import { FontPicker } from '@proveanything/smartlinks-utils-ui/font-picker';
210
+
211
+ <FontPicker
212
+ mode="dialog"
213
+ value="Inter"
214
+ showPreview
215
+ onSelect={(font) => {
216
+ console.log(font.family); // "Inter"
217
+ console.log(font.cssFontFamily); // "'Inter', ui-sans-serif, system-ui, sans-serif"
218
+ console.log(font.loadSnippet); // <link href="..." rel="stylesheet">
219
+ }}
220
+ />
221
+
222
+ {/* With custom fonts uploaded into the collection */}
223
+ <FontPicker
224
+ mode="dialog"
225
+ showCustomFonts
226
+ scope={{ collectionId: 'abc123' }}
227
+ onSelect={(font) => /* ... */}
228
+ />
229
+ ```
230
+
231
+ **What it does:**
232
+ - Full **Google Fonts** catalogue plus any **custom fonts** uploaded for the brand (stored via `appConfiguration` under `customFonts`)
233
+ - Upload zone auto-detects weight/style from the filename (e.g. `MyFont-BoldItalic.woff2`)
234
+ - Returns a `FontSelection` with `family`, `cssFontFamily`, and a ready-to-inject `loadSnippet` (`<link>` for Google fonts, `@font-face` CSS for custom uploads)
235
+ - Lazy-loads previews via `IntersectionObserver`; includes a management UI for editing custom font definitions
236
+
237
+ ---
238
+
239
+ ## Conditions Editor
240
+
241
+ ```tsx
242
+ import { ConditionsEditor } from '@proveanything/smartlinks-utils-ui/conditions-editor';
243
+
244
+ <ConditionsEditor
245
+ value={rules}
246
+ onChange={setRules}
247
+ collectionId={collectionId} // auto-loads facet definitions
248
+ versions={[{ title: 'Default', value: '' }]}
249
+ tags={['featured', 'new']}
250
+ />
251
+ ```
252
+
253
+ **What it does:**
254
+ - Recursive AND / OR group builder — nest conditions to any depth
255
+ - **12 condition types:** Version, Country, Value, User, Date, Device, Tag, Facet, Geofence, Product, Item Status, Condition Reference
256
+ - **Facet condition** auto-fetches definitions from `facets.publicList(collectionId, { includeValues: true })` when only `collectionId` is passed (SDK ≥ 1.9.20)
257
+ - **Country picker** is a searchable multi-select with removable ISO 3166-1 chips and a "Use regions" toggle
258
+ - Renders correctly inside iframe contexts — avoids `overflow-hidden` so dropdowns escape their cards
259
+
260
+ ---
261
+
262
+ ## Tree shaking
263
+
264
+ Each component has its own subpath export:
265
+
266
+ ```tsx
267
+ // Bundles only the Asset Picker
268
+ import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
269
+
270
+ // Barrel import — bundler tree-shakes the rest
271
+ import { AssetPicker } from '@proveanything/smartlinks-utils-ui';
272
+ ```
273
+
274
+ If you use subpath imports, import `styles.css` separately — subpaths do not pull it in automatically.
106
275
 
107
276
  ---
108
277
 
109
278
  ## Relationship to the core SDK
110
279
 
111
280
  ```
112
- @proveanything/smartlinks ← data layer (records, config, interactions, …)
281
+ @proveanything/smartlinks ← data layer (records, config, interactions, …)
113
282
 
114
- @proveanything/ui-utils ← UI layer (components, hooks, admin shells)
283
+ @proveanything/smartlinks-utils-ui ← UI layer (components, hooks, admin shells)
115
284
 
116
- your microapp ← domain logic and custom forms
285
+ your microapp ← domain logic and custom forms
117
286
  ```
118
287
 
119
- `ui-utils` imports from `@proveanything/smartlinks` internally. You should install both packages, but only import directly from each for their respective concerns — don't reach into `ui-utils` for SDK data primitives.
120
-
121
288
  ---
122
289
 
123
290
  ## Further reading
124
291
 
125
292
  - [records-admin-pattern.md](records-admin-pattern.md) — the data contract that `RecordsAdminShell` implements
126
- - [building-react-components.md](building-react-components.md) — dual-mode rendering rules that apply to all ui-utils components
293
+ - [building-react-components.md](building-react-components.md) — dual-mode rendering rules that apply to all components
127
294
  - [app-manifest.md](app-manifest.md) — the `records` manifest block that drives tab generation
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.9.21 | Generated: 2026-04-25T10:48:10.932Z
3
+ Version: 1.9.22 | Generated: 2026-04-25T11:11:00.344Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
package/docs/overview.md CHANGED
@@ -67,7 +67,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
67
67
  | **Forms** | `docs/forms.md` | Form definitions, schema-driven rendering, submission patterns |
68
68
  | **Auth Kit** | `docs/auth-kit.md` | End-user sign-in: email/password, magic links, phone OTP, Google OAuth |
69
69
  | **Records Admin Pattern** | `docs/records-admin-pattern.md` | Standard pattern for per-product/facet/variant/batch admin UIs |
70
- | **UI Utils** | `docs/ui-utils.md` | `@proveanything/ui-utils` — React shells, hooks, and primitives for records-based apps |
70
+ | **UI Utils** | `docs/ui-utils.md` | `@proveanything/smartlinks-utils-ui` — React shells, hooks, and primitives for records-based apps |
71
71
 
72
72
  ---
73
73
 
@@ -79,7 +79,7 @@ Notes:
79
79
  - Variants and batches are **always nested under a product** in the SDK, so their refs include `productId`.
80
80
  - `facet:` refs are **collection-wide** — facets cross products by design.
81
81
  - `default` is a single record per (app, recordType) used as the global fallback.
82
- - Refs are opaque to the SDK. Apps parse them. A helper module (`@proveanything/ui-utils/records`) exports `parseRef`/`buildRef` so all apps agree on syntax. See Appendix A for the full implementation.
82
+ - Refs are opaque to the SDK. Apps parse them. A helper module (`@proveanything/smartlinks-utils-ui/records-admin`) exports `parseRef`/`buildRef` so all apps agree on syntax. See Appendix A for the full implementation.
83
83
 
84
84
  ### Adding scopes later
85
85
 
@@ -99,10 +99,10 @@ proof → batch → variant → product → facet(*) → default
99
99
 
100
100
  > **Rule:** Resolution is **first-match-wins, not merge**. If you need field-level merging, build it on top with explicit `inheritsFrom` markers in the payload — but the default for shared infra is whole-record replacement, because it is far easier to reason about.
101
101
 
102
- The canonical resolver lives in `@proveanything/ui-utils/records` so every app behaves identically:
102
+ The canonical resolver lives in `@proveanything/smartlinks-utils-ui/records-admin` so every app behaves identically:
103
103
 
104
104
  ```ts
105
- import { resolveRecord } from '@proveanything/ui-utils/records';
105
+ import { resolveRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
106
106
 
107
107
  const resolved = await resolveRecord({
108
108
  appId,
@@ -147,29 +147,24 @@ See [app-manifest.md](app-manifest.md) for the full schema reference.
147
147
 
148
148
  ## 6. Discovering whether variants / batches are in use
149
149
 
150
- The SDK does **not** expose a `collection.variantsEnabled` flag, by design. Whether a product has variants/batches is an **emergent** property: it has them iff someone created some.
151
-
152
- The standard probe pattern:
150
+ The `Collection` object exposes top-level `variants: boolean` and `batches: boolean` flags that indicate whether the collection has these features enabled. Read them directly rather than probing by listing:
153
151
 
154
152
  ```ts
155
- import { variant, batch } from '@proveanything/smartlinks';
153
+ import { appConfiguration } from '@proveanything/smartlinks';
156
154
 
157
- const [variants, batches] = await Promise.all([
158
- variant.list(collectionId, productId).catch(() => []),
159
- batch.list(collectionId, productId).catch(() => []),
160
- ]);
155
+ const collection = await appConfiguration.getCollection(collectionId);
161
156
 
162
- // scopeConfig is the parsed manifest records entry for this recordType
163
- // e.g. manifest.records?.nutrition → { scopes: ['product', 'facet', 'batch'], ... }
164
- const showVariantTab = variants.length > 0 || scopeConfig?.scopes.includes('variant') === true;
165
- const showBatchTab = batches.length > 0 || scopeConfig?.scopes.includes('batch') === true;
157
+ const showVariantTab = collection.variants && scopeConfig?.scopes.includes('variant') === true;
158
+ const showBatchTab = collection.batches && scopeConfig?.scopes.includes('batch') === true;
166
159
  ```
167
160
 
161
+ `scopeConfig` is the parsed manifest `records` entry for this `recordType` — e.g. `manifest.records?.nutrition`.
162
+
168
163
  Rules:
169
164
 
170
- 1. If the app **declares** support for the scope, always offer "Add variant" / "Add batch" affordances even when the list is empty.
171
- 2. If the app does **not** declare support, hide the tab entirely even if some exist (another app created them).
172
- 3. Cache list results per `(collectionId, productId)` with a short TTL the shell will hit them often.
165
+ 1. If the collection has the feature **and** the app **declares** support for the scope, show the tab and offer "Add variant" / "Add batch" affordances.
166
+ 2. If the app does **not** declare support, hide the tab entirely even if `collection.variants` is true (another app created them).
167
+ 3. If the collection does not have the feature enabled, hide the tab even if the app declares support.
173
168
 
174
169
  ---
175
170
 
@@ -181,7 +176,7 @@ Because resolution is first-match-wins, the admin UI must make inheritance **vis
181
176
  - Each field in the editor displays a small **↩ "Inherited"** marker when its value matches the parent and **● "Override"** when it differs.
182
177
  - A row-level "Reset to inherited" action removes the override (deletes the record at the current scope if all overrides are reset).
183
178
 
184
- Apps don't have to implement this themselves — the `<RecordEditor>` primitive in `@proveanything/ui-utils` does it given the resolved parent payload.
179
+ Apps don't have to implement this themselves — the `<RecordEditor>` primitive in `@proveanything/smartlinks-utils-ui` does it given the resolved parent payload.
185
180
 
186
181
  ---
187
182
 
@@ -195,23 +190,19 @@ Standard verbs every shell should expose:
195
190
  | **Copy from** | Pick a source scope, copy its payload to the current scope. |
196
191
  | **Clear** | Delete records at the current scope (children unaffected). |
197
192
 
198
- The SDK does not currently expose a bulk write endpoint for records. Implement these by fanning out individual `app.records.create` / `app.records.update` calls with bounded concurrency (e.g. 10 in-flight at a time):
193
+ Use the `bulkUpsert` / `bulkDelete` helpers from `@proveanything/smartlinks-utils-ui/records-admin`, which handle batching and error collection for you:
199
194
 
200
195
  ```ts
201
- import { app } from '@proveanything/smartlinks';
196
+ import { bulkUpsert, bulkDelete } from '@proveanything/smartlinks-utils-ui/records-admin';
202
197
 
203
- // fan-out with concurrency cap
204
- const chunks = chunkArray(targetRefs, 10);
205
- for (const chunk of chunks) {
206
- await Promise.all(
207
- chunk.map((ref) =>
208
- app.records.update(collectionId, appId, refToRecordId(ref), { data: payload }, true)
209
- )
210
- );
211
- }
198
+ // Apply current payload to many refs
199
+ await bulkUpsert({ SL, collectionId, appId, recordType, refs: targetRefs, data: payload });
200
+
201
+ // Clear records at selected refs
202
+ await bulkDelete({ SL, collectionId, appId, recordType, refs: targetRefs });
212
203
  ```
213
204
 
214
- If you are writing to **hundreds** of records at once, **flag it** to the platform teama batch endpoint should be added rather than saturating the API from the browser.
205
+ If you are using `<RecordsAdminShell>`, the bulk actions menu is included and wired automatically via the `onTelemetry` hookyou don't call these directly unless you're building a custom shell.
215
206
 
216
207
  ---
217
208
 
@@ -238,7 +229,7 @@ facet,bread_type/sourdough,240,11.0,...
238
229
  To keep widgets consistent across apps, expose one hook per record type (implemented in `@proveanything/ui-utils`):
239
230
 
240
231
  ```ts
241
- import { useResolvedRecord } from '@proveanything/ui-utils/records';
232
+ import { useResolvedRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
242
233
 
243
234
  const { data, source, isLoading } = useResolvedRecord({
244
235
  appId,
@@ -269,7 +260,7 @@ All admin shells emit these events. The `<RecordsAdminShell>` from `@proveanythi
269
260
  ## 12. Required reading for app authors
270
261
 
271
262
  1. This document.
272
- 2. The companion **[UI utils guide](ui-utils.md)** — explains the React primitives that implement this pattern.
263
+ 2. The companion **[UI utils guide](ui-utils.md)** — explains the React primitives (`<RecordsAdminShell>`, `useResolvedRecord`, etc.) that implement this pattern.
273
264
  3. [PRODUCT_FACETS_SDK.md](PRODUCT_FACETS_SDK.md) — facet model.
274
265
  4. [app-data-storage.md](app-data-storage.md) — `app.records` surface.
275
266
 
@@ -279,9 +270,9 @@ All admin shells emit these events. The `<RecordsAdminShell>` from `@proveanythi
279
270
  - [ ] Move to `app.records` with `recordType` + `ref`.
280
271
  - [ ] Adopt the `ref` syntax in §3.
281
272
  - [ ] Add a `records` block to `app.manifest.json`.
282
- - [ ] Replace bespoke admin browser with `<RecordsAdminShell>` from `@proveanything/ui-utils`.
273
+ - [ ] Replace bespoke admin browser with `<RecordsAdminShell>` from `@proveanything/smartlinks-utils-ui`.
283
274
  - [ ] Replace bespoke public hook with `useResolvedRecord`.
284
- - [ ] Remove any "is variants enabled?" config — probe instead (§6).
275
+ - [ ] Remove any "is variants enabled?" config — use `collection.variants` / `collection.batches` flags instead (§6).
285
276
 
286
277
  ---
287
278
 
package/docs/ui-utils.md CHANGED
@@ -1,67 +1,133 @@
1
- # SmartLinks UI Utils (`@proveanything/ui-utils`)
1
+ # SmartLinks UI Utils (`@proveanything/smartlinks-utils-ui`)
2
2
 
3
- > Companion module to the SmartLinks SDK. Provides React primitives, hooks, and admin shells for building consistent microapp UIs.
3
+ > Companion React component library for the SmartLinks SDK. Ships the heavy, opinionated admin UI pieces that almost every SmartLinks microapp ends up needing — built once, theme-able, tree-shakeable, and wired straight into the SmartLinks SDK.
4
4
  >
5
- > Package: `@proveanything/ui-utils`
6
- > Install: `npm install @proveanything/ui-utils`
5
+ > Package: `@proveanything/smartlinks-utils-ui`
6
+ > Tracks: `@proveanything/smartlinks 1.9`
7
7
 
8
8
  ---
9
9
 
10
10
  ## What is this module for?
11
11
 
12
- `@proveanything/ui-utils` sits on top of `@proveanything/smartlinks`. The core SDK handles data — authentication, records, configurations, interactions. This module handles **UI** — the shared React components, hooks, and shell primitives that translate SDK data into consistent admin and public interfaces.
12
+ `@proveanything/smartlinks-utils-ui` sits on top of `@proveanything/smartlinks`. The core SDK handles data — records, configurations, interactions. This module handles **UI** — the shared React components, hooks, and admin shells that translate SDK data into consistent admin interfaces.
13
13
 
14
14
  **When do you need it?**
15
15
 
16
16
  - You are building an admin UI for a records-based microapp (see [records-admin-pattern.md](records-admin-pattern.md))
17
+ - You need a media asset picker, icon picker, or font picker in an admin panel
18
+ - You need a recursive rule/conditions editor for targeting or audience logic
17
19
  - You want the standard inheritance/override editor for scoped records
18
20
  - You need the `useResolvedRecord` hook on the public widget side
19
- - You are using `parseRef` / `buildRef` for the standard `ref` convention
20
21
 
21
22
  You do **not** need it for apps that only use `appConfiguration`, basic widgets without scoped data, or executor bundles.
22
23
 
24
+ > **Admin-only**: all components call the SDK with `admin: true`. Do not render them in public-facing views.
25
+
23
26
  ---
24
27
 
25
- ## Key exports
28
+ ## Install
29
+
30
+ ```bash
31
+ npm install @proveanything/smartlinks-utils-ui
32
+ ```
33
+
34
+ Peer dependencies (you already have these in a SmartLinks app):
35
+
36
+ ```bash
37
+ npm install react react-dom @proveanything/smartlinks
38
+ # Recommended — enables caching and pagination in Records Admin Shell:
39
+ npm install @tanstack/react-query
40
+ ```
26
41
 
27
- ### Records admin shell
42
+ Import the compiled styles **once** in your app entry (e.g. `main.tsx`):
28
43
 
29
44
  ```tsx
30
- import { RecordsAdminShell } from '@proveanything/ui-utils';
45
+ import '@proveanything/smartlinks-utils-ui/styles.css';
46
+ ```
47
+
48
+ Components inherit your shadcn-compatible CSS variables (`--primary`, `--background`, `--border`, …) so they pick up your theme automatically.
49
+
50
+ ---
31
51
 
32
- // Drop-in admin interface for any records-based app.
33
- // Reads the app's manifest `records` block to determine tabs and scopes.
34
- <RecordsAdminShell
52
+ ## What's in the box
53
+
54
+ | Module | What it is | When to reach for it |
55
+ |--------|------------|----------------------|
56
+ | [Records Admin Shell](#records-admin-shell) | Full admin UI for `app.records` with scope inheritance | Per-product / per-variant / per-batch / per-facet config tools |
57
+ | [Asset Picker](#asset-picker) | Browse / upload / paste / URL-import images and files | Any time the admin needs to pick or upload media |
58
+ | [Icon Picker](#icon-picker) | Searchable Font Awesome 7 Pro picker | Configurable buttons, badges, menus, tiles |
59
+ | [Font Picker](#font-picker) | Google Fonts + custom uploaded fonts | Theme editors, brand customisation panels |
60
+ | [Conditions Editor](#conditions-editor) | Recursive AND/OR rule builder for 12 condition types | Targeting, gating, audience rules, segmentation |
61
+
62
+ ---
63
+
64
+ ## Records Admin Shell
65
+
66
+ The primary export. A complete admin UI for managing `app.records` — typed JSON blobs attached to facets, products, variants, and batches — with scope inheritance built in. You provide the form for one record; the shell handles everything else.
67
+
68
+ ```tsx
69
+ import {
70
+ RecordsAdminShell,
71
+ InheritanceMarker,
72
+ type EditorContext,
73
+ } from '@proveanything/smartlinks-utils-ui/records-admin';
74
+ import * as SL from '@proveanything/smartlinks';
75
+
76
+ <RecordsAdminShell<NutritionData>
77
+ SL={SL}
35
78
  collectionId={collectionId}
36
79
  appId={appId}
37
80
  recordType="nutrition"
38
- renderForm={(record, parentRecord) => <NutritionForm record={record} parent={parentRecord} />}
81
+ label="Nutrition info"
82
+ scopes={['facet', 'product', 'variant', 'batch']}
83
+ contextScope={{ productId, variantId, batchId }} // from iframe URL — optional
84
+ defaultData={() => ({})}
85
+ csvSchema={{ columns: [/* ... */] }}
86
+ renderEditor={(ctx) => <NutritionForm ctx={ctx} />}
87
+ renderPreview={({ resolved }) => <pre>{JSON.stringify(resolved, null, 2)}</pre>}
39
88
  />
40
89
  ```
41
90
 
42
- `RecordsAdminShell` handles: left-rail product/scope browsing, variant/batch tab discovery, the "New record" button, inheritance markers, bulk operations, CSV import/export, and telemetry events. Your app only provides the domain form.
91
+ **What it handles:**
92
+
93
+ - **Browser pane** with scope tabs (Facet / Product / Variant / Batch), search, and status filter pills (All / Configured / Partial / Empty)
94
+ - **Editor pane** with sticky save / discard / delete footer
95
+ - **Per-field `<InheritanceMarker>`** showing whether a value is the record's own or inherited from a parent scope, with one-click revert-to-inherited
96
+ - **Inheritance resolver** walks `batch → variant → product → facet` and returns both the resolved and parent values
97
+ - **Collection-aware tabs**: calls `collection.get` and hides Variants / Batches tabs unless `collection.variants` / `collection.batches` are true — no flicker
98
+ - **Server-side pagination** via `useInfiniteQuery` — handles thousands of products with a "Load more" button
99
+ - **Context-aware**: pass `contextScope` from your iframe URL (`productId` / `variantId` / `batchId`) and the browser is constrained to that subtree with the right tab auto-selected
100
+ - **CSV import / export** with a schema you define; failed rows come back as an annotated CSV
101
+ - **Bulk actions menu** (apply-to-many, copy-from, clear) via `bulkUpsert` / `bulkDelete`
102
+ - **Telemetry hook** (`onTelemetry`) emits typed events for save, delete, scope change, CSV import/export, bulk apply
103
+ - **i18n strings** fully overridable
104
+
105
+ > Requires a `<QueryClientProvider>` from `@tanstack/react-query` somewhere up the tree.
43
106
 
44
- ### Record editor primitive
107
+ ### Lower-level pieces (advanced use)
45
108
 
46
109
  ```tsx
47
- import { RecordEditor } from '@proveanything/ui-utils';
48
-
49
- // Lower-level editor with inheritance diffing. Use when you need
50
- // custom layout but still want the inherited/override markers.
51
- <RecordEditor
52
- record={variantRecord}
53
- parentRecord={productRecord}
54
- fields={nutritionFields}
55
- onSave={(data) => app.records.update(collectionId, appId, record.id, { data }, true)}
56
- />
110
+ import {
111
+ // Hooks
112
+ useRecordList, useRecordEditor, useResolvedRecord, useScopeProbe,
113
+ // Data helpers
114
+ parseRef, buildRef, resolutionChain,
115
+ listRecords, getRecordByRef, upsertRecord, deleteRecord,
116
+ bulkUpsert, bulkDelete,
117
+ exportCsv, importCsv, downloadBlob,
118
+ // UI pieces
119
+ RecordBrowser, RecordEditor, ScopeBreadcrumb,
120
+ InheritanceMarker, ResolvedPreview, BulkActionsMenu,
121
+ } from '@proveanything/smartlinks-utils-ui/records-admin';
57
122
  ```
58
123
 
59
124
  ### `useResolvedRecord` hook
60
125
 
61
126
  ```ts
62
- import { useResolvedRecord } from '@proveanything/ui-utils/records';
127
+ import { useResolvedRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
63
128
 
64
129
  const { data, source, isLoading } = useResolvedRecord({
130
+ SL,
65
131
  appId,
66
132
  recordType: 'nutrition',
67
133
  collectionId,
@@ -73,27 +139,12 @@ const { data, source, isLoading } = useResolvedRecord({
73
139
  // source: 'proof' | 'batch' | 'variant' | 'product' | 'facet' | 'default' | null
74
140
  ```
75
141
 
76
- Walks the resolution chain defined in [records-admin-pattern.md §4](records-admin-pattern.md#4-resolution-order-required) and returns the first matching record plus its source scope.
77
-
78
- ### `resolveRecord` function
79
-
80
- ```ts
81
- import { resolveRecord } from '@proveanything/ui-utils/records';
82
-
83
- // Async, non-hook version for executors and server-side logic.
84
- const resolved = await resolveRecord({
85
- appId,
86
- recordType: 'nutrition',
87
- scope: { collectionId, productId, variantId, batchId, proofId },
88
- supportedScopes: ['product', 'facet'], // optional — skip unsupported scopes
89
- });
90
- // → { record: AppRecord, source: string } | null
91
- ```
142
+ Walks the resolution chain defined in [records-admin-pattern.md §4](records-admin-pattern.md#4-resolution-order-required). Use this on the **public widget side** to read the correct value for a given context.
92
143
 
93
144
  ### `parseRef` / `buildRef` utilities
94
145
 
95
146
  ```ts
96
- import { parseRef, buildRef } from '@proveanything/ui-utils/records';
147
+ import { parseRef, buildRef } from '@proveanything/smartlinks-utils-ui/records-admin';
97
148
 
98
149
  const parsed = parseRef('variant:prod_abc:var_500ml');
99
150
  // → { kind: 'variant', productId: 'prod_abc', variantId: 'var_500ml' }
@@ -102,26 +153,142 @@ const ref = buildRef({ kind: 'facet', facetKey: 'bread_type', valueKey: 'sourdou
102
153
  // → 'facet:bread_type:sourdough'
103
154
  ```
104
155
 
105
- See [records-admin-pattern.md Appendix A](records-admin-pattern.md#appendix-a--ref-parser-reference) for the full `ParsedRef` type and standalone implementation.
156
+ See [records-admin-pattern.md Appendix A](records-admin-pattern.md#appendix-a--ref-parser-reference) for the full `ParsedRef` type.
157
+
158
+ ---
159
+
160
+ ## Asset Picker
161
+
162
+ ```tsx
163
+ import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
164
+
165
+ <AssetPicker
166
+ scope={{ type: 'collection', collectionId: 'abc123' }}
167
+ mode="dialog" // or "inline"
168
+ allowUpload
169
+ accept={['image/*']} // MIME filtering
170
+ onSelect={(asset) => setHeroUrl(asset.url)}
171
+ trigger={<Button>Choose image</Button>}
172
+ />
173
+ ```
174
+
175
+ **What it does:**
176
+ - Browses assets at **collection** or **product** scope (dual-scope tabs)
177
+ - Four ingest paths: file upload, URL import, clipboard paste (with rename preview), and selection from existing assets
178
+ - Inline mode for embedding in a panel; dialog mode for modal pickers
179
+ - Double-click a tile to confirm instantly
180
+
181
+ ---
182
+
183
+ ## Icon Picker
184
+
185
+ ```tsx
186
+ import { IconPicker } from '@proveanything/smartlinks-utils-ui/icon-picker';
187
+
188
+ <IconPicker
189
+ mode="dialog"
190
+ value="fa-solid fa-heart"
191
+ onSelect={(icon) => setIcon(icon.name)}
192
+ trigger={<Button>Pick icon</Button>}
193
+ />
194
+ ```
195
+
196
+ **What it does:**
197
+ - Searches **Font Awesome 7 Pro** (uses the shared kit `75493b59b3`)
198
+ - Family hierarchy: Classic (Solid / Regular / Light), Duotone, and Brands
199
+ - Search-first with a background catalogue crawler — first results appear instantly, the full index fills in behind
200
+ - Auto-switches families intelligently (e.g. brand searches surface brand icons even when "Classic" is selected)
201
+
202
+ > Requires the FA kit script to be present on the host page.
203
+
204
+ ---
205
+
206
+ ## Font Picker
207
+
208
+ ```tsx
209
+ import { FontPicker } from '@proveanything/smartlinks-utils-ui/font-picker';
210
+
211
+ <FontPicker
212
+ mode="dialog"
213
+ value="Inter"
214
+ showPreview
215
+ onSelect={(font) => {
216
+ console.log(font.family); // "Inter"
217
+ console.log(font.cssFontFamily); // "'Inter', ui-sans-serif, system-ui, sans-serif"
218
+ console.log(font.loadSnippet); // <link href="..." rel="stylesheet">
219
+ }}
220
+ />
221
+
222
+ {/* With custom fonts uploaded into the collection */}
223
+ <FontPicker
224
+ mode="dialog"
225
+ showCustomFonts
226
+ scope={{ collectionId: 'abc123' }}
227
+ onSelect={(font) => /* ... */}
228
+ />
229
+ ```
230
+
231
+ **What it does:**
232
+ - Full **Google Fonts** catalogue plus any **custom fonts** uploaded for the brand (stored via `appConfiguration` under `customFonts`)
233
+ - Upload zone auto-detects weight/style from the filename (e.g. `MyFont-BoldItalic.woff2`)
234
+ - Returns a `FontSelection` with `family`, `cssFontFamily`, and a ready-to-inject `loadSnippet` (`<link>` for Google fonts, `@font-face` CSS for custom uploads)
235
+ - Lazy-loads previews via `IntersectionObserver`; includes a management UI for editing custom font definitions
236
+
237
+ ---
238
+
239
+ ## Conditions Editor
240
+
241
+ ```tsx
242
+ import { ConditionsEditor } from '@proveanything/smartlinks-utils-ui/conditions-editor';
243
+
244
+ <ConditionsEditor
245
+ value={rules}
246
+ onChange={setRules}
247
+ collectionId={collectionId} // auto-loads facet definitions
248
+ versions={[{ title: 'Default', value: '' }]}
249
+ tags={['featured', 'new']}
250
+ />
251
+ ```
252
+
253
+ **What it does:**
254
+ - Recursive AND / OR group builder — nest conditions to any depth
255
+ - **12 condition types:** Version, Country, Value, User, Date, Device, Tag, Facet, Geofence, Product, Item Status, Condition Reference
256
+ - **Facet condition** auto-fetches definitions from `facets.publicList(collectionId, { includeValues: true })` when only `collectionId` is passed (SDK ≥ 1.9.20)
257
+ - **Country picker** is a searchable multi-select with removable ISO 3166-1 chips and a "Use regions" toggle
258
+ - Renders correctly inside iframe contexts — avoids `overflow-hidden` so dropdowns escape their cards
259
+
260
+ ---
261
+
262
+ ## Tree shaking
263
+
264
+ Each component has its own subpath export:
265
+
266
+ ```tsx
267
+ // Bundles only the Asset Picker
268
+ import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
269
+
270
+ // Barrel import — bundler tree-shakes the rest
271
+ import { AssetPicker } from '@proveanything/smartlinks-utils-ui';
272
+ ```
273
+
274
+ If you use subpath imports, import `styles.css` separately — subpaths do not pull it in automatically.
106
275
 
107
276
  ---
108
277
 
109
278
  ## Relationship to the core SDK
110
279
 
111
280
  ```
112
- @proveanything/smartlinks ← data layer (records, config, interactions, …)
281
+ @proveanything/smartlinks ← data layer (records, config, interactions, …)
113
282
 
114
- @proveanything/ui-utils ← UI layer (components, hooks, admin shells)
283
+ @proveanything/smartlinks-utils-ui ← UI layer (components, hooks, admin shells)
115
284
 
116
- your microapp ← domain logic and custom forms
285
+ your microapp ← domain logic and custom forms
117
286
  ```
118
287
 
119
- `ui-utils` imports from `@proveanything/smartlinks` internally. You should install both packages, but only import directly from each for their respective concerns — don't reach into `ui-utils` for SDK data primitives.
120
-
121
288
  ---
122
289
 
123
290
  ## Further reading
124
291
 
125
292
  - [records-admin-pattern.md](records-admin-pattern.md) — the data contract that `RecordsAdminShell` implements
126
- - [building-react-components.md](building-react-components.md) — dual-mode rendering rules that apply to all ui-utils components
293
+ - [building-react-components.md](building-react-components.md) — dual-mode rendering rules that apply to all components
127
294
  - [app-manifest.md](app-manifest.md) — the `records` manifest block that drives tab generation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "1.9.21",
3
+ "version": "1.9.22",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",