@uicast/core 0.0.1 → 0.0.2

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
@@ -1,3 +1,9 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://uicast.dev/uicast-logo-dark.svg">
4
+ <img alt="" src="https://uicast.dev/uicast-logo.svg" width="54">
5
+ </picture>
6
+ </p>
1
7
  <h1 align="center">@uicast/core</h1>
2
8
  <p align="center">Part of <a href="https://github.com/finom/uicast"><strong>uicast</strong></a>, the expression-driven generative UI framework.</p>
3
9
  <p align="center"><a href="https://uicast.dev">uicast.dev</a></p>
@@ -37,7 +37,7 @@ const stepsFault = (steps, at, setRequired) => {
37
37
  }
38
38
  return null;
39
39
  };
40
- const STRING_FIELDS = ["hidden", "loading", "each", "as", "keyBy"];
40
+ const STRING_FIELDS = ["hidden", "busy", "each", "as", "keyBy"];
41
41
  const shapeFault = (entry) => {
42
42
  if (entry.props !== undefined) {
43
43
  if (!isRecord(entry.props))
@@ -18,8 +18,8 @@ export function extractDeps(entry, evaluator, part) {
18
18
  add(entry.props.expr);
19
19
  if (entry.hidden)
20
20
  add(entry.hidden);
21
- if (entry.loading)
22
- add(entry.loading);
21
+ if (entry.busy)
22
+ add(entry.busy);
23
23
  }
24
24
  // `each` reads both the list and anything its filter touches (e.g. a search term).
25
25
  if (part !== "render" && isComponentListEntry(entry))
@@ -1 +1 @@
1
- "# Output Format\n\nBuild UI as JSONL: one **entry** per line, each complete JSON object. Output raw JSONL only — no prose, no reasoning, no code fences, no array wrapper. Plan silently.\n\nEntry fields:\n\n- `key` — unique id, kebab-case: `\"save-btn\"`.\n- `component` — name from **Available Components**.\n- `props` — `{ \"literal\": { ... } }` or `{ \"expr\": \"...\" }`.\n- `children` — child keys, in render order.\n- `seed` — steps run once, at mount.\n- `callbacks` — steps run on events: `{ \"onClick\": [...] }`.\n- `hidden`, `loading` — bare expressions.\n- `each`, `as`, `keyBy` — make entry a list.\n\nEmit parent before its children: root first, then depth-first.\n\n`## Shared Types` in component or function listing names types reused in that listing: prop documented as `Person` means `Person` there. Type may refer to itself: `Node: { children?: Node[] }`.\n\n# Rules\n\n## 1. Structure\n\n- Entries flat, never nested. Parent lists child keys in `children`.\n- Exactly one root: entry no `children` lists. Every other entry listed exactly once. Every listed key emitted.\n\n## 2. Props and expressions\n\n- `literal`: JSON used verbatim, never evaluated. Use when props never change.\n- `expr`: expression returning props object: `\"props\": { \"expr\": \"({ value: scopes.root.count })\" }`.\n- Expression only computes. Only step's `set` writes state. Derive values inline; `props` re-run when field they read changes: `\"props\": { \"expr\": \"({ rows: scopes.root.tasks.toSorted((a, b) => a.due - b.due) })\" }`.\n- Same state, same result. `props`, `hidden`, `loading`, `each` re-run only when field they read changes, maybe more than once. No clock, no `Math.random()`: time and random values come from host functions, if offered, called in step: `{ \"set\": \"scopes.root.savedAt\", \"expr\": \"now()\" }` when `now` exists.\n- `children` never a prop. Text goes in component's text prop, often `text`.\n- String or number prop takes string or number, never object or array: `({ text: scopes.member.name })`, not `({ text: scopes.member })`. Object and array props (`rows`, `options`) take what their type says.\n\n## 3. State and seed\n\n- `seed` steps: `{ \"set\": \"scopes.<scope>.<field>\", \"literal\": <value> }` or `{ \"set\": \"scopes.<scope>.<field>\", \"expr\": \"...\" }`. Every seed step has `set`.\n- `set` names one field: `scopes.<scope>.<field>`. Nothing deeper, no index. To change part of object or array, write whole field: `{ \"set\": \"scopes.root.filters\", \"expr\": \"({ ...currentValue, status: 'open' })\" }`.\n- Seed runs once, at first mount, writes only unset fields (first writer wins): initializes, never resets. Change state from callbacks.\n- Seed calling host function shows element's skeleton until resolved. Never write `await`: runtime awaits.\n- Step reading field earlier step writes waits for it; independent steps run in parallel. Child seeds run after parent's.\n- Seed only reads data. Create, update, delete only from callbacks.\n- No derived values in seed: runs once, value goes stale. Derive inline in `props`, `hidden`, `each`.\n - ❌ `\"seed\": [{ \"set\": \"scopes.root.visible\", \"expr\": \"scopes.root.rows.filter(r => r.name.includes(scopes.root.q))\" }]` with `\"each\": \"scopes.root.visible\"` — typing in search does nothing.\n - ✅ `\"each\": \"scopes.root.rows.filter(r => r.name.includes(scopes.root.q))\"`\n- Unset field reads `undefined`; read below it throws. Seed state before reading, or guard with `?.` / `??`.\n- Root entry seeds all root state.\n\n## 4. Scopes\n\n- `scopes.root`: page state.\n- List with `\"as\": \"row\"` gives each item two scopes:\n - `scopes.row` — item itself: `scopes.row.name` reads its `name`.\n - `scopes.$row` — row: `index` (0-based), `id` (`keyBy` value, else index), `value` (item, when array holds strings or numbers). These three read-only. Host call takes data field, `scopes.row.id`.\n- `as` names new scope: never `root`, host scope, or enclosing list's `as`; no leading `$`.\n- Row's own UI state (expanded, editing, draft) goes in `scopes.$row`: starts unset, stays out of data, write re-renders only that row. Toggle: `{ \"set\": \"scopes.$row.open\", \"expr\": \"!currentValue\" }`. Only row and its children see it.\n- State read outside row (select all, selected count) lives at root, keyed by row id. Read: `scopes.root.selected[scopes.$row.id] ?? false`. Toggle: `{ \"set\": \"scopes.root.selected\", \"expr\": \"({ ...currentValue, [scopes.$row.id]: !currentValue[scopes.$row.id] })\" }`. Select all, with `\"keyBy\": \"id\"`: `{ \"set\": \"scopes.root.selected\", \"expr\": \"scopes.root.rows.reduce((m, r) => ({ ...m, [r.id]: true }), {})\" }`. Seed `selected: {}` on root. Nested list: key by both ids, `scopes.$order.id + '/' + scopes.$line.id`.\n- No other scopes unless note names them. Other `scopes.x` reads `undefined`; writing it fails.\n\n## 5. Reactivity\n\n- Automatic. `props`, `hidden`, `loading`, `each` subscribe to every `scopes.X.Y` they read; write re-renders readers. Reading whole scope (`Object.keys(scopes.root)`) subscribes to all its fields. `seed`, `callbacks` never subscribe.\n- Write wakes readers below it: writing `scopes.root.user` wakes reader of `scopes.root.user.name`.\n- Write to item field puts edited copy of item into array `each` reads, and in nested list into arrays above. `{ \"set\": \"scopes.row.qty\", \"expr\": \"evt.value\" }` updates `scopes.root.rows`, so totals and filters reading it update. Live total inline in props: `scopes.root.rows.reduce((s, r) => s + r.qty * r.price, 0)`.\n- So `each` reads data itself, or `filter`, `toSorted`, `slice` of it. When `each` builds new objects (`rows.map(r => ({ ...r, total: r.qty * r.price }))`), row writes fail: compute per-row values in `props`.\n\n## 6. Lists\n\n- `each`: bare expression giving array, `\"scopes.root.rows\"` or derived `\"scopes.root.rows.filter(r => r.active)\"`; `null` or `undefined` gives no rows. `as`: required, names item scopes (§4). `keyBy`: item field holding unique id — set whenever items have one; without it rows key by index.\n- List entry's component renders once per item: `\"component\": \"TableRow\"` gives one row per item. Its `props` and `children` see item scopes.\n- Cap list whose size you don't control: `\"each\": \"scopes.root.rows.slice(0, 🔴MAX_LIST_ITEMS🔴)\"`, no-op on smaller data. Data past cap gets pages: function's window (§9) when it has one, else slice by page, `\"each\": \"scopes.root.rows.slice((scopes.root.page - 1) * 50, scopes.root.page * 50)\"`, callbacks writing `scopes.root.page`. Hide pager on one page: `\"hidden\": \"scopes.root.rows.length <= 50\"`. Search or filters only for data that can pass cap.\n- List's own `seed` runs once for whole list, outside rows: no `scopes.$<as>` there.\n\n## 7. Callbacks\n\n- `\"callbacks\": { \"onClick\": [steps] }`. Only handlers component lists; others never fire.\n- Step: `{ \"set\": \"scopes.<scope>.<field>\", \"expr\": \"...\" }`, or `\"literal\"` instead of `\"expr\"`. Step without `set` runs for its effect.\n- `evt`: handler's payload; fields listed under component.\n- `currentValue`: value at step's `set` field, `undefined` when unset. Toggle `!currentValue`, increment `currentValue + 1`, append `[...currentValue, item]`.\n- Order: step reading field earlier step writes waits for it; host function call waits for every earlier step; row field write counts as write to list's array. Other steps may run together. Save-then-refetch just works.\n- `\"confirm\": \"Delete this order?\"` on step opens dialog first; cancel skips that step and every later one. Put on first step of every callback that deletes or cannot be undone. Never build confirm dialog entries.\n- `\"debounce\": true` on step: it and later steps run after 300 ms without another call, with latest `evt`; earlier steps run at once. Search: `\"onChange\": [{ \"set\": \"scopes.root.q\", \"expr\": \"evt.value\" }, { \"set\": \"scopes.root.rows\", \"expr\": \"Api_search({ q: scopes.root.q })\", \"debounce\": true }]`. Never call host function on every keystroke.\n- Patterns:\n - Remove item: `{ \"set\": \"scopes.root.rows\", \"expr\": \"currentValue.filter(r => r.id !== scopes.row.id)\" }`\n - Edit row field: `{ \"set\": \"scopes.row.qty\", \"expr\": \"evt.value\" }`\n - Edit row from outside its list, replacing array: `{ \"set\": \"scopes.root.rows\", \"expr\": \"currentValue.map(r => r.id === scopes.root.selectedId ? { ...r, qty: 0 } : r)\" }`\n\n## 8. Hidden and loading\n\n- `hidden`: bare expression, not `{ \"expr\": ... }`. Truthy hides element. Hidden element keeps state, and its expressions still run: guard them, `scopes.root.user?.name`.\n- `loading`: bare expression. Truthy shows component busy, content kept. For refreshes; first load shows skeleton by itself. Set flag, call, clear flag: `{ \"loading\": \"scopes.root.busy\" }` and `\"onClick\": [{ \"set\": \"scopes.root.busy\", \"literal\": true }, { \"set\": \"scopes.root.rows\", \"expr\": \"Api_list()\" }, { \"set\": \"scopes.root.busy\", \"literal\": false }]`. Seed `busy: false` on root.\n\n## 9. Host functions\n\n- Listed under **Available Functions**. Call in `seed` and `callbacks` steps only, never in `props`, `hidden`, `loading`, `each`.\n- One argument matching documented input; none when no input: `Api_list()`, `Api_delete({ id: scopes.row.id })`, never `Api_delete(scopes.row.id)`.\n- Call is step's result: whole expression, `?:` branch, or right side of `&&`, `||`, `??` (`scopes.root.q ? Api_search({ q: scopes.root.q }) : []`). Runtime awaits it, writes result to `set`. Read result in later step or in `props`. Rejected: `Api_list().length`, `[A(), B()]`, `ids.map(id => Api_get({ id }))`. Many ids: call one function taking all.\n- Function offers window, sort or filters (limit, offset, page, cursor, sort, filter fields): use them. Fetch slice you render; refetch when page or filter changes. Never filter or sort fetched slice in expression unless it holds every matching row (`total <= limit`). Read returned totals instead of summing rows.\n- Mutate in callback, then refetch.\n- Function returning `unknown`: shape undeclared, not empty. Call for effect only (step without `set`), then refetch through function with documented return. Never read fields off it; never call in seed.\n - ✅ `[{ \"expr\": \"Api_archive({ id: scopes.row.id })\" }, { \"set\": \"scopes.root.rows\", \"expr\": \"Api_list()\" }]`\n\n## 10. Replacing entries\n\n- Re-emit `key` to replace its entry, later in same output or later turn. New `children` define subtree: listed old children stay as they are (don't re-emit), unlisted dropped, new keys follow as usual. To fix only entry, keep `children` same.\n- State survives. Seed that succeeded never re-runs; failed one runs again once fixed. New state: new key's seed, or callback.\n- Broken entry (bad expression, unknown function, bad syntax) shows error in its place; rest of UI works. Re-emit it fixed, once, only when sure. No trial and error.\n\n# Expression Context\n\nExpressions use language under **JavaScript Expressions**, plus these names:\n\n- `scopes` — state (§4). `scopes.root` always exists; list rows add `scopes.<as>` and `scopes.$<as>`.\n- `evt` — handler's payload, callbacks only.\n- `currentValue` — value at step's `set` field, only in steps with `set`.\n- Host functions under **Available Functions** — steps only, called bare: `getUsers()`, never `await getUsers()`.\n"
1
+ "# Output Format\n\nBuild UI as JSONL: one **entry** per line, each complete JSON object. Output raw JSONL only — no prose, no reasoning, no code fences, no array wrapper. Plan silently.\n\nEntry fields:\n\n- `key` — unique id, kebab-case: `\"save-btn\"`.\n- `component` — name from **Available Components**.\n- `props` — `{ \"literal\": { ... } }` or `{ \"expr\": \"...\" }`.\n- `children` — child keys, in render order.\n- `seed` — steps run once, at mount.\n- `callbacks` — steps run on events: `{ \"onClick\": [...] }`.\n- `hidden`, `busy` — bare expressions.\n- `each`, `as`, `keyBy` — make entry a list.\n\nEmit parent before its children: root first, then depth-first.\n\n`## Shared Types` in component or function listing names types reused in that listing: prop documented as `Person` means `Person` there. Type may refer to itself: `Node: { children?: Node[] }`.\n\n# Rules\n\n## 1. Structure\n\n- Entries flat, never nested. Parent lists child keys in `children`.\n- Exactly one root: entry no `children` lists. Every other entry listed exactly once. Every listed key emitted.\n\n## 2. Props and expressions\n\n- `literal`: JSON used verbatim, never evaluated. Use when props never change.\n- `expr`: expression returning props object: `\"props\": { \"expr\": \"({ value: scopes.root.count })\" }`.\n- Expression only computes. Only step's `set` writes state. Derive values inline; `props` re-run when field they read changes: `\"props\": { \"expr\": \"({ rows: scopes.root.tasks.toSorted((a, b) => a.due - b.due) })\" }`.\n- Same state, same result. `props`, `hidden`, `busy`, `each` re-run only when field they read changes, maybe more than once. No clock, no `Math.random()`: time and random values come from host functions, if offered, called in step: `{ \"set\": \"scopes.root.savedAt\", \"expr\": \"now()\" }` when `now` exists.\n- `children` never a prop. Text goes in component's text prop, often `text`.\n- String or number prop takes string or number, never object or array: `({ text: scopes.member.name })`, not `({ text: scopes.member })`. Object and array props (`rows`, `options`) take what their type says.\n\n## 3. State and seed\n\n- `seed` steps: `{ \"set\": \"scopes.<scope>.<field>\", \"literal\": <value> }` or `{ \"set\": \"scopes.<scope>.<field>\", \"expr\": \"...\" }`. Every seed step has `set`.\n- `set` names one field: `scopes.<scope>.<field>`. Nothing deeper, no index. To change part of object or array, write whole field: `{ \"set\": \"scopes.root.filters\", \"expr\": \"({ ...currentValue, status: 'open' })\" }`.\n- Seed runs once, at first mount, writes only unset fields (first writer wins): initializes, never resets. Change state from callbacks.\n- Seed calling host function shows element's skeleton until resolved. Never write `await`: runtime awaits.\n- Step reading field earlier step writes waits for it; independent steps run in parallel. Child seeds run after parent's.\n- Seed only reads data. Create, update, delete only from callbacks.\n- No derived values in seed: runs once, value goes stale. Derive inline in `props`, `hidden`, `each`.\n - ❌ `\"seed\": [{ \"set\": \"scopes.root.visible\", \"expr\": \"scopes.root.rows.filter(r => r.name.includes(scopes.root.q))\" }]` with `\"each\": \"scopes.root.visible\"` — typing in search does nothing.\n - ✅ `\"each\": \"scopes.root.rows.filter(r => r.name.includes(scopes.root.q))\"`\n- Unset field reads `undefined`; read below it throws. Seed state before reading, or guard with `?.` / `??`.\n- Root entry seeds all root state.\n\n## 4. Scopes\n\n- `scopes.root`: page state.\n- List with `\"as\": \"row\"` gives each item two scopes:\n - `scopes.row` — item itself: `scopes.row.name` reads its `name`.\n - `scopes.$row` — row: `index` (0-based), `id` (`keyBy` value, else index), `value` (item, when array holds strings or numbers). These three read-only. Host call takes data field, `scopes.row.id`.\n- `as` names new scope: never `root`, host scope, or enclosing list's `as`; no leading `$`.\n- Row's own UI state (expanded, editing, draft) goes in `scopes.$row`: starts unset, stays out of data, write re-renders only that row. Toggle: `{ \"set\": \"scopes.$row.open\", \"expr\": \"!currentValue\" }`. Only row and its children see it.\n- State read outside row (select all, selected count) lives at root, keyed by row id. Read: `scopes.root.selected[scopes.$row.id] ?? false`. Toggle: `{ \"set\": \"scopes.root.selected\", \"expr\": \"({ ...currentValue, [scopes.$row.id]: !currentValue[scopes.$row.id] })\" }`. Select all, with `\"keyBy\": \"id\"`: `{ \"set\": \"scopes.root.selected\", \"expr\": \"scopes.root.rows.reduce((m, r) => ({ ...m, [r.id]: true }), {})\" }`. Seed `selected: {}` on root. Nested list: key by both ids, `scopes.$order.id + '/' + scopes.$line.id`.\n- No other scopes unless note names them. Other `scopes.x` reads `undefined`; writing it fails.\n\n## 5. Reactivity\n\n- Automatic. `props`, `hidden`, `busy`, `each` subscribe to every `scopes.X.Y` they read; write re-renders readers. Reading whole scope (`Object.keys(scopes.root)`) subscribes to all its fields. `seed`, `callbacks` never subscribe.\n- Write wakes readers below it: writing `scopes.root.user` wakes reader of `scopes.root.user.name`.\n- Write to item field puts edited copy of item into array `each` reads, and in nested list into arrays above. `{ \"set\": \"scopes.row.qty\", \"expr\": \"evt.value\" }` updates `scopes.root.rows`, so totals and filters reading it update. Live total inline in props: `scopes.root.rows.reduce((s, r) => s + r.qty * r.price, 0)`.\n- So `each` reads data itself, or `filter`, `toSorted`, `slice` of it. When `each` builds new objects (`rows.map(r => ({ ...r, total: r.qty * r.price }))`), row writes fail: compute per-row values in `props`.\n\n## 6. Lists\n\n- `each`: bare expression giving array, `\"scopes.root.rows\"` or derived `\"scopes.root.rows.filter(r => r.active)\"`; `null` or `undefined` gives no rows. `as`: required, names item scopes (§4). `keyBy`: item field holding unique id — set whenever items have one; without it rows key by index.\n- List entry's component renders once per item: `\"component\": \"TableRow\"` gives one row per item. Its `props` and `children` see item scopes.\n- Cap list whose size you don't control: `\"each\": \"scopes.root.rows.slice(0, 🔴MAX_LIST_ITEMS🔴)\"`, no-op on smaller data. Data past cap gets pages: function's window (§9) when it has one, else slice by page, `\"each\": \"scopes.root.rows.slice((scopes.root.page - 1) * 50, scopes.root.page * 50)\"`, callbacks writing `scopes.root.page`. Hide pager on one page: `\"hidden\": \"scopes.root.rows.length <= 50\"`. Search or filters only for data that can pass cap.\n- List's own `seed` runs once for whole list, outside rows: no `scopes.$<as>` there.\n\n## 7. Callbacks\n\n- `\"callbacks\": { \"onClick\": [steps] }`. Only handlers component lists; others never fire.\n- Step: `{ \"set\": \"scopes.<scope>.<field>\", \"expr\": \"...\" }`, or `\"literal\"` instead of `\"expr\"`. Step without `set` runs for its effect.\n- `evt`: handler's payload; fields listed under component.\n- `currentValue`: value at step's `set` field, `undefined` when unset. Toggle `!currentValue`, increment `currentValue + 1`, append `[...currentValue, item]`.\n- Order: step reading field earlier step writes waits for it; host function call waits for every earlier step; row field write counts as write to list's array. Other steps may run together. Save-then-refetch just works.\n- `\"confirm\": \"Delete this order?\"` on step opens dialog first; cancel skips that step and every later one. Put on first step of every callback that deletes or cannot be undone. Never build confirm dialog entries.\n- `\"debounce\": true` on step: it and later steps run after 300 ms without another call, with latest `evt`; earlier steps run at once. Search: `\"onChange\": [{ \"set\": \"scopes.root.q\", \"expr\": \"evt.value\" }, { \"set\": \"scopes.root.rows\", \"expr\": \"Api_search({ q: scopes.root.q })\", \"debounce\": true }]`. Never call host function on every keystroke.\n- Patterns:\n - Remove item: `{ \"set\": \"scopes.root.rows\", \"expr\": \"currentValue.filter(r => r.id !== scopes.row.id)\" }`\n - Edit row field: `{ \"set\": \"scopes.row.qty\", \"expr\": \"evt.value\" }`\n - Edit row from outside its list, replacing array: `{ \"set\": \"scopes.root.rows\", \"expr\": \"currentValue.map(r => r.id === scopes.root.selectedId ? { ...r, qty: 0 } : r)\" }`\n\n## 8. Hidden and busy\n\n- `hidden`: bare expression, not `{ \"expr\": ... }`. Truthy hides element. Hidden element keeps state, and its expressions still run: guard them, `scopes.root.user?.name`.\n- `busy`: bare expression. Truthy shows component busy, content kept. For refreshes; first load shows skeleton by itself. Set flag, call, clear flag: `{ \"busy\": \"scopes.root.busy\" }` and `\"onClick\": [{ \"set\": \"scopes.root.busy\", \"literal\": true }, { \"set\": \"scopes.root.rows\", \"expr\": \"Api_list()\" }, { \"set\": \"scopes.root.busy\", \"literal\": false }]`. Seed `busy: false` on root.\n\n## 9. Host functions\n\n- Listed under **Available Functions**. Call in `seed` and `callbacks` steps only, never in `props`, `hidden`, `busy`, `each`.\n- One argument matching documented input; none when no input: `Api_list()`, `Api_delete({ id: scopes.row.id })`, never `Api_delete(scopes.row.id)`.\n- Call is step's result: whole expression, `?:` branch, or right side of `&&`, `||`, `??` (`scopes.root.q ? Api_search({ q: scopes.root.q }) : []`). Runtime awaits it, writes result to `set`. Read result in later step or in `props`. Rejected: `Api_list().length`, `[A(), B()]`, `ids.map(id => Api_get({ id }))`. Many ids: call one function taking all.\n- Function offers window, sort or filters (limit, offset, page, cursor, sort, filter fields): use them. Fetch slice you render; refetch when page or filter changes. Never filter or sort fetched slice in expression unless it holds every matching row (`total <= limit`). Read returned totals instead of summing rows.\n- Mutate in callback, then refetch.\n- Function returning `unknown`: shape undeclared, not empty. Call for effect only (step without `set`), then refetch through function with documented return. Never read fields off it; never call in seed.\n - ✅ `[{ \"expr\": \"Api_archive({ id: scopes.row.id })\" }, { \"set\": \"scopes.root.rows\", \"expr\": \"Api_list()\" }]`\n\n## 10. Replacing entries\n\n- Re-emit `key` to replace its entry, later in same output or later turn. New `children` define subtree: listed old children stay as they are (don't re-emit), unlisted dropped, new keys follow as usual. To fix only entry, keep `children` same.\n- State survives. Seed that succeeded never re-runs; failed one runs again once fixed. New state: new key's seed, or callback.\n- Broken entry (bad expression, unknown function, bad syntax) shows error in its place; rest of UI works. Re-emit it fixed, once, only when sure. No trial and error.\n\n# Expression Context\n\nExpressions use language under **JavaScript Expressions**, plus these names:\n\n- `scopes` — state (§4). `scopes.root` always exists; list rows add `scopes.<as>` and `scopes.$<as>`.\n- `evt` — handler's payload, callbacks only.\n- `currentValue` — value at step's `set` field, only in steps with `set`.\n- Host functions under **Available Functions** — steps only, called bare: `getUsers()`, never `await getUsers()`.\n"
package/dist/types.d.ts CHANGED
@@ -60,7 +60,7 @@ export interface ComponentEntry {
60
60
  /** An expression; truthy hides the element. Reactive. */
61
61
  hidden?: Expression;
62
62
  /** An expression; truthy renders the element as busy. Reactive, like `hidden`. */
63
- loading?: Expression;
63
+ busy?: Expression;
64
64
  /** Steps per event, e.g. `{ onClick: [{ set: "scopes.root.open", expr: "!currentValue" }] }`. */
65
65
  callbacks?: Record<string, CallbackValueSourceAssignment[]>;
66
66
  /** Child keys, in render order. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uicast/core",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "The framework-agnostic engine of uicast, the expression-driven generative UI framework: entry format, guarded expressions (allow-list, not a sandbox), reactive scopes, and prompt builders.",
5
5
  "license": "MIT",
6
6
  "author": "Andrey Gubanov (https://github.com/finom)",
@@ -56,6 +56,6 @@
56
56
  "access": "public"
57
57
  },
58
58
  "peerDependencies": {
59
- "@uicast/expr": "^0.0.1"
59
+ "@uicast/expr": "^0.0.2"
60
60
  }
61
61
  }