@elaraai/east-claude-plugin 1.0.97 → 1.0.99

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.
@@ -50,7 +50,7 @@ e3-ui https://e3.example.com # a bare origin → the repositories li
50
50
  # Screenshots need the browser once per machine (add --with-deps on fresh Linux servers):
51
51
  e3-ui install-browser
52
52
  e3-ui shot --from-source ./dashboard.tsx -o dashboard.png
53
- e3-ui shot --from-source test/collections/schematic.examples.tsx -e schematicNets -o nets.png
53
+ e3-ui shot --from-source test/collections/table.examples.tsx -e tableBasic -o table.png
54
54
  e3-ui shot --from-task main.dashboard --repo ./.repos -o dashboard.png
55
55
  e3-ui shots src --out .shots --json # EVERYTHING renderable under src/
56
56
  ```
@@ -299,7 +299,7 @@ npm run shot # = e3-ui shot --from-source src/ui/index.tsx --export surfa
299
299
  e3-ui shot --from-source test/display/combine.examples.tsx -e combineDensities -o combine.png
300
300
 
301
301
  # Width-flexible component (Schematic/Plan/Table) — mount the frame at viewport width
302
- e3-ui shot --from-source test/collections/schematic.examples.tsx -e schematicStress --frame-width full -o stress.png
302
+ e3-ui shot --from-source test/collections/table.examples.tsx -e tableFrozen --frame-width full -o frozen.png
303
303
 
304
304
  # Sweep a whole project (the --ui scaffold wires this as `npm run shots`):
305
305
  e3-ui shots src --out .shots # PNGs, faithful width, typed skip reasons
@@ -120,7 +120,8 @@ Task → What do you need?
120
120
  │ │ ├─ Convert → .stringJoin(), .toSet(), .toDict(), .flattenToSet(), .flattenToDict(), .encodeCsv()
121
121
  │ │ ├─ Group → .groupReduce(), .groupSize(), .groupSum(), .groupMean(), .groupMinimum(), .groupMaximum(),
122
122
  │ │ │ .groupToArrays(), .groupToSets(), .groupToDicts(), .groupEvery(), .groupSome()
123
- │ │ └─ Tree → .toTree(Node, key, parent, build) — flat parent-keyed rows to nested nodes (below)
123
+ │ │ └─ Tree → .toTree(Node, key, parent, build) — flat parent-keyed rows to nested nodes;
124
+ │ │ .flattenTree(children) — nested nodes back to { path, node } rows, depth first (below)
124
125
  │ ├─ Set
125
126
  │ │ ├─ Read and mutate → .size(), .has(), .insert(), .tryInsert(), .delete(), .tryDelete(), .clear(), .unionInPlace()
126
127
  │ │ ├─ Set ops → .copy(), .union(), .intersection(), .difference(), .symmetricDifference(), .isSubsetOf(),
@@ -129,6 +130,7 @@ Task → What do you need?
129
130
  │ │ reduce .reduce(), .scan(), .every(), .some(), .sum(), .mean();
130
131
  │ │ convert .toArray(), .toSet(), .toDict(), .flattenToArray(), .flattenToSet(), .flattenToDict(),
131
132
  │ │ .toTree(Node, parent, build) (an element / a key is its own key; Dict callbacks take (value, key));
133
+ │ │ Dict only: .flattenTree(children), the nodes back again (below);
132
134
  │ │ group .groupReduce(), .groupSize(), .groupSum(), .groupMean(), .groupToArrays(), .groupToSets(),
133
135
  │ │ .groupToDicts(), .groupEvery(), .groupSome()
134
136
  │ ├─ Dict → .size(), .has(), .get(), .tryGet(), .keys(), .getKeys(), .insert(), .insertOrUpdate(), .update(),
@@ -164,6 +166,9 @@ Task → What do you need?
164
166
  │ East.is() is identity for mutable collections — FROZEN ones (task inputs) compare by value
165
167
  ├─ Patches (East.*) → East.diff(before, after), East.applyPatch(value, patch),
166
168
  │ East.composePatch(first, second, type), East.invertPatch(patch, type)
169
+ ├─ Build East over types you are handed (a library, a generator) → East.field(v, name, T?), East.fields(v, names?),
170
+ │ East.struct(T, fields), East.withField(v, name, x), East.inCase(T, name, x), East.patchesFor(T): each name
171
+ │ checked as the program builds → "Names and types held as values"
167
172
  │
168
173
  ├─ Ask a question of a value in jq → East.jq(input, program, resultType), checked when the program
169
174
  │ builds → "Queries (typed jq)"
@@ -239,6 +244,42 @@ A variant is built with `variant()`, `some()` or `none` — never a
239
244
  `{ type, value }` literal, which lacks the marker the encoders read. Variants are
240
245
  encoded, compared and validated by case **name**.
241
246
 
247
+ ### Names and types held as values
248
+
249
+ East names a struct's fields and a variant's cases where its code is written:
250
+ `row.site`, `variant("load", x)`. Code that builds programs over types it is
251
+ handed — a library generating a program for whatever record its caller
252
+ declares — holds the names, and the types, as values. These build the same
253
+ program pieces from them, and check each name against the type as the program
254
+ is built: a name the type lacks fails there, naming the name and the type
255
+ (`East.field: .Struct {name, site, status} has no field 'job'`). Nothing in the
256
+ program differs from what the written form builds, so every runtime runs it.
257
+
258
+ ```typescript
259
+ const Machine = StructType({ name: StringType, site: StringType, status: StringType });
260
+ const Reading = VariantType({ load: FloatType, output: FloatType });
261
+
262
+ const site = "site"; // a name a caller hands you
263
+ const siteOf = East.function([Machine], StringType, ($, machine) =>
264
+ East.field(machine, site, StringType)); // typed by the type given, which is checked
265
+
266
+ const stopped = East.function([Machine], Machine, ($, machine) =>
267
+ East.withField(machine, "status", "down")); // the other fields as they are
268
+
269
+ const Place = StructType({ site: StringType, name: StringType });
270
+ const placeOf = East.function([Machine], Place, ($, machine) =>
271
+ East.struct(Place, East.fields(machine, ["site", "name"]))); // by name, or in the type's order
272
+
273
+ const readingOf = (place: string) => East.function([FloatType], Reading, ($, x) =>
274
+ East.inCase(Reading, place, x)); // a variant's case by name
275
+
276
+ const { diff, apply, compose, invert } = East.patchesFor(Machine); // East functions of a type held as a value
277
+ ```
278
+
279
+ A recursive value is read, and rebuilt, through its node. `fields` and
280
+ `withField` read the value once a field: give them a variable, not a
281
+ computation. Code whose names are written keeps writing `row.site`.
282
+
242
283
  ### Error handling
243
284
 
244
285
  ```typescript
