@gravity-platform/unoverse-react 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/dist/index.d.cts CHANGED
@@ -1,13 +1,77 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
3
- import { UnoverseNode, UnoverseClient, ComponentStore } from '@gravity-platform/unoverse-core';
2
+ import { CSSProperties, ReactNode } from 'react';
3
+ import { ResolvedTheme, ActionSpec, UnoverseNode, UnoverseClient, ComponentStore } from '@gravity-platform/unoverse-core';
4
+ export { ResolvedTheme } from '@gravity-platform/unoverse-core';
4
5
 
5
6
  /**
6
- * The generic tree renderer: UnoverseNodeReact. No per-component code.
7
+ * The style + animation INTERPRETER — neutral vocab CSS, resolving token NAMES
8
+ * against a theme FETCHED from the server. ⛔ OWNS ZERO STYLE VALUES (no hex, no
9
+ * px/rem/em, no recipes); every value lives in rx/styles and is served. See
10
+ * FRAMEWORK.md and render.tsx's header. Enforced by test/golden-rule.test.mjs.
7
11
  */
8
12
 
9
- type ActionHandler = (action: string, data: Record<string, unknown>) => void;
10
- declare function renderNode(node: UnoverseNode, data: Record<string, unknown>, onAction?: ActionHandler, key?: React.Key): ReactNode;
13
+ /**
14
+ * Map the neutral style vocab CSS, resolving token names against the theme. Unknown
15
+ * values pass through. When `data` is given, a style value that is a `{{field}}` binding
16
+ * is first resolved from the data scope (e.g. a bar's `height: "{{pct}}"` ← data.pct =
17
+ * "72%") — the data-driven twin of token resolution. This is what lets bar/progress
18
+ * charts be DATA (Box + Each), not a primitive: the producer supplies the proportion,
19
+ * the SDK authors nothing. (Same `{{...}}` resolver the action vocab uses.)
20
+ */
21
+ declare function styleToCss(s: Record<string, unknown> | undefined, theme: ResolvedTheme, data?: Record<string, unknown>): CSSProperties;
22
+ /**
23
+ * Serialize the SERVED keyframes (theme.keyframes ← rx/styles/semantic/keyframes.json)
24
+ * into a CSS string, injected ONCE per render root (<style>). This authors zero values —
25
+ * it only structures DATA into `@keyframes uno-<name>{ <stop>{ <prop>:<val> } }`. Adding a
26
+ * new animation = adding a keyframe to rx/ + a `style.animation` ref in a definition. No
27
+ * SDK edit, no new primitive. Keyframe decls use real CSS prop names (transform, opacity…).
28
+ */
29
+ declare function keyframesCss(theme: ResolvedTheme): string;
30
+
31
+ /**
32
+ * ════════════════════════════════════════════════════════════════════════════
33
+ * THE DISPATCHER: UnoverseNode → React. ⛔ READ BEFORE EDITING ⛔
34
+ * ════════════════════════════════════════════════════════════════════════════
35
+ *
36
+ * THIS FILE RENDERS NOTHING. It only WALKS the tree and DISPATCHES `node.type`
37
+ * to a primitive component in ./primitives. There is NOT A SINGLE raw element
38
+ * here — no `<div>`, `<span>`, `<button>`, `<img>`, `<input>`, `<svg>`. If you
39
+ * are about to write one, STOP: it belongs in ./primitives. This is enforced by
40
+ * test/dispatcher-only.test.mjs (the build fails if a raw element appears here),
41
+ * so the engine/primitive split can't silently rot — which is exactly how
42
+ * `Button` (and its styling logic) kept leaking back into the dispatcher.
43
+ *
44
+ * THE FRAMEWORK IN ONE SENTENCE: this SDK is a FIXED, GENERIC interpreter. ALL
45
+ * UX is built in DATA (rx/ definitions + the served theme) — an author creates
46
+ * ANY interface WITHOUT editing this SDK. (See FRAMEWORK.md.)
47
+ *
48
+ * WHERE THINGS LIVE:
49
+ * • ./render — this dispatcher (control flow: visibleWhen, Each, slots) ONLY.
50
+ * • ./primitives — EVERY primitive (Box/Text/Image/Button/Icon/Skeleton/Input/
51
+ * Markdown/Unknown) + the shared per-element chrome.
52
+ * • ./style — the style + animation interpreter (styleToCss/keyframesCss).
53
+ *
54
+ * Two laws (govern all three files + the whole package):
55
+ * LAW 1 — OWN ZERO STYLE VALUES. No hex/px/rem/em/recipes; resolve token NAMES
56
+ * against the SERVED theme. Enforced by test/golden-rule.test.mjs.
57
+ * LAW 2 — OWN ZERO UX SHAPE. A primitive renders ONE generic element, nothing
58
+ * about a specific UX. A composer/card/loader is a LAYOUT — compose it
59
+ * in a DEFINITION. Never a per-UX primitive or per-UX config.
60
+ *
61
+ * Need something the vocab can't express? Prefer extending the generic interpreter
62
+ * ONCE (a new style-vocab key in ./style, a new model projection) over a primitive.
63
+ * You almost never need to touch this file — build it in rx/ first.
64
+ * ════════════════════════════════════════════════════════════════════════════
65
+ */
66
+
67
+ type ActionHandler = (action: string | ActionSpec, data: Record<string, unknown>) => void;
68
+ /**
69
+ * Resolves a `ComponentSlot` node against the store (provided by the template
70
+ * renderer). Returns the rendered leaves (or the slot's fallback). Components
71
+ * don't use slots, so this is undefined on the component render path.
72
+ */
73
+ type SlotResolver = (node: UnoverseNode, key?: React.Key) => ReactNode;
74
+ declare function renderNode(node: UnoverseNode, data: Record<string, unknown>, onAction: ActionHandler | undefined, theme: ResolvedTheme, key?: React.Key, slot?: SlotResolver): ReactNode;
11
75
 
