@uidu/skills 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/skills/uidu/SKILL.md +275 -66
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uidu/skills",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Agent skills for the uidu SDK — installable via the open agent skills ecosystem (skills.sh).",
5
5
  "license": "MIT",
6
6
  "author": "uidu",
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: uidu
3
- description: Use when building on the uidu platform — a public website or an app that runs inside uidu (a custom app in a Space or workspace); reading CMS pages, events, stories, donations, help center, forms; rendering WYSIWYG content; scaffolding projects; OR provisioning/authoring uidu content (create/update/delete) from the terminal via the @uidu/cli (`uidu`). Triggers on any task involving uidu, the @uidu/client SDK, @uidu/react bindings, or the uidu CLI.
3
+ description: Use when building on the uidu platform — a public website or an app that runs inside uidu (a custom app in a Space or workspace); reading CMS pages, events, stories, donations, help center, forms, or the workspace's own data (goals/OKRs, contacts, deals, people); rendering WYSIWYG content; scaffolding projects; OR provisioning/authoring uidu content (create/update/delete) from the terminal via the @uidu/cli (`uidu`). Triggers on any task involving uidu, the @uidu/client SDK, @uidu/react bindings, or the uidu CLI.
4
4
  license: MIT
5
5
  metadata:
6
6
  author: uidu
7
- version: "0.5.0"
7
+ version: '0.6.0'
8
8
  ---
9
9
 
10
10
  # uidu SDK
@@ -13,7 +13,7 @@ The uidu SDK is a TypeScript GraphQL toolkit for building websites and apps powe
13
13
 
14
14
  - **`@uidu/client`** — framework-agnostic GraphQL client. Safe for server-side use (RSC, route handlers, server actions, scripts).
15
15
  - **`@uidu/app-bridge`** — the browser half of a custom app: the handshake with the uidu page that frames it, and its short-lived session token. Only for apps that run inside uidu.
