@loomwork/blueprint-runtime 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 (117) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +83 -2
  4. package/dist/Blueprint.d.ts +14 -0
  5. package/dist/Blueprint.d.ts.map +1 -0
  6. package/dist/Blueprint.js +18 -0
  7. package/dist/Blueprint.js.map +1 -0
  8. package/dist/RendererRegistry.d.ts +9 -0
  9. package/dist/RendererRegistry.d.ts.map +1 -0
  10. package/dist/RendererRegistry.js +39 -0
  11. package/dist/RendererRegistry.js.map +1 -0
  12. package/dist/catalog.d.ts +129 -0
  13. package/dist/catalog.d.ts.map +1 -0
  14. package/dist/catalog.js +435 -0
  15. package/dist/catalog.js.map +1 -0
  16. package/dist/coverage.d.ts +18 -0
  17. package/dist/coverage.d.ts.map +1 -0
  18. package/dist/coverage.js +22 -0
  19. package/dist/coverage.js.map +1 -0
  20. package/dist/customWidget.d.ts +10 -0
  21. package/dist/customWidget.d.ts.map +1 -0
  22. package/dist/customWidget.js +34 -0
  23. package/dist/customWidget.js.map +1 -0
  24. package/dist/esm/Blueprint.js +15 -0
  25. package/dist/esm/Blueprint.js.map +1 -0
  26. package/dist/esm/RendererRegistry.js +35 -0
  27. package/dist/esm/RendererRegistry.js.map +1 -0
  28. package/dist/esm/catalog.js +430 -0
  29. package/dist/esm/catalog.js.map +1 -0
  30. package/dist/esm/coverage.js +19 -0
  31. package/dist/esm/coverage.js.map +1 -0
  32. package/dist/esm/customWidget.js +31 -0
  33. package/dist/esm/customWidget.js.map +1 -0
  34. package/dist/esm/identity.js +116 -0
  35. package/dist/esm/identity.js.map +1 -0
  36. package/dist/esm/index.js +15 -0
  37. package/dist/esm/index.js.map +1 -0
  38. package/dist/esm/irVersion.js +150 -0
  39. package/dist/esm/irVersion.js.map +1 -0
  40. package/dist/esm/layout.js +63 -0
  41. package/dist/esm/layout.js.map +1 -0
  42. package/dist/esm/normalize.js +115 -0
  43. package/dist/esm/normalize.js.map +1 -0
  44. package/dist/esm/pins.js +82 -0
  45. package/dist/esm/pins.js.map +1 -0
  46. package/dist/esm/traverseBlueprint.js +38 -0
  47. package/dist/esm/traverseBlueprint.js.map +1 -0
  48. package/dist/esm/traverseBlueprintAsync.js +48 -0
  49. package/dist/esm/traverseBlueprintAsync.js.map +1 -0
  50. package/dist/esm/types.js +2 -0
  51. package/dist/esm/types.js.map +1 -0
  52. package/dist/esm/widgetStatus.js +137 -0
  53. package/dist/esm/widgetStatus.js.map +1 -0
  54. package/dist/esm/widgets.js +105 -0
  55. package/dist/esm/widgets.js.map +1 -0
  56. package/dist/identity.d.ts +58 -0
  57. package/dist/identity.d.ts.map +1 -0
  58. package/dist/identity.js +122 -0
  59. package/dist/identity.js.map +1 -0
  60. package/dist/index.d.ts +24 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +61 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/irVersion.d.ts +23 -0
  65. package/dist/irVersion.d.ts.map +1 -0
  66. package/dist/irVersion.js +153 -0
  67. package/dist/irVersion.js.map +1 -0
  68. package/dist/layout.d.ts +57 -0
  69. package/dist/layout.d.ts.map +1 -0
  70. package/dist/layout.js +71 -0
  71. package/dist/layout.js.map +1 -0
  72. package/dist/normalize.d.ts +29 -0
  73. package/dist/normalize.d.ts.map +1 -0
  74. package/dist/normalize.js +126 -0
  75. package/dist/normalize.js.map +1 -0
  76. package/dist/pins.d.ts +27 -0
  77. package/dist/pins.d.ts.map +1 -0
  78. package/dist/pins.js +86 -0
  79. package/dist/pins.js.map +1 -0
  80. package/dist/traverseBlueprint.d.ts +27 -0
  81. package/dist/traverseBlueprint.d.ts.map +1 -0
  82. package/dist/traverseBlueprint.js +40 -0
  83. package/dist/traverseBlueprint.js.map +1 -0
  84. package/dist/traverseBlueprintAsync.d.ts +18 -0
  85. package/dist/traverseBlueprintAsync.d.ts.map +1 -0
  86. package/dist/traverseBlueprintAsync.js +51 -0
  87. package/dist/traverseBlueprintAsync.js.map +1 -0
  88. package/dist/types.d.ts +176 -0
  89. package/dist/types.d.ts.map +1 -0
  90. package/dist/types.js +3 -0
  91. package/dist/types.js.map +1 -0
  92. package/dist/widgetStatus.d.ts +66 -0
  93. package/dist/widgetStatus.d.ts.map +1 -0
  94. package/dist/widgetStatus.js +141 -0
  95. package/dist/widgetStatus.js.map +1 -0
  96. package/dist/widgets.d.ts +101 -0
  97. package/dist/widgets.d.ts.map +1 -0
  98. package/dist/widgets.js +115 -0
  99. package/dist/widgets.js.map +1 -0
  100. package/package.json +60 -3
  101. package/src/Blueprint.ts +28 -0
  102. package/src/RendererRegistry.ts +38 -0
  103. package/src/catalog.ts +638 -0
  104. package/src/coverage.ts +52 -0
  105. package/src/customWidget.ts +42 -0
  106. package/src/identity.ts +178 -0
  107. package/src/index.ts +84 -0
  108. package/src/irVersion.ts +195 -0
  109. package/src/layout.ts +105 -0
  110. package/src/normalize.ts +130 -0
  111. package/src/pins.ts +84 -0
  112. package/src/traverseBlueprint.bench.ts +47 -0
  113. package/src/traverseBlueprint.ts +94 -0
  114. package/src/traverseBlueprintAsync.ts +92 -0
  115. package/src/types.ts +195 -0
  116. package/src/widgetStatus.ts +231 -0
  117. package/src/widgets.ts +213 -0
