@close-by/clay 0.2.6 → 0.2.8
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 +1 -1
- package/docs/README.md +80 -0
- package/docs/ai.md +65 -0
- package/docs/api.md +2136 -0
- package/docs/architecture.md +103 -0
- package/docs/boot.md +103 -0
- package/docs/browser-apis.md +65 -0
- package/docs/clickhouse.md +59 -0
- package/docs/concepts.md +269 -0
- package/docs/duckdb.md +68 -0
- package/docs/elements.md +194 -0
- package/docs/examples.md +314 -0
- package/docs/getting-started.md +278 -0
- package/docs/jsx-investigation.md +167 -0
- package/docs/kibana.md +138 -0
- package/docs/llms.txt +44 -0
- package/docs/ops-patterns.md +176 -0
- package/docs/production.md +71 -0
- package/docs/protocol.md +238 -0
- package/docs/reactive-let.md +217 -0
- package/docs/tailwind.md +73 -0
- package/docs/tutorial.md +234 -0
- package/package.json +13 -6
- package/src/docs-dir.ts +44 -0
- package/src/index.ts +27 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
- [Bun](https://bun.sh/) 1.1+ (runtime for `clay` and app code)
|
|
6
|
+
- Node-compatible OS (macOS / Linux / WSL)
|
|
7
|
+
- For local tarball installs: npm (links co-installed `file:` packages; Bun may try the registry for rewritten versions)
|
|
8
|
+
|
|
9
|
+
## Outside the monorepo
|
|
10
|
+
|
|
11
|
+
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
|
+
|
|
13
|
+
Walkthrough (tasks list → two pages + `--app`): [Sample app](./tutorial.md).
|
|
14
|
+
|
|
15
|
+
### From local packs (this repo)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# In the Clay checkout:
|
|
19
|
+
bun install
|
|
20
|
+
bun run pack:publishable # build:client + bun pm pack → dist-pack/*.tgz
|
|
21
|
+
# workspace:* is rewritten to real versions inside each tarball
|
|
22
|
+
|
|
23
|
+
# In your app directory:
|
|
24
|
+
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
|
+
```
|
|
34
|
+
|
|
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
|
+
```
|
|
44
|
+
|
|
45
|
+
### Publishing to npm (maintainers)
|
|
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`
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# In the Clay checkout:
|
|
60
|
+
bun install
|
|
61
|
+
npm login # once; needs rights on the @close-by org
|
|
62
|
+
|
|
63
|
+
bun run publish:dry # pack + validate + npm publish --dry-run (no upload)
|
|
64
|
+
bun run publish:npm # pack + validate + real npm publish (same order)
|
|
65
|
+
# Re-run is safe: versions already on npm are skipped.
|
|
66
|
+
```
|
|
67
|
+
|
|
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
|
+
`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
|
+
|
|
84
|
+
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`).
|
|
85
|
+
|
|
86
|
+
### Run
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// hello.ts
|
|
90
|
+
import { ui } from '@close-by/clay';
|
|
91
|
+
|
|
92
|
+
export default function () {
|
|
93
|
+
ui.label('Hello Clay');
|
|
94
|
+
ui.button('Ping', { onClick: () => ui.notify('hi', 'success') });
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
bunx clay hello.ts # → http://localhost:3000 (opens browser)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`ui.run(() => { … })` also works; CLI `-p` / `--title` / `--app` apply when the entry uses a default export or `ui.page` and lets the CLI start the server.
|
|
103
|
+
|
|
104
|
+
## Three-line app (`clay`) — monorepo
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// hello.ts
|
|
108
|
+
import { ui } from '@close-by/clay';
|
|
109
|
+
|
|
110
|
+
ui.run(() => {
|
|
111
|
+
ui.label('Hello Clay');
|
|
112
|
+
ui.button('Ping', { onClick: () => ui.notify('hi', 'success') });
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**This monorepo:** build the client once (also copies assets into `@close-by/clay-cli` for packaging), then use the workspace CLI:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
bun install
|
|
120
|
+
bun run build:client # Vite → packages/client/dist (+ copy → packages/cli/client-dist)
|
|
121
|
+
bun run clay hello.ts # → http://localhost:3000 (opens browser)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The CLI prefers `packages/cli/client-dist` when present, otherwise falls back to `packages/client/dist` so local `bun run clay` / `demo:cli` work after a single `build:client`.
|
|
125
|
+
|
|
126
|
+
Or a default export (CLI registers `/` and starts the server for you):
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { ui } from '@close-by/clay';
|
|
130
|
+
|
|
131
|
+
export default function () {
|
|
132
|
+
ui.label('Hello Clay');
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
bun run clay hello.ts --port 4000
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Multi-page SPA with shell
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
bun run clay ./pages --app --title "My App"
|
|
144
|
+
# loadPages + ui.run({ app: { title, nav: navFromPages() } })
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| Flag | Meaning |
|
|
148
|
+
|------|---------|
|
|
149
|
+
| `-p, --port` | Port (default `3000`) |
|
|
150
|
+
| `-t, --title` | HTML / shell title |
|
|
151
|
+
| `--app` | Dashboard shell + nav from discovered pages |
|
|
152
|
+
| `--no-open` | Do not open the browser |
|
|
153
|
+
| `--reload` | Restart on file changes (`bun --watch`); opens the browser once; prints `↻ clay: reloading…` and re-imports pages (clears `require.cache`) |
|
|
154
|
+
| `--reactive-let` | Enable compile-time reactive `let` Bun loader (**off** by default; still needs `// @clay-reactive` or `"use reactive";` in source) |
|
|
155
|
+
| `--no-reactive-let` | Disable the reactive-let plugin (default) |
|
|
156
|
+
|
|
157
|
+
See [`docs/reactive-let.md`](./reactive-let.md) for `ui.state` / `ui.auto` (recommended), `ui.label(() => …)`, and the opt-in Phase 2 `let` transform.
|
|
158
|
+
|
|
159
|
+
**Complex pages (Orders-scale):** use Phase 1 `ui.state` + `ui.auto`, or opt-in reactive `let` with the [region decision table](./reactive-let.md#decision-table-complex-pages) — who wraps `ui.auto` for top-level helpers, widget callbacks, inline UI, and handlers.
|
|
160
|
+
|
|
161
|
+
For production deployment, auth, and server exposure, see [Production & security](./production.md).
|
|
162
|
+
|
|
163
|
+
## Install and run the demo
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
bun install
|
|
167
|
+
bun run build:client # builds packages/client → dist, copies into @close-by/clay-cli
|
|
168
|
+
bun run demo # starts apps/demo on :4000
|
|
169
|
+
# or
|
|
170
|
+
bun run demo:cli # same examples via `clay … --app`
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Or in one step:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
bun run dev
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Open:
|
|
180
|
+
|
|
181
|
+
- http://localhost:4000 — home with links to examples
|
|
182
|
+
- http://localhost:4000/examples/counter
|
|
183
|
+
- http://localhost:4000/examples/todo
|
|
184
|
+
- http://localhost:4000/examples/form-demo
|
|
185
|
+
- http://localhost:4000/examples/auth
|
|
186
|
+
|
|
187
|
+
| Script | What it does |
|
|
188
|
+
|--------|----------------|
|
|
189
|
+
| `bun run build:client` | Vite production build of the React client + copy into `@close-by/clay-cli` |
|
|
190
|
+
| `bun run pack:publishable` | `build:client` + pack `@close-by/clay-core` … `@close-by/clay` … `@close-by/clay-cli` → `dist-pack/` |
|
|
191
|
+
| `bun run publish:dry` | Pack + validate tarballs + `npm publish --dry-run` (no registry upload) |
|
|
192
|
+
| `bun run publish:npm` | Pack + validate + `npm publish` in order (requires `npm login`) |
|
|
193
|
+
| `bun run demo` | Start demo server (`main.ts`; expects client already built) |
|
|
194
|
+
| `bun run demo:cli` | Start demo via `clay apps/demo/src/examples --app` (loads `_run.ts` auth config) |
|
|
195
|
+
| `bun run clay …` | CLI runtime (`@close-by/clay-cli`) |
|
|
196
|
+
| `bun run dev` | Build client, then start demo |
|
|
197
|
+
| `bun test` | Run package tests |
|
|
198
|
+
|
|
199
|
+
### Packaging notes (maintainers)
|
|
200
|
+
|
|
201
|
+
- Source packages keep `workspace:*` for the monorepo; `bun pm pack` / publish rewrites them to the package version.
|
|
202
|
+
- `@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
|
+
- 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).
|
|
205
|
+
|
|
206
|
+
## Library mode (multi-page entrypoint)
|
|
207
|
+
|
|
208
|
+
Create a page file and an entrypoint:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// pages/counter.ts
|
|
212
|
+
import { ui } from '@close-by/clay';
|
|
213
|
+
|
|
214
|
+
export const pageMeta = { label: 'Counter', icon: 'hash', order: 10 };
|
|
215
|
+
|
|
216
|
+
ui.page('/counter', () => {
|
|
217
|
+
let count = 0;
|
|
218
|
+
const label = ui.label(`Count: ${count}`).classes('text-2xl');
|
|
219
|
+
|
|
220
|
+
ui.row(() => {
|
|
221
|
+
ui.button('-', {
|
|
222
|
+
variant: 'destructive',
|
|
223
|
+
onClick: () => {
|
|
224
|
+
count--;
|
|
225
|
+
label.setText(`Count: ${count}`);
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
ui.button('+', {
|
|
229
|
+
onClick: () => {
|
|
230
|
+
count++;
|
|
231
|
+
label.setText(`Count: ${count}`);
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}, { gap: 2 });
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// main.ts
|
|
240
|
+
import { ui } from '@close-by/clay';
|
|
241
|
+
|
|
242
|
+
await ui.loadPages(new URL('./pages', import.meta.url));
|
|
243
|
+
|
|
244
|
+
ui.run({
|
|
245
|
+
port: 4000,
|
|
246
|
+
title: 'My App',
|
|
247
|
+
css: './globals.css', // optional theme overrides
|
|
248
|
+
app: {
|
|
249
|
+
title: 'My App',
|
|
250
|
+
nav: ui.navFromPages(),
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Point `clientDir` with `resolveClayClientDir()` (or omit it — `ui.run` does the same) instead of hardcoding `node_modules/@close-by/clay-cli/client-dist`. Details: [Boot](./boot.md).
|
|
256
|
+
|
|
257
|
+
Use `css` to inject your own stylesheet after the built client styles. **Token overrides** (`--primary`, `--background`, …) always work. **Tailwind utilities** in page modules are auto-scanned by default (`clay` / `ui.run`) — see [Tailwind](./tailwind.md).
|
|
258
|
+
|
|
259
|
+
## Project layout (monorepo)
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
apps/demo/ Demo pages + server entry (`loadPages` + `ui.run({ app })`)
|
|
263
|
+
packages/cli/ `clay` binary — file/dir launcher + shipped client-dist
|
|
264
|
+
packages/ui/ NiceGUI-style ui facade (`loadPages`, `navFromPages`, `run`)
|
|
265
|
+
packages/core/ Element tree, session, page wrapper, reactive, storage (tab/user/app)
|
|
266
|
+
packages/auth/ Optional password hash, login limiter, requireAuth / requireRole, audit
|
|
267
|
+
packages/persistence-file/ File-backed PersistenceAdapter for storage.configure
|
|
268
|
+
packages/components/ Element factories (button, input, dataTable, areaChart / barChart / lineChart / pieChart / radarChart / radialChart, …)
|
|
269
|
+
packages/client/ React + ShadCN renderer (Sonner toasts, BoundDataTable, …) — private, not published
|
|
270
|
+
packages/server/ Bun HTTP + WebSocket
|
|
271
|
+
docs/ This documentation
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Next
|
|
275
|
+
|
|
276
|
+
- [Concepts](./concepts.md) — sessions, patches, refreshable
|
|
277
|
+
- [API](./api.md) — full `ui.*` reference
|
|
278
|
+
- [Examples](./examples.md) — demo catalog
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# JSX investigation — context & recommendation
|
|
2
|
+
|
|
3
|
+
Status: **investigation complete** (2026-08). Spike B POC landed in `@close-by/clay-core/jsx-runtime` (experimental). Spike A deferred.
|
|
4
|
+
|
|
5
|
+
Related backlog: [TASKS.md](../TASKS.md) Later → Investigate `ui.jsx`. Historical HTMX-era note: [iterations/iteration-10.md](../iterations/iteration-10.md) (string `render()` → JSX; **superseded** by today’s Element + WebSocket model).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
Apps (notably UQ Hub) still build markup as **strings** and pass them to `ui.html`:
|
|
12
|
+
|
|
13
|
+
| Use (uq-hub) | Example |
|
|
14
|
+
|--------------|---------|
|
|
15
|
+
| Status dots | `` ui.html(`<span class="… rounded-full ${color}">`) `` |
|
|
16
|
+
| Sparkline bars | `` ui.html(`<div style="width:…;background:…">`) `` |
|
|
17
|
+
| SigNoz iframe | `` ui.html(`<iframe src="${url}" …>`) `` |
|
|
18
|
+
| Trace icon link | HTML `<a>` + inline SVG string |
|
|
19
|
+
|
|
20
|
+
Pain:
|
|
21
|
+
|
|
22
|
+
1. No IDE structure / prop types / rename
|
|
23
|
+
2. Easy XSS if values aren’t escaped
|
|
24
|
+
3. Can’t compose real Clay components inside the blob
|
|
25
|
+
4. Client path is `dangerouslySetInnerHTML` — no Clay events inside the string
|
|
26
|
+
|
|
27
|
+
Goal: **author markup as markup** with IDE support for tags + user-defined components — without inventing a second UI stack.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Current architecture (constraints)
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Page builder (Bun) → Element tree → JSON mount/patch → ElementRenderer (React)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- `new Element(type, props)` **attaches immediately** to `getCurrentParent()` via `attachToContext`.
|
|
38
|
+
- Layout uses **callback children**: `ui.row(props, () => { ui.button(…) })` so children run *after* the parent is on the stack.
|
|
39
|
+
- Standard JSX evaluates **children before parents**. That fights attach-on-construct unless we create nodes **detached** and **reparent**.
|
|
40
|
+
|
|
41
|
+
POC fix: `Element.adopt(child)` + `withDetached(() => new Element(…))` in `jsx()`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Spike A — JSX → static HTML → `ui.html`
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
ui.html(renderToStaticMarkup(
|
|
49
|
+
<div className="flex">
|
|
50
|
+
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
|
|
51
|
+
</div>
|
|
52
|
+
));
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
| Pros | Cons |
|
|
56
|
+
|------|------|
|
|
57
|
+
| Familiar HTML tags | Still opaque to Clay (one `html` node) |
|
|
58
|
+
| IDE for DOM + React-ish function components | **No** `onClick` / Clay handlers inside |
|
|
59
|
+
| Fits iframe / SVG blobs | Pulls React (or similar) into the **server** app, or a custom HTML serializer |
|
|
60
|
+
| | Duplicates styling (Tailwind must scan these modules too) |
|
|
61
|
+
|
|
62
|
+
**Verdict:** Only useful for true opaque HTML (iframe, third-party SVG). Most UQ Hub dots/bars should become Clay nodes instead. Prefer first-class `ui.iframe` over A for SigNoz embed.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Spike B — JSX → Clay `Element` tree
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
/** @jsxImportSource @close-by/clay-core */
|
|
70
|
+
function StatusDot({ ok }: { ok: boolean }) {
|
|
71
|
+
return <badge text={ok ? 'up' : 'down'} size="xs" color={ok ? 'emerald' : 'red'} />;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
ui.page('/', () => {
|
|
75
|
+
<row gap={2}>
|
|
76
|
+
<StatusDot ok={true} />
|
|
77
|
+
<button text="Refresh" icon="refresh-cw" onClick={() => void load()} />
|
|
78
|
+
</row>;
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Pros | Cons |
|
|
83
|
+
|------|------|
|
|
84
|
+
| Same protocol / renderer / events | Intrinsics are Clay types (`button`, `row`), **not** HTML (`div`, `span`) |
|
|
85
|
+
| IDE + custom components with props | Children-first requires `adopt` (POC done) |
|
|
86
|
+
| Mix with `ui.*` under same parent | Layout/API mismatch: `ui.button(text, props)` vs `<button text props>` |
|
|
87
|
+
| Aligns with long-term DX | Need docs so people don’t expect DOM JSX |
|
|
88
|
+
|
|
89
|
+
**POC (landed):**
|
|
90
|
+
|
|
91
|
+
- `Element.adopt`
|
|
92
|
+
- `@close-by/clay-core/jsx-runtime` (`jsx` / `jsxs` / `Fragment`)
|
|
93
|
+
- Tests: `packages/core/src/jsx-runtime.test.ts`
|
|
94
|
+
|
|
95
|
+
Opt-in:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
"compilerOptions": {
|
|
99
|
+
"jsx": "react-jsx",
|
|
100
|
+
"jsxImportSource": "@close-by/clay-core"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
(Use `.tsx` page modules; keep default Clay apps on `.ts` + `ui.*` until DX is documented. Resolves to `@close-by/clay-core/jsx-runtime`.)
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Mixing `ui.*` and JSX
|
|
109
|
+
|
|
110
|
+
| Pattern | Supported? |
|
|
111
|
+
|---------|------------|
|
|
112
|
+
| JSX inside `ui.row(() => { … })` | Yes — `jsx()` attaches to current parent |
|
|
113
|
+
| `ui.button` inside a function component used as `<Foo />` | Yes — if the component runs under `withParent` / current session |
|
|
114
|
+
| `<div>` HTML intrinsic | **No** (Spike B) — use Clay `container` / `row` / future `box`, or Spike A / `ui.html` |
|
|
115
|
+
| `ui.html` string escape hatch | Keep forever for trusted blobs |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Trust model
|
|
120
|
+
|
|
121
|
+
| API | Trust |
|
|
122
|
+
|-----|--------|
|
|
123
|
+
| `ui.html(string)` | Trusted HTML only (XSS if user data interpolated) |
|
|
124
|
+
| Spike A static markup | Same as `ui.html` once stringified |
|
|
125
|
+
| Spike B Clay JSX | Props go through Element / protocol; handlers stay on server — **preferred** |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Recommendation (phased)
|
|
130
|
+
|
|
131
|
+
### Phase 0 — eliminate most strings without JSX (high ROI)
|
|
132
|
+
|
|
133
|
+
Ship small primitives that replace UQ Hub’s `ui.html` call sites:
|
|
134
|
+
|
|
135
|
+
1. **`ui.iframe({ src, title?, className?, … })`** — SigNoz embed
|
|
136
|
+
2. **`ui.dot({ color / tone, className? })`** or document `badge` + `size: 'xs'` as status chip (dots may still want a 6px circle — tiny `container`/`html` one-liner ok)
|
|
137
|
+
3. Prefer **`ui.externalLink` / `ui.copyButton`** (already shipped) over HTML `<a>` icon buttons; SVG brand icons can stay `ui.html` until `ui.image` / icon upload exists
|
|
138
|
+
|
|
139
|
+
### Phase 1 — productize Spike B (recommended north-star)
|
|
140
|
+
|
|
141
|
+
1. Export jsx runtime from `@close-by/clay` as well (`jsxImportSource: "@close-by/clay"`) for one import path
|
|
142
|
+
2. Document Clay intrinsics list + function-component rules in `docs/jsx.md`
|
|
143
|
+
3. Demo page mixing `<row>` JSX with `ui.auto` / `ui.state`
|
|
144
|
+
4. Consider `ui.jsx` as a **hyperscript alias** only if needed; automatic runtime is enough
|
|
145
|
+
5. Do **not** default `jsxImportSource` in the Clay monorepo root (client already uses React JSX)
|
|
146
|
+
|
|
147
|
+
### Phase 2 — Spike A only if still needed
|
|
148
|
+
|
|
149
|
+
If trusted SVG/iframe authoring remains painful after Phase 0–1, add optional `renderClayHtml(vnode)` **without** depending on React (small tag→string walker), feeding `ui.html`. Otherwise skip.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Decision
|
|
154
|
+
|
|
155
|
+
| Spike | Decision |
|
|
156
|
+
|-------|----------|
|
|
157
|
+
| **B (Clay JSX)** | **Pursue** — POC in core; next = docs + clay re-export + demo |
|
|
158
|
+
| **A (SSR HTML)** | **Defer / maybe skip** — replaced by `ui.iframe` + Clay nodes for hub cases |
|
|
159
|
+
| **`ui.html(string)`** | **Keep** as escape hatch |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Open questions
|
|
164
|
+
|
|
165
|
+
1. Should Clay intrinsics use DOM-like names (`div` → `container`) for familiarity, or stay honest (`container` / `row`)?
|
|
166
|
+
2. TypeScript JSX intrinsic elements (`JSX.IntrinsicElements`) for autocomplete of `button` / `row` props?
|
|
167
|
+
3. Can `ui.auto(() => <row>…</row>)` be the documented reactive pattern once B is stable?
|
package/docs/kibana.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Kibana (`@close-by/clay-kibana`)
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { Kibana } from '@close-by/clay-kibana';
|
|
7
|
+
|
|
8
|
+
// Modern Kibana (console proxy)
|
|
9
|
+
const kbn = new Kibana({
|
|
10
|
+
baseUrl: 'https://kibana.example.com',
|
|
11
|
+
apiKey: process.env.KIBANA_API_KEY!, // or username + password
|
|
12
|
+
space: 'sales', // optional
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Kibana 6.x style (same as `…/elasticsearch/{index}/_search`)
|
|
16
|
+
const logs = new Kibana({
|
|
17
|
+
baseUrl: 'https://kibana.internal.factsandit.pt',
|
|
18
|
+
kbnVersion: '6.8.0',
|
|
19
|
+
esTransport: 'path', // → /elasticsearch/…
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Auth & headers
|
|
24
|
+
|
|
25
|
+
| Option | Description |
|
|
26
|
+
|--------|-------------|
|
|
27
|
+
| `apiKey` | `Authorization: ApiKey …` (optional) |
|
|
28
|
+
| `username` + `password` | `Authorization: Basic …` (optional) |
|
|
29
|
+
| `kbnVersion` | Sets `kbn-version` (needed on older Kibana ES proxies) |
|
|
30
|
+
| `space` | Prefixes Kibana API paths with `/s/{space}` |
|
|
31
|
+
| `headers` | Extra headers on every request |
|
|
32
|
+
| `fetch` | Inject a custom `fetch` (tests) |
|
|
33
|
+
|
|
34
|
+
Every request also sends `kbn-xsrf: true`.
|
|
35
|
+
|
|
36
|
+
## Elasticsearch transport
|
|
37
|
+
|
|
38
|
+
| `esTransport` | Behavior |
|
|
39
|
+
|---------------|----------|
|
|
40
|
+
| `'console'` (default) | `POST /api/console/proxy?path=…&method=…` |
|
|
41
|
+
| `'path'` | `{esPathPrefix}/{index}/_search` (default prefix `/elasticsearch`) |
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const result = await logs.search('logstash-production-logback-*', {
|
|
45
|
+
query: { match_all: {} },
|
|
46
|
+
size: 10,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await logs.es('GET', '_cluster/health');
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Paginate every hit (`search_after`)
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
for await (const hit of logs.paginateSearch(index, body, { pageSize: 1000 })) {
|
|
56
|
+
// …
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Guided query builder
|
|
61
|
+
|
|
62
|
+
Chain filters instead of hand-writing Elasticsearch JSON:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
type LogLine = { '@timestamp': string; message: string };
|
|
66
|
+
|
|
67
|
+
for await (const hit of logs
|
|
68
|
+
.query<LogLine>('logstash-production-logback-*')
|
|
69
|
+
.lastDays(1) // or .between(start, end)
|
|
70
|
+
.match('*receivePartnerEvent*')
|
|
71
|
+
.exclude('*ag_ibersol_webhook_notify_queue*')
|
|
72
|
+
.select('@timestamp', 'message')
|
|
73
|
+
.pageSize(1000)
|
|
74
|
+
.stream()) {
|
|
75
|
+
console.log(hit._source?.message);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Or collect sources
|
|
79
|
+
const lines = await logs
|
|
80
|
+
.query<LogLine>('logstash-production-logback-*')
|
|
81
|
+
.between(start, end)
|
|
82
|
+
.match('*receivePartnerEvent*')
|
|
83
|
+
.select('@timestamp', 'message')
|
|
84
|
+
.sources();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Method | Purpose |
|
|
88
|
+
|--------|---------|
|
|
89
|
+
| `from` / `to` / `between` / `lastDays` | `@timestamp` range (override field with `timeField`) |
|
|
90
|
+
| `match(query, field?)` | `query_string` must (default field `message`) |
|
|
91
|
+
| `exclude(query, field?)` | `query_string` must_not |
|
|
92
|
+
| `term` / `where` / `whereNot` | Exact term or raw clauses |
|
|
93
|
+
| `select(...fields)` | `_source` filter |
|
|
94
|
+
| `pageSize` / `sort` | Pagination controls |
|
|
95
|
+
| `body()` | Inspect built ES JSON |
|
|
96
|
+
| `first()` / `stream()` / `all()` / `sources()` | Execute |
|
|
97
|
+
| `toParquet(path)` | Export all hits to a Parquet file (via DuckDB) |
|
|
98
|
+
|
|
99
|
+
### Export to Parquet
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const { path, rows } = await logs
|
|
103
|
+
.query('logstash-production-logback-*')
|
|
104
|
+
.lastDays(1)
|
|
105
|
+
.match('*receivePartnerEvent*')
|
|
106
|
+
.select('@timestamp', 'message')
|
|
107
|
+
.toParquet('./data/logs.parquet');
|
|
108
|
+
|
|
109
|
+
// Later in DuckDB:
|
|
110
|
+
// SELECT * FROM 'data/logs.parquet'
|
|
111
|
+
// or COPY / CREATE TABLE … AS SELECT * FROM 'data/logs.parquet'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Options: optional `map(hit => row)` for a custom shape. By default each Parquet row is the document **`_source` only** (no `_id`, `_index`, `_type`, `_score`, or `sort`).
|
|
115
|
+
|
|
116
|
+
```sql
|
|
117
|
+
SELECT message, "@timestamp" FROM 'data/logs.parquet'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Raw Kibana REST
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const spaces = await kbn.request('GET', '/api/spaces/space');
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Failures throw `KibanaError` with `status` and parsed `body`.
|
|
127
|
+
|
|
128
|
+
## Saved Objects
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const found = await kbn.findSavedObjects({
|
|
132
|
+
type: ['dashboard', 'visualization'],
|
|
133
|
+
search: 'Sales',
|
|
134
|
+
perPage: 20,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const dash = await kbn.getSavedObject('dashboard', 'my-dashboard-id');
|
|
138
|
+
```
|
package/docs/llms.txt
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Clay
|
|
2
|
+
|
|
3
|
+
> Server-driven UI for TypeScript. Write imperative `ui.*` on the server; Clay owns a per-tab element tree and syncs it to a React + ShadCN client over WebSocket. Inspired by NiceGUI.
|
|
4
|
+
|
|
5
|
+
Clay is not a React app framework. Do not write React, JSX, or client components for screens. App code uses `@close-by/clay` (`ui.page`, `ui.run`, `ui.button`, …). The React client is a renderer.
|
|
6
|
+
|
|
7
|
+
Notes for agents:
|
|
8
|
+
|
|
9
|
+
- Public API and wire protocol are camelCase (`onClick`, `bindValue`, `setText`).
|
|
10
|
+
- Page builders run once per WebSocket session (per browser tab). `let` is per-tab, not shared.
|
|
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
|
+
- Never use `window` / `navigator` in page code — use `ui.clipboard`, `ui.setUrlHash`, `ui.openExternal`.
|
|
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
|
+
- `ui.ai.*` is visual primitives only — no model or streaming runtime.
|
|
15
|
+
|
|
16
|
+
## Docs
|
|
17
|
+
|
|
18
|
+
- [Human README](README.md): What Clay is, what you can build, try-it commands
|
|
19
|
+
- [Getting started](docs/getting-started.md): Install, `clay` CLI, first app, publishing
|
|
20
|
+
- [Boot](docs/boot.md): CLI vs library, `clientDir`, `_run.ts`, reload stubs
|
|
21
|
+
- [Tailwind](docs/tailwind.md): Scanned utilities + `ui.run({ css })`
|
|
22
|
+
- [Sample app](docs/tutorial.md): Step-by-step tasks list, then two pages + shell
|
|
23
|
+
- [Concepts](docs/concepts.md): Sessions, element tree, patches, refreshable, bindings, storage
|
|
24
|
+
- [Browser APIs](docs/browser-apis.md): `ui.clipboard` / hash / `openExternal` — no `window` in page code
|
|
25
|
+
- [Ops patterns](docs/ops-patterns.md): Dense consoles — chips, feeds, sensitive codeBlock, auth nav
|
|
26
|
+
- [API reference](docs/api.md): Complete `ui.*` and Element API
|
|
27
|
+
- [Elements](docs/elements.md): Wire types and client mapping
|
|
28
|
+
- [Documentation index](docs/README.md): Full guide list
|
|
29
|
+
|
|
30
|
+
## Examples
|
|
31
|
+
|
|
32
|
+
- [Examples catalog](docs/examples.md): Demo routes and patterns
|
|
33
|
+
- [Demo pages](apps/demo/src/examples/): Runnable `ui.page` modules
|
|
34
|
+
|
|
35
|
+
## Optional
|
|
36
|
+
|
|
37
|
+
- [Architecture](docs/architecture.md): Packages and data flow
|
|
38
|
+
- [WebSocket protocol](docs/protocol.md): Message formats
|
|
39
|
+
- [AI UI](docs/ai.md): `ui.ai.*` visual primitives
|
|
40
|
+
- [Reactive let](docs/reactive-let.md): `ui.state` / `ui.auto` and the compile-time `let` transform
|
|
41
|
+
- [DuckDB](docs/duckdb.md): `@close-by/clay-duckdb`
|
|
42
|
+
- [ClickHouse](docs/clickhouse.md): `@close-by/clay-clickhouse`
|
|
43
|
+
- [Kibana](docs/kibana.md): `@close-by/clay-kibana`
|
|
44
|
+
- [NiceGUI](https://nicegui.io/documentation): Inspiration for the `ui.*` style
|