12
76
  interface UnoverseComponentProps {
13
77
  client: UnoverseClient;
@@ -16,8 +80,14 @@ interface UnoverseComponentProps {
16
80
  /** instance data bound into the definition (the streamed props) */
17
81
  data?: Record<string, unknown>;
18
82
  onAction?: ActionHandler;
83
+ /**
84
+ * Resolved theme. Optional — if omitted, the SDK FETCHES it from the server
85
+ * (`unoverse://theme/light`). The SDK bundles no tokens. Pass one to override
86
+ * (e.g. the workbench's light/dark toggle); swap it to restyle, zero def changes.
87
+ */
88
+ theme?: ResolvedTheme;
19
89
  }
20
- declare function UnoverseComponent({ client, uri, data, onAction }: UnoverseComponentProps): react.JSX.Element;
90
+ declare function UnoverseComponent({ client, uri, data, onAction, theme }: UnoverseComponentProps): react.JSX.Element;
21
91
 
22
92
  /** Subscribe to a component instance in the store (re-renders on merge). */
23
93
  declare function useUnoverseInstance(store: ComponentStore, chatId: string, nodeId: string): {
@@ -30,8 +100,67 @@ interface StreamedUnoverseComponentProps {
30
100
  chatId: string;
31
101
  nodeId: string;
32
102
  onAction?: ActionHandler;
103
+ theme?: ResolvedTheme;
104
+ /** Neutral turn state merged into the component's data scope (e.g. `streaming`), so a
105
+ * component can reflect it — like legacy passing `streamingState` to AIResponse. */
106
+ extraData?: Record<string, unknown>;
33
107
  }
34
108
  /** Resolves `type` from the store → its definition → renders with merged data. */
35
- declare function StreamedUnoverseComponent({ client, store, chatId, nodeId, onAction }: StreamedUnoverseComponentProps): react.JSX.Element;
109
+ declare function StreamedUnoverseComponent({ client, store, chatId, nodeId, onAction, theme, extraData }: StreamedUnoverseComponentProps): react.JSX.Element;
110
+
111
+ interface StreamedUnoverseTemplateProps {
112
+ client: UnoverseClient;
113
+ store: ComponentStore;
114
+ /** e.g. "unoverse://templates/KeyService" */
115
+ uri: string;
116
+ onAction?: ActionHandler;
117
+ theme?: ResolvedTheme;
118
+ /** Channel overrides for the template's declared props (e.g. per-tenant logoUrl/brandName).
119
+ * Merged OVER the definition's own `props` defaults into the root data scope. */
120
+ props?: Record<string, unknown>;
121
+ }
122
+ /** Resolve a ComponentSlot's `select` against the store → ordered pointers. Exported for testing. */
123
+ declare function selectPointers(store: ComponentStore, node: UnoverseNode): string[];
124
+ declare function StreamedUnoverseTemplate({ client, store, uri, onAction, theme: themeProp, props }: StreamedUnoverseTemplateProps): react.JSX.Element;
125
+
126
+ /** Returns the resolved theme (null until the first fetch resolves). */
127
+ declare function useUnoverseTheme(client: UnoverseClient, name?: string): ResolvedTheme | null;
128
+
129
+ interface UnoverseConnectionConfig {
130
+ /** Gravity API base, e.g. "http://localhost:4100". REST execute lives here. */
131
+ apiUrl: string;
132
+ /** Unoverse server base, e.g. "http://localhost:4105" (or the workbench origin in
133
+ * dev). The `/stream` MCP data-plane endpoint (§5b) is appended. */
134
+ streamUrl: string;
135
+ /** Optional JWT provider — the token rides the MCP transport fetch + REST Authorization header. */
136
+ getAccessToken?: () => Promise<string | null>;
137
+ }
138
+ interface UnoverseSessionParams {
139
+ workflowId: string;
140
+ targetTriggerNode: string;
141
+ userId: string;
142
+ conversationId: string;
143
+ /** Groups one request→response turn. Defaults to conversationId if omitted. */
144
+ chatId?: string;
145
+ }
146
+ interface UnoverseConnection {
147
+ isConnected: boolean;
148
+ isReady: boolean;
149
+ /** Composer target: optimistic user turn + REST workflow trigger. */
150
+ sendMessage: (text: string) => void;
151
+ /** Fire the bound workflow WITHOUT a user turn — the "load → run the workflow" ping
152
+ * (its component streams back into the shell). `input` shapes the execute payload per
153
+ * the app's `inputSchema`; omit for a bare trigger. (§5b REST `/execute`; the future
154
+ * MCP-native form is a `tools/call` on the InputTrigger tool, same call site.) */
155
+ trigger: (input?: Record<string, unknown>) => void;
156
+ /** Other component actions (button clicks, etc.) → MCP `user_action` tool. */
157
+ sendAction: (action: string, data: Record<string, unknown>) => void;
158
+ /** The workflow-selected template (MCP app) name, or null. The channel renders
159
+ * `unoverse://templates/{activeTemplate}` when set — the official resources/read load. */
160
+ activeTemplate: string | null;
161
+ }
162
+ declare function useUnoverseConnection(config: UnoverseConnectionConfig, session: UnoverseSessionParams, store: ComponentStore, options?: {
163
+ enabled?: boolean;
164
+ }): UnoverseConnection;
36
165
 
37
- export { type ActionHandler, StreamedUnoverseComponent, type StreamedUnoverseComponentProps, UnoverseComponent, type UnoverseComponentProps, renderNode, useUnoverseInstance };
166
+ export { type ActionHandler, type SlotResolver, StreamedUnoverseComponent, type StreamedUnoverseComponentProps, StreamedUnoverseTemplate, type StreamedUnoverseTemplateProps, UnoverseComponent, type UnoverseComponentProps, type UnoverseConnection, type UnoverseConnectionConfig, type UnoverseSessionParams, keyframesCss, renderNode, selectPointers, styleToCss, useUnoverseConnection, useUnoverseInstance, useUnoverseTheme };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,77 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
3
- import { UnoverseNode, UnoverseClient, ComponentStore } from '@gravity-platform/unoverse-core';
2
+ import { CSSProperties, ReactNode } from 'react';
3
+ import { ResolvedTheme, ActionSpec, UnoverseNode, UnoverseClient, ComponentStore } from '@gravity-platform/unoverse-core';
4
+ export { ResolvedTheme } from '@gravity-platform/unoverse-core';
4
5
 
5
6
  /**
6
- * The generic tree renderer: UnoverseNodeReact. No per-component code.
7
+ * The style + animation INTERPRETER — neutral vocab CSS, resolving token NAMES
8
+ * against a theme FETCHED from the server. ⛔ OWNS ZERO STYLE VALUES (no hex, no
9
+ * px/rem/em, no recipes); every value lives in rx/styles and is served. See
10
+ * FRAMEWORK.md and render.tsx's header. Enforced by test/golden-rule.test.mjs.
7
11
  */
8
12
 
9
- type ActionHandler = (action: string, data: Record<string, unknown>) => void;
10
- declare function renderNode(node: UnoverseNode, data: Record<string, unknown>, onAction?: ActionHandler, key?: React.Key): ReactNode;
13
+ /**
14
+ * Map the neutral style vocab CSS, resolving token names against the theme. Unknown
15
+ * values pass through. When `data` is given, a style value that is a `{{field}}` binding
16
+ * is first resolved from the data scope (e.g. a bar's `height: "{{pct}}"` ← data.pct =
17
+ * "72%") — the data-driven twin of token resolution. This is what lets bar/progress
18
+ * charts be DATA (Box + Each), not a primitive: the producer supplies the proportion,
19
+ * the SDK authors nothing. (Same `{{...}}` resolver the action vocab uses.)
20
+ */
21
+ declare function styleToCss(s: Record<string, unknown> | undefined, theme: ResolvedTheme, data?: Record<string, unknown>): CSSProperties;
22
+ /**
23
+ * Serialize the SERVED keyframes (theme.keyframes ← rx/styles/semantic/keyframes.json)
24
+ * into a CSS string, injected ONCE per render root (<style>). This authors zero values —
25
+ * it only structures DATA into `@keyframes uno-<name>{ <stop>{ <prop>:<val> } }`. Adding a
26
+ * new animation = adding a keyframe to rx/ + a `style.animation` ref in a definition. No
27
+ * SDK edit, no new primitive. Keyframe decls use real CSS prop names (transform, opacity…).
28
+ */
29
+ declare function keyframesCss(theme: ResolvedTheme): string;
30
+
31
+ /**
32
+ * ════════════════════════════════════════════════════════════════════════════
33
+ * THE DISPATCHER: UnoverseNode → React. ⛔ READ BEFORE EDITING ⛔
34
+ * ════════════════════════════════════════════════════════════════════════════
35
+ *
36
+ * THIS FILE RENDERS NOTHING. It only WALKS the tree and DISPATCHES `node.type`
37
+ * to a primitive component in ./primitives. There is NOT A SINGLE raw element
38
+ * here — no `<div>`, `<span>`, `<button>`, `<img>`, `<input>`, `<svg>`. If you
39
+ * are about to write one, STOP: it belongs in ./primitives. This is enforced by
40
+ * test/dispatcher-only.test.mjs (the build fails if a raw element appears here),
41
+ * so the engine/primitive split can't silently rot — which is exactly how
42
+ * `Button` (and its styling logic) kept leaking back into the dispatcher.
43
+ *
44
+ * THE FRAMEWORK IN ONE SENTENCE: this SDK is a FIXED, GENERIC interpreter. ALL
45
+ * UX is built in DATA (rx/ definitions + the served theme) — an author creates
46
+ * ANY interface WITHOUT editing this SDK. (See FRAMEWORK.md.)
47
+ *
48
+ * WHERE THINGS LIVE:
49
+ * • ./render — this dispatcher (control flow: visibleWhen, Each, slots) ONLY.
50
+ * • ./primitives — EVERY primitive (Box/Text/Image/Button/Icon/Skeleton/Input/
51
+ * Markdown/Unknown) + the shared per-element chrome.
52
+ * • ./style — the style + animation interpreter (styleToCss/keyframesCss).
53
+ *
54
+ * Two laws (govern all three files + the whole package):
55
+ * LAW 1 — OWN ZERO STYLE VALUES. No hex/px/rem/em/recipes; resolve token NAMES
56
+ * against the SERVED theme. Enforced by test/golden-rule.test.mjs.
57
+ * LAW 2 — OWN ZERO UX SHAPE. A primitive renders ONE generic element, nothing
58
+ * about a specific UX. A composer/card/loader is a LAYOUT — compose it
59
+ * in a DEFINITION. Never a per-UX primitive or per-UX config.
60
+ *
61
+ * Need something the vocab can't express? Prefer extending the generic interpreter
62
+ * ONCE (a new style-vocab key in ./style, a new model projection) over a primitive.
63
+ * You almost never need to touch this file — build it in rx/ first.
64
+ * ════════════════════════════════════════════════════════════════════════════
65
+ */
66
+
67
+ type ActionHandler = (action: string | ActionSpec, data: Record<string, unknown>) => void;
68
+ /**
69
+ * Resolves a `ComponentSlot` node against the store (provided by the template
70
+ * renderer). Returns the rendered leaves (or the slot's fallback). Components
71
+ * don't use slots, so this is undefined on the component render path.
72
+ */
73
+ type SlotResolver = (node: UnoverseNode, key?: React.Key) => ReactNode;
74
+ declare function renderNode(node: UnoverseNode, data: Record<string, unknown>, onAction: ActionHandler | undefined, theme: ResolvedTheme, key?: React.Key, slot?: SlotResolver): ReactNode;
11
75
 
12
76
  interface UnoverseComponentProps {
13
77
  client: UnoverseClient;
@@ -16,8 +80,14 @@ interface UnoverseComponentProps {
16
80
  /** instance data bound into the definition (the streamed props) */
17
81
  data?: Record<string, unknown>;
18
82
  onAction?: ActionHandler;
83
+ /**
84
+ * Resolved theme. Optional — if omitted, the SDK FETCHES it from the server
85
+ * (`unoverse://theme/light`). The SDK bundles no tokens. Pass one to override
86
+ * (e.g. the workbench's light/dark toggle); swap it to restyle, zero def changes.
87
+ */
88
+ theme?: ResolvedTheme;
19
89
  }
20
- declare function UnoverseComponent({ client, uri, data, onAction }: UnoverseComponentProps): react.JSX.Element;
90
+ declare function UnoverseComponent({ client, uri, data, onAction, theme }: UnoverseComponentProps): react.JSX.Element;
21
91
 
22
92
  /** Subscribe to a component instance in the store (re-renders on merge). */
23
93
  declare function useUnoverseInstance(store: ComponentStore, chatId: string, nodeId: string): {
@@ -30,8 +100,67 @@ interface StreamedUnoverseComponentProps {
30
100
  chatId: string;
31
101
  nodeId: string;
32
102
  onAction?: ActionHandler;
103
+ theme?: ResolvedTheme;
104
+ /** Neutral turn state merged into the component's data scope (e.g. `streaming`), so a
105
+ * component can reflect it — like legacy passing `streamingState` to AIResponse. */
106
+ extraData?: Record<string, unknown>;
33
107
  }
34
108
  /** Resolves `type` from the store → its definition → renders with merged data. */
35
- declare function StreamedUnoverseComponent({ client, store, chatId, nodeId, onAction }: StreamedUnoverseComponentProps): react.JSX.Element;
109
+ declare function StreamedUnoverseComponent({ client, store, chatId, nodeId, onAction, theme, extraData }: StreamedUnoverseComponentProps): react.JSX.Element;
110
+
111
+ interface StreamedUnoverseTemplateProps {
112
+ client: UnoverseClient;
113
+ store: ComponentStore;
114
+ /** e.g. "unoverse://templates/KeyService" */
115
+ uri: string;
116
+ onAction?: ActionHandler;
117
+ theme?: ResolvedTheme;
118
+ /** Channel overrides for the template's declared props (e.g. per-tenant logoUrl/brandName).
119
+ * Merged OVER the definition's own `props` defaults into the root data scope. */
120
+ props?: Record<string, unknown>;
121
+ }
122
+ /** Resolve a ComponentSlot's `select` against the store → ordered pointers. Exported for testing. */
123
+ declare function selectPointers(store: ComponentStore, node: UnoverseNode): string[];
124
+ declare function StreamedUnoverseTemplate({ client, store, uri, onAction, theme: themeProp, props }: StreamedUnoverseTemplateProps): react.JSX.Element;
125
+
126
+ /** Returns the resolved theme (null until the first fetch resolves). */
127
+ declare function useUnoverseTheme(client: UnoverseClient, name?: string): ResolvedTheme | null;
128
+
129
+ interface UnoverseConnectionConfig {
130
+ /** Gravity API base, e.g. "http://localhost:4100". REST execute lives here. */
131
+ apiUrl: string;
132
+ /** Unoverse server base, e.g. "http://localhost:4105" (or the workbench origin in
133
+ * dev). The `/stream` MCP data-plane endpoint (§5b) is appended. */
134
+ streamUrl: string;
135
+ /** Optional JWT provider — the token rides the MCP transport fetch + REST Authorization header. */
136
+ getAccessToken?: () => Promise<string | null>;
137
+ }
138
+ interface UnoverseSessionParams {
139
+ workflowId: string;
140
+ targetTriggerNode: string;
141
+ userId: string;
142
+ conversationId: string;
143
+ /** Groups one request→response turn. Defaults to conversationId if omitted. */
144
+ chatId?: string;
145
+ }
146
+ interface UnoverseConnection {
147
+ isConnected: boolean;
148
+ isReady: boolean;
149
+ /** Composer target: optimistic user turn + REST workflow trigger. */
150
+ sendMessage: (text: string) => void;
151
+ /** Fire the bound workflow WITHOUT a user turn — the "load → run the workflow" ping
152
+ * (its component streams back into the shell). `input` shapes the execute payload per
153
+ * the app's `inputSchema`; omit for a bare trigger. (§5b REST `/execute`; the future
154
+ * MCP-native form is a `tools/call` on the InputTrigger tool, same call site.) */
155
+ trigger: (input?: Record<string, unknown>) => void;
156
+ /** Other component actions (button clicks, etc.) → MCP `user_action` tool. */
157
+ sendAction: (action: string, data: Record<string, unknown>) => void;
158
+ /** The workflow-selected template (MCP app) name, or null. The channel renders
159
+ * `unoverse://templates/{activeTemplate}` when set — the official resources/read load. */
160
+ activeTemplate: string | null;
161
+ }
162
+ declare function useUnoverseConnection(config: UnoverseConnectionConfig, session: UnoverseSessionParams, store: ComponentStore, options?: {
163
+ enabled?: boolean;
164
+ }): UnoverseConnection;
36
165
 
37
- export { type ActionHandler, StreamedUnoverseComponent, type StreamedUnoverseComponentProps, UnoverseComponent, type UnoverseComponentProps, renderNode, useUnoverseInstance };
166
+ export { type ActionHandler, type SlotResolver, StreamedUnoverseComponent, type StreamedUnoverseComponentProps, StreamedUnoverseTemplate, type StreamedUnoverseTemplateProps, UnoverseComponent, type UnoverseComponentProps, type UnoverseConnection, type UnoverseConnectionConfig, type UnoverseSessionParams, keyframesCss, renderNode, selectPointers, styleToCss, useUnoverseConnection, useUnoverseInstance, useUnoverseTheme };