@loomwork/ui-renderer-terminal 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +369 -2
  4. package/dist/esm/index.js +6 -0
  5. package/dist/esm/index.js.map +1 -0
  6. package/dist/esm/inert.js +13 -0
  7. package/dist/esm/inert.js.map +1 -0
  8. package/dist/esm/interactive.js +677 -0
  9. package/dist/esm/interactive.js.map +1 -0
  10. package/dist/esm/registry.js +428 -0
  11. package/dist/esm/registry.js.map +1 -0
  12. package/dist/esm/status.js +64 -0
  13. package/dist/esm/status.js.map +1 -0
  14. package/dist/esm/watch.js +17 -0
  15. package/dist/esm/watch.js.map +1 -0
  16. package/dist/index.d.ts +9 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +22 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/inert.d.ts +12 -0
  21. package/dist/inert.d.ts.map +1 -0
  22. package/dist/inert.js +17 -0
  23. package/dist/inert.js.map +1 -0
  24. package/dist/interactive.d.ts +142 -0
  25. package/dist/interactive.d.ts.map +1 -0
  26. package/dist/interactive.js +685 -0
  27. package/dist/interactive.js.map +1 -0
  28. package/dist/registry.d.ts +55 -0
  29. package/dist/registry.d.ts.map +1 -0
  30. package/dist/registry.js +434 -0
  31. package/dist/registry.js.map +1 -0
  32. package/dist/status.d.ts +49 -0
  33. package/dist/status.d.ts.map +1 -0
  34. package/dist/status.js +69 -0
  35. package/dist/status.js.map +1 -0
  36. package/dist/watch.d.ts +9 -0
  37. package/dist/watch.d.ts.map +1 -0
  38. package/dist/watch.js +20 -0
  39. package/dist/watch.js.map +1 -0
  40. package/package.json +60 -3
  41. package/src/index.ts +25 -0
  42. package/src/inert.ts +14 -0
  43. package/src/interactive.ts +868 -0
  44. package/src/registry.ts +630 -0
  45. package/src/status.ts +103 -0
  46. package/src/watch.ts +22 -0