16
- - **`@uidu/react`** — React hooks (`useUiduClient`, `useQuery`, `useFields`, `toText`, `useUiduApp`) and components (`<UiduAppProvider>`, `<PageBlocks>`, `<BlockRenderer>`, `<RichText>`, `<DynamicForm>`). That list is the **complete** public surface — there is no `useForm`, `usePage` or `useChannel`; for anything else, call a `@uidu/client` function.
16
+ - **`@uidu/react`** — React hooks (`useUiduClient`, `useQuery`, `useFields`, `toText`, `useUiduApp`) and components (`<UiduAppProvider>`, `<PageBlocks>`, `<BlockRenderer>`, `<RichText>`, `<DynamicForm>`). That list is the **complete** public surface — there is no `useForm`, `usePage` or `useChannel`; for anything else, call a `@uidu/client` function. `@uidu/client` has one for each entity uidu already holds (goals, contacts, deals, people, events… — see [Workspace data](#workspace-data)): look there before writing a query or creating a Model.
17
17
 
18
18
  `@uidu/api.js` is a legacy bridge package and is **deprecated** — use `@uidu/react` directly.
19
19
 
@@ -31,12 +31,12 @@ Use this skill when the user is:
31
31
 
32
32
  ## Package Map
33
33
 
34
- | Package | When to use | Notes |
35
- |---|---|---|
36
- | `@uidu/client` | Server-side queries (RSC, server actions, scripts) | Framework-agnostic |
37
- | `@uidu/react` | React hooks, providers, and components | Some hooks are client-only |
38
- | `@uidu/app-bridge` | An app that runs inside uidu (custom app) | Browser only; in React use `<UiduAppProvider>` |
39
- | `@uidu/api.js` | **Do not use for new code.** | Deprecated; re-exports `@uidu/react` |
34
+ | Package | When to use | Notes |
35
+ | ------------------ | -------------------------------------------------- | ---------------------------------------------- |
36
+ | `@uidu/client` | Server-side queries (RSC, server actions, scripts) | Framework-agnostic |
37
+ | `@uidu/react` | React hooks, providers, and components | Some hooks are client-only |
38
+ | `@uidu/app-bridge` | An app that runs inside uidu (custom app) | Browser only; in React use `<UiduAppProvider>` |
39
+ | `@uidu/api.js` | **Do not use for new code.** | Deprecated; re-exports `@uidu/react` |
40
40
 
41
41
  Install:
42
42
 
@@ -72,22 +72,62 @@ uidu workspace credentials --json # api key + secret
72
72
  uidu create my-app -t events # delegates to create-uidu-app
73
73
  ```
74
74
 
75
- Entities: `events, stories, donations, courses, forms, contacts, deals, employees,
76
- bookings, calls, campaigns, kb-collections, kb-articles, channel` (read) and
77
- `tasks, notes, spaces` (write-only). Not every verb exists for every entity — the CLI
78
- reports `does not support <verb>` when it doesn't. Config resolves **flags → env
79
- (`UIDU_*`) → `~/.uidu/config.json`** (written by `uidu login`).
75
+ Entities and the verbs each supports (generated from the CLI's registry — a verb marked `—`
76
+ fails with `does not support <verb>`):
77
+
78
+ <!-- BEGIN sdk:cli-resources — generated by scripts/sdk-manifest.mjs, do not edit -->
79
+
80
+ | Entity | list | get | create | update | delete |
81
+ | --------------------- | :--: | :-: | :----: | :----: | :----: |
82
+ | `events` | ✓ | ✓ | ✓ | ✓ | — |
83
+ | `attendances` | — | — | ✓ | — | — |
84
+ | `stories` | ✓ | ✓ | ✓ | ✓ | — |
85
+ | `donations` | ✓ | ✓ | ✓ | ✓ | ✓ |
86
+ | `courses` | ✓ | ✓ | ✓ | ✓ | ✓ |
87
+ | `forms` | ✓ | ✓ | ✓ | ✓ | ✓ |
88
+ | `contacts` | ✓ | ✓ | ✓ | — | ✓ |
89
+ | `deals` | ✓ | ✓ | ✓ | ✓ | — |
90
+ | `goals` | ✓ | ✓ | — | ✓ | — |
91
+ | `timeframes` | ✓ | — | — | — | — |
92
+ | `employees` | ✓ | — | ✓ | ✓ | — |
93
+ | `employments` | — | — | ✓ | ✓ | ✓ |
94
+ | `offices` | ✓ | — | — | — | — |
95
+ | `roles` | ✓ | — | — | — | — |
96
+ | `ccnls` | ✓ | — | ✓ | — | — |
97
+ | `bookings` | ✓ | ✓ | — | — | — |
98
+ | `calls` | ✓ | ✓ | — | — | — |
99
+ | `jobs` | ✓ | ✓ | — | — | — |
100
+ | `applications` | ✓ | — | ✓ | — | — |
101
+ | `campaigns` | ✓ | ✓ | — | — | — |
102
+ | `kb-collections` | ✓ | ✓ | ✓ | ✓ | ✓ |
103
+ | `kb-articles` | ✓ | ✓ | ✓ | ✓ | ✓ |
104
+ | `channel` | ✓ | ✓ | ✓ | ✓ | ✓ |
105
+ | `time-clocks` | — | — | ✓ | — | — |
106
+ | `compensations` | — | — | ✓ | ✓ | ✓ |
107
+ | `tasks` | — | — | ✓ | ✓ | ✓ |
108
+ | `benefits` | — | — | ✓ | — | — |
109
+ | `benefit-enrollments` | — | — | ✓ | — | — |
110
+ | `notes` | — | — | ✓ | ✓ | ✓ |
111
+ | `projects` | ✓ | — | — | — | — |
112
+ | `sites` | — | — | ✓ | — | — |
113
+ | `spaces` | — | — | ✓ | ✓ | ✓ |
114
+
115
+ <!-- END sdk:cli-resources -->
116
+
117
+ Anything the backend exposes as a tool-flagged action but the CLI has no noun for yet:
118
+ `uidu tools call <ActionKey> --attributes '<json>'` (associations as GlobalIDs). Config resolves
119
+ **flags → env (`UIDU_*`) → `~/.uidu/config.json`** (written by `uidu login`).
80
120
 
81
121
  ## Who the app acts for — one rule
82
122
 
83
- What an app *looks like* (landing page, blog, dashboard, booking tool) changes nothing.
123
+ What an app _looks like_ (landing page, blog, dashboard, booking tool) changes nothing.
84
124
  What changes the code is **who the request acts for**:
85
125
 
86
- | Operation | Auth | Where it runs |
87
- |---|---|---|
88
- | **Reads** for anyone (`get*` / `list*`, `<entity> list/get`) | `publicToken` | anywhere — server, CLI, **and the browser** |
89
- | **Writes** as the workspace (`create*` / `update*` / `delete*`, `<entity> create/update/delete`) | account **Bearer** (`apiKey`) | CLI and **server-side** only |
90
- | **Anything as the signed-in member**, inside uidu (a custom app) | session token from the host, over `@uidu/app-bridge` | **the browser only** |
126
+ | Operation | Auth | Where it runs |
127
+ | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | ------------------------------------------- |
128
+ | **Reads** for anyone (`get*` / `list*`, `<entity> list/get`) | `publicToken` | anywhere — server, CLI, **and the browser** |
129
+ | **Writes** as the workspace (`create*` / `update*` / `delete*`, `<entity> create/update/delete`) | account **Bearer** (`apiKey`) | CLI and **server-side** only |
130
+ | **Anything as the signed-in member**, inside uidu (a custom app) | session token from the host, over `@uidu/app-bridge` | **the browser only** |
91
131
 
92
132
  The write functions live in `@uidu/client`, so anything the CLI provisions you can also do
93
133
  from a Next.js server action / route handler / RSC. **Never put the account Bearer token in a
@@ -111,10 +151,13 @@ that person can do, and no more. It changes five things:
111
151
  needs a server credential uidu doesn't issue yet.
112
152
  3. **Treat the token as opaque.** Never decode it. The expiry comes with it, the bridge refreshes
113
153
  it, and `graphqlUrl` comes with it too: never build `https://<workspace>.uidu.org` from a slug.
114
- 4. **Data goes in the app's own Models.** The session reaches the app's own Models, fields and
115
- items through `node(id:)`, and the data-engine mutations. Everything else in the workspace comes
116
- back as an error. Keep your data there rather than in a database of your own: it stays
117
- searchable and permissioned in uidu.
154
+ 4. **Read what the workspace already has; keep only new data in the app's own Models.** The
155
+ session reaches the app's own Models, fields and items (`node(id:)` and the data-engine
156
+ mutations) and, read-only, the workspace's **goals and timeframes** (`listGoals`, `getGoal`,
157
+ `listTimeframes`) — those of the workspace and of the Space the app sits in. Everything else in
158
+ the workspace comes back as an error for now: see [Workspace data](#workspace-data) for which
159
+ entity is reachable from where. Keep the app's own data in Models rather than in a database of
160
+ your own: it stays searchable and permissioned in uidu.
118
161
  `listModelItems` reads uidu's **search index**, which catches up a moment after a write:
119
162
  after `createModelItem` / `deleteModelItem`, update your list from the mutation payload
120
163
  instead of re-listing, or the new item is missing. And load models once per app instance
@@ -129,24 +172,39 @@ import { useEffect, useState } from 'react';
129
172
  import { UiduAppProvider, useUiduApp } from '@uidu/react';
130
173
  import { DEFAULT_HOST_ORIGINS } from '@uidu/app-bridge';
131
174
  import {
132
- createModelItem, ensureModel, listModelItems, toFieldValuesAttributes,
133
- type ModelItem, type UiduClient,
175
+ createModelItem,
176
+ ensureModel,
177
+ listModelItems,
178
+ toFieldValuesAttributes,
179
+ type ModelItem,
180
+ type UiduClient,
134
181
  } from '@uidu/client';
135
182
 
136
183
  export function AppProvider({ children }: { children: React.ReactNode }) {
137
184
  // https://*.uidu.org by default; add a local uidu or a custom domain
138
- return <UiduAppProvider hostOrigins={[...DEFAULT_HOST_ORIGINS]}>{children}</UiduAppProvider>;
185
+ return (
186
+ <UiduAppProvider hostOrigins={[...DEFAULT_HOST_ORIGINS]}>
187
+ {children}
188
+ </UiduAppProvider>
189
+ );
139
190
  }
140
191
 
141
192
  async function loadBookings(client: UiduClient, workspaceAppId: string) {
142
- // interim: the app creates its own model on first load (install-time manifest comes later)
193
+ // interim: the app creates its own model on first load (install-time manifest comes later).
194
+ // Bookings of a room are data uidu has no place for — hence a Model. Goals, contacts or
195
+ // people already exist: read them (see Workspace data).
143
196
  const model = await ensureModel(client, {
144
197
  workspaceAppId,
145
198
  name: 'Booking',
146
199
  fields: [{ shortname: 'room', name: 'Room', kind: 'string' }],
147
200
  });
148
201
  await createModelItem(client, {
149
- input: { attributes: { modelId: model.id, fieldValuesAttributes: toFieldValuesAttributes(model, { room: 'Blu' }) } },
202
+ input: {
203
+ attributes: {
204
+ modelId: model.id,
205
+ fieldValuesAttributes: toFieldValuesAttributes(model, { room: 'Blu' }),
206
+ },
207
+ },
150
208
  });
151
209
  return listModelItems(client, { modelId: model.id }); // item.fieldValuesByShortname.room
152
210
  }
@@ -156,19 +214,83 @@ export function Bookings() {
156
214
  const [items, setItems] = useState<ModelItem[]>([]);
157
215
 
158
216
  useEffect(() => {
159
- if (app.status === 'ready') loadBookings(app.client, app.context.workspaceApp.id).then(setItems);
217
+ if (app.status === 'ready')
218
+ loadBookings(app.client, app.context.workspaceApp.id).then(setItems);
160
219
  }, [app]);
161
220
 
162
- if (app.status === 'error') return <p>Open this app from uidu ({app.error.code})</p>; // NOT_EMBEDDED, TIMEOUT…
163
- return <ul>{items.map((item) => <li key={item.id}>{item.fieldValuesByShortname?.room}</li>)}</ul>;
221
+ if (app.status === 'error')
222
+ return <p>Open this app from uidu ({app.error.code})</p>; // NOT_EMBEDDED, TIMEOUT…
223
+ return (
224
+ <ul>
225
+ {items.map((item) => (
226
+ <li key={item.id}>{item.fieldValuesByShortname?.room}</li>
227
+ ))}
228
+ </ul>
229
+ );
164
230
  }
165
231
  ```
166
232
 
233
+ Field `kind`s for `ensureModel` (and `public/uidu.app.json`): `string` (one line), `text`
234
+ (several lines), `number`, `currency`, `percent`, `date`, `datetime`, `checkbox`, `email`,
235
+ `phone`, `url`, `rating`, `member` (a person of the workspace). `singleSelect` /
236
+ `multipleSelect` need their options created too (`createFieldOption`) — prefer `string` unless
237
+ the choices matter. uidu knows more (`attachments`, `linkedRecord`, `formula`, `progress`,
238
+ `richText`…); leave them out unless the person asks for one.
239
+
167
240
  `app.context` carries `user`, `space`, `workspaceApp`, `locale`, `theme` and `accent` (the page's colour, to set as `--primary`). Outside React: `connect()` from
168
241
  `@uidu/app-bridge`, then `createClient(fromBridge(bridge))` from `@uidu/client`. Start a new
169
242
  one with `npm create uidu-app@latest my-app -- -t custom-app`: it declares its Models in
170
243
  `public/uidu.app.json` and sets `frame-ancestors` so only uidu can frame it.
171
244
 
245
+ ## Workspace data
246
+
247
+ **A uidu workspace is not empty.** It already holds the organisation's goals, contacts, deals,
248
+ people, events, courses… An app that asks for "a dashboard of the OKRs behind schedule" or "our
249
+ open deals" is asking about data uidu has — read it. **Create a Model only for data uidu has no
250
+ place for** (the app's own bookings, a checklist, a log). Copying existing records into a Model
251
+ gives the person a second copy that drifts from the real one.
252
+
253
+ | The workspace's… | Functions (`@uidu/client`) | Inside a custom app |
254
+ | -------------------------------------- | ------------------------------------------------------------------------- | -------------------------- |
255
+ | Goals / OKRs (key results = subgoals) | `listGoals`, `getGoal`, `listTimeframes`; `updateGoal` | **read** (no `updateGoal`) |
256
+ | Contacts, organisations | `listContacts`, `getContact`; `createContact` | not yet |
257
+ | Deals (CRM) | `listDeals`, `getDeal`; `createDeal`, `updateDeal` | not yet |
258
+ | People (employees) | `listEmployees`, `getEmployee`, `listOffices`, `listCircles`, `listRoles` | not yet |
259
+ | Tasks, notes | `updateTask`, `createTask`, `updateNote`, `createNote` (no list) | not yet |
260
+ | Events, courses, bookings, calendars | `listEvents`, `listCourses`, `listBookings`, `listCalendars`, … | not yet |
261
+ | Forms, donations, stories, help center | see [Patterns by Scope](#patterns-by-scope) | not yet |
262
+ | The app's own Models | `ensureModel`, `listModelItems`, `createModelItem`, … | read and write |
263
+
264
+ On a server with an account token (`apiKey`) every row works; "not yet" means a custom app's
265
+ session token is refused for it. **When the data exists in uidu but is not reachable from a custom
266
+ app yet, tell the person** — don't rebuild it in a Model. If the functions you need aren't in the
267
+ table, look for `listX` / `getX` in `@uidu/client` before concluding they don't exist.
268
+
269
+ ### Goals (OKRs)
270
+
271
+ An objective is a `Goal`; its key results are its `subgoals`; the period it runs over is its
272
+ `timeframe` (`startDate`, `endDate`). `metricKind` is `number`, `percentage`, `currency`,
273
+ `checkbox` or `subgoal` (measured by its key results); `status` is the owner's own judgement:
274
+ `on_track`, `needs_attention`, `off_track`, `accomplished`.
275
+
276
+ **Values are stored x100.** `initialValue`, `currentValue` and `targetValue` never hold the number
277
+ a person typed, and `progress` is percent x100 (10000 = done). Use the helpers, don't divide by hand:
278
+
279
+ ```ts
280
+ import { listGoals, goalValue, goalProgress, isGoalBehind } from '@uidu/client';
281
+
282
+ const goals = await listGoals(app.client); // all of them, objectives and key results
283
+ const objectives = goals.filter((g) => !g.parentId);
284
+ const behind = objectives.filter((g) => isGoalBehind(g, { tolerance: 0.1 }));
285
+
286
+ goalValue(goal, goal.targetValue); // 45 for a 45% target, 1200 for €1,200
287
+ goalProgress(goal); // 0.45 — done fraction
288
+ ```
289
+
290
+ `isGoalBehind` compares progress with the share of the timeframe already gone (`goalTimeElapsed`);
291
+ show the goal's `status` next to it, it says something different. `updateGoal` takes raw values:
292
+ `toGoalRaw(45)`. A goal's owner, members and activity are not readable from a custom app.
293
+
172
294
  ## Working transparently
173
295
 
174
296
  The user often can't tell what you're doing when you drive the CLI. Stay legible:
@@ -215,23 +337,66 @@ import { createClient } from '@uidu/client';
215
337
  export const uidu = createClient({
216
338
  workspace: process.env.UIDU_WORKSPACE ?? '',
217
339
  publicToken: process.env.UIDU_PUBLIC_TOKEN, // safe to expose
218
- apiKey: process.env.UIDU_API_KEY, // server-only secret
340
+ apiKey: process.env.UIDU_API_KEY, // server-only secret
219
341
  });
220
342
  ```
221
343
 
222
344
  Required environment:
223
345
 
224
- | Variable | Required | Notes |
225
- |---|---|---|
226
- | `UIDU_WORKSPACE` | yes | Your workspace slug (e.g. `acme`) |
227
- | `UIDU_PUBLIC_TOKEN` | no | Public read-only token, safe to expose |
228
- | `UIDU_API_KEY` | no | Server-only secret for privileged ops |
346
+ | Variable | Required | Notes |
347
+ | ------------------- | -------- | -------------------------------------- |
348
+ | `UIDU_WORKSPACE` | yes | Your workspace slug (e.g. `acme`) |
349
+ | `UIDU_PUBLIC_TOKEN` | no | Public read-only token, safe to expose |
350
+ | `UIDU_API_KEY` | no | Server-only secret for privileged ops |
229
351
 
230
352
  ## Patterns by Scope
231
353
 
232
- ### CMS Pages
354
+ ### CMS Sites (new CMS)
233
355
 
234
- CMS pages are made of `pageBlocks`. Fetch a page server-side, pass blocks to `<PageBlocks>` with a component map keyed by the block's `shortname`:
356
+ uidu has two CMS generations. **Sites** are the new one: a `Site` has page types (page-kind Models), pages (ModelItems of a page type) and, on each page, an ordered list of blocks (ModelItems of a block-kind Model). **Projects** (`getPage`/`listPages`, next section) are the legacy CMS — keep using them only for sites already deployed on a Project. Build new sites on Sites. Every read works with a `publicToken`, in RSC and in the browser.
357
+
358
+ ```tsx
359
+ // src/app/[[...slug]]/page.tsx — Server Component
360
+ import { getPageBySlug, getSiteByDomain, type SiteBlock } from '@uidu/client';
361
+ import { notFound } from 'next/navigation';
362
+ import { uidu } from '@/lib/uidu';
363
+
364
+ const components: Record<string, React.ComponentType<{ block: SiteBlock }>> = {
365
+ Hero,
366
+ FAQ,
367
+ };
368
+
369
+ export default async function Page({
370
+ params,
371
+ }: {
372
+ params: Promise<{ slug?: string[] }>;
373
+ }) {
374
+ const { slug = ['home'] } = await params;
375
+ const site = await getSiteByDomain(uidu, {
376
+ domain: process.env.SITE_DOMAIN!,
377
+ });
378
+ const page =
379
+ site &&
380
+ (await getPageBySlug(uidu, { siteId: site.id, slug: slug.join('/') }));
381
+ if (!page) notFound();
382
+ return page.blocks.map((block) => {
383
+ const Component = components[block.model.name ?? ''];
384
+ return Component ? <Component key={block.id} block={block} /> : null;
385
+ });
386
+ }
387
+ ```
388
+
389
+ - `getSiteByDomain(client, { domain })` / `getSite(client, { id })` — the Site (by id in previews, where there is no domain)
390
+ - `getPageBySlug(client, { siteId, slug, includeDrafts? })` → `{ id, slug, model, fields, blocks: [{ id, model, fields, position }] }`, published only by default
391
+ - `listSitePages(client, { siteId, pageType?, includeDrafts? })` — nav/sitemaps; published only, singletons excluded; `pageType` = Model id or name
392
+ - `getSingletonBlock(client, { siteId, shortname })` — header/footer/nav
393
+ - `fields` is already a `{ shortname: value }` map — no `useFields` needed.
394
+ - Block kinds have a `shortname`, but `Model.shortname` isn't exposed by the API yet: key component maps on `block.model.name` for now.
395
+ - Don't call the legacy `listPages`/`getPage` with a Site id — they read Projects only.
396
+
397
+ ### CMS Pages (legacy Projects)
398
+
399
+ Legacy CMS pages are made of `pageBlocks`. Fetch a page server-side, pass blocks to `<PageBlocks>` with a component map keyed by the block's `shortname`:
235
400
 
236
401
  ```tsx
237
402
  // src/app/page.tsx — Server Component
@@ -249,10 +414,7 @@ export default async function HomePage() {
249
414
  if (!page) return <div>Not found</div>;
250
415
 
251
416
  return (
252
- <PageBlocks
253
- pageBlocks={page.pageBlocks}
254
- components={{ Hero, Feature }}
255
- />
417
+ <PageBlocks pageBlocks={page.pageBlocks} components={{ Hero, Feature }} />
256
418
  );
257
419
  }
258
420
  ```
@@ -296,19 +458,19 @@ const event = await getEvent(uidu, { id });
296
458
 
297
459
  Event fields:
298
460
 
299
- | Field | Notes |
300
- |---|---|
301
- | `event.id` | |
302
- | `event.name` | Display name |
303
- | `event.body` | WYSIWYG doc — render with `<RichText>` |
304
- | `event.cover` | Image URL (string) or null |
305
- | `event.description` | Plain string summary |
306
- | `event.instance.beginsAt` | ISO datetime of start |
307
- | `event.instance.finishesAt` | ISO datetime of end |
308
- | `event.instance.id` | Instance identifier |
309
- | `event.primaryAddress` | Location data |
310
- | `event.currentCapacity` | Numeric capacity |
311
- | `event.isPaidEvent` | Boolean |
461
+ | Field | Notes |
462
+ | --------------------------- | -------------------------------------- |
463
+ | `event.id` | |
464
+ | `event.name` | Display name |
465
+ | `event.body` | WYSIWYG doc — render with `<RichText>` |
466
+ | `event.cover` | Image URL (string) or null |
467
+ | `event.description` | Plain string summary |
468
+ | `event.instance.beginsAt` | ISO datetime of start |
469
+ | `event.instance.finishesAt` | ISO datetime of end |
470
+ | `event.instance.id` | Instance identifier |
471
+ | `event.primaryAddress` | Location data |
472
+ | `event.currentCapacity` | Numeric capacity |
473
+ | `event.isPaidEvent` | Boolean |
312
474
 
313
475
  ### Stories (Blog)
314
476
 
@@ -395,10 +557,12 @@ later throws. `FieldValueAttributes` exposes no `value` key — `content` is the
395
557
 
396
558
  ```ts
397
559
  // ✅ Correct — every answer wrapped
398
- fieldValuesAttributes: [{ fieldId: 'f1', content: { value: 'hello@example.com' } }]
560
+ fieldValuesAttributes: [
561
+ { fieldId: 'f1', content: { value: 'hello@example.com' } },
562
+ ];
399
563
 
400
564
  // ❌ Wrong — silently loses the answer
401
- fieldValuesAttributes: [{ fieldId: 'f1', content: 'hello@example.com' }]
565
+ fieldValuesAttributes: [{ fieldId: 'f1', content: 'hello@example.com' }];
402
566
  ```
403
567
 
404
568
  It holds for **every** field kind, not just strings — `{ value: 42 }`, `{ value: true }`,
@@ -465,7 +629,9 @@ All `body` fields on Event, Story, DonationCampaign, etc. are structured documen
465
629
  ```tsx
466
630
  import { RichText } from '@uidu/react';
467
631
 
468
- {story.body != null && <RichText doc={story.body} />}
632
+ {
633
+ story.body != null && <RichText doc={story.body} />;
634
+ }
469
635
  ```
470
636
 
471
637
  The prop is `doc`, not `children`. The component handles paragraphs, headings, lists, links, and inline marks.
@@ -517,12 +683,12 @@ Or pick a template directly:
517
683
  npm create uidu-app@latest my-app -- -t events
518
684
  ```
519
685
 
520
- | Template | What it builds |
521
- |---|---|
522
- | `minimal` | CMS landing page with `PageBlocks` + Hero/Feature blocks |
523
- | `events` | Event listing + `/event/[id]` detail |
524
- | `stories` | Blog listing + `/story/[id]` detail (magazine layout) |
525
- | `donations` | Campaigns listing + `/campaign/[id]` detail with progress bars |
686
+ | Template | What it builds |
687
+ | ------------ | ------------------------------------------------------------------------------------------------ |
688
+ | `minimal` | CMS landing page with `PageBlocks` + Hero/Feature blocks |
689
+ | `events` | Event listing + `/event/[id]` detail |
690
+ | `stories` | Blog listing + `/story/[id]` detail (magazine layout) |
691
+ | `donations` | Campaigns listing + `/campaign/[id]` detail with progress bars |
526
692
  | `custom-app` | An app inside a uidu Space: `<UiduAppProvider>`, data in its own Models (`public/uidu.app.json`) |
527
693
 
528
694
  Every template ships with Next.js 16 App Router, Tailwind v4 and an `.env.example` (the CLI
@@ -530,6 +696,48 @@ offers to fill it in for you). The site templates add a configured `src/lib/uidu
530
696
  server-rendered listing + dynamic detail page; `custom-app` has no token to configure and
531
697
  reads everything in client components.
532
698
 
699
+ ## Full API Index
700
+
701
+ Every `@uidu/client` function, by domain — generated from the source, so if a function isn't
702
+ here it doesn't exist (yet). Domains marked "reads need Bearer" hold private data: a public token
703
+ sees nothing. Per-function auth, signatures and descriptions: https://docs.uidu.org/docs/reference/client.
704
+
705
+ <!-- BEGIN sdk:domain-index — generated by scripts/sdk-manifest.mjs, do not edit -->
706
+
707
+ - **Client & utilities** — `createClient`, `fromBridge`, `normalizeFieldValueContent`, `normalizeFieldValuesAttributes`, `paginate`
708
+ - **CMS — Sites** · CLI: `sites` — `createSite`, `getPageBySlug`, `getSingletonBlock`, `getSite`, `getSiteByDomain`, `listSitePages`
709
+ - **CMS — Projects (legacy)** · CLI: `projects` — `createFieldValue`, `createPage`, `createPageBlock`, `createProject`, `getPage`, `getTemplate`, `listPages`, `listProjects`
710
+ - **Models (custom data)** — `createField`, `createFieldOption`, `createModel`, `createModelItem`, `deleteField`, `deleteFieldOption`, `deleteFieldValue`, `deleteModelItem`, `ensureModel`, `getModel`, `getModelItem`, `listModelItems`, `listModels`, `toFieldValuesAttributes`, `updateField`, `updateFieldOption`, `updateFieldValue`, `updateModel`, `updateModelItem`
711
+ - **Forms** · CLI: `forms` — `createForm`, `createFormResponse`, `deleteForm`, `getForm`, `listForms`, `updateForm`, `updateFormResponse`
712
+ - **Events** · CLI: `events`, `attendances` — `createAttendance`, `createEvent`, `getEvent`, `listEvents`, `updateEvent`
713
+ - **Calls** · CLI: `calls` — `getCall`, `listCalls`
714
+ - **Jobs & applications** · CLI: `jobs`, `applications` — `completeApplication`, `createApplication`, `getJob`, `listApplications`, `listJobs`, `updateApplication`
715
+ - **Donations** · CLI: `donations` — `createDonation`, `createDonationCampaign`, `deleteDonationCampaign`, `getDonationCampaign`, `listDonationCampaigns`, `updateDonationCampaign`
716
+ - **Stories** · CLI: `stories` — `createStory`, `getStory`, `listStories`, `updateStory`
717
+ - **Help center** · CLI: `channel` — `createChannel`, `deleteChannel`, `getChannel`, `listChannels`, `updateChannel`
718
+ - **Knowledge base** · CLI: `kb-collections`, `kb-articles` — `createKbArticle`, `createKbCollection`, `deleteKbArticle`, `deleteKbCollection`, `getKbArticle`, `getKbCollection`, `listKbArticles`, `listKbCollections`, `updateKbArticle`, `updateKbCollection`
719
+ - **Search** — `search`
720
+ - **Courses** · CLI: `courses` — `createCourse`, `deleteCourse`, `getCourse`, `getEnrollment`, `getLecture`, `listCourses`, `listEnrollments`, `listLectures`, `updateCourse`
721
+ - **Bookings & calendars** · reads need Bearer · CLI: `bookings` — `getBooking`, `getCalendar`, `getCalendarEvent`, `listBookings`, `listCalendarEvents`, `listCalendars`
722
+ - **Campaigns** · reads need Bearer · CLI: `campaigns` — `getCampaign`, `listCampaigns`, `listEmailCampaigns`
723
+ - **Contacts & deals** · reads need Bearer · CLI: `contacts`, `deals` — `createContact`, `createDeal`, `deleteContact`, `getContact`, `getDeal`, `listContacts`, `listDeals`, `updateDeal`
724
+ - **Goals (OKRs)** · reads need Bearer · CLI: `goals`, `timeframes` — `getGoal`, `goalProgress`, `goalTimeElapsed`, `goalValue`, `isGoalBehind`, `listGoals`, `listTimeframes`, `toGoalRaw`, `updateGoal`
725
+ - **People (HR)** · reads need Bearer · CLI: `employees`, `employments`, `offices`, `roles`, `ccnls`, `time-clocks`, `compensations`, `benefits`, `benefit-enrollments` — `contractEvents`, `createBenefit`, `createBenefitEnrollment`, `createCcnl`, `createCompensation`, `createEmployee`, `createEmployment`, `createTimeClock`, `deleteCompensation`, `deleteEmployment`, `getEmployee`, `listCcnls`, `listCircles`, `listEmployees`, `listEmploymentHistory`, `listKinds`, `listOffices`, `listRoles`, `terminateEmployment`, `updateCompensation`, `updateEmployee`, `updateEmployment`, `weekStartFor`
726
+ - **Spaces, tasks & notes** · CLI: `tasks`, `notes`, `spaces` — `createNote`, `createSpace`, `createTask`, `deleteNote`, `deleteSpace`, `deleteTask`, `updateNote`, `updateSpace`, `updateTask`
727
+ - **Provisioning & actions** — `createWorkspace`, `executeAction`, `generateWorkspaceApiCredentials`
728
+
729
+ <!-- END sdk:domain-index -->
730
+
731
+ `@uidu/react`:
732
+
733
+ <!-- BEGIN sdk:react-surface — generated by scripts/sdk-manifest.mjs, do not edit -->
734
+
735
+ - **Components:** `BlockRenderer` — Render exactly one block from a page's `pageBlocks` array, by shortname.; `DynamicForm` — Render a uidu form (from `getForm`, `call.form`, …) as HTML inputs and hand the parsed values to your `action`, typically a `createFormResponse` Server Action.; `PageBlocks` — Render a page's blocks in order, each with the component keyed by its shortname in `components`; blocks with no match render `fallback`, or nothing.; `RichText` — Render a Tiptap (ProseMirror) JSON document as React elements.; `UiduAppProvider` — Connects a custom app to the uidu page framing it (`@uidu/app-bridge`) and hands the tree its state through `useUiduApp`. Once connected, the children also sit inside a `<UiduProvider>` with a session-backed client, so `useUiduClient()` works as anywhere else.; `UiduProvider` — Provide a `UiduClient` to the tree, either a `client` you built or one made from `workspace`/`publicToken`/`endpoint`, plus an SWR fetcher backed by it.
736
+ - **Hooks:** `useFields` — Normalize a `fieldValues` array into a `{ shortname: value }` map so blocks can access their fields by name without walking the GraphQL shape.; `useQuery` — Generic client-side data-fetching hook: runs `document` against the `UiduClient` from the nearest `<UiduProvider>` via SWR, so calls are cached and deduplicated by operation name + variables.; `useUidu` — The nearest `UiduProvider`'s `{ client, endpoint }`; throws outside a provider.; `useUiduApp` — The custom app's bridge state: `connecting`, `ready` or `error`.; `useUiduClient` — The `UiduClient` from the nearest `UiduProvider`; throws outside a provider.
737
+ - **Utilities:** `formatSalaryRange` — Format a job's salary range (`salaryMin`/`salaryMax`) into a display string. Returns `null` when the job advertises no salary, so callers can hide the field. Handles open-ended ranges ("From …", "Up to …") and a single figure.; `getBlockShortname` — The shortname a page block is rendered by (its template block's, else its own), or null.; `toText` — Flatten any uidu field-content value into a plain string. Handles raw strings, numbers, Slate-style rich text nodes (`{ type, children }`, `{ text }`), arrays of nodes, and wrapper objects with a `value` field.
738
+
739
+ <!-- END sdk:react-surface -->
740
+
533
741
  ## See Also
534
742
 
535
743
  - uidu docs: https://docs.uidu.org
@@ -537,3 +745,4 @@ reads everything in client components.
537
745
  - `@uidu/client`: https://github.com/uidu-org/api.js/tree/main/packages/client
538
746
  - `@uidu/react`: https://github.com/uidu-org/api.js/tree/main/packages/react
539
747
  - `create-uidu-app`: https://github.com/uidu-org/api.js/tree/main/packages/create-uidu-app
748
+ - Machine-readable manifest of the whole SDK (exports, domains, auth, CLI verbs): https://github.com/uidu-org/api.js/blob/main/docs/sdk-manifest.json