@close-by/clay 0.2.24 → 0.2.26

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/README.md CHANGED
@@ -18,4 +18,6 @@ export default function () {
18
18
  bunx clay hello.ts
19
19
  ```
20
20
 
21
- Docs: shipped with the package at `node_modules/@close-by/clay/docs/`. Agent skill: `node_modules/@close-by/clay/SKILL.md` (or `@close-by/clay/skill`). See [getting-started.md](docs/getting-started.md) after install.
21
+ Live demo: [clay.close-by.org](https://clay.close-by.org/).
22
+
23
+ Docs: shipped with the package at `node_modules/@close-by/clay/docs/`. Agent skill: `node_modules/@close-by/clay/SKILL.md` (or `@close-by/clay/skill`). See [getting-started.md](https://github.com/closeby-corp/clay/blob/main/docs/getting-started.md) after install.
package/docs/README.md CHANGED
@@ -6,11 +6,14 @@ You write imperative `ui.*` code on the server. Clay owns a per-client element t
6
6
 
7
7
  For coding agents: the repo-root [llms.txt](../llms.txt) is a short map of these guides. The [agent skill](./SKILL.md) ships with `@close-by/clay` at package root (`SKILL.md`).
8
8
 
9
+ [Live demo](https://clay.close-by.org/) · [Examples catalog](./examples.md)
10
+
9
11
  ## Guides
10
12
 
11
13
  | Doc | Description |
12
14
  |-----|-------------|
13
15
  | [Getting started](./getting-started.md) | Install, `clay` CLI, create a page |
16
+ | [Mobile and installable apps](./mobile.md) | Phone tab bar (`mobileNav`), installable app (`pwa`, `--pwa`) |
14
17
  | [Boot: CLI vs library](./boot.md) | Production `ui.run` path, `clientDir`, `_run.ts`, reload stubs |
15
18
  | [Tailwind](./tailwind.md) | Zero-config utilities from page modules; optional tokens |
16
19
  | [Sample app](./tutorial.md) | Step-by-step tasks list, then two pages + shell |
@@ -24,6 +27,7 @@ For coding agents: the repo-root [llms.txt](../llms.txt) is a short map of these
24
27
  | [Elements](./elements.md) | All element types and props |
25
28
  | [Maps](./maps.md) | `ui.map`: MapLibre markers, routes, arcs, GeoJSON, clusters; palette theming and in-place updates |
26
29
  | [WebSocket protocol](./protocol.md) | Client ↔ server message formats |
30
+ | [Compatibility](./compatibility.md) | Public API promise, aliases, and compatibility checks |
27
31
  | [Architecture](./architecture.md) | Packages, data flow, and ownership |
28
32
  | [Examples](./examples.md) | Demo apps and patterns |
29
33
  | [Reactive `let`](./reactive-let.md) | NiceGUI-style `let` transform (opt-in `--reactive-let`) |
@@ -34,20 +38,19 @@ For coding agents: the repo-root [llms.txt](../llms.txt) is a short map of these
34
38
 
35
39
  ## Quick start
36
40
 
41
+ The runtime packages are published to npm. The CLI includes prebuilt client assets.
42
+
37
43
  ```bash
38
- bun install
39
- bun run build:client # monorepo: build + copy client into @close-by/clay-cli
40
- bun run clay hello.ts
44
+ bun add @close-by/clay-cli @close-by/clay
45
+ bunx clay hello.ts
41
46
  # → http://localhost:3000
42
47
  ```
43
48
 
44
- Outside the monorepo: `bun run pack:publishable` then install the `dist-pack/*.tgz` set (see [Getting started](./getting-started.md)), or `bun add @close-by/clay-cli @close-by/clay` once published. Maintainers: `bun run publish:dry` / `publish:npm` (order core → … → cli). Shipped CLI includes prebuilt client assets — no separate `build:client` after install.
45
-
46
49
  ```typescript
47
50
  // hello.ts
48
51
  import { ui } from '@close-by/clay';
49
52
 
50
- ui.run(() => {
53
+ export default function () {
51
54
  let count = 0;
52
55
  const label = ui.label(`Count: ${count}`);
53
56
 
@@ -65,15 +68,17 @@ ui.run(() => {
65
68
  },
66
69
  });
67
70
  });
68
- });
71
+ }
69
72
  ```
70
73
 
71
74
  Multi-page with shell:
72
75
 
73
76
  ```bash
74
- bun run clay ./pages --app --title "My App"
77
+ bunx clay ./pages --app --title "My App"
75
78
  ```
76
79
 
80
+ `ui.run` is library mode; see [Boot](./boot.md).
81
+
77
82
  Or library-style entry (`loadPages` + `ui.run({ app })`) — see [Getting started](./getting-started.md).
78
83
 
79
84
  ## Naming
package/docs/api.md CHANGED
@@ -93,6 +93,7 @@ ui.run({
93
93
  | `css` | `string \| string[]` | | Extra CSS file path(s) served after the client bundle (absolute or cwd-relative). Override theme tokens without rebuilding the client. |
94
94
  | `sound` | `boolean` | | Play short synthesized interface sounds by default (`false`). See [Sound](#sound). |
95
95
  | `app` | `AppProps` | | Global dashboard shell; client keeps chrome sticky across navigate |
96
+ | `pwa` | `boolean \| ClayPwaConfig` | | Installable app: web manifest, icons, and a service worker that caches the app shell. See [Mobile and installable apps](./mobile.md#installable-app-pwa) |
96
97
  | `authSecret` | `string` | | Enables signed auth cookies (`POST`/`DELETE /auth/session`), `ui.establishAuthSession` / `ui.clearAuthSession`, and default `resolveUserId` from the cookie |
97
98
  | `authCookieMaxAgeSec` | `number` | `43200` (12h) | Auth cookie Max-Age |
98
99
  | `sessionIdleMs` | `number` | | Optional: clear auth + redirect after this idle time |
@@ -999,7 +1000,7 @@ registerFlowEdgeTypes({ labeled: BaduiLabeledEdge });
999
1000
 
1000
1001
  Then reference those keys from the server: `flow.node({ nodeType: 'fancy', … })` / edge `type: 'labeled'`, and optionally list them on the flow as `customNodeTypes` / `customEdgeTypes`.
1001
1002
 
1002
- See Flow Demo (`/examples/flow`) for richer patterns:
1003
+ See the Flow demos (`/examples/flow` for 1–2, `/examples/flow-editing` for 3–4) for richer patterns:
1003
1004
 
1004
1005
  1. **ETL pipeline** — select/switch/progress/buttons inside nodes; labeled smoothstep edges + dagre auto-layout
1005
1006
  2. **Branching approval** — multi-handle triage (`yes` / `no` sources), rating + badges
@@ -1822,7 +1823,7 @@ onClick: async () => {
1822
1823
  | `ui.redirect(url)` | `void` | Full-page navigation in the current tab (`location.assign`) — for sign-in routes / external pages; use `ui.navigate` for app pages |
1823
1824
  | `ui.theme.set(mode)` | `void` | Push `light` \| `dark` \| `system` to this client |
1824
1825
  | `ui.theme.get()` | `ThemeMode \| null` | Last value set on this session |
1825
- | `ui.theme.palette(name)` | `void` | Switch colour palette for this client (`kiln`, `cobalt`, `ocean`, `forest`, `orchid`, `citrus`, `graphite`, `neon`, `sky`, `navy`, `indigo`, `denim`, `midnight`, or a custom one); remembered across reloads |
1826
+ | `ui.theme.palette(name)` | `void` | Switch colour palette for this client (`kiln`, `cobalt`, `ocean`, `forest`, `orchid`, `citrus`, `graphite`, `chalk`, `brutal`, `terminal`, `editorial`, `bubble`, `soothe`, `nebula`, `studio`, `iris`, `ledger`, `contrast`, `dim`, `neon`, `sky`, `navy`, `indigo`, `denim`, `midnight`, or a custom one); remembered across reloads |
1826
1827
  | `ui.theme.getPalette()` | `ThemePalette \| null` | Last palette set, else the one the client reported on connect |
1827
1828
  | `ui.theme.palettes` | `readonly string[]` | Built-in palette names |
1828
1829
  | `ui.sound.set(on)` | `void` | Turn automatic interface sounds on/off for this client; remembered across reloads. See [Sound](#sound) |
@@ -1832,6 +1833,7 @@ onClick: async () => {
1832
1833
  | `ui.runJavaScript(code)` | `void` | Eval trusted snippet in the browser |
1833
1834
  | `ui.scroll.to(opts)` | `void` | Scroll window (`top` / `bottom` / px) |
1834
1835
  | `ui.scroll.intoView(selector, opts?)` | `void` | `Element.scrollIntoView` via selector |
1836
+ | `ui.onDispose(callback)` | `void` | Register synchronous cleanup during a page build or event; runs once on disconnect, navigation, or reconnect page replacement. Requires an active session, so call it synchronously during the build or at the start of a handler (not after an `await`); callbacks run in registration order and errors are logged without stopping other cleanup. |
1835
1837
  | `ui.timer(interval, callback, options?)` | `TimerHandle` | Session-scoped timer; `interval` in **seconds**; `{ once? }`; `.activate()` / `.deactivate()` / `.cancel()` |
1836
1838
  | `ui.upload(props?)` | `Element` | Multipart HTTP upload; see below |
1837
1839
  | `ui.storage.tab` | | Per-browser-tab map (sessionStorage mirror) |
@@ -1893,9 +1895,10 @@ User storage requires a `userId` on the session. The built-in client sends an an
1893
1895
  const messages = ui.storage.app.create<Message[]>('chatMessages', []);
1894
1896
  const online = ui.storage.app.create<string[]>('onlineUsers', [], { persist: false });
1895
1897
 
1896
- messages.subscribe((list) => {
1898
+ // Inside a page builder, unsubscribe when this page is disposed.
1899
+ ui.onDispose(messages.subscribe((list) => {
1897
1900
  // update UI for this session
1898
- });
1901
+ }));
1899
1902
 
1900
1903
  await messages.set([...(await messages.get()), newMessage]);
1901
1904
  await messages.update((prev) => [...prev, newMessage]);
@@ -1909,12 +1912,12 @@ await messages.update((prev) => [...prev, newMessage]);
1909
1912
  | `storage.app.create(key, initial, options?)` | Get or create named store; `{ persist?: boolean }` |
1910
1913
  | `.get()` | `Promise<T>` — load from adapter when persisted |
1911
1914
  | `.set(v)` / `.update(fn)` | `Promise<void>` — save when persisted |
1912
- | `.subscribe(listener)` | Listen for changes |
1915
+ | `.subscribe(listener)` | Listen for changes; returns an unsubscribe function. Pass it to `ui.onDispose` for page-scoped listeners; subscriptions created outside a session remain process-wide. |
1913
1916
  | `storage.app.clearAll()` | Test helper (clears app stores only) |
1914
1917
  | `storage.clearAll()` | Test helper (clears app stores + user bags + both adapters) |
1915
1918
  | `createMemoryPersistence()` | In-memory adapter for tests (`@close-by/clay-core`) |
1916
1919
  | `createFilePersistence({ dir })` | File-backed adapter (`@close-by/clay-persistence-file`) |
1917
- | `createRedisPersistence({ client, keyPrefix? })` | Redis adapter (`@close-by/clay-persistence-redis`) — multi-process app/user bags; keep WS sticky |
1920
+ | `createRedisPersistence({ client, keyPrefix? })` | Redis adapter (`@close-by/clay-persistence-redis`; monorepo only — not published to npm yet) — multi-process app/user bags; keep WS sticky |
1918
1921
 
1919
1922
  `PersistenceAdapter`:
1920
1923
 
@@ -1960,6 +1963,7 @@ Sidebar layout for multi-page apps. Prefer `ui.run({ app })` so every page inher
1960
1963
  | `user` | `AppUser` | Optional user menu (`name`, `email`, `avatar?`) |
1961
1964
  | `variant` | `'sidebar' \| 'inset'` | Shell layout variant |
1962
1965
  | `collapsible` | `'offcanvas' \| 'icon' \| 'none'` | Sidebar collapse behavior |
1966
+ | `mobileNav` | `'tabs' \| 'menu'` | Phone navigation. Default `'tabs'` (bottom tab bar); `'menu'` keeps only the sidebar sheet. See [Mobile](./mobile.md#keep-only-the-menu) |
1963
1967
  | `className` | `string` | Extra classes on the shell |
1964
1968
 
1965
1969
  `AppNavItem`: `{ label, href, icon?, description?, items? }` — `icon` is a Lucide kebab-case name (full set; e.g. `home`, `gauge`, `sparkles`). Nested `items` render as collapsible sub-links in the sidebar.
@@ -2114,7 +2118,7 @@ form.name = 'Ada'; // persisted
2114
2118
  ui.draft.clear('formDemo'); // drop storage key; reset the object yourself if needed
2115
2119
  ```
2116
2120
 
2117
- See Form Demo (`/examples/form-demo`).
2121
+ See Forms (`/examples/forms`).
2118
2122
 
2119
2123
  ### `ui.auto(fn)`
2120
2124
 
package/docs/boot.md CHANGED
@@ -98,7 +98,7 @@ If you run **`tsc --noEmit`** (or IDE typecheck) on the app repo, exclude reload
98
98
 
99
99
  Without that exclude, a leftover stub from a crashed dev session can produce spurious TS errors until you delete `.clay-reload/`. Clay apps that only typecheck `pages/` and `src/` rarely need more; monorepos should exclude `.clay-reload` at the app root `tsconfig.json`.
100
100
 
101
- Legacy `_clay-reload-*.ts` files under `pages/` (pre-0.2.5) should be deleted; modern Clay writes outside the tree.
101
+ Legacy `_clay-reload-*.ts` files under `pages/` (pre-0.2.5) should be deleted; modern Clay writes to `<cwd>/.clay-reload/`, inside the project tree but outside `pages/`.
102
102
 
103
103
  ## Reactive-let
104
104
 
@@ -0,0 +1,79 @@
1
+ # Compatibility
2
+
3
+ An app written for a Clay release keeps working on later releases unchanged.
4
+ If an app breaks after a Clay update, that is a bug in Clay unless the release
5
+ notes list the change under **Breaking**.
6
+
7
+ ## What this covers
8
+
9
+ The promise covers the public surface apps use:
10
+
11
+ - `ui.*` functions and namespaces, including `ui.ai`, `ui.storage`, and `ui.theme`.
12
+ - Element methods such as `setText`, `classes`, and `bindValue`.
13
+ - Package exports, including exports moved to another module.
14
+ - Factory option names and element prop names.
15
+ - Event names and their payloads.
16
+ - Element wire types and protocol operations.
17
+ - CLI flags, defaults, and behavior.
18
+
19
+ The client and server ship together, but the wire format still matters.
20
+ Apps use `ui.runJavaScript` and custom clients, and old browser tabs reconnect
21
+ to new servers. Updating the server must account for those callers too.
22
+
23
+ ## Rules for changes
24
+
25
+ 1. What a release shipped stays available in later releases, unless its removal
26
+ is explicitly listed under Breaking in the release notes.
27
+ 2. A rename keeps the old name as an alias of the same implementation.
28
+ Mark the old name with JSDoc `@deprecated Use newName.` so editors can guide
29
+ callers to the replacement. Moving an export keeps it in the old module too.
30
+ 3. Types only widen: options gain optional members, unions gain cases, and
31
+ functions gain optional trailing parameters or overloads. Apps should handle
32
+ values they do not know, for example with a default branch in a switch.
33
+ 4. Behavior does not change silently. Defaults, units, payload shapes, and
34
+ callback order are part of the contract even when the names stay the same.
35
+
36
+ For example, a renamed helper keeps a direct alias:
37
+
38
+ ```typescript
39
+ export function newName(text: string) {
40
+ return ui.label(text);
41
+ }
42
+
43
+ /** @deprecated Use newName. */
44
+ export const oldName = newName;
45
+ ```
46
+
47
+ ## How we check it
48
+
49
+ [compat/surface.json](../compat/surface.json) records the public names.
50
+ [packages/ui/src/compat.test.ts](../packages/ui/src/compat.test.ts) compares that
51
+ record with the current source as part of `bun run test`.
52
+ Missing names fail the test; new names also fail until the record is updated.
53
+
54
+ The record contains:
55
+
56
+ - `ui.<path>`: functions and plain-object namespaces, up to three levels deep.
57
+ Session-created values and getters requiring a session are excluded.
58
+ - `export:<package-name>:<name>`: runtime exports from Clay, core, components,
59
+ server, auth, and file persistence. Type-only exports are absent at runtime.
60
+ - `element.<method>`: methods on the core Element prototype chain.
61
+ - `class.<ExportName>.<method>`: own methods of other Element classes exported
62
+ by `@close-by/clay`.
63
+ - `wire.<type>`: element types handled by the client renderer.
64
+ - `op.server.<op>`, `op.client.<op>`, and `op.patch.<op>`: protocol operations.
65
+ - `cli.<flag>`: CLI flags, including short aliases.
66
+
67
+ After adding public API, run `bun scripts/compat-surface.ts --write` and commit
68
+ `compat/surface.json` with the implementation. Review its diff before committing.
69
+ For a rename, restore the old alias before updating the record.
70
+ For an intentional removal, list it under Breaking in the release notes and
71
+ regenerate the record. Run `bun test packages/ui` to check it.
72
+ Run `bun scripts/compat-surface.ts` for counts without changing the record.
73
+
74
+ This check records names, not full signatures or behavior. Option and prop names,
75
+ event payload shapes, defaults, units, and callback order are not checked
76
+ automatically yet. Reviewers check these, along with type compatibility.
77
+ Native and network adapter packages are outside the mechanical record.
78
+
79
+ Practice adapted from [Native Shell's compatibility policy](https://nsh.dev/docs/compatibility).
package/docs/duckdb.md CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  Multi-database DuckDB wrapper: named instances, `ATTACH` for DuckDB/SQLite/Postgres/MySQL, and insert/update/delete helpers.
4
4
 
5
- ```bash
6
- bun add @close-by/clay-duckdb # or depend on workspace:* inside this monorepo
7
- ```
5
+ **Monorepo only — not published to npm yet.** Depend on `@close-by/clay-duckdb` with `workspace:*` inside this monorepo.
8
6
 
9
7
  ## Connect multiple databases
10
8
 
package/docs/elements.md CHANGED
@@ -201,6 +201,12 @@ Server patches still reconcile when `props.value` changes (e.g. `draft.text = ''
201
201
 
202
202
  Field `error` is **server-owned** (via initial props, `.setError`, or `ui.validate`). It is not optimistic — the client shows `props.error` after `updateProps` patches.
203
203
 
204
+ ## On phones
205
+
206
+ On phone-sized touch screens (max-width 767px and coarse pointer), `select` uses the native picker
207
+ and `date` uses the native date picker. `dropdownmenu` and `combobox` open bottom sheets.
208
+ Props and events stay the same. Desktop rendering is unchanged.
209
+
204
210
  ## Styling
205
211
 
206
212
  Prefer Tailwind utility classes via `.classes(...)`:
@@ -213,4 +219,4 @@ Layout `gap` uses a small numeric scale mapped to `gap-0` … `gap-8` in the cli
213
219
 
214
220
  The client ships the "kiln" theme — warm neutrals, a fired-clay primary, vivid chart colours, light + dark (ShadCN-style CSS variables in `packages/client/src/index.css`, Plus Jakarta Sans bundled). Override tokens at runtime with `ui.run({ css: './globals.css' })`; Clay extras include `--clay-brand-from` / `--clay-brand-to` and the `clay-brand-gradient`, `clay-brand-text`, `clay-ambient` classes.
215
221
 
216
- Twelve more palettes ship alongside kiln — `cobalt`, `ocean`, `forest`, `orchid`, `citrus`, `graphite`, `neon`, plus the blues `sky`, `navy`, `indigo`, `denim`, `midnight` (each light + dark, in `packages/client/src/palettes.css`). Pick the app default with `ui.run({ palette: 'ocean' })` or switch per session with `ui.theme.palette('neon')`. Custom palettes: define `:root[data-palette="mine"] { … }` and `:root.dark[data-palette="mine"] { … }` in your `css` file, then `ui.theme.palette('mine')`.
222
+ Twenty-four more palettes ship alongside kiln — `cobalt`, `ocean`, `forest`, `orchid`, `citrus`, `graphite`, `chalk`, `brutal`, `terminal`, `editorial`, `bubble`, `soothe`, `nebula`, `studio`, `iris`, `ledger`, `contrast`, `dim`, `neon`, plus the blues `sky`, `navy`, `indigo`, `denim`, `midnight` (each light + dark, in `packages/client/src/palettes.css`). Pick the app default with `ui.run({ palette: 'ocean' })` or switch per session with `ui.theme.palette('neon')`. `chalk`, `brutal`, `terminal`, `editorial`, `bubble`, `soothe`, `nebula`, `studio`, `iris`, `ledger`, `contrast`, and `dim` also change font, radius and shadows. `contrast` is the accessibility option, with high-contrast colours and explicit focus indicators. Custom palettes: define `:root[data-palette="mine"] { … }` and `:root.dark[data-palette="mine"] { … }` in your `css` file, then `ui.theme.palette('mine')`.
package/docs/examples.md CHANGED
@@ -3,11 +3,15 @@
3
3
  Demo entry: [`apps/demo/src/main.ts`](../apps/demo/src/main.ts)
4
4
  Pages: [`apps/demo/src/examples/`](../apps/demo/src/examples/) (auto-loaded via `ui.loadPages`)
5
5
 
6
- After `bun run build:client && bun run demo` (or `bun run demo:cli`), open http://localhost:4000.
6
+ Live: https://clay.close-by.org/ · locally in this repo: `bun run build:client && bun run demo` (or `bun run demo:cli`), then open http://localhost:4000.
7
+
8
+ ## Standalone apps
9
+
10
+ - [`examples/tailwind-app/`](../examples/tailwind-app/): Standalone app with Tailwind utilities.
7
11
 
8
12
  ## Sidebar groups
9
13
 
10
- Each page sets `group` / `groupIcon` in its `pageMeta`, so the sidebar and the home page's catalog share one structure: **Start here** (themes, fill layout, counter, todo, kitchen sink) → **Reactivity** → **Data** → **Maps** (gallery, delivery tracker, store locator, globe, uptime monitor) → **Forms & controls** → **Overlays & layout** → **Apps** → **Account** → **ReUI batches**. Order within a group is `pageMeta.order`.
14
+ Each page sets `group` / `groupIcon` in its `pageMeta`, so the sidebar and the home page's catalog share one structure: **Start here** (themes, fill layout, counter, todo, kitchen sink) → **Reactivity** → **Data** → **Maps** (map gallery, delivery tracker, store locator, globe, uptime monitor) → **Forms & controls** → **Components** → **Apps** (including List, Kanban, Gantt, Flow, and Ops console) → **Account**. Order within a group is `pageMeta.order`.
11
15
 
12
16
  ## Catalog
13
17
 
@@ -16,34 +20,30 @@ Each page sets `group` / `groupIcon` in its `pageMeta`, so the sidebar and the h
16
20
  | `/` | `Home.ts` | Landing page: hero, live counter + source side by side (`ui.codeBlock`), feature grid, catalog grouped via `ui.navFromPages()` (same as sidebar) |
17
21
  | `/examples/counter` | `Counter.ts` | `setText`, `refreshable`, button variants |
18
22
  | `/examples/todo` | `Todo.ts` | `reactive`, `bindValue`, list `refreshable`, filters |
23
+ | `/examples/reactive-let-orders` | `ReactiveLetOrders.ts` | Order filters and master–detail views with `ui.state` / `ui.auto`, plus the equivalent reactive `let` source (runnable copy: `examples/reactive-let-orders.ts` with the Clay CLI) |
19
24
  | `/examples/chat` | `Chat.ts` | `ui.storage.app` (persisted messages + ephemeral presence), async `get`/`set` |
20
25
  | `/examples/ai` | `AiDemo.ts` | `ui.ai.*` gallery — chat, prompt bar, loader/thinking, agent cards, diff/insights/fine-tune (static props; no model runtime) |
21
26
  | `/examples/upload` | `FileUpload.ts` | `ui.upload` button + dropzone (progress/abort/size), `ui.storage.tab` / `user`, `ui.download`, `ui.clipboard` |
22
27
  | `/examples/dashboard` | `Dashboard.ts` | `stat`, `areaChart`, full-chrome `dataTable` (views, editors, detail drawer) |
23
28
  | `/examples/datatable` | `DataTableDemo.ts` | Multi-sort (Shift+click / badges), footer aggregates + a11y hints, column pin, remote filter/sort + pagination; density/zebra, editors (Enter/Esc + focus restore), resize, virtualization (≥40, coexists with reorder via windowed drop targets); facets, bulk, collapse/expand, loading/empty; `ui.table` sugar |
24
- | `/examples/composed` | `ComposedDemo.ts` | ReUI batch 1: `ui.timeline`, `ui.stepper`, `ui.dateRange`, `ui.feedList`/`ui.feedRow`, `ui.sparkline` |
25
- | `/examples/ops-console` | `OpsConsoleDemo.ts` | Dogfood ops console: `filterBar` → `feedList` → `descriptionList` / `staticTable` (batches 1–7 in one surface) |
26
- | `/examples/reui-batches` | `ReuiBatchesDemo.ts` | ReUI batches 3–6: `buttonGroup`, `empty`, `pagination`, `filterBar`, form inputs, `navigationMenu`, `notice`, `stat`+sparkline, `eventCalendar`, datatable `showFilterChips` |
27
- | `/examples/reui-batch-7` | `ReuiBatch7Demo.ts` | ReUI batch 7: `inputGroup`, `toggle`, `descriptionList`, `staticTable`, `aspectRatio`, `itemList`, `checkboxGroup` |
28
- | `/examples/charts` | `ChartDemo.ts` | `ui.chart.*`, `ui.sparkline`, composed/scatter charts |
29
+ | `/examples/ops-console` | `OpsConsoleDemo.ts` | Order triage: `filterBar` → `feedList` → `descriptionList` / `staticTable` |
30
+ | `/examples/charts` | `ChartDemo.ts` | `ui.chart.*`, `ui.sparkline`, composed/scatter charts, `stat` + sparkline |
31
+ | `/examples/live-table` | `LiveTableStress.ts` | High-frequency table updates, configurable row count, and live price changes |
29
32
  | `/examples/live-charts` | `LiveCharts.ts` | Live charts: `appendData` sliding windows, `setData` tweens, `upsertData` buckets, live gauge and sparklines |
30
- | `/examples/slider-demo` | `SliderDemo.ts` | Slider, checkbox, select + bindings |
31
33
  | `/examples/themes` | `ThemesDemo.ts` | Palette gallery: `ui.theme.palette`, light/dark, component preview |
32
34
  | `/examples/layout` | `LayoutDemo.ts` | Fill layout: `fillViewport`, `grow`, `scroll`, `ui.grid` |
33
- | `/examples/feedback` | `FeedbackDemo.ts` | Alerts, progress, timer, `ui.theme`, `ui.runJavaScript` / `ui.scroll` |
34
- | `/examples/form-demo` | `FormDemo.ts` | Form + validate + `ui.draft` + rating / colorPicker / tags |
35
+ | `/examples/feedback` | `FeedbackDemo.ts` | Feedback & status: alert, spinner, progress, `runJavaScript` / scroll, live timer clock, notice, empty, timeline |
36
+ | `/examples/forms` | `FormDemo.ts` | Forms: validation, `ui.draft`, slider, checkbox, select, rating, color picker, tags, field, numberField, phoneInput, nativeSelect, inputGroup, toggle, checkboxGroup, dateRange, stepper |
35
37
  | `/examples/auth` | `Auth.ts` (+ login/admin/change-password) | Signed cookie session, hashed passwords, login lockout, role-gated admin + audit log |
36
- | `/examples/timer-content` | `TimerContent.ts` | `ui.timer`, markdown, html, image |
37
- | `/examples/overlays` | `OverlaysDemo.ts` | Breadcrumb, dropdown/context menu, hover card, popover, dialog/sheet/drawer |
38
- | `/examples/dialog-stack` | `DialogStackDemo.ts` | `ui.dialogStack` multi-step stacked modal |
39
- | `/examples/controls` | `ControlsDemo.ts` | Menubar (submenu/checkbox/radio), command dialog + inline, carousel, resizable, scroll-area, `ui.state` / `ui.auto` |
40
- | `/examples/controls-extra` | `ControlsExtra.ts` | `ui.codeBlock` (Shiki), `ui.tree` |
41
- | `/examples/media-extra` | `MediaExtra.ts` | `ui.relativeTime`, `ui.qrCode`, `ui.imageZoom`, `ui.imageCrop` |
38
+ | `/examples/overlays` | `OverlaysDemo.ts` | Breadcrumb, dropdown/context menu, hover card, popover, dialog/sheet/drawer, `dialogStack` multi-step stacked modal |
39
+ | `/examples/navigation` | `NavigationDemo.ts` | Navigation & layout: menubar, command, carousel, resizable, scrollArea, keybind, viewportEnter, navigationMenu, buttonGroup, pagination, aspectRatio |
40
+ | `/examples/content` | `ContentDemo.ts` | Content & media: markdown, html, codeBlock, descriptionList, staticTable, itemList, tree, image, imageZoom, imageCrop, qrCode, iframe, relativeTime, eventCalendar |
42
41
  | `/examples/editor` | `EditorDemo.ts` | `ui.editor` HTML + Markdown side-by-side, bindValue |
43
42
  | `/examples/kanban` | `KanbanDemo.ts` | `ui.kanban` swimlanes, card detail drawer, element-owned `cardMove`/`cardSelect`, reset via APIs |
44
- | `/examples/list` | `ListDemo.ts` | `ui.list` element-owned groups, `itemMove` settle, reset via `setGroups` |
43
+ | `/examples/list` | `ListDemo.ts` | Apps: `ui.list` groups, drag to reorder, reset via `setGroups` |
45
44
  | `/examples/gantt` | `GanttDemo.ts` | `ui.gantt` owned rows/dates/markers/deps, cross-row drag, header double-click markers, `itemMove` / `markerAdd` settle |
46
- | `/examples/flow` | `FlowDemo.ts` | Four `ui.flow` demos: ETL with in-node controls + labeled edges + dagre auto-layout, multi-handle approval branches, fan-in/out + `addNode` dynamic stages, group/`parentId` nesting (flow-owned edges/positions; no outer diagram `ui.auto`) |
45
+ | `/examples/flow` | `FlowDemo.ts` | `ui.flow` ETL pipeline with in-node controls, labeled edges and dagre auto-layout; multi-handle approval branches |
46
+ | `/examples/flow-editing` | `FlowEditingDemo.ts` | Fan-in/out with `addNode` dynamic stages; group/`parentId` nesting |
47
47
  | `/examples/data-clients` | `DataClientsDemo.ts` | DuckDB / Kibana / ClickHouse integration story (mock-friendly) |
48
48
  | `/examples/maps` | `MapsGallery.ts` | `ui.map` gallery: markers and popups, routes, arcs, choropleth, clusters, compact analytics map |
49
49
  | `/examples/delivery-tracker` | `DeliveryTracker.ts` | `ui.map` route progress and courier marker: `updateRoute` / `upsertMarker` on a timer, `fill` layout |
@@ -6,13 +6,21 @@
6
6
  - Node-compatible OS (macOS / Linux / WSL)
7
7
  - For local tarball installs: npm (links co-installed `file:` packages; Bun may try the registry for rewritten versions)
8
8
 
9
- ## Outside the monorepo
9
+ ## Install from npm
10
+
11
+ ```bash
12
+ bun add @close-by/clay-cli @close-by/clay
13
+ # transitive: core, components, server, persistence-file
14
+ # optional: bun add @close-by/clay-auth
15
+ ```
16
+
17
+ Use `bun init`’s tsconfig or set `"extends": "@close-by/clay/page-tsconfig"` in your `tsconfig.json`.
10
18
 
11
19
  Consumers import `@close-by/clay` and run apps with the `clay` binary from `@close-by/clay-cli`. The CLI ships prebuilt Vite assets (`client-dist`); you do **not** need `@close-by/clay-client` or a monorepo `build:client`.
12
20
 
13
21
  Walkthrough (tasks list → two pages + `--app`): [Sample app](./tutorial.md).
14
22
 
15
- ### From local packs (this repo)
23
+ ### Contributors: local packs
16
24
 
17
25
  ```bash
18
26
  # In the Clay checkout:
@@ -22,38 +30,21 @@ bun run pack:publishable # build:client + bun pm pack → dist-pack/*.tgz
22
30
 
23
31
  # In your app directory:
24
32
  npm install \
25
- /path/to/clay/dist-pack/close-by-clay-core-0.1.0.tgz \
26
- /path/to/clay/dist-pack/close-by-clay-auth-0.1.0.tgz \
27
- /path/to/clay/dist-pack/close-by-clay-compiler-0.1.0.tgz \
28
- /path/to/clay/dist-pack/close-by-clay-persistence-file-0.1.0.tgz \
29
- /path/to/clay/dist-pack/close-by-clay-components-0.1.0.tgz \
30
- /path/to/clay/dist-pack/close-by-clay-server-0.1.0.tgz \
31
- /path/to/clay/dist-pack/close-by-clay-0.1.0.tgz \
32
- /path/to/clay/dist-pack/close-by-clay-cli-0.1.0.tgz
33
+ '/path/to/clay/dist-pack/close-by-clay-core-<version>.tgz' \
34
+ '/path/to/clay/dist-pack/close-by-clay-auth-<version>.tgz' \
35
+ '/path/to/clay/dist-pack/close-by-clay-compiler-<version>.tgz' \
36
+ '/path/to/clay/dist-pack/close-by-clay-persistence-file-<version>.tgz' \
37
+ '/path/to/clay/dist-pack/close-by-clay-components-<version>.tgz' \
38
+ '/path/to/clay/dist-pack/close-by-clay-server-<version>.tgz' \
39
+ '/path/to/clay/dist-pack/close-by-clay-<version>.tgz' \
40
+ '/path/to/clay/dist-pack/close-by-clay-cli-<version>.tgz'
33
41
  ```
34
42
 
35
- Install **all** runtime tarballs together so `@close-by/clay` and `@close-by/clay-*@0.1.0` resolve from the co-installed packs (they are not on the public registry yet).
36
-
37
- ### When published to npm
38
-
39
- ```bash
40
- bun add @close-by/clay-cli @close-by/clay
41
- # transitive: core, components, server, persistence-file
42
- # optional: bun add @close-by/clay-auth
43
- ```
43
+ Replace `<version>` with the packed version. Install **all** runtime tarballs together so `@close-by/clay` and `@close-by/clay-*@<version>` resolve from the co-installed packs.
44
44
 
45
45
  ### Publishing to npm (maintainers)
46
46
 
47
- Runtime packages are scoped (`@close-by/clay` and `@close-by/clay-*`) and public (`publishConfig.access: public`). Publish **in dependency order** so the registry can resolve rewritten versions:
48
-
49
- 1. `@close-by/clay-core`
50
- 2. `@close-by/clay-auth`
51
- 3. `@close-by/clay-compiler`
52
- 4. `@close-by/clay-persistence-file`
53
- 5. `@close-by/clay-components`
54
- 6. `@close-by/clay-server`
55
- 7. `@close-by/clay`
56
- 8. `@close-by/clay-cli`
47
+ Runtime packages are scoped and public. The canonical package list and dependency publish order are in [`scripts/publishable.ts`](../scripts/publishable.ts).
57
48
 
58
49
  ```bash
59
50
  # In the Clay checkout:
@@ -65,20 +56,6 @@ bun run publish:npm # pack + validate + real npm publish (same or
65
56
  # Re-run is safe: versions already on npm are skipped.
66
57
  ```
67
58
 
68
- Equivalent manual flow from packed tarballs:
69
-
70
- ```bash
71
- bun run pack:publishable
72
- npm publish ./dist-pack/close-by-clay-core-0.1.0.tgz --access public
73
- npm publish ./dist-pack/close-by-clay-auth-0.1.0.tgz --access public
74
- npm publish ./dist-pack/close-by-clay-compiler-0.1.0.tgz --access public
75
- npm publish ./dist-pack/close-by-clay-persistence-file-0.1.0.tgz --access public
76
- npm publish ./dist-pack/close-by-clay-components-0.1.0.tgz --access public
77
- npm publish ./dist-pack/close-by-clay-server-0.1.0.tgz --access public
78
- npm publish ./dist-pack/close-by-clay-0.1.0.tgz --access public
79
- npm publish ./dist-pack/close-by-clay-cli-0.1.0.tgz --access public
80
- ```
81
-
82
59
  `publish:dry` checks each tarball for rewritten deps (no `workspace:*`), `license` / README / LICENSE, CLI `bin` + `client-dist`, `@close-by/clay` `docs/` (via `prepack` → `scripts/sync-docs.ts`), then runs `npm publish --dry-run`. `publish:npm` looks up each `name@version` on the registry and **skips** if it is already published (resume after a partial failure). Use `--skip-pack` to reuse an existing `dist-pack/`: `bun scripts/publish-from-pack.ts --dry-run --skip-pack`.
83
60
 
84
61
  After install, version-matched docs live at `node_modules/@close-by/clay/docs/` (import paths like `@close-by/clay/docs/api.md` with Bun, or `resolveClayDocsDir` from `@close-by/clay/docs-dir`).
@@ -149,6 +126,7 @@ bun run clay ./pages --app --title "My App"
149
126
  | `-p, --port` | Port (default `3000`) |
150
127
  | `-t, --title` | HTML / shell title |
151
128
  | `--app` | Dashboard shell + nav from discovered pages |
129
+ | `--pwa` | Installable app: manifest + service worker ([Mobile and installable apps](./mobile.md)) |
152
130
  | `--no-open` | Do not open the browser |
153
131
  | `--reload` | Restart on file changes (`bun --watch`); opens the browser once; prints `↻ clay: reloading…` and re-imports pages (clears `require.cache`) |
154
132
  | `--reactive-let` | Enable compile-time reactive `let` Bun loader (**off** by default; still needs `// @clay-reactive` or `"use reactive";` in source) |
@@ -181,7 +159,7 @@ Open:
181
159
  - http://localhost:4000 — home with links to examples
182
160
  - http://localhost:4000/examples/counter
183
161
  - http://localhost:4000/examples/todo
184
- - http://localhost:4000/examples/form-demo
162
+ - http://localhost:4000/examples/forms
185
163
  - http://localhost:4000/examples/auth
186
164
 
187
165
  | Script | What it does |
@@ -201,7 +179,7 @@ Open:
201
179
  - Source packages keep `workspace:*` for the monorepo; `bun pm pack` / publish rewrites them to the package version.
202
180
  - `@close-by/clay-client` is **private**; only its Vite `dist` is copied into `@close-by/clay-cli` (`copy-client` / CLI `prepack`, also invoked by `build:client`).
203
181
  - Root stays `private: true`. Each runtime package ships `license`, `README.md`, `LICENSE`, and `publishConfig.access: public`.
204
- - Publish order: `core` → `auth` → `compiler` → `persistence-file` → `components` → `server` → `ui` → `cli` (see **Publishing to npm** above).
182
+ - Publishable packages and dependency order: [`scripts/publishable.ts`](../scripts/publishable.ts).
205
183
 
206
184
  ## Library mode (multi-page entrypoint)
207
185
 
package/docs/kibana.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Authenticated (or internal) Kibana REST client: arbitrary API calls, Elasticsearch via console proxy or `/elasticsearch`, and `search_after` pagination for walking every matching log line.
4
4
 
5
+ **Monorepo only — not published to npm yet.** Depend on `@close-by/clay-kibana` with `workspace:*` inside this monorepo.
6
+
5
7
  ```typescript
6
8
  import { Kibana } from '@close-by/clay-kibana';
7
9
 
package/docs/llms.txt CHANGED
@@ -11,6 +11,8 @@ Notes for agents:
11
11
  - Update existing nodes with `setText` / `setValue` / bindings. Prefer `ui.state` + `ui.auto` (+ `ui.timer`) for async/live UIs; `ui.refreshable` for simple one-shot rebuilds. Shared or persisted state: `ui.storage` (`tab` / `user` / `app`).
12
12
  - Never use `window` / `navigator` in page code — use `ui.clipboard`, `ui.setUrlHash`, `ui.getUrlSearch` / `ui.updateUrlSearch`, `ui.openExternal`.
13
13
  - Runtime is Bun. In this repo: `bun run build:client`, then `bun run demo` (http://localhost:4000) or `bun run clay hello.ts`.
14
+ - Installable app is opt-in: `ui.run({ pwa: true })` or `clay ./pages --app --pwa` adds a manifest and an app-shell service worker (see docs/mobile.md).
15
+ - On phones the `app` nav becomes a bottom tab bar; `app: { mobileNav: 'menu' }` keeps only the sidebar sheet.
14
16
  - `ui.ai.*` is visual primitives only — no model or streaming runtime.
15
17
  - Agent skill (Cursor / compatible tools): `@close-by/clay/SKILL.md` — copy to `.cursor/skills/clay/SKILL.md` or read from `node_modules` when installed.
16
18
 
@@ -20,6 +22,7 @@ Notes for agents:
20
22
  - [Getting started](docs/getting-started.md): Install, `clay` CLI, first app, publishing
21
23
  - [Boot](docs/boot.md): CLI vs library, `clientDir`, `_run.ts`, reload stubs
22
24
  - [Tailwind](docs/tailwind.md): Scanned utilities + `ui.run({ css })`
25
+ - [Mobile and installable apps](docs/mobile.md): Phone tab bar (`mobileNav`), installable PWA (`ui.run({ pwa })`, `--pwa`)
23
26
  - [Sample app](docs/tutorial.md): Step-by-step tasks list, then two pages + shell
24
27
  - [Concepts](docs/concepts.md): Sessions, element tree, patches, refreshable, bindings, storage
25
28
  - [Browser APIs](docs/browser-apis.md): `ui.clipboard` / hash / `openExternal` — no `window` in page code
@@ -30,6 +33,8 @@ Notes for agents:
30
33
 
31
34
  ## Examples
32
35
 
36
+ - [Live demo](https://clay.close-by.org/): Browse the running examples
37
+
33
38
  - [Examples catalog](docs/examples.md): Demo routes and patterns
34
39
  - [Demo pages](apps/demo/src/examples/): Runnable `ui.page` modules
35
40
 
@@ -37,9 +42,11 @@ Notes for agents:
37
42
 
38
43
  - [Architecture](docs/architecture.md): Packages and data flow
39
44
  - [WebSocket protocol](docs/protocol.md): Message formats
45
+ - [Compatibility](docs/compatibility.md): Public API promise, aliases, and compatibility checks
40
46
  - [AI UI](docs/ai.md): `ui.ai.*` visual primitives
41
47
  - [Reactive let](docs/reactive-let.md): `ui.state` / `ui.auto` and the compile-time `let` transform
42
- - [DuckDB](docs/duckdb.md): `@close-by/clay-duckdb`
48
+ - [DuckDB](docs/duckdb.md): `@close-by/clay-duckdb` — monorepo only — not published to npm yet
49
+ - Redis persistence: `@close-by/clay-persistence-redis` — monorepo only — not published to npm yet (see [storage API](docs/api.md))
43
50
  - [ClickHouse](docs/clickhouse.md): `@close-by/clay-clickhouse`
44
- - [Kibana](docs/kibana.md): `@close-by/clay-kibana`
51
+ - [Kibana](docs/kibana.md): `@close-by/clay-kibana` — monorepo only — not published to npm yet
45
52
  - [NiceGUI](https://nicegui.io/documentation): Inspiration for the `ui.*` style
package/docs/mobile.md ADDED
@@ -0,0 +1,111 @@
1
+ # Mobile and installable apps
2
+
3
+ Apps built with the `app` shell work on phones without changes. You can also make an app installable to the home screen, which is opt-in.
4
+
5
+ ## Phone layout
6
+
7
+ On phone-sized screens (narrow, with a touch pointer) the shell changes on its own:
8
+
9
+ - **Bottom tab bar.** Up to 4 top-level `nav` items become tabs. When there are more items, or `navSecondary`, `documents` or `user` is set, a **More** tab opens the sidebar sheet. A tab is highlighted when its page, or one of its child pages, is active.
10
+ - **Bottom sheets.** Dialogs open as bottom sheets.
11
+ - **Safe areas.** Content clears the iPhone notch and home indicator.
12
+ - **Toasts and the connection chip** sit above the tab bar.
13
+ - **16px form text.** iOS doesn't zoom when a field is focused.
14
+
15
+ `select`, `combobox`, `date` and `dropdownMenu` also use native pickers or bottom drawers on phones. See [Elements](./elements.md#on-phones).
16
+
17
+ ### Keep only the menu
18
+
19
+ Set `mobileNav: 'menu'` to keep the hamburger sheet and skip the tab bar:
20
+
21
+ ```typescript
22
+ ui.run({
23
+ app: {
24
+ title: 'My App',
25
+ nav: ui.navFromPages(),
26
+ mobileNav: 'menu', // default: 'tabs'
27
+ },
28
+ });
29
+ ```
30
+
31
+ `mobileNav` is also a [`ui.app()`](./api.md#uiappprops-fn) prop.
32
+
33
+ ## Installable app (PWA)
34
+
35
+ Opt in with the CLI flag or the `pwa` option:
36
+
37
+ ```bash
38
+ clay ./pages --app --pwa
39
+ ```
40
+
41
+ ```typescript
42
+ ui.run({ pwa: true }); // defaults
43
+
44
+ ui.run({
45
+ title: 'My App',
46
+ pwa: {
47
+ shortName: 'App',
48
+ description: 'Track my work',
49
+ themeColor: '#1d4ed8',
50
+ backgroundColor: '#ffffff',
51
+ display: 'standalone',
52
+ startUrl: '/',
53
+ icon: './assets/icon-512.png',
54
+ },
55
+ });
56
+ ```
57
+
58
+ `ClayPwaConfig` is exported from `@close-by/clay-server`.
59
+
60
+ | Option | Default | Description |
61
+ |--------|---------|-------------|
62
+ | `name` | `title` | App name in the install prompt and splash screen |
63
+ | `shortName` | `name` | Label under the home-screen icon |
64
+ | `description` | | Manifest description |
65
+ | `themeColor` | Clay light/dark background | Browser and OS chrome color |
66
+ | `backgroundColor` | Clay light background | Splash screen background |
67
+ | `display` | `'standalone'` | `'standalone' \| 'fullscreen' \| 'minimal-ui' \| 'browser'` |
68
+ | `startUrl` | `'/'` | URL opened from the home screen |
69
+ | `icon` | Clay's bundled icons | Square PNG (512px+ recommended) or SVG, relative to the working directory |
70
+
71
+ It adds:
72
+
73
+ - `/manifest.webmanifest`, with name, icons, colors and display mode.
74
+ - `theme-color` and Apple meta tags, plus `viewport-fit=cover` so content can use the full screen.
75
+ - `/sw.js`, a service worker that caches the app shell (HTML, JS, CSS and icons). It uses network-first, so online users always get the latest build.
76
+
77
+ ### Icons
78
+
79
+ - Without `icon`, Clay's bundled icons are used.
80
+ - `icon` must be a `.png` or `.svg` file. A missing or unsupported file throws at startup.
81
+ - iOS home-screen icons need a PNG. SVG gets no apple-touch-icon, so use a PNG if the app will be installed on iOS.
82
+
83
+ ### Offline
84
+
85
+ The installed app still opens without a connection and shows Clay's reconnecting state. There is no offline mode for pages, since every screen runs on the server.
86
+
87
+ ### HTTPS
88
+
89
+ Service workers require HTTPS. `localhost` is exempt, so you can test locally. Serve the app over HTTPS before asking people to install it.
90
+
91
+ ### Install
92
+
93
+ - **Android and desktop Chrome:** an install prompt or menu item appears when the app is installable.
94
+ - **iOS Safari:** Share → Add to Home Screen.
95
+
96
+ ### Turn it off
97
+
98
+ Remove `pwa` (or the `--pwa` flag). Clay then serves a `/sw.js` that clears the app cache and unregisters itself. Browsers that installed the app earlier clean up the next time they load it.
99
+
100
+ ### Override the worker or manifest
101
+
102
+ `/sw.js` and `/manifest.webmanifest` can be replaced with `routes`, which match by exact path before pages:
103
+
104
+ ```typescript
105
+ ui.run({
106
+ pwa: true,
107
+ routes: {
108
+ '/sw.js': () => new Response(mySource, { headers: { 'Content-Type': 'text/javascript' } }),
109
+ },
110
+ });
111
+ ```
@@ -2,7 +2,7 @@
2
2
 
3
3
  Dense internal tools (live feeds, master–detail, logs/traces) push Clay past card dashboards. Prefer Phase 1 reactivity ([Concepts](./concepts.md#canonical-recipe-state--auto--timer)) and the helpers below.
4
4
 
5
- **Runnable dogfood:** `/examples/ops-console` (`OpsConsoleDemo.ts`) — one Orders-shaped surface using `filterBar`, `inputGroup`, `feedList`/`feedRow`, `descriptionList`, `staticTable`, `pagination`, `empty`, `notice`, and `toggle`. Use that page to find API friction before adding more wire types.
5
+ **Runnable example:** `/examples/ops-console` (`OpsConsoleDemo.ts`) — one Orders-shaped surface using `filterBar`, `inputGroup`, `feedList`/`feedRow`, `descriptionList`, `staticTable`, `pagination`, `empty`, `notice`, and `toggle`. Use that page to find API friction before adding more wire types.
6
6
 
7
7
  ## Status chips
8
8
 
@@ -66,7 +66,7 @@ Clay lifts `count` into `ui.state`, turns the label into `bindText`, and leaves
66
66
  | When to use `let` + pragma | When to stay on Phase 1 |
67
67
  |-----------------------------|-------------------------|
68
68
  | Counters, toggles, filters on demo pages | Async fetch before first paint, heavy CJS imports |
69
- | Orders-shaped proof pages (`ReactiveLetOrdersLet.ts`) | Per-row mutable state without stable row keys |
69
+ | Orders-shaped proof pages (`examples/reactive-let-orders.ts`) | Per-row mutable state without stable row keys |
70
70
  | Teaching / prototyping NiceGUI-style UX | You want zero Bun loader / compiler in the path |
71
71
 
72
72
  Phase 1 remains the **escape hatch** — copy patterns from [`ReactiveLetOrders.ts`](../apps/demo/src/examples/ReactiveLetOrders.ts) when Phase 2 limits bite.
@@ -206,13 +206,15 @@ Tests: `packages/compiler/src/transform.test.ts`, `packages/core/src/auto.test.t
206
206
  | Page | Syntax | Run |
207
207
  |------|--------|-----|
208
208
  | [`ReactiveLetOrders.ts`](../apps/demo/src/examples/ReactiveLetOrders.ts) | Phase 1 `state` / `auto` | `bun run cli` |
209
- | [`ReactiveLetOrdersLet.ts`](../apps/demo/src/examples/ReactiveLetOrdersLet.ts) | `let` + `// @clay-reactive` | `bun run cli:reactive-let` |
209
+ | [`examples/reactive-let-orders.ts`](../examples/reactive-let-orders.ts) | `let` + `// @clay-reactive` | `bun run clay examples/reactive-let-orders.ts --reactive-let` (from repo root) |
210
210
 
211
211
  ```bash
212
- cd apps/demo && bun run cli # Orders (state/auto) — always interactive
213
- cd apps/demo && bun run cli:reactive-let # both pages; let page needs this
212
+ bun run demo:cli # Orders at /examples/reactive-let-orders, always interactive
213
+ bun run clay examples/reactive-let-orders.ts --reactive-let # standalone let proof, same route
214
214
  ```
215
215
 
216
- Compiler coverage: `packages/compiler/src/orders-proof.test.ts` transforms the let demo and asserts separate list/detail regions (no mega-`auto`).
216
+ The demo Orders page includes the let source as a code block. The standalone copy stays outside the auto-loaded demo pages.
217
+
218
+ Compiler coverage: `packages/compiler/src/orders-proof.test.ts` transforms the standalone let example and asserts separate list/detail regions (no mega-`auto`).
217
219
 
218
220
  Without `--reactive-let`, `let` assignments are silent no-ops (no error).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@close-by/clay",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "description": "NiceGUI-like ui.* facade for Clay apps",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,10 +37,10 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@close-by/clay-compiler": "0.2.24",
41
- "@close-by/clay-core": "0.2.24",
42
- "@close-by/clay-components": "0.2.24",
43
- "@close-by/clay-server": "0.2.24",
40
+ "@close-by/clay-compiler": "0.2.26",
41
+ "@close-by/clay-core": "0.2.26",
42
+ "@close-by/clay-components": "0.2.26",
43
+ "@close-by/clay-server": "0.2.26",
44
44
  "@tailwindcss/cli": "^4.3.3",
45
45
  "tailwindcss": "^4.3.3"
46
46
  }
package/src/index.ts CHANGED
@@ -501,10 +501,12 @@ import {
501
501
  } from '@close-by/clay-core';
502
502
  import {
503
503
  ClayServer,
504
+ onDispose,
504
505
  type ClayServerConfig,
505
506
  establishAuthSession as establishAuthSessionCore,
506
507
  clearAuthSession as clearAuthSessionCore,
507
508
  } from '@close-by/clay-server';
509
+ export { onDispose };
508
510
  import {
509
511
  loadPages,
510
512
  navFromPages,
@@ -2075,6 +2077,7 @@ export const ui = {
2075
2077
  runJavaScript,
2076
2078
  scroll,
2077
2079
  timer,
2080
+ onDispose,
2078
2081
  storage,
2079
2082
  theme,
2080
2083
  sound,