package/src/status.ts ADDED
@@ -0,0 +1,103 @@
1
+ import type { Blueprint, RendererRegistry } from "@loomwork/blueprint-runtime";
2
+ import {
3
+ type TerminalWidgetRenderer,
4
+ type WidgetValueCheck,
5
+ renderBlueprintToString,
6
+ } from "@loomwork/plugin-renderer-terminal";
7
+ import { type ContractRegistry, checkKindValue } from "@loomwork/ui-core";
8
+ import { HYDRATION_PROJECTION_ID, type HydrationStore } from "@loomwork/ui-hydration/client";
9
+ import { inert } from "./inert";
10
+ import { type TerminalRegistryOptions, terminalRegistry } from "./registry";
11
+
12
+ // ── The status matrix is loom's ───────────────────────────────────────────────
13
+ // Since Blueprint IR 2.0.0 (ADR 0097 D2) the eight-row decision table lives in loom's own
14
+ // traversal: each binding DECLARES its status channel (`status.<handle>`, beside the value's
15
+ // `values.<handle>` in the same projection state), and `renderBlueprintToString` applies the
16
+ // matrix. The framework contributes exactly one thing to it — what a bound VALUE must look
17
+ // like, which is catalogue vocabulary loom deliberately knows nothing about — through the
18
+ // `check` seam loom leaves open between `error` and `stale`.
19
+
20
+ /** The framework's per-kind value contract, in loom's `check` seam (between `error` and `stale`). */
21
+ export const kindValueCheck: WidgetValueCheck = kindValueCheckFor();
22
+
23
+ /**
24
+ * {@link kindValueCheck} that also checks the host's own kinds against `contracts` (a catalogue's
25
+ * `valueContracts`). The framework's contract wins for a framework kind. Absent = framework only.
26
+ */
27
+ export function kindValueCheckFor(contracts?: ContractRegistry): WidgetValueCheck {
28
+ return (widget, value) => {
29
+ const c = checkKindValue(widget.kind, value, contracts);
30
+ return c.ok ? undefined : { code: c.finding.code, message: c.finding.message };
31
+ };
32
+ }
33
+
34
+ export interface RenderExperienceOptions extends TerminalRegistryOptions {
35
+ /** The catalogue's `valueContracts`: bound values of the host's own kinds are checked against them. */
36
+ readonly contracts?: ContractRegistry;
37
+ /**
38
+ * Overrides the registry. Passing it together with `chrome` throws — `chrome` only configures the
39
+ * default registry; build yours with `terminalRegistry({ chrome })` instead.
40
+ * NOTE for `chrome: "host"`: the framework kinds' own renderings carry no title, but the rows
41
+ * loom's traversal itself writes — `✗`/`○`/`~`/`⋯` status lines and loom's eight built-in kinds —
42
+ * are loom's output and keep theirs. Drive a widget renderer directly for a fully title-free body.
43
+ */
44
+ readonly registry?: RendererRegistry<TerminalWidgetRenderer>;
45
+ /**
46
+ * The current state of any projection the blueprint declares OTHER than the store's own
47
+ * hydration projection (a node bound through `SemanticNode.projection`): `(id) => state`,
48
+ * `undefined` for one the host has no state for (its widgets render as loading). Typically
49
+ * `(id) => runtime.getState(id, executionId)`. Never consulted for the hydration id — the store
50
+ * is that projection's only source here.
51
+ */
52
+ readonly projectionState?: (projectionId: string) => unknown;
53
+ /**
54
+ * How `watch` hears that a non-hydration projection changed: called once, returns an
55
+ * unsubscribe. Without it `watch` re-renders on hydration changes only.
56
+ */
57
+ readonly projectionSubscribe?: (onChange: () => void) => () => void;
58
+ }
59
+
60
+ /**
61
+ * Render the store's projection through loom's terminal renderer: loom's matrix, the framework's
62
+ * contract check.
63
+ *
64
+ * The state map is keyed by `HYDRATION_PROJECTION_ID` — the one id a `HydrationStore` publishes
65
+ * under. A blueprint compiled with any OTHER `hydrationProjectionId` therefore matches no state
66
+ * here: every binding resolves to an undefined value and an undefined status, so the surface
67
+ * renders as loading rows. That is the honest rendering of "this store holds nothing for that
68
+ * projection", not an error — pass the store's own id to `compile` and the rows fill.
69
+ */
70
+ export function renderExperience(
71
+ blueprint: Blueprint,
72
+ store: HydrationStore,
73
+ opts: RenderExperienceOptions = {}
74
+ ): string {
75
+ if (opts.registry !== undefined && opts.chrome !== undefined)
76
+ throw new Error(
77
+ "renderExperience: `chrome` configures the default registry and would be ignored next to `registry` — pass one, or build your registry with terminalRegistry({ chrome })"
78
+ );
79
+ const state = store.state();
80
+ const stateMap = new Map<string, unknown>();
81
+ // `HydrationStore` publishes under one well-known projection id; every other projection the
82
+ // blueprint declares is somebody else's, and has state here only if the host supplies it. A
83
+ // throwing supplier is "no state yet" (a loading row), never a throw out of a render.
84
+ for (const id of blueprint.projections) {
85
+ if (id === HYDRATION_PROJECTION_ID) stateMap.set(id, state);
86
+ else stateMap.set(id, readProjection(opts.projectionState, id));
87
+ }
88
+ return inert(
89
+ renderBlueprintToString(blueprint, stateMap, {
90
+ registry: opts.registry ?? terminalRegistry(opts),
91
+ check: opts.contracts === undefined ? kindValueCheck : kindValueCheckFor(opts.contracts),
92
+ })
93
+ );
94
+ }
95
+
96
+ function readProjection(read: RenderExperienceOptions["projectionState"], id: string): unknown {
97
+ if (read === undefined) return undefined;
98
+ try {
99
+ return read(id);
100
+ } catch {
101
+ return undefined;
102
+ }
103
+ }
package/src/watch.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { Blueprint } from "@loomwork/blueprint-runtime";
2
+ import type { HydrationStore } from "@loomwork/ui-hydration/client";
3
+ import { type RenderExperienceOptions, renderExperience } from "./status";
4
+
5
+ /**
6
+ * Render now and on every change to the store's projection (and, with `projectionSubscribe`, to
7
+ * any other projection the blueprint binds). Returns an unsubscribe.
8
+ */
9
+ export function watch(
10
+ blueprint: Blueprint,
11
+ store: HydrationStore,
12
+ onFrame: (frame: string) => void,
13
+ opts: RenderExperienceOptions = {}
14
+ ): () => void {
15
+ onFrame(renderExperience(blueprint, store, opts));
16
+ const frame = (): void => onFrame(renderExperience(blueprint, store, opts));
17
+ const stops = [store.runtimeSubscribe(frame)];
18
+ if (opts.projectionSubscribe !== undefined) stops.push(opts.projectionSubscribe(frame));
19
+ return () => {
20
+ for (const stop of stops) stop();
21
+ };
22
+ }