@@ -268,11 +309,37 @@ const nest = East.function([ArrayType(Row)], ArrayType(Node), ($, rows) =>
268
309
  ❗ `toTree: cycle through key <key>` — both before any `build`.
269
310
  - A non-recursive `Node` folds bottom-up: `IntegerType` with
270
311
  `children.sum().add(1n)` is each root's subtree size.
312
+
313
+ `flattenTree` is the way back: every node of nested data, each with its path
314
+ of keys (a Dict's) or indices (an Array's) from its root, depth first — each
315
+ node before its descendants, siblings in key or array order. `children` names
316
+ a node's children, of the collection's own type.
317
+
318
+ ```typescript
319
+ const Part = RecursiveType(self => StructType({ name: StringType, cost: FloatType, parts: DictType(StringType, self) }));
320
+
321
+ const flat = East.function([DictType(StringType, Part)], ArrayType(StructType({ path: ArrayType(StringType), name: StringType })), ($, parts) =>
322
+ parts.flattenTree(($, part) => part.unwrap().parts).map(($, e) => ({ path: e.path, name: e.node.unwrap().name })));
323
+ // [{ path: ["pump"], … }, { path: ["pump", "motor"], … }, { path: ["pump", "motor", "rotor"], … }, { path: ["pump", "seal"], … }]
324
+
325
+ // A rollup at every root: toTree over the paths, a node's parent its path less its last key
326
+ const totals = East.function([DictType(StringType, Part)], ArrayType(FloatType), ($, parts) =>
327
+ parts.flattenTree(($, part) => part.unwrap().parts).toTree(FloatType,
328
+ ($, e) => e.path,
329
+ ($, e) => East.greater(e.path.size(), 1n).ifElse(() => some(e.path.slice(0n, e.path.size().subtract(1n))), () => none),
330
+ ($, e, _i, children) => children.sum().add(e.node.unwrap().cost)));
331
+ ```
332
+
333
+ - The result is an Array of `{ path, node }`, not a Dict keyed by path: a Dict
334
+ key is immutable, and a path is an Array.
335
+ - `children` runs once per node, in the result's order. The walk is iterative;
336
+ each path is an array of its own, so a chain n deep holds n(n+1)/2 path
337
+ elements.
271
338
  - **Recursion** — a function calling itself through a captured variable
272
339
  (`$.let(fn, FunctionType(...))`, then `$.assign(fn, East.function(...))`) —
273
340
  is for shallow depths, in the hundreds. Deeper, a call ❗
274
341
  `call stack exhausted: East calls nested too deeply`, a catchable error on
275
- every runtime. Walk deep data with `$.while` or `toTree`, which is iterative;
342
+ every runtime. Walk deep data with `$.while`, `toTree` or `flattenTree`, which are iterative;
276
343
  the C and Python runtimes still bound a recursive VALUE's depth to tens of
277
344
  thousands of levels.
278
345
 
@@ -333,7 +333,6 @@ gh pr create --repo elaraai/east-workspace --title "fix(<lib>): …" --body "…
333
333
  - `hooks/useDatasetValue.ts` — `[...StateImpl, ...BindPlatform, ...OverlayImpl]`.
334
334
  - Import the impl from `@elaraai/east-ui-components/platform`. **Manifest-scoped** functions (data/func/record) go in via their `createScoped*(manifest…)` builder; **browser-local-state** functions (State, overlay, slice) go in **unscoped**, alongside `...StateImpl` / `...OverlayImpl`.
335
335
  - Grep guard before you call a UI platform fn done: `rg -n "MyImpl" libs/east-ui/packages/e3-ui-components/src` must hit `UITaskPreview.tsx` **and** `useDatasetValue.ts`, not just the registering module.
336
- - **Also rebuild + reinstall the `east-ui-preview` VS Code extension** — its webview bundles e3-ui-components, so a deployed package using the new fn renders blank / errors until the extension itself ships the array change. A version bump alone is not enough.
337
336
 
338
337
  ## Do NOT
339
338
 
@@ -132,6 +132,8 @@ Task → What do you need?
132
132
  │ │ East.break_ / continue_ / label → "Control flow — the expression forms", "Sequential logic that stays in east-c"
133
133
  │ ├─ Nest flat parent-keyed rows (any depth) → xs.to_tree(Node, key, parent, build) — not recursion, which is for
134
134
  │ │ shallow depths → "Trees from flat rows, and recursion"
135
+ │ ├─ Flatten nested data back to {path, node} rows, depth first → xs.flatten_tree(children) (an Array's or a Dict's)
136
+ │ │ → "Trees from flat rows, and recursion"
135
137
  │ ├─ Query a value with jq → East.jq(input, "jq", result_type) ❗ result_type is required, and types the result ·
136
138
  │ │ a dict of inputs is read as an e3 root → "Queries — East.jq"
137
139
  │ ├─ What a body may capture — and what it refuses, naming the binding → "What a body may reference"
@@ -325,13 +327,20 @@ or call it (a `Call`; in an `East.asyncFunction`, calling an async one is a
325
327
 
326
328
  **Error locations.** A build records the python frames that built each node,
327
329
  so a runtime error names its authoring site: `EastError.location` is the stack —
328
- the lambda's `file:line:column` first, then the `East.function(...)` call and
329
- its callers — and a platform-signature mismatch at `East.compile` names the call
330
- the same way. The map rides the function's beast2 encoding, so the error reads
331
- the same on east-c or east-node. Paths are relative to the working directory;
332
- `set_location_base_path(dir)` (from `east`) pins the base for reproducible
333
- fixtures. An error inside a callback a builtin invokes (`arr.map(...)` and
334
- friends) resolves to the builtin's call site, on every runner.
330
+ the lambda's `file:line:column` first, then any helper between, then the
331
+ `East.function(...)` call — and a platform-signature mismatch at `East.compile`
332
+ names the call the same way. The stack stops at that call: the code that
333
+ imported or called the module is not recorded. A recorded path is relative to
334
+ its file's project root, the nearest directory above it holding a
335
+ `pyproject.toml`, so a function's IR is the same whichever module imported it
336
+ and whatever directory the process ran in. A file in no project is relative to
337
+ the working directory, and `set_location_base_path(dir)` (from `east`) pins one
338
+ base for every path, for reproducible fixtures (`None` restores the default);
339
+ `east-py check` names a refusal's frames relative to the working directory, as
340
+ an editor opens them. The map rides the function's beast2 encoding, so the
341
+ error reads the same on east-c or east-node. An error inside a callback a
342
+ builtin invokes (`arr.map(...)` and friends) resolves to the builtin's call
343
+ site, on every runner.
335
344
  `set_location_capture(False)` (from `east`) builds without recording any
336
345
  locations: there is then no stack walk per node, and an error raised by such a
337
346
  function carries no location. Binding names are read separately and keep
@@ -854,6 +863,13 @@ const score = East.importFunction("pricing", "score", FunctionType([RowType], Fl
854
863
  await e3.export(pkg, "out.zip");
855
864
  ```
856
865
 
866
+ e3 exports each workspace package in one `east-py export-functions --batch`
867
+ process, however many tasks import from it, and keeps the manifests, so a
868
+ redeploy whose python has not changed runs no export (the **e3** skill). A
869
+ function's manifest entry is its own: the same bytes whatever else the process
870
+ built first and whatever directory it ran in, so a task embedding it keeps its
871
+ hash until the function, or a line it was written at, changes.
872
+
857
873
  For a package built elsewhere (published, another repo), or to link
858
874
  in-process, write the manifest where the package lives and pass it:
859
875
 
@@ -1117,7 +1133,7 @@ at the call. `.element_type` is the logical element type.
1117
1133
  | Search | `find_first(target, key=None) -> some/none` · `find_all(value, by=None) -> Array<Integer>` · `find_maximum/find_minimum(by=None) -> some(index)/none` · `find_sorted_first/last(target, key=None) -> int` · `find_sorted_range(target, key=None) -> {start,end}` · `first_map(fn(el)->some/none, out=None)` · `is_sorted(key=None) -> bool` |
1118
1134
  | Flatten | `flat_map(fn(el)->arr, out=None)` (`flatten_to_array` deprecated) · `flatten_to_set(fn(el)->set, out=None)` · `flatten_to_dict(fn(el)->dict, combine=None)` (a duplicate key errors without `combine`) |
1119
1135
  | Columnar | `to_columns(fields=None) -> dict` (numpy per numeric/bool column, `Option<Float>`→NaN, interned strings) · `EastArray.from_columns(element_type, columns)` *(static)* (C-side fill needs numpy columns — float64/int64/bool, `Option<Float>` as float64+NaN; python lists convert per cell) · `map_batches(fn(cols)->cols, out=None, batch_size=100_000)` |
1120
- | Convert | `to_tree(node, key(el[, i]), parent(el[, i])->some/none, build(el, i, children)) -> Array<node>` (flat parent-keyed rows → nested nodes of any depth, iterative — [Trees from flat rows](#trees-from-flat-rows-and-recursion)) · `string_join(sep) -> str` (String arrays) · `to_vector() -> EastVector` (Float/Integer/Boolean elements, in order — east-c VectorFromArray; the expression method emits the same builtin) · `encode_csv(config=None, **options) -> EastBlob` (Array of structs; east-c ArrayEncodeCsv) |
1136
+ | Convert | `to_tree(node, key(el[, i]), parent(el[, i])->some/none, build(el, i, children)) -> Array<node>` (flat parent-keyed rows → nested nodes of any depth, iterative — [Trees from flat rows](#trees-from-flat-rows-and-recursion)) · `flatten_tree(children(el[, i])) -> Array<{path, node}>` (nested nodes back: every node with its path of indices, depth first, iterative) · `string_join(sep) -> str` (String arrays) · `to_vector() -> EastVector` (Float/Integer/Boolean elements, in order — east-c VectorFromArray; the expression method emits the same builtin) · `encode_csv(config=None, **options) -> EastBlob` (Array of structs; east-c ArrayEncodeCsv) |
1121
1137
  | Mutate (in place, the TS names) | `push_last(item)` · `push_first(item)` · `append(array)` (ArrayAppend — a whole array, NOT one element) · `prepend(array)` · `pop_last()` · `pop_first()` · `update(i, item)` ❗bounds · `merge(i, value, update_fn(existing, incoming[, i]))` · `merge_all(array, merge_fn(existing, incoming[, i]))` · `clear()` · `sort_in_place(by=None)` · `reverse_in_place()` — plus the python protocol: `extend(iterable)` (bulk: one crossing; C-to-C for same-type East arrays, raw buffers for numpy) · `insert(i, item)` · `pop(i=-1)` · `remove(item)` · `count(value) -> int` · `index(value) -> int` |
1122
1138
 
1123
1139
  ### EastSet — complete method surface
@@ -1159,7 +1175,7 @@ in `other`'s values; `merge` takes a single differently-typed value.
1159
1175
  | Reduce | `reduce(fn(acc, value[, key]), init)` (TS order) · `scan(fn(acc, value[, key]), init) -> Array` (running fold in key order) · `map_reduce(map_fn(value[, key]), reduce_fn(a, b), out=None)` (raises on empty) · `sum(fn(value[, key])=None)` · `mean(fn(value[, key])=None) -> float` · `every(pred(value[, key])=None) -> bool` · `some(pred(value[, key])=None) -> bool` (native short-circuit) |
1160
1176
  | Group | `group_reduce(key_fn(value[, key]), init_fn(gk), fold_fn(acc, value[, key]), key_out=None, acc_out=None) -> Dict` · `group_size(key_fn)` · `group_sum(key_fn, fn=None)` · `group_mean(key_fn, fn=None)` · `group_every/group_some(key_fn, pred(value[, key]))` · `group_to_arrays/group_to_sets(key_fn, value_fn=None)` · `group_to_dicts(key_fn, key2_fn, value_fn=None, combine=None)` · ⚠️ `group_fold(...)` is the DEPRECATED alias of `group_reduce` |
1161
1177
  | Flatten | `flatten_to_array(fn(value[, key])->arr, out=None)` (the TS Dict name; `flat_map` is Array's and a deprecated spelling here) · `flatten_to_set(fn(value[, key])->set, out=None)` · `flatten_to_dict(fn(value[, key])->dict, combine=None)` (a duplicate key errors without `combine`) |
1162
- | Convert | `keys() -> Set` · `to_array(fn(value[, key]), out=None)` · `to_set(fn(value[, key]), out=None)` · `to_dict(key_fn, value_fn=None, combine=None, key_out=None, value_out=None)` (the value itself when `value_fn` is omitted; a duplicate key errors without `combine`) · `to_tree(node, parent(value[, key])->some/none, build(value, key, children)) -> Array<node>` (each entry's key its key) · `copy()` |
1178
+ | Convert | `keys() -> Set` · `to_array(fn(value[, key]), out=None)` · `to_set(fn(value[, key]), out=None)` · `to_dict(key_fn, value_fn=None, combine=None, key_out=None, value_out=None)` (the value itself when `value_fn` is omitted; a duplicate key errors without `combine`) · `to_tree(node, parent(value[, key])->some/none, build(value, key, children)) -> Array<node>` (each entry's key its key) · `flatten_tree(children(value[, key])) -> Array<{path, node}>` (nested nodes back: every node with its path of keys, depth first, iterative) · `copy()` |
1163
1179
  | Mutate (in place) | `d[k]=v` · `del d[k]` · `insert(k, v)` (errors if present) · `get_or_insert(k, fn(k))` · `insert_or_update(k, v, combine(existing, incoming, k))` · `update(k, v)` (TS; the read-modify-write `update(k, fn(current))` is deprecated) · `swap(k, v) -> prev` · `delete(k)` · `try_delete(k) -> bool` (`update`/`swap`/`delete` error on a missing key) · `pop(k, *default)` · `clear()` |
1164
1180
  | Bulk (in place) | `update_many(keys, values, combine(existing, incoming)=None)` — the whole batch crosses once; a pure/precompiled `combine` resolves collisions C-to-C (dicts as hot-loop accumulators) |
1165
1181
 
@@ -1598,7 +1614,7 @@ east-py version -p east-py-std # the runner's version, and each
1598
1614
  | `exec <unit> [-v] [--profile]` | The runner protocol: one unit file says what to run — a program over its inputs, read as its `decode` says (as `run --decode`), or a merge of an output's parts — and where; the output is written by its kind and a result recorded. Exit 0 when it succeeded, 1 when it failed (the message and locations also go to stderr), 2 when the unit cannot be read or the result written. Relative paths in the unit are relative to its directory |
1599
1615
  | `convert <file> [-o FILE.east] [-v]` | Decodes a beast2 value (its header names the type) and prints it as East text, or writes it to a `.east` file |
1600
1616
  | `transpile <ir> [-o FILE] [--name NAME] [-p PACKAGE]…` | IR as a python module rebuilding it (`--name` binds it, default `main`; each `-p` prints its calls as that package's own functions) — [IR ↔ python](#ir--python-east-py-transpile-and-the-east-c-ir-toolbox) |
1601
- | `export-functions <module> -o FILE [-p PACKAGE]… [--name NAME] [--package-version V] [--only NAME]…` | A module's (a dotted name, or a `.py` path) `east_functions` as a function manifest; every platform call must be provided by a `-p` package; `--name` defaults to the module's top-level name, the version to the installed distribution's (else `0.0.0`); `--only` exports just those — [Cross-language functions](#cross-language-functions-east-py-export-functions-and-eastimport_function) |
1617
+ | `export-functions <module> -o FILE [-p PACKAGE]… [--name NAME] [--package-version V] [--only NAME]…` | A module's (a dotted name, or a `.py` path) `east_functions` as a function manifest; every platform call must be provided by a `-p` package; `--name` defaults to the module's top-level name, the version to the installed distribution's (else `0.0.0`); `--only` exports just those, building only them and what they call (a sibling that does not build fails nothing). `--batch FILE` in place of `-o`/`-p`/`--only` writes several manifests from one import, FILE a beast2 batch (`FunctionExportBatchType`: each entry's `output`, `packages` and `only`) — what `e3.export` runs per workspace package — [Cross-language functions](#cross-language-functions-east-py-export-functions-and-eastimport_function) |
1602
1618
  | `lint [paths…] [--format text\|json] [--disable RULE]… [--exclude DIR]… [--list-rules]` | The rules (default path `.`); exit 1 on any finding, a warning included, 2 on a usage error — [Diagnostics](#diagnostics-at-edit-time--east-py-lint-east-py-check-flake8-east-py-lsp) |
1603
1619
  | `check <targets…> [--format text\|json] [--only-if-enabled]` | The build's own errors; exit 1 on any |
1604
1620
  | `lsp [--probe]` | The Language Server over stdio (needs `pygls`); `--probe` reports whether it can start here |
@@ -1899,10 +1915,30 @@ roots = rows.to_tree(Node,
1899
1915
  - A non-recursive `Node` folds bottom-up: `IntegerType` with
1900
1916
  `children.sum() + 1` is each root's subtree size. A Set's element and a
1901
1917
  Dict's key are their own keys, so those take no `key`.
1918
+
1919
+ `flatten_tree` is the way back: every node of nested data, each with its path
1920
+ of keys (a Dict's) or indices (an Array's) from its root, depth first — each
1921
+ node before its descendants, siblings in key or array order. `children` names a
1922
+ node's children, of the collection's own type:
1923
+
1924
+ ```python
1925
+ from east import DictType, FloatType, StringType, StructType, recursive_type
1926
+
1927
+ Part = recursive_type(lambda self: StructType([("name", StringType), ("cost", FloatType), ("parts", DictType(StringType, self))]))
1928
+
1929
+ nodes = parts.flatten_tree(lambda b, part: part.unwrap().parts)
1930
+ # an Array of {path, node}: ["pump"], ["pump", "motor"], ["pump", "motor", "rotor"], ["pump", "seal"]
1931
+ ```
1932
+
1933
+ - An Array of `{path, node}`, not a Dict keyed by path: a Dict key is
1934
+ immutable, and a path is an Array. `to_tree` over it, keyed by `path` and
1935
+ parented by `path` less its last step, rolls the tree up at every root.
1936
+ - `children` runs once per node, in the result's order. Each path is an array
1937
+ of its own, so a chain n deep holds n(n+1)/2 path elements.
1902
1938
  - **Recursion** — a function calling itself through a captured variable — is
1903
1939
  for shallow depths, in the hundreds. Deeper, a call ❗ `call stack exhausted:
1904
1940
  East calls nested too deeply`, a catchable `EastError` on every runtime. Walk
1905
- deep data with `b.while_` or `to_tree`; a recursive VALUE's depth is still
1941
+ deep data with `b.while_`, `to_tree` or `flatten_tree`; a recursive VALUE's depth is still
1906
1942
  bounded, at tens of thousands of levels, by east-c's recursive collect and
1907
1943
  free.
1908
1944
 
@@ -1997,6 +2033,12 @@ out = EastMatrix(FloatType, model(t).detach().cpu().numpy()) # bridge canonica
1997
2033
  - **Read a captured East collection with `.get(expr)` /
1998
2034
  `.get_or_default(expr, d)` / `.try_get(expr)`** — `[expr]` on a captured
1999
2035
  constant does not build IR (python coerces the index via `__index__`).
2036
+ - **A python set has no order of its own.** Under a Set type
2037
+ (`b.const({"a", "b"}, SetType(StringType))`, `East.new_set(T, {…})`) its
2038
+ elements are built in East's order of `T`, the order an `EastSet` keeps, so
2039
+ the IR is the same in every process; a list keeps the order it is written in.
2040
+ A python set holding a traced value (`{some(x)}`) has nothing to order by and
2041
+ is refused — write a list.
2000
2042
  - **Genuinely-python loops cross the boundary once** — `to_columns()` /
2001
2043
  `EastArray.from_columns` / `map_batches`, never a platform call or a decode per
2002
2044
  element.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: east-ui
3
- description: "Type-safe UI component library for the East language, authored as JSX tags. Use when writing East programs that define user interfaces. Triggers for: (1) Authoring `.tsx` component trees with `@elaraai/east-ui` tags, (2) Layout with <Box>, <Flex>, <Stack>/<VStack>/<HStack>, <Grid>, <SnapGrid> (a page of tiles held as data on the 12-column grid, and its wireframe thumbnail), <Splitter>, <ScrollArea>, <Sticky>, <Expandable>, <Dock>, <Configurator> (control table + live preview + spec readout), (3) Forms with <Input>, <Textarea>, <Select>, <Combobox>, <Checkbox>, <Switch>, <Slider>, <RadioGroup>, <RadioCardGroup>, <TagsInput>, <FileUpload>, <Field>, <DateRangeInput>, <TimeRangeInput>, (4) Data display with <Table>, <TreeView>, <ValueTree>, <DataList>, <Deck>, <Matrix>, <Schematic>, <Map>, <Library> (a drag palette, or a gallery of large cards whose media is any component), <Blend>, <Slice.Rail>, <Pagination>, <ChipRail>, <Trace>, (5) Charts with <Chart layers={Chart.Line/Column/Bar/Area/Scatter/Band(...)}/> (Column = vertical, Bar = horizontal) plus Chart.refLine/refBand/refDot, <Sparkline>, (6) Overlays with <Dialog>, <Drawer>, <Popover>, <Menu>, <Tooltip>, <HoverCard>, <ToggleTip>, <ActionBar>, <CommandPalette>, <Hotkey>, (7) Feedback with <Banner>, <Status>, <Progress>, <Skeleton>, <EmptyState>, (8) Disclosure with <Tabs>, <Accordion>, <Carousel>, <Collapsible>, <SegmentGroup>, <OptionList>, <Story>, (9) Navigation within a page with <Breadcrumb> and <NavList> (an app's own navigation is its host's), (10) Reactive UI via <Reactive>{$ => …}</Reactive> + State.bind, and conditional hosting of stateful components via <Match on cases> (remounts the active variant case on tag change), (11) Value formatting — Chart.format.* specs (chart axes, Slice fields, Deck metrics, e3-ui's Plan) and Format.* specs (<Numeric>, <Stat>, Table columns) through one interpreter, in the viewer's locale (react-aria's I18nProvider) with every date in UTC, (12) Status colour vocabulary — the five status tokens, the Deck.statuses registry, Library.status, rowStatus tints and tone props."
3
+ description: "Type-safe UI component library for the East language, authored as JSX tags. Use when writing East programs that define user interfaces. Triggers for: (1) Authoring `.tsx` component trees with `@elaraai/east-ui` tags, (2) Layout with <Box>, <Flex>, <Stack>/<VStack>/<HStack>, <Grid>, <SnapGrid> (a page of tiles held as data on the 12-column grid, and its wireframe thumbnail), <Splitter>, <ScrollArea>, <Sticky>, <Expandable>, <Dock>, <Configurator> (control table + live preview + spec readout), (3) Forms with <Input>, <Textarea>, <Select>, <Combobox>, <Checkbox>, <Switch>, <Slider>, <RadioGroup>, <RadioCardGroup>, <TagsInput>, <FileUpload>, <Field>, <DateRangeInput>, <TimeRangeInput>, (4) Data display with <Table>, <TreeView>, <ValueTree>, <DataList>, <Deck>, <Matrix>, <Map>, <Library> (a drag palette, or a gallery of large cards whose media is any component), <Blend>, <Slice.Rail>, <Pagination>, <ChipRail>, <Trace>, (5) Charts with <Chart layers={Chart.Line/Column/Bar/Area/Scatter/Band(...)}/> (Column = vertical, Bar = horizontal) plus Chart.refLine/refBand/refDot, <Sparkline>, (6) Overlays with <Dialog>, <Drawer>, <Popover>, <Menu>, <Tooltip>, <HoverCard>, <ToggleTip>, <ActionBar>, <CommandPalette>, <Hotkey>, (7) Feedback with <Banner>, <Status>, <Progress>, <Skeleton>, <EmptyState>, (8) Disclosure with <Tabs>, <Accordion>, <Carousel>, <Collapsible>, <SegmentGroup>, <OptionList>, <Story>, (9) Navigation within a page with <Breadcrumb> and <NavList> (an app's own navigation is its host's), (10) Reactive UI via <Reactive>{$ => …}</Reactive> + State.bind, and conditional hosting of stateful components via <Match on cases> (remounts the active variant case on tag change), (11) Value formatting — Chart.format.* specs (chart axes, Slice fields, Deck metrics, e3-ui's Plan) and Format.* specs (<Numeric>, <Stat>, Table columns) through one interpreter, in the viewer's locale (react-aria's I18nProvider) with every date in UTC, (12) Status colour vocabulary — the five status tokens, the Deck.statuses registry, Library.status, rowStatus tints and tone props — and ColorTokenType, the typed colour an app gives for a data fill (a theme hue at a shade, Style.ColorToken(\"teal\", \"500\"))."
4
4
  ---
5
5
 
6
6
  # East UI
@@ -138,10 +138,10 @@ Task → Which tag?
138
138
  │ │ │ ├─ variant (optional) — "tiles" (default) | "wireframe": each tile an outline at its size with its content left out, its spans kept at any width and its declared height exact — a page's thumbnail. It draws no frame of its own: its host frames it (a Library gallery card's media) and it fills the host's height. With no rows it is the blank page — a band over the body, each dashed
139
139
  │ │ │ ├─ width (optional) — the design width ("1440px"): a page's grid is that wide, scrolls inside a narrower host, and its spans answer to it; on the editing canvas it is the most the grid lays out at — a narrower column lays it out at the column's width, a wider one centres it — and the toolbar reads it out
140
140
  │ │ │ ├─ height / maxHeight (optional) — pin or cap the SnapGrid; the grid scrolls within
141
- │ │ │ ├─ edit + editing (optional, given together) — the builder's canvas: every tile keeps its declared span at any width, and each gesture is a DRAFT of the shared editing session — e3-ui's Sheet's and Plan's — each ONE undoable transaction (origin drop / move / resize / remove); the history item (Undo · Redo · Discard · Save) sits in the canvas's toolbar (see The frame), and ⌘Z / Ctrl+Z undo, ⌘⇧Z / Ctrl+Y redo. Editing takes an `Array` source — its order is the tiles' order, so a move is a new order and a new row key; a `Dict` is refused. `edit` names the row fields a gesture writes: { key: "id" (the String identity field), row: "row" (String), span: "span" (Integer), height?: "height" (Option<Integer>; omitted, heights are not edited), align?: "align" (SnapGrid.Types.Align; the field an align request writes — omitted, one is refused), create?: ($, card, at) => R — a dropped library card's new row: `card` the drag grammar's { library, key }, `at` { key (a fresh identity), row (the row it lands in) }; the canvas then writes `at.row` and fits the span }. `editing` is { onApply? | onUpdate?, onPatch?, onDrafted?, mode?, ready? } as on e3-ui's Plan: `onUpdate={handle.write}` with `data={handle}` is the inline adapter; `onPatch` hears SnapGrid.Types.PatchEvent(R); `onDrafted` fn(Array<R>) => Null hears the rows the canvas draws — the source with its drafts, exactly as Save would leave them — as it mounts and whenever they change (a gesture, an undo or redo, a Discard, a source that moved), so a pane beside it reads what it shows; `ready` fn(R) => Editing.Types.Readiness marks the tile it refuses and holds Save. Save leaves exactly the order the canvas shows
141
+ │ │ │ ├─ edit + editing (optional, given together) — the builder's canvas: every tile keeps its declared span at any width, and each gesture is a DRAFT of the shared editing session — e3-ui's Sheet's and Plan's — each ONE undoable transaction (origin drop / move / resize / remove); the history item (Undo · Redo · Discard · Save) sits in the canvas's toolbar (see The frame), and ⌘Z / Ctrl+Z undo, ⌘⇧Z / Ctrl+Y redo; the drafts are kept in the tab through a reload, and come back carried over to the source as it is then. Editing takes an `Array` source — its order is the tiles' order, so a move is a new order and a new row key; a `Dict` is refused. `edit` names the row fields a gesture writes: { key: "id" (the String identity field), row: "row" (String), span: "span" (Integer), height?: "height" (Option<Integer>; omitted, heights are not edited), align?: "align" (SnapGrid.Types.Align; the field an align request writes — omitted, one is refused), create?: ($, card, at) => R — a dropped library card's new row: `card` the drag grammar's { library, key }, `at` { key (a fresh identity), row (the row it lands in) }; the canvas then writes `at.row` and fits the span }. `editing` is { onApply? | onUpdate?, onPatch?, onDrafted?, mode?, ready? } as on e3-ui's Plan: `onUpdate={handle.write}` with `data={handle}` is the inline adapter; `onPatch` hears SnapGrid.Types.PatchEvent(R); `onDrafted` fn(Array<R>) => Null hears the rows the canvas draws — the source with its drafts, exactly as Save would leave them — as it mounts and whenever they change (a gesture, an undo or redo, a Discard, a source that moved), so a pane beside it reads what it shows; `ready` fn(R) => Editing.Types.Readiness marks the tile it refuses and holds Save. Save leaves exactly the order the canvas shows
142
142
  │ │ │ ├─ ui (optional) — the bound selection: `State.bind([SnapGrid.Types.UiState], key, SnapGrid.uiState())` — the canvas draws the tile it holds and writes the one the user picks, so an inspector beside it reads and sets it; omitted, the canvas keeps its own. Its `request` asks the editing canvas for a change, as e3-ui's Plan's `focus` asks for a row: `ui.write(SnapGrid.uiState({ selected: key, request: { key, change: variant("span", 8n) } }))` — `span` (held to the row's room), `row` (counting from 1; a row already holding 6 tiles, or one past the last, is a new row after it), `height` (`none` for its content's), `align` (written to `edit.align`) — taken as ONE gesture of the session by the rules the handles and drags keep, undoable with the rest, and written back `none`
143
143
  │ │ │ ├─ view (optional) — the bound design width and zoom: `State.bind([SnapGrid.Types.ViewState], key, SnapGrid.viewState())` under a key of its own, apart from `ui` — the editing canvas draws at them and writes the width the user presses and the zoom step they take; omitted, the canvas keeps them, starting from `width` and `zoom`
144
- │ │ │ ├─ apply (optional) — the bound Apply request: `State.bind([SnapGrid.Types.ApplyState], key, variant("idle", null))` under a key of its own — a screen that must not leave drafts behind (a publish) writes `variant("asked", id)` with an id of its own, and the editing canvas saves its drafts as its history item does — waiting out a Save already running, retrying one of unknown outcome — and answers under the same id: `applied(id)` once the source confirms them, or at once with none; `refused({ id, reason })` when they cannot land, `reason` in the canvas's words (the conflict's, the rejection's, a check's or the stale source's). The canvas stays the only writer of its drafts; omitted, only the history item saves
144
+ │ │ │ ├─ apply (optional) — the bound Apply request: `State.bind([SnapGrid.Types.ApplyState], key, variant("idle", null))` under a key of its own — a screen that must not leave drafts behind (a publish) writes `variant("asked", id)` with an id of its own, and the editing canvas saves its drafts as its history item does — waiting out a Save already running, retrying one of unknown outcome — and answers under the same id: `applied(id)` once the source confirms them, or at once with none; `refused({ id, reason })` when they cannot land, `reason` in the canvas's words (the conflict's, the rejection's, a check's, the drafts still being carried over to a source that moved, or the conflicts still to choose). The canvas stays the only writer of its drafts; omitted, only the history item saves
145
145
  │ │ │ ├─ widths (optional) — [{ label, icon?, width }] the design widths the editing canvas's toolbar offers, a device each (`{ label: "Desktop", icon: "desktop", width: "1440px" }`, `{ label: "Tablet", icon: "tablet-screen-button", width: "1024px" }`): the one the canvas draws at is pressed, and pressing another draws it at that width
146
146
  │ │ │ ├─ toolbar (optional) — { start?, end? } the host's items in the editing canvas's toolbar, each an array of UIComponents: `start` leads the row (a `<Status>`), `end` closes it (Preview, Publish buttons); they never fold
147
147
  │ │ │ ├─ panes (optional) — { start?, end? } the panes beside the editing canvas, under its toolbar — a palette, an inspector: a `<Dock surface="shell">` pane collapses to its rail and the canvas column takes the room
@@ -529,35 +529,7 @@ Task → Which tag?
529
529
  │ │ ├─ Matrix.cell({ segments?, markers?, orientation?, slot?, popover? }) — one cell (slot = arbitrary UIComponent; popover = click body)
530
530
  │ │ ├─ Matrix.segment({ fill?, weight, label?, color?, min?, max?, step? }) — one segment of the cell bar
531
531
  │ │ └─ Matrix.marker({ status?, message, at?, label? }) — corner status marker
532
- │ ├─ <Schematic items={rows} extent={{width,height}} item={r => ({…})} /> — 2D world-coord canvas: items / zones / links / nets from flat tables
533
- │ │ ├─ Props:
534
- │ │ │ ├─ items + extent (required) — item rows + world bounds (canvas scales to fit)
535
- │ │ │ ├─ item (optional) — row mapper to { key, x, y, label, sublabel?, icon?, status?, meter?{value,max}, metric?, width?, footprint?, tone?, color?, bg?, fillOpacity?, weight?, excluded?, layer? } (omit when already Schematic.Types.Item)
536
- │ │ │ ├─ zones + zone (optional) — zone rows + mapper to { key, label, x, y, width, height, pattern?, geometry?, tone?, color?, bg?, fillOpacity?, weight?, layer? }
537
- │ │ │ ├─ links + link (optional) — link rows + mapper to { key, from, to, label?, metric?, style?, route?, via?, layer? }
538
- │ │ │ ├─ nets + net (optional) — manifold/bus rows (ONE row = many sources → many destinations) + mapper to { key, sources, destinations, label?, metric?, style?, route?, via?, layer? }; drawn P&ID-style — a header BAR spans each multi-endpoint side with a stub per endpoint, the trunk runs bar → via… → bar (junction dots ONLY where a tap 3-way joins a bar)
539
- │ │ │ ├─ selectionMode (optional) — "single" (default) | "multiple" | "range"; multiple ⇒ marquee tool (drag-box multi-select w/ live preview + count; plain box/tap replaces, Shift extends)
540
- │ │ │ ├─ onSelect / onSelectionChange (optional) — item click (key) / full selection-set events ({key?, selected, selectedKeys, additive, region?}); works in every tool (grab/zoom/marquee)
541
- │ │ │ ├─ onSelectZone / onZoneSelectionChange (optional) — zone click-select (items win hit-test; innermost zone wins; Shift extends) reporting the zones AND their childItemKeys
542
- │ │ │ ├─ selectZoomFocus (optional) — a canvas selection also moves the camera (tap flies, marquee fits)
543
- │ │ │ ├─ onItemOpen (optional) — double-click drill-in (background double-click keeps Fit/reset)
544
- │ │ │ ├─ onViewportChange (optional) — debounced viewport-settled reporting ({zoom, minX, minY, maxX, maxY}) for sync / lazy-load / persist
545
- │ │ │ ├─ linkMode (optional) — connect-gesture mode: "draw" (adds locally, form-input style) | "connect" (event-only, repeatable: plan operations); Shift+drag ADDS to the session
546
- │ │ │ ├─ onCreateLink / onSelectLink / onEditLink / onDeleteLink (optional) — link lifecycle ({link, links, additive, existing} on create; click-select key; endpoint re-target; Del delete)
547
- │ │ │ ├─ canConnect (optional) — fn(from, to) => Bool vetoes pairs BEFORE they resolve (the draft never snaps; one rule covers links, Shift-session/net extensions, and re-targets; a throwing validator fails OPEN)
548
- │ │ │ ├─ onEditNet (optional) — net membership edits: trunk/bar click selects the WHOLE net, a STUB click selects ONE leg (Del removes just that endpoint; onEditNet reports membership AFTER; a side emptying deletes the whole net via onDeleteLink). Shift-session gestures keep ONE bus (a member never flips sides); with a LINK selected, Shift-drag from its endpoint SEEDS the session with that link (onCreateLink.absorbed lists absorbed keys — delete those rows when upserting); with a NET selected, Shift-drag out of a member adds the target as a leg; a Shift connect-session commits as a net (onCreateLink.net = {key, sources, destinations}, stable session key — upsert by net.key)
549
- │ │ │ ├─ readOnly / readOnlyLinks / readOnlyItems (optional) — flattened per-domain edit gates
550
- │ │ │ ├─ onMoveItem (optional) — move tool (readOnlyItems off): drags items to new positions; a selected item moves the WHOLE selection rigidly, local-first; fires once per gesture ({key, x, y, keys, dx, dy})
551
- │ │ │ ├─ itemHover / zoneHover / linkHover (optional) — East fn key => UIComponent lazy hover cards (charts in a card over a tank / pipe); any camera or edit gesture closes them; hover ignores readOnly + locked layers
552
- │ │ │ ├─ slice + affordances (optional) — bound slice chrome (default ["search"]); flat effect props sliceHidden / sliceOpacity / sliceDesaturate / sliceDot / sliceEmphasis:"halo"|"pulse" / sliceFrame / sliceFrameFit keep filtered-OUT items as ghost/desaturate/dot context + emphasise the remainder instead of hiding — feed the FULL set (Slice.partition) and mark item.excluded (e.g. t.matched.not())
553
- │ │ │ ├─ sliceSelectField (optional) — bound-slice fieldId a marquee/tap selection writes an `in` filter of selected item keys into (one-directional selection→slice) — pair with the ghost effect (not a Slice.rows feed on the same slice)
554
- │ │ │ ├─ layers (optional) — [{ key, label, tone?, visible?, locked?, opacity? }] + tag items/zones/links with layer:"key"; a canvas layer button opens a show/hide/solo/lock panel (persists per panel); lock ⇒ non-selectable (click-through), opacity dims
555
- │ │ │ ├─ scaleUnit / grid / navigator / minimap (optional) — scale bar unit, metric grid (default on), zones→items TOC (default when zones exist), minimap (default 25+ items)
556
- │ │ │ └─ height / maxHeight (optional) — uniform sizing (#320); default aspect-driven, capped 75vh
557
- │ │ └─ Factories:
558
- │ │ ├─ Schematic.circle(r) / .polyline(verts, {width}) / .polygon(verts) / .rect() — item footprints + zone geometry
559
- │ │ ├─ Schematic.outline() / .hatch() — zone boundary patterns
560
- │ │ └─ Schematic.solid() / .dashed() — link / net stroke styles
532
+ │ ├─ <Schematic> — the 2D world-coordinate drawing of the app's own records (items, zones, links and nets, each a kind over a record, placed where its row says; a camera, layers and selection) is e3-ui's (#1384, #1386): `import { Schematic } from "@elaraai/e3-ui"` — its props, factories and patterns are in the e3-ui skill
561
533
  │ ├─ <Flowchart> — the state-transition flowchart (states in ORDERED phase lanes, its layout derived — no coordinates; H/V-routed transitions; per-link decision triggers and evidence) is e3-ui's (#1243): `import { Flowchart } from "@elaraai/e3-ui"` — its props, factories and patterns are in the e3-ui skill
562
534
  │ ├─ <Map markers={…} center={Map.at(lat,lng)} zoom={n} /> — interactive geographic basemap; read-only / selection-only
563
535
  │ │ ├─ Props:
@@ -657,11 +629,11 @@ Task → Which tag?
657
629
  │ │ │ ├─ slice (required) — the bound handle from Slice.bind
658
630
  │ │ │ ├─ affordances (optional) — ["filter","search","range","breakdown","cohort","presets","brush","legend"]; legends are explicit-only (list "legend" or compose <Slice.Legend>)
659
631
  │ │ │ ├─ persist (optional) — "local" | "session" | "url" opts the state into reload-surviving / shareable-link storage
660
- │ │ │ └─ brush (optional) — the brush strip is rich by default (the range field's format drives the axis labels; a self-excluding count histogram shows the row distribution); brush={{ axis?, count?, buckets? }} opts down to the bare track. The applied window is a full brush selection: drag its body to slide (width preserved), an edge to resize, empty track to draw (also the Plan's horizon-brush gesture)
632
+ │ │ │ └─ brush (optional) — the brush strip is rich by default (the range field's format drives the axis labels; a self-excluding count histogram shows the row distribution); brush={{ axis?, count?, buckets? }} opts down to the bare track. The applied window is a full brush selection: drag its body to slide (width preserved), an edge to resize, empty track to draw (also the Plan's horizon-brush gesture). Its look is the horizon brush's (#1438): bars of one width toned on the heat ramp by count, whole inside the window's band and faded outside it, an outlined tab on each edge, and a hover's tooltip saying a bin's range and count; the start handle, the window and the end handle are three tab stops — ← → move a bin, Shift ten, Home / End the ends, Esc clears
661
633
  │ │ ├─ Nested tags: <Slice.Filter/Search/Range/Breakdown/Legend/Cohort/Presets/Summary slice={slice} /> — per-affordance chrome; <Slice.Cohort mode="toggle"|"manage" allowCreate? group?> (cohorts toggle on chip click; cohorts sharing a `group` render as one captioned run — a FAMILY of alternatives: active members OR within a family, families AND with each other and with standalone cohorts; an empty family member hides on the preset bar unless on; `group="…"` shows one family alone; <Slice.Presets> = toggle-only preset bar); <Slice.Legend> = facet bar (click = in-set multi-select over self-excluding slice.facetGroups(); mode="visibility" = eye rail); Summary/Filter footers read "N of M"
662
634
  │ │ └─ Factories:
663
635
  │ │ ├─ Slice.bind(key, rows, options?) — local Array rows; infers the row type. Options: fields (label/hints/format), range, search, initial, presets, breakdown and match. Initial/preset clauses name typed fields; local operators include gte/contains. The positional form is removed.
664
- │ │ ├─ Slice.bindRecord(key, indexed, options?) — Data.bindPaged(record, { indexes }) from e3-ui; fields/range/search/initial/presets come from declared field indexes. No browser predicate, local operators, match or breakdown. Several exact values then one range/prefix must follow one index; a Set field takes one exact member.
636
+ │ │ ├─ Slice.bindRecord(key, indexed, options?) — Data.bindPaged(record, { indexes }) from e3-ui; fields/range/search/initial/presets come from declared field indexes: a field is any row field an index is led by, whatever its name — `area` and `__tile` included, e3's parts of a generated key being its index's kind's (#1432). No browser predicate, local operators, match or breakdown. Several exact values then one range/prefix must follow one index; a Set field takes one exact member. A source's `pageWhere`/`countWhere` take a read, `{ where, area }`: the narrowing, and a read by area's window or unplaced clause beside it, none for any other read.
665
637
  │ │ ├─ Slice.bindRecords(key, { name: { source: indexed, fields?, range?, search?, initial? }, … }, { presets? }) — several records narrowed from ONE rail: one Slice.bindRecord per named source under `${key}.${name}`; a choice in one source never changes another's rows or counts (no index relates two records). Presets narrow any sources at once (a source a preset doesn't name is cleared); slice.togglePreset(id) / slice.clearAll() act on every part. A component that narrows several kinds of row takes it as slice={{ slice }} with its sources named after them (the e3-ui Allocation's `lanes` and `resources`) and reads each source itself.
666
638
  │ │ ├─ Slice.config(Row, { fields, rangeFieldId?, searchFieldIds?, breakdownFieldIds? }) / Slice.state({…}) — low-level config/state for pure Slice.apply and state operations; bind options infer and seed these automatically.
667
639
  │ │ ├─ Slice.rows(slice) — the narrowed Array for local rows, or for records a live ordered source: one per binding whatever the narrowing, its windows read under the narrowing as it stands, its `query()` naming it (#1354); record entries retain original key beside tagged ik, covering value and optional joined row. Slice.rows(slice, source) reads one source of a Slice.bindRecords handle.
@@ -788,7 +760,7 @@ Task → Which tag?
788
760
  │ └─ plotGutter (optional) — shared gutter (#147): pins the step lane so a Trace stacked under a Chart lines up (left supersedes labelWidth)
789
761
  │
790
762
  ├─ Feedback (status & async signals)
791
- │ ├─ <Banner> — page-spanning notice
763
+ │ ├─ <Banner> — page-spanning notice, the design system's Banner card: one row centred on itself — the status's mark (a triangle for warning/guard, a circle for error, else its 8px dot), the title as its message in the body face at 13, the description under it, the actions at its end (one Default button makes it 48 tall)
792
764
  │ │ └─ Props:
793
765
  │ │ ├─ status (required) — info | warning | success | error | neutral | change | guard | stale
794
766
  │ │ ├─ title (required) — string or UIComponent
@@ -1017,12 +989,14 @@ Task → Which tag?
1017
989
  │ ├─ Where a bare token / status value plugs in:
1018
990
  │ │ ├─ Library.status(label, tone, ring?) — palette card status chip, a gallery card's dot (`ring` open) · Library.glyph(icon, label, tone?) — its trailing glyph
1019
991
  │ │ ├─ Table rowStatus — fn(rowIndex) => StatusToken row tint
1020
- │ │ ├─ Schematic item { status } (Option token dot) and { tone } (brand|ink|muted|success|warning|danger stroke override)
992
+ │ │ ├─ e3-ui's Schematic.items { status: { field, cases } } (a dot per case of a variant field) and { tone } (brand|ink|muted|success|warning|danger stroke override)
1021
993
  │ │ ├─ e3-ui's Plan.run { status } (stuck ring) · Plan.marker { status } (cell rings, default danger) · Plan.event { tone } — tile tints, all ORTHOGONAL to `state`
1022
994
  │ │ ├─ Matrix.marker { status } — corner markers
1023
995
  │ │ ├─ <Status value> — dot + word (`ring` an open ring — not live yet); <Banner status> uses the wider notice set (info|warning|success|error|neutral|change|guard|stale)
1024
996
  │ │ └─ <Meter tone> / BarStrip item { tone } / SegmentedMeter segment { tone } — bar fills
1025
997
  │ ├─ Sentiment (value direction, NOT state): <MetricChip tone> positive|negative|neutral|info · <Numeric sentiment> positive|negative|neutral · <Stat indicator.sentiment> positive|negative|neutral · <Progress tone> brand|pos|neg
998
+ │ ├─ A colour the APP gives, not a state (#1406): ColorTokenType — a theme hue ("brand" | "gray" | "red" | "orange" | "yellow" | "green" | "teal" | "blue" | "cyan" | "purple" | "pink") holding its shade ColorShadeType ("50" … "900"); Style.ColorToken("teal", "500") is variant("teal", variant("500", null)). The typed value every colour accessor returns from here on (e3-ui's Roster.shifts `color`, Option<ColorToken>); a renderer resolves it as the theme's {colors.<hue>.<shade>}, the same in both colour modes, the words on it in the ink the shade takes (the foreground up to 400, the paper's from 500). `brand` and `gray` are the design system's scales, the rest Chakra's stock palettes
999
+ │ ├─ The Elara tokens (#1409): `elara` (the eye's iris and the banner's mark), `elara.ink`, `elara.edge` and `elara.soft` — the ONE accent for what the app's optimiser did, never a state and never an app's colour. Only east-ui-components' shared parts draw them: `OptimiseButton` (the Elara eye, `optimiseToolbarItem` in a builder's toolbar before the history item), `OptimiseBanner` (the `elara` banner tone), `OptimiseLegendItem` (`Elara change`), and the `optimise` recipe's landing over `data-op` / `data-fill` / `data-opn` — e3-ui's five builders list them when `optimise` is bound; an app writes none of them
1026
1000
  │ ├─ state ≠ status: the AUDIT LIFECYCLE is orthogonal to the status tint. Blend allocations speak PlannerStateType ("committed"|"added"|"model"|"removed"|"rejected" — committed solid, proposals dashed/ghost/struck, rejected greyed; only proposed items drag); Plan elements speak the richer EventStateType ladder ("estimated"→"proposed"(added|recommended|removed)→"confirmed"→"in-progress"→"actual", plus "rejected"). A status token is the ORTHOGONAL semantic tint layered on top
1027
1001
  │ └─ tone vs colorPalette: tone/status/sentiment = the semantic vocabulary above (meaning-bearing, theme-stable); colorPalette = decorative hue theming (Chakra palettes) for buttons/badges/tags where the colour carries no state meaning
1028
1002
  │
@@ -1085,8 +1059,9 @@ pinned read the server refuses because the dataset has moved on (409
1085
1059
  `dataset_hash_mismatch`) rediscovers the current snapshot instead of failing
1086
1060
  its window. How a consumer bridges the move is its own: e3-ui's `<Plan>` and
1087
1061
  `<Sheet>` keep the rows they show until the new snapshot's windows land; the
1088
- Sheet clears a standing key search, and the Plan asks it again there, its
1089
- text kept.
1062
+ Sheet clears a standing key search, and the Plan asks it again there: a
1063
+ match its box shows stays while the new snapshot holds it in place, else the
1064
+ box shows its query again.
1090
1065
 
1091
1066
  ## Key Patterns
1092
1067
 
@@ -1218,7 +1193,7 @@ Non-UI sub-structures are never child sub-tags.
1218
1193
  Every size prop is a **plain string** and every renderer parses it the same
1219
1194
  way (`parseCssSize`). Four spellings, uniform across data components (`<Table>`,
1220
1195
  e3-ui's `<Plan>`, `<Matrix>`,
1221
- `<Library>`, `<Schematic>`, `<SnapGrid>`) and layout primitives (`<Box>` / `<Flex>` /
1196
+ `<Library>`, `<SnapGrid>`) and layout primitives (`<Box>` / `<Flex>` /
1222
1197
  `<Stack>` / `<Grid>` / `<Card>`):
1223
1198
 
1224
1199
  | Value | Means |
@@ -1510,11 +1485,13 @@ tier so they stay subordinate to tags at every density.
1510
1485
  and written back (through an e3 record's patch, or `onUpdate` / `onApply`)
1511
1486
  — the e3-ui skill documents it; for reading, sorting and reviewing a
1512
1487
  dataset, `<Table>`.
1513
- - **Flowchart vs Schematic** — `<Schematic>` is a world-coordinate 2D canvas
1514
- (data carries x/y; zones, footprints, camera); e3-ui's `<Flowchart>`
1515
- (#1243) derives its whole layout from lanes + links (no coordinates) for
1488
+ - **Flowchart vs Schematic** — both are e3-ui's, and the e3-ui skill
1489
+ documents them: `<Schematic>` (#1384, #1386) is a world-coordinate 2D
1490
+ drawing of the app's records (each row carries its x/y; zones, footprints,
1491
+ camera); `<Flowchart>` (#1243)
1492
+ derives its whole layout from lanes + links (no coordinates) for
1516
1493
  state-transition / process-flow reading, with decision triggers and
1517
- evidence on the links — the e3-ui skill documents it.
1494
+ evidence on the links.
1518
1495
  - **Library vs Deck** — `<Library>` is the draggable palette (DnD source),
1519
1496
  or with `variant="gallery"` a library a person browses — large cards, each
1520
1497
  with its media, a byline and an action a click follows;