@@ -0,0 +1,231 @@
1
+ import type { BindingStatus, Widget } from "./types";
2
+
3
+ // ── The canonical binding-status matrix, as one pure decision ─────────────────
4
+ //
5
+ // redacted > error > empty > contract > stale > pending/loading > ready (IR 2.0.0; `empty` since 2.3.0,
6
+ // ADR 0100). Every first-party renderer (React, terminal, DOM) used to carry its own copy of
7
+ // this ordering, its `degradedFrom` chain and its tolerance for a malformed `source`. They now
8
+ // DRAW from this descriptor and own only their output format (ADR 0127). The function reads
9
+ // nothing but its arguments and never throws on a malformed widget: a `source.degradedFrom` that
10
+ // is not an array of objects yields `{ tag: "invalid" }`, which every renderer draws as its
11
+ // error placeholder.
12
+
13
+ /** A caller-supplied value-contract check — the slot the matrix leaves between `empty` and `stale`. */
14
+ export type WidgetValueCheck = (
15
+ widget: Widget,
16
+ value: unknown
17
+ ) => { code: string; message: string } | undefined;
18
+
19
+ /** What a widget with a declared status channel is showing. */
20
+ export type WidgetShownState =
21
+ | "redacted"
22
+ | "error"
23
+ | "empty"
24
+ | "stale"
25
+ | "pending"
26
+ | "loading"
27
+ | "ready";
28
+
29
+ /** The decided status of one widget. `inert` = no status channel declared (IR 1.0.0 behaviour). */
30
+ export type WidgetStatusDescriptor =
31
+ | {
32
+ /** The widget (or its `source`) is malformed; draw the error placeholder, never the cause. */
33
+ readonly tag: "invalid";
34
+ readonly reason: string;
35
+ }
36
+ | {
37
+ readonly tag: "inert" | WidgetShownState;
38
+ /** The finding message (a failed contract check wins), with the matrix default applied. */
39
+ readonly message: string;
40
+ /** The raw finding message, no default: what a `stale` marker carries as its tooltip. */
41
+ readonly finding: string | undefined;
42
+ /** Whether a value is present beside the status. */
43
+ readonly hasValue: boolean;
44
+ /** Whether the widget's own body is drawn (inert, ready, pending, or stale with a value). */
45
+ readonly showBody: boolean;
46
+ /** `type@version › …`, outermost first; undefined when the widget is not degraded. */
47
+ readonly degradedChain: string | undefined;
48
+ };
49
+
50
+ const DEFAULT_MESSAGE: Record<WidgetShownState, string> = {
51
+ redacted: "redacted",
52
+ error: "error",
53
+ empty: "no data",
54
+ stale: "stale",
55
+ pending: "",
56
+ loading: "",
57
+ ready: "",
58
+ };
59
+
60
+ const INVALID = Symbol("invalid");
61
+
62
+ const degradedChainOf = (widget: Widget): string | undefined | typeof INVALID => {
63
+ const degraded: unknown = widget.source?.degradedFrom;
64
+ if (degraded === undefined) return undefined;
65
+ if (!Array.isArray(degraded)) return INVALID;
66
+ const parts: string[] = [];
67
+ for (const d of degraded as unknown[]) {
68
+ if (typeof d !== "object" || d === null) return INVALID;
69
+ const { type, version } = d as { type: unknown; version: unknown };
70
+ parts.push(`${String(type)}@${String(version)}`);
71
+ }
72
+ return parts.join(" › ") || undefined;
73
+ };
74
+
75
+ /**
76
+ * Decide what a widget shows. `check` is the optional caller value-contract check; it runs only
77
+ * for a widget whose binding declares a status channel, and only when there is a subject to check
78
+ * (not under `error` or `empty`). A finding outranks `stale`, `pending` and `ready`.
79
+ */
80
+ export function describeWidgetStatus(
81
+ widget: Widget,
82
+ value: unknown,
83
+ status: BindingStatus | undefined,
84
+ check?: WidgetValueCheck
85
+ ): WidgetStatusDescriptor {
86
+ // IR 2.5.0: a redacted widget outranks everything — it is decided before `value`, `status`,
87
+ // `check`, `binding`, `title` or a malformed `source.degradedFrom` is looked at, so none of them
88
+ // can reach a renderer's output. The message is the constant, never data from the widget.
89
+ if ((widget.source as { redacted?: unknown } | undefined)?.redacted === true) {
90
+ return {
91
+ tag: "redacted",
92
+ message: DEFAULT_MESSAGE.redacted,
93
+ finding: undefined,
94
+ hasValue: false,
95
+ showBody: false,
96
+ degradedChain: undefined,
97
+ };
98
+ }
99
+ const chain = degradedChainOf(widget);
100
+ if (chain === INVALID)
101
+ return { tag: "invalid", reason: "source.degradedFrom is not an array of objects" };
102
+ const hasValue = value !== undefined;
103
+ const base = { hasValue, degradedChain: chain };
104
+ if (widget.binding?.status === undefined) {
105
+ return { tag: "inert", message: "", finding: undefined, showBody: true, ...base };
106
+ }
107
+ const noValue = status?.state === "error" || status?.state === "empty";
108
+ const contract = noValue ? undefined : check?.(widget, value);
109
+ const finding = contract ? contract.message : status?.finding?.message;
110
+ let tag: WidgetShownState;
111
+ if (status?.state === "error") tag = "error";
112
+ else if (status?.state === "empty") tag = "empty";
113
+ else if (contract) tag = "error";
114
+ else if (status?.state === "stale") tag = "stale";
115
+ // `status` undefined (not yet written, or malformed) is treated like `ready`: a value that
116
+ // already exists is shown rather than hidden behind a spinner.
117
+ else if (!hasValue) tag = "loading";
118
+ else tag = status?.state === "pending" ? "pending" : "ready";
119
+ return {
120
+ tag,
121
+ message: finding ?? DEFAULT_MESSAGE[tag],
122
+ finding,
123
+ showBody: tag === "ready" || tag === "pending" || (tag === "stale" && hasValue),
124
+ ...base,
125
+ };
126
+ }
127
+
128
+ // ── What every surface says about a decided status: the frame facts ───────────
129
+ //
130
+ // `describeWidgetStatus` decides WHICH state wins; this names WHAT is shown for it — the
131
+ // `data-binding-state` value, the marker's name / ARIA role / glyph / line, the `data-source-*`
132
+ // attributes and the degraded-from tooltip. A renderer draws these in its own medium (DOM
133
+ // elements, React elements, text) and adds nothing of its own to the vocabulary. Pure; reads only
134
+ // its arguments. Consumers: `@loomwork/plugin-renderer-react`, `-terminal`, `-dom` (ADR 0127).
135
+
136
+ /** The marker one status draws. `text` is the whole line for the markers that carry one. */
137
+ export interface WidgetStatusMarker {
138
+ /** `data-binding-marker`. */
139
+ readonly name: Exclude<WidgetShownState, "ready">;
140
+ /** `alert` for error; `status` for empty and redacted (a polite live region); otherwise none. */
141
+ readonly role: "alert" | "status" | undefined;
142
+ readonly glyph: string;
143
+ /** `glyph title: message` (error, empty), `… — no value` (stale, no value); else undefined. */
144
+ readonly text: string | undefined;
145
+ /** A stale marker beside a drawn value is glyph-only and carries the finding as its tooltip. */
146
+ readonly tooltip: string | undefined;
147
+ /** Block-level (a row) vs inline (a glyph beside the body). */
148
+ readonly block: boolean;
149
+ }
150
+
151
+ export interface WidgetStatusFrame {
152
+ /** `data-binding-state`; undefined for an inert widget. */
153
+ readonly state: WidgetShownState | undefined;
154
+ readonly sourceType: string | undefined;
155
+ readonly sourceVersion: string | undefined;
156
+ /** `data-degraded`: the chain, outermost first. */
157
+ readonly degraded: string | undefined;
158
+ /** The `title` attribute of a degraded widget. */
159
+ readonly tooltip: string | undefined;
160
+ /** The marker to draw before the body, or undefined (inert, ready). */
161
+ readonly marker: WidgetStatusMarker | undefined;
162
+ }
163
+
164
+ /**
165
+ * The surface-independent facts of a decided status. A redacted widget yields the constant marker
166
+ * and no source/degraded facts (nothing of the widget may reach the output).
167
+ */
168
+ export function describeWidgetFrame(
169
+ widget: Widget,
170
+ d: Exclude<WidgetStatusDescriptor, { tag: "invalid" }>
171
+ ): WidgetStatusFrame {
172
+ if (d.tag === "redacted") {
173
+ return {
174
+ state: "redacted",
175
+ sourceType: undefined,
176
+ sourceVersion: undefined,
177
+ degraded: undefined,
178
+ tooltip: undefined,
179
+ marker: {
180
+ name: "redacted",
181
+ role: "status",
182
+ glyph: "⊘",
183
+ text: `⊘ ${d.message}`,
184
+ tooltip: undefined,
185
+ block: true,
186
+ },
187
+ };
188
+ }
189
+ const label = widget.title ?? widget.kind;
190
+ const degraded = d.degradedChain;
191
+ let marker: WidgetStatusMarker | undefined;
192
+ const row = (
193
+ name: WidgetStatusMarker["name"],
194
+ role: WidgetStatusMarker["role"],
195
+ glyph: string,
196
+ text: string | undefined,
197
+ block: boolean,
198
+ tooltip?: string
199
+ ): WidgetStatusMarker => ({ name, role, glyph, text, tooltip, block });
200
+ switch (d.tag) {
201
+ case "error":
202
+ marker = row("error", "alert", "✗", `✗ ${label}: ${d.message}`, true);
203
+ break;
204
+ case "empty":
205
+ marker = row("empty", "status", "○", `○ ${label}: ${d.message}`, true);
206
+ break;
207
+ case "stale":
208
+ marker = d.hasValue
209
+ ? row("stale", undefined, "~", undefined, false, d.finding)
210
+ : row("stale", undefined, "~", `~ ${label}: ${d.message} — no value`, true);
211
+ break;
212
+ case "pending":
213
+ marker = row("pending", undefined, "⋯", undefined, false);
214
+ break;
215
+ case "loading":
216
+ marker = row("loading", undefined, "…", undefined, true);
217
+ break;
218
+ default:
219
+ marker = undefined;
220
+ }
221
+ return {
222
+ state: d.tag === "inert" ? undefined : d.tag,
223
+ sourceType: widget.source?.type,
224
+ sourceVersion: widget.source?.version,
225
+ degraded,
226
+ tooltip: degraded
227
+ ? `${widget.source?.type}@${widget.source?.version} shown as ${widget.kind} (degraded from ${degraded})`
228
+ : undefined,
229
+ marker,
230
+ };
231
+ }
package/src/widgets.ts ADDED
@@ -0,0 +1,213 @@
1
+ import type { DataBinding, Widget } from "./types";
2
+
3
+ // A derived id, used when the caller supplies none. It is an IDENTITY, not a
4
+ // content address: a 32-bit non-cryptographic hash, scoped to one blueprint.
5
+ // Never treat it as an audit anchor for "which view was this" — use a real digest
6
+ // (`contentAddress` in @loomwork/plugin-content) for that.
7
+ //
8
+ // Because it is derived from the props, it CHANGES when any prop changes. That is
9
+ // correct for a view that is authored once and wrong for one a composer revises,
10
+ // which is why every builder accepts an explicit `id`.
11
+ function stableId(kind: string, props: object): string {
12
+ const str = kind + JSON.stringify(props, Object.keys(props as Record<string, unknown>).sort());
13
+ let hash = 0;
14
+ for (let i = 0; i < str.length; i++) {
15
+ hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0;
16
+ }
17
+ return `${kind}-${(hash >>> 0).toString(16)}`;
18
+ }
19
+
20
+ export interface MetricCardProps {
21
+ title: string;
22
+ binding: DataBinding;
23
+ unit?: string;
24
+ format?: "number" | "percent" | "bytes";
25
+ /**
26
+ * Stable node id. Supply it when a composer will REVISE this view: the derived
27
+ * id is a hash of kind + props, so editing any prop re-identifies the node —
28
+ * remounting it, and breaking anything keyed on it (a pin, an annotation, an
29
+ * agent's reference to it).
30
+ */
31
+ id?: string;
32
+ }
33
+
34
+ export function MetricCard(props: MetricCardProps): Widget {
35
+ const { title, id, ...rest } = props;
36
+ return {
37
+ _tag: "Widget",
38
+ id: id ?? stableId("MetricCard", props),
39
+ kind: "MetricCard",
40
+ title,
41
+ props: rest,
42
+ binding: props.binding,
43
+ };
44
+ }
45
+
46
+ export interface TimelineProps {
47
+ title?: string;
48
+ binding: DataBinding;
49
+ maxItems?: number;
50
+ /**
51
+ * Stable node id. Supply it when a composer will REVISE this view: the derived
52
+ * id is a hash of kind + props, so editing any prop re-identifies the node —
53
+ * remounting it, and breaking anything keyed on it (a pin, an annotation, an
54
+ * agent's reference to it).
55
+ */
56
+ id?: string;
57
+ }
58
+
59
+ export function Timeline(props: TimelineProps): Widget {
60
+ const { title, id, ...rest } = props;
61
+ return {
62
+ _tag: "Widget",
63
+ id: id ?? stableId("Timeline", props),
64
+ kind: "Timeline",
65
+ ...(title !== undefined && { title }),
66
+ props: rest,
67
+ binding: props.binding,
68
+ };
69
+ }
70
+
71
+ export interface ApprovalCardProps {
72
+ title: string;
73
+ binding: DataBinding;
74
+ approveLabel?: string;
75
+ rejectLabel?: string;
76
+ /**
77
+ * Stable node id. Supply it when a composer will REVISE this view: the derived
78
+ * id is a hash of kind + props, so editing any prop re-identifies the node —
79
+ * remounting it, and breaking anything keyed on it (a pin, an annotation, an
80
+ * agent's reference to it).
81
+ */
82
+ id?: string;
83
+ }
84
+
85
+ export function ApprovalCard(props: ApprovalCardProps): Widget {
86
+ const { title, id, ...rest } = props;
87
+ return {
88
+ _tag: "Widget",
89
+ id: id ?? stableId("ApprovalCard", props),
90
+ kind: "ApprovalCard",
91
+ title,
92
+ props: rest,
93
+ binding: props.binding,
94
+ };
95
+ }
96
+
97
+ export interface StatusBadgeProps {
98
+ title?: string;
99
+ binding: DataBinding;
100
+ /**
101
+ * Stable node id. Supply it when a composer will REVISE this view: the derived
102
+ * id is a hash of kind + props, so editing any prop re-identifies the node —
103
+ * remounting it, and breaking anything keyed on it (a pin, an annotation, an
104
+ * agent's reference to it).
105
+ */
106
+ id?: string;
107
+ }
108
+
109
+ export function StatusBadge(props: StatusBadgeProps): Widget {
110
+ const { title, id, ...rest } = props;
111
+ return {
112
+ _tag: "Widget",
113
+ id: id ?? stableId("StatusBadge", props),
114
+ kind: "StatusBadge",
115
+ ...(title !== undefined && { title }),
116
+ props: rest,
117
+ binding: props.binding,
118
+ };
119
+ }
120
+
121
+ export interface ProgressBarProps {
122
+ title?: string;
123
+ binding: DataBinding;
124
+ max?: number;
125
+ /**
126
+ * Stable node id. Supply it when a composer will REVISE this view: the derived
127
+ * id is a hash of kind + props, so editing any prop re-identifies the node —
128
+ * remounting it, and breaking anything keyed on it (a pin, an annotation, an
129
+ * agent's reference to it).
130
+ */
131
+ id?: string;
132
+ }
133
+
134
+ export function ProgressBar(props: ProgressBarProps): Widget {
135
+ const { title, id, ...rest } = props;
136
+ return {
137
+ _tag: "Widget",
138
+ id: id ?? stableId("ProgressBar", props),
139
+ kind: "ProgressBar",
140
+ ...(title !== undefined && { title }),
141
+ props: rest,
142
+ binding: props.binding,
143
+ };
144
+ }
145
+
146
+ export interface DataTableProps {
147
+ title?: string;
148
+ binding: DataBinding;
149
+ columns?: string[];
150
+ /**
151
+ * Stable node id. Supply it when a composer will REVISE this view: the derived
152
+ * id is a hash of kind + props, so editing any prop re-identifies the node —
153
+ * remounting it, and breaking anything keyed on it (a pin, an annotation, an
154
+ * agent's reference to it).
155
+ */
156
+ id?: string;
157
+ }
158
+
159
+ export function DataTable(props: DataTableProps): Widget {
160
+ const { title, id, ...rest } = props;
161
+ return {
162
+ _tag: "Widget",
163
+ id: id ?? stableId("DataTable", props),
164
+ kind: "DataTable",
165
+ ...(title !== undefined && { title }),
166
+ props: rest,
167
+ binding: props.binding,
168
+ };
169
+ }
170
+
171
+ export interface AlertCardProps {
172
+ title?: string;
173
+ binding: DataBinding;
174
+ severity?: "info" | "warning" | "error" | "critical";
175
+ /**
176
+ * Stable node id. Supply it when a composer will REVISE this view: the derived
177
+ * id is a hash of kind + props, so editing any prop re-identifies the node —
178
+ * remounting it, and breaking anything keyed on it (a pin, an annotation, an
179
+ * agent's reference to it).
180
+ */
181
+ id?: string;
182
+ }
183
+
184
+ export function AlertCard(props: AlertCardProps): Widget {
185
+ const { title, id, ...rest } = props;
186
+ return {
187
+ _tag: "Widget",
188
+ id: id ?? stableId("AlertCard", props),
189
+ kind: "AlertCard",
190
+ ...(title !== undefined && { title }),
191
+ props: rest,
192
+ binding: props.binding,
193
+ };
194
+ }
195
+
196
+ export interface StreamingTextProps {
197
+ title?: string;
198
+ binding: DataBinding;
199
+ placeholder?: string;
200
+ id?: string;
201
+ }
202
+
203
+ export function StreamingText(props: StreamingTextProps): Widget {
204
+ const { title, id, ...rest } = props;
205
+ return {
206
+ _tag: "Widget",
207
+ id: id ?? stableId("StreamingText", props),
208
+ kind: "StreamingText",
209
+ ...(title !== undefined && { title }),
210
+ props: rest,
211
+ binding: props.binding,
212
+ };
213
+ }