@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.
- package/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +83 -2
- package/dist/Blueprint.d.ts +14 -0
- package/dist/Blueprint.d.ts.map +1 -0
- package/dist/Blueprint.js +18 -0
- package/dist/Blueprint.js.map +1 -0
- package/dist/RendererRegistry.d.ts +9 -0
- package/dist/RendererRegistry.d.ts.map +1 -0
- package/dist/RendererRegistry.js +39 -0
- package/dist/RendererRegistry.js.map +1 -0
- package/dist/catalog.d.ts +129 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +435 -0
- package/dist/catalog.js.map +1 -0
- package/dist/coverage.d.ts +18 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +22 -0
- package/dist/coverage.js.map +1 -0
- package/dist/customWidget.d.ts +10 -0
- package/dist/customWidget.d.ts.map +1 -0
- package/dist/customWidget.js +34 -0
- package/dist/customWidget.js.map +1 -0
- package/dist/esm/Blueprint.js +15 -0
- package/dist/esm/Blueprint.js.map +1 -0
- package/dist/esm/RendererRegistry.js +35 -0
- package/dist/esm/RendererRegistry.js.map +1 -0
- package/dist/esm/catalog.js +430 -0
- package/dist/esm/catalog.js.map +1 -0
- package/dist/esm/coverage.js +19 -0
- package/dist/esm/coverage.js.map +1 -0
- package/dist/esm/customWidget.js +31 -0
- package/dist/esm/customWidget.js.map +1 -0
- package/dist/esm/identity.js +116 -0
- package/dist/esm/identity.js.map +1 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/irVersion.js +150 -0
- package/dist/esm/irVersion.js.map +1 -0
- package/dist/esm/layout.js +63 -0
- package/dist/esm/layout.js.map +1 -0
- package/dist/esm/normalize.js +115 -0
- package/dist/esm/normalize.js.map +1 -0
- package/dist/esm/pins.js +82 -0
- package/dist/esm/pins.js.map +1 -0
- package/dist/esm/traverseBlueprint.js +38 -0
- package/dist/esm/traverseBlueprint.js.map +1 -0
- package/dist/esm/traverseBlueprintAsync.js +48 -0
- package/dist/esm/traverseBlueprintAsync.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/widgetStatus.js +137 -0
- package/dist/esm/widgetStatus.js.map +1 -0
- package/dist/esm/widgets.js +105 -0
- package/dist/esm/widgets.js.map +1 -0
- package/dist/identity.d.ts +58 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +122 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/irVersion.d.ts +23 -0
- package/dist/irVersion.d.ts.map +1 -0
- package/dist/irVersion.js +153 -0
- package/dist/irVersion.js.map +1 -0
- package/dist/layout.d.ts +57 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +71 -0
- package/dist/layout.js.map +1 -0
- package/dist/normalize.d.ts +29 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +126 -0
- package/dist/normalize.js.map +1 -0
- package/dist/pins.d.ts +27 -0
- package/dist/pins.d.ts.map +1 -0
- package/dist/pins.js +86 -0
- package/dist/pins.js.map +1 -0
- package/dist/traverseBlueprint.d.ts +27 -0
- package/dist/traverseBlueprint.d.ts.map +1 -0
- package/dist/traverseBlueprint.js +40 -0
- package/dist/traverseBlueprint.js.map +1 -0
- package/dist/traverseBlueprintAsync.d.ts +18 -0
- package/dist/traverseBlueprintAsync.d.ts.map +1 -0
- package/dist/traverseBlueprintAsync.js +51 -0
- package/dist/traverseBlueprintAsync.js.map +1 -0
- package/dist/types.d.ts +176 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/widgetStatus.d.ts +66 -0
- package/dist/widgetStatus.d.ts.map +1 -0
- package/dist/widgetStatus.js +141 -0
- package/dist/widgetStatus.js.map +1 -0
- package/dist/widgets.d.ts +101 -0
- package/dist/widgets.d.ts.map +1 -0
- package/dist/widgets.js +115 -0
- package/dist/widgets.js.map +1 -0
- package/package.json +60 -3
- package/src/Blueprint.ts +28 -0
- package/src/RendererRegistry.ts +38 -0
- package/src/catalog.ts +638 -0
- package/src/coverage.ts +52 -0
- package/src/customWidget.ts +42 -0
- package/src/identity.ts +178 -0
- package/src/index.ts +84 -0
- package/src/irVersion.ts +195 -0
- package/src/layout.ts +105 -0
- package/src/normalize.ts +130 -0
- package/src/pins.ts +84 -0
- package/src/traverseBlueprint.bench.ts +47 -0
- package/src/traverseBlueprint.ts +94 -0
- package/src/traverseBlueprintAsync.ts +92 -0
- package/src/types.ts +195 -0
- package/src/widgetStatus.ts +231 -0
- package/src/widgets.ts +213 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { hasLayoutId, isLayoutRole } from "./layout";
|
|
2
|
+
import { IR_VERSIONS_READ_AS_IS, IR_VERSION_LEGACY, asCell, isWidget } from "./normalize";
|
|
3
|
+
/** Identity helper — declare a catalogue with inferred types. */
|
|
4
|
+
export function defineWidgetCatalog(catalog) {
|
|
5
|
+
return catalog;
|
|
6
|
+
}
|
|
7
|
+
export const BUILTIN_CATALOG_ID = "loom.builtin.v1";
|
|
8
|
+
/**
|
|
9
|
+
* The eight widgets `@loomwork/blueprint-runtime` ships, as a catalogue.
|
|
10
|
+
*
|
|
11
|
+
* `props` mirrors each builder's props interface minus `title` (hoisted onto
|
|
12
|
+
* `Widget.title`) and minus `binding` (reserved). Every built-in requires a
|
|
13
|
+
* binding — none of them render anything without one.
|
|
14
|
+
*/
|
|
15
|
+
export const builtinCatalog = {
|
|
16
|
+
id: BUILTIN_CATALOG_ID,
|
|
17
|
+
entries: [
|
|
18
|
+
{ kind: "MetricCard", props: ["unit", "format"], binding: "required" },
|
|
19
|
+
{ kind: "Timeline", props: ["maxItems"], binding: "required" },
|
|
20
|
+
{ kind: "ApprovalCard", props: ["approveLabel", "rejectLabel"], binding: "required" },
|
|
21
|
+
{ kind: "StatusBadge", props: [], binding: "required" },
|
|
22
|
+
{ kind: "ProgressBar", props: ["max"], binding: "required" },
|
|
23
|
+
{ kind: "DataTable", props: ["columns"], binding: "required" },
|
|
24
|
+
{ kind: "AlertCard", props: ["severity"], binding: "required" },
|
|
25
|
+
{ kind: "StreamingText", props: ["placeholder"], binding: "required" },
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
/** Duplicated into `props` by every built-in builder; never a payload. */
|
|
29
|
+
const RESERVED_PROPS = new Set(["binding"]);
|
|
30
|
+
const KNOWN_IR_VERSIONS = new Set([IR_VERSION_LEGACY, ...IR_VERSIONS_READ_AS_IS]);
|
|
31
|
+
const DATA_CLASSES = new Set(["secret", "sensitive", "internal"]);
|
|
32
|
+
const DOTTED = /^[^.\s]+(\.[^.\s]+)*$/;
|
|
33
|
+
/**
|
|
34
|
+
* A blueprint is DATA a model may have composed, so the walk cannot assume its own types hold.
|
|
35
|
+
* A `Layout` here is structural: an object with a `type` and an array of `children`. Anything
|
|
36
|
+
* else reaching the stack — `null`, a string, a `GridCell` stranded in a flex/stack — is a
|
|
37
|
+
* `malformed-node` finding, never a property access on a non-object.
|
|
38
|
+
*/
|
|
39
|
+
const isLayoutNode = (n) => typeof n === "object" &&
|
|
40
|
+
n !== null &&
|
|
41
|
+
"type" in n &&
|
|
42
|
+
Array.isArray(n.children);
|
|
43
|
+
/**
|
|
44
|
+
* Iterative walk — an explicit stack, so a deep tree cannot blow the call stack.
|
|
45
|
+
* Visits widgets (pre-order) and grid cells. A grid's cells are visited in
|
|
46
|
+
* forward sibling order (so findings on them read in document order) before
|
|
47
|
+
* their nodes are pushed onto the stack in reverse, which keeps the overall
|
|
48
|
+
* widget walk pre-order.
|
|
49
|
+
*
|
|
50
|
+
* The loop is length-driven rather than `pop() !== undefined`: a literal `undefined` child is
|
|
51
|
+
* one of the malformed shapes this walk must REPORT, and a sentinel loop would silently end the
|
|
52
|
+
* walk on it instead.
|
|
53
|
+
*/
|
|
54
|
+
function eachNode(root, visit) {
|
|
55
|
+
const stack = [root];
|
|
56
|
+
while (stack.length > 0) {
|
|
57
|
+
const node = stack.pop();
|
|
58
|
+
if (isWidget(node)) {
|
|
59
|
+
visit({ widget: node });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!isLayoutNode(node)) {
|
|
63
|
+
visit({ malformed: { node } });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// `undefined` is absent (it does not survive serialisation, and every builder omits it).
|
|
67
|
+
// A malformed id is ABSENT for every other purpose: it names nothing and collides with nothing.
|
|
68
|
+
const id = node.id;
|
|
69
|
+
const nodeId = hasLayoutId(node) ? node.id : "<layout>";
|
|
70
|
+
if (hasLayoutId(node))
|
|
71
|
+
visit({ layoutId: { id: node.id } });
|
|
72
|
+
else if (id !== undefined)
|
|
73
|
+
visit({ layoutIdInvalid: { id } });
|
|
74
|
+
const role = node.role;
|
|
75
|
+
if (role !== undefined && !isLayoutRole(role))
|
|
76
|
+
visit({ roleInvalid: { role, nodeId } });
|
|
77
|
+
if (node.type === "grid") {
|
|
78
|
+
const children = node.children;
|
|
79
|
+
const columns = node.columns;
|
|
80
|
+
// A grid with no usable column count cannot place ANY cell, so every span would be
|
|
81
|
+
// "out of range" against it. Report the cause once and skip the cascade.
|
|
82
|
+
const columnsOk = Number.isInteger(columns) && columns >= 1;
|
|
83
|
+
if (!columnsOk) {
|
|
84
|
+
visit({ columnsInvalid: { columns, nodeId } });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
for (let i = 0; i < children.length; i++) {
|
|
88
|
+
const c = asCell(children[i]);
|
|
89
|
+
visit({
|
|
90
|
+
cell: {
|
|
91
|
+
span: c.span ?? 1,
|
|
92
|
+
columns: columns,
|
|
93
|
+
cellNodeId: isWidget(c.node)
|
|
94
|
+
? c.node.id
|
|
95
|
+
: hasLayoutId(c.node)
|
|
96
|
+
? c.node.id
|
|
97
|
+
: undefined,
|
|
98
|
+
row: c.row,
|
|
99
|
+
rowSpan: c.rowSpan,
|
|
100
|
+
column: c.column,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
106
|
+
stack.push(asCell(children[i]).node);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const children = node.children;
|
|
111
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
112
|
+
stack.push(children[i]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** A bounded description of a malformed role or id: a model-composed value can be any size. */
|
|
118
|
+
function describeValue(value) {
|
|
119
|
+
if (typeof value !== "string") {
|
|
120
|
+
if (value === null)
|
|
121
|
+
return "null";
|
|
122
|
+
return Array.isArray(value) ? "(an array)" : `(a ${typeof value})`;
|
|
123
|
+
}
|
|
124
|
+
return value.length > 64
|
|
125
|
+
? `${JSON.stringify(value.slice(0, 64))}… (${value.length} chars)`
|
|
126
|
+
: JSON.stringify(value);
|
|
127
|
+
}
|
|
128
|
+
/** A short, safe description of a malformed node for a finding message. */
|
|
129
|
+
function describeNode(node) {
|
|
130
|
+
if (node === null)
|
|
131
|
+
return "null";
|
|
132
|
+
if (Array.isArray(node))
|
|
133
|
+
return "an array";
|
|
134
|
+
const t = typeof node;
|
|
135
|
+
if (t !== "object")
|
|
136
|
+
return `a ${t}`;
|
|
137
|
+
if ("node" in node)
|
|
138
|
+
return "a GridCell outside a grid";
|
|
139
|
+
return "an object with no layout `type` or `children`";
|
|
140
|
+
}
|
|
141
|
+
const isPositiveInt = (v) => Number.isInteger(v) && v >= 1;
|
|
142
|
+
const show = (v) => JSON.stringify(v) ?? String(v);
|
|
143
|
+
/**
|
|
144
|
+
* Why a cell's placement cannot be laid out — empty when it can. `spanOk` gates the overflow
|
|
145
|
+
* test so an invalid span is reported once (as `span-out-of-range`), not again as an overflow.
|
|
146
|
+
*/
|
|
147
|
+
function placementProblems(c) {
|
|
148
|
+
const out = [];
|
|
149
|
+
if (c.row !== undefined && !isPositiveInt(c.row))
|
|
150
|
+
out.push(`row ${show(c.row)} is not a positive integer`);
|
|
151
|
+
if (c.rowSpan !== undefined && !isPositiveInt(c.rowSpan))
|
|
152
|
+
out.push(`rowSpan ${show(c.rowSpan)} is not a positive integer`);
|
|
153
|
+
if (c.column !== undefined) {
|
|
154
|
+
if (!isPositiveInt(c.column) || c.column > c.columns)
|
|
155
|
+
out.push(`column ${show(c.column)} is outside 1..${c.columns}`);
|
|
156
|
+
else if (c.spanOk && c.column + c.span - 1 > c.columns)
|
|
157
|
+
out.push(`column ${c.column} + span ${c.span} overflows the ${c.columns}-column grid`);
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Check a blueprint against a catalogue. Never throws; returns every finding it
|
|
163
|
+
* can see in one pass, so a caller degrades or records a gap on the whole report
|
|
164
|
+
* rather than on the first failure.
|
|
165
|
+
*/
|
|
166
|
+
export function validateBlueprint(blueprint, catalog) {
|
|
167
|
+
const entries = new Map();
|
|
168
|
+
for (const entry of catalog.entries)
|
|
169
|
+
entries.set(entry.kind, entry);
|
|
170
|
+
const projections = new Set(blueprint.projections);
|
|
171
|
+
const findings = [];
|
|
172
|
+
// ONE namespace: widget ids and layout ids (IR 2.4.0) are both node identity.
|
|
173
|
+
// The value records which kind of node claimed the id first, so a widget↔widget collision keeps
|
|
174
|
+
// its message and a collision involving a layout says so.
|
|
175
|
+
const seenIds = new Map();
|
|
176
|
+
let widgets = 0;
|
|
177
|
+
if (blueprint.irVersion !== undefined && !KNOWN_IR_VERSIONS.has(blueprint.irVersion)) {
|
|
178
|
+
findings.push({
|
|
179
|
+
code: "ir-version-unknown",
|
|
180
|
+
severity: "error",
|
|
181
|
+
nodeId: "<layout>",
|
|
182
|
+
kind: "<layout>",
|
|
183
|
+
message: `irVersion "${blueprint.irVersion}" is not a version this module recognizes — a reader may not know how to interpret the artifact`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
eachNode(blueprint.layout, (v) => {
|
|
187
|
+
if (v.layoutId) {
|
|
188
|
+
const { id } = v.layoutId;
|
|
189
|
+
if (seenIds.has(id)) {
|
|
190
|
+
findings.push({
|
|
191
|
+
code: "duplicate-node-id",
|
|
192
|
+
severity: "error",
|
|
193
|
+
nodeId: id,
|
|
194
|
+
kind: "<layout>",
|
|
195
|
+
message: `a layout shares the id "${id}" with an earlier node — widget and layout ids are one namespace, so node identity and the renderer's key collide`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (!seenIds.has(id))
|
|
199
|
+
seenIds.set(id, "layout");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (v.layoutIdInvalid) {
|
|
203
|
+
findings.push({
|
|
204
|
+
code: "layout-id-invalid",
|
|
205
|
+
severity: "warn",
|
|
206
|
+
nodeId: "<layout>",
|
|
207
|
+
kind: "<layout>",
|
|
208
|
+
message: `layout id ${describeValue(v.layoutIdInvalid.id)} is not a non-empty string — renderers treat it as absent and key this container by position, so its state is not kept across a recompose`,
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (v.columnsInvalid) {
|
|
213
|
+
findings.push({
|
|
214
|
+
code: "columns-invalid",
|
|
215
|
+
severity: "error",
|
|
216
|
+
nodeId: v.columnsInvalid.nodeId,
|
|
217
|
+
kind: "<layout>",
|
|
218
|
+
message: `grid columns ${JSON.stringify(v.columnsInvalid.columns) ?? "undefined"} is not a positive integer — there is no column count to place cells into, so this grid's spans are not checked`,
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (v.roleInvalid) {
|
|
223
|
+
findings.push({
|
|
224
|
+
code: "role-invalid",
|
|
225
|
+
severity: "warn",
|
|
226
|
+
nodeId: v.roleInvalid.nodeId,
|
|
227
|
+
kind: "<layout>",
|
|
228
|
+
message: `layout role ${describeValue(v.roleInvalid.role)} is not a well-formed role (a lowercase letter, then lowercase letters, digits or hyphens, at most 64 characters) — renderers draw the plain container`,
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (v.malformed) {
|
|
233
|
+
findings.push({
|
|
234
|
+
code: "malformed-node",
|
|
235
|
+
severity: "error",
|
|
236
|
+
nodeId: "<layout>",
|
|
237
|
+
kind: "<layout>",
|
|
238
|
+
message: `a layout child is neither a Widget nor a Layout (${describeNode(v.malformed.node)}) — it renders nothing, and the subtree below it is not validated`,
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (v.cell) {
|
|
243
|
+
const { span, columns, cellNodeId, row, rowSpan, column } = v.cell;
|
|
244
|
+
const spanOk = Number.isInteger(span) && span >= 1 && span <= columns;
|
|
245
|
+
if (!spanOk) {
|
|
246
|
+
findings.push({
|
|
247
|
+
code: "span-out-of-range",
|
|
248
|
+
severity: "error",
|
|
249
|
+
nodeId: cellNodeId ?? "<layout>",
|
|
250
|
+
kind: "<layout>",
|
|
251
|
+
message: `cell span ${span} is outside 1..${columns} — a renderer cannot lay this cell out`,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const problems = placementProblems({ row, rowSpan, column, span, spanOk, columns });
|
|
255
|
+
if (problems.length > 0) {
|
|
256
|
+
findings.push({
|
|
257
|
+
code: "placement-out-of-range",
|
|
258
|
+
severity: "error",
|
|
259
|
+
nodeId: cellNodeId ?? "<layout>",
|
|
260
|
+
kind: "<layout>",
|
|
261
|
+
message: `cell placement cannot be laid out — ${problems.join("; ")}`,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const widget = v.widget;
|
|
267
|
+
if (!widget)
|
|
268
|
+
return;
|
|
269
|
+
widgets++;
|
|
270
|
+
const { id, kind } = widget;
|
|
271
|
+
const earlier = seenIds.get(id);
|
|
272
|
+
if (earlier !== undefined) {
|
|
273
|
+
findings.push({
|
|
274
|
+
code: "duplicate-node-id",
|
|
275
|
+
severity: "error",
|
|
276
|
+
nodeId: id,
|
|
277
|
+
kind,
|
|
278
|
+
message: earlier === "widget"
|
|
279
|
+
? `two widgets share the id "${id}" — node identity and the renderer's key collide, so one silently replaces the other`
|
|
280
|
+
: `a widget shares the id "${id}" with an earlier layout — widget and layout ids are one namespace, so node identity and the renderer's key collide`,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
seenIds.set(id, "widget");
|
|
285
|
+
}
|
|
286
|
+
// IR 2.5.0: `source.redacted` is `true` or absent (an explicit `undefined` is absent), and a
|
|
287
|
+
// redacted widget carries nothing it withheld: no binding, no title, no props, no degradedFrom,
|
|
288
|
+
// and the constant source type/version. Runs BEFORE the unknown-kind return, so a redacted
|
|
289
|
+
// widget of an unknown kind that still carries data is caught too.
|
|
290
|
+
{
|
|
291
|
+
const rs = widget.source;
|
|
292
|
+
if (rs !== null && typeof rs === "object" && rs.redacted !== undefined) {
|
|
293
|
+
const bad = (message) => findings.push({ code: "redacted-invalid", severity: "error", nodeId: id, kind, message });
|
|
294
|
+
if (rs.redacted !== true) {
|
|
295
|
+
bad(`widget.source.redacted must be true or absent, got ${JSON.stringify(rs.redacted)}`);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const leaks = [];
|
|
299
|
+
if (widget.binding !== undefined)
|
|
300
|
+
leaks.push("binding");
|
|
301
|
+
if (widget.title !== undefined)
|
|
302
|
+
leaks.push("title");
|
|
303
|
+
if (widget.props && Object.keys(widget.props).length > 0)
|
|
304
|
+
leaks.push("props");
|
|
305
|
+
if (rs.degradedFrom !== undefined)
|
|
306
|
+
leaks.push("source.degradedFrom");
|
|
307
|
+
if (rs.type !== "redacted" || rs.version !== "1")
|
|
308
|
+
leaks.push(`original source type/version (must be "redacted"/"1")`);
|
|
309
|
+
if (leaks.length > 0)
|
|
310
|
+
bad(`a redacted widget must carry no ${leaks.join(" and no ")} — it would leave the withheld content on the artifact`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const entry = entries.get(kind);
|
|
315
|
+
if (!entry) {
|
|
316
|
+
findings.push({
|
|
317
|
+
code: "unknown-kind",
|
|
318
|
+
severity: "error",
|
|
319
|
+
nodeId: id,
|
|
320
|
+
kind,
|
|
321
|
+
message: `"${kind}" is not in catalogue "${catalog.id}" — a composer may only assemble from the audited set`,
|
|
322
|
+
});
|
|
323
|
+
// Nothing further is checkable without an entry: the allow-list and the
|
|
324
|
+
// binding requirement both come from it. Reporting invented follow-on
|
|
325
|
+
// findings here would inflate the count for one real defect.
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (entry.binding === "required" && !widget.binding) {
|
|
329
|
+
findings.push({
|
|
330
|
+
code: "missing-binding",
|
|
331
|
+
severity: "error",
|
|
332
|
+
nodeId: id,
|
|
333
|
+
kind,
|
|
334
|
+
message: `"${kind}" renders bound data but carries no binding — it would render blank with no error`,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
if (entry.binding === "none" && widget.binding) {
|
|
338
|
+
findings.push({
|
|
339
|
+
code: "unexpected-binding",
|
|
340
|
+
severity: "warn",
|
|
341
|
+
nodeId: id,
|
|
342
|
+
kind,
|
|
343
|
+
message: `"${kind}" is declared as taking no binding, but this widget carries one`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (widget.binding && !projections.has(widget.binding.projectionId)) {
|
|
347
|
+
findings.push({
|
|
348
|
+
code: "undeclared-projection",
|
|
349
|
+
severity: "error",
|
|
350
|
+
nodeId: id,
|
|
351
|
+
kind,
|
|
352
|
+
message: `binds to projection "${widget.binding.projectionId}", which the blueprint does not declare — renderers hydrate only declared projections, so this widget resolves to undefined and renders blank`,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
const allowed = new Set(entry.props);
|
|
356
|
+
for (const prop of Object.keys(widget.props)) {
|
|
357
|
+
if (RESERVED_PROPS.has(prop) || allowed.has(prop))
|
|
358
|
+
continue;
|
|
359
|
+
findings.push({
|
|
360
|
+
code: "undeclared-prop",
|
|
361
|
+
severity: "error",
|
|
362
|
+
nodeId: id,
|
|
363
|
+
kind,
|
|
364
|
+
message: `prop "${prop}" is not declared by "${kind}" in catalogue "${catalog.id}" — a blueprint carries bindings, never fetched data, and an undeclared prop is how data gets inlined into a shared artifact`,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
// `binding` and `source` are typed, but a blueprint is data a model can compose —
|
|
368
|
+
// a malformed value (null, a string, ...) must be a finding, never a property
|
|
369
|
+
// access on a non-object, so every read below goes through a typeof guard first.
|
|
370
|
+
const status = widget.binding?.status;
|
|
371
|
+
if (status !== undefined) {
|
|
372
|
+
const path = status !== null && typeof status === "object"
|
|
373
|
+
? status.path
|
|
374
|
+
: undefined;
|
|
375
|
+
if (typeof path !== "string" || path === "" || !DOTTED.test(path)) {
|
|
376
|
+
findings.push({
|
|
377
|
+
code: "status-path-invalid",
|
|
378
|
+
severity: "error",
|
|
379
|
+
nodeId: id,
|
|
380
|
+
kind,
|
|
381
|
+
message: `binding.status.path ${JSON.stringify(path)} is not a non-empty dotted path`,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const sensitivity = widget.binding?.sensitivity;
|
|
386
|
+
if (sensitivity !== undefined && !DATA_CLASSES.has(sensitivity)) {
|
|
387
|
+
findings.push({
|
|
388
|
+
code: "sensitivity-unknown",
|
|
389
|
+
severity: "error",
|
|
390
|
+
nodeId: id,
|
|
391
|
+
kind,
|
|
392
|
+
message: `binding.sensitivity ${JSON.stringify(sensitivity)} is not a @loomwork/classify DataClass`,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
const source = widget.source;
|
|
396
|
+
if (source !== undefined) {
|
|
397
|
+
const src = source !== null && typeof source === "object"
|
|
398
|
+
? source
|
|
399
|
+
: undefined;
|
|
400
|
+
const degradedFrom = Array.isArray(src?.degradedFrom) ? src.degradedFrom : undefined;
|
|
401
|
+
// Typed as `string`, so a NON-string is exactly what a hand-assembled artifact gets wrong.
|
|
402
|
+
// A truthiness test would accept `type: 123` and `version: {}` — both render as garbage.
|
|
403
|
+
const notAName = (v) => typeof v !== "string" || v === "";
|
|
404
|
+
const invalid = src === undefined ||
|
|
405
|
+
notAName(src.type) ||
|
|
406
|
+
notAName(src.version) ||
|
|
407
|
+
(src.degradedFrom !== undefined && degradedFrom === undefined) ||
|
|
408
|
+
(degradedFrom?.some((d) => typeof d !== "object" ||
|
|
409
|
+
d === null ||
|
|
410
|
+
notAName(d.type) ||
|
|
411
|
+
notAName(d.version)) ??
|
|
412
|
+
false);
|
|
413
|
+
if (invalid) {
|
|
414
|
+
findings.push({
|
|
415
|
+
code: "source-invalid",
|
|
416
|
+
severity: "error",
|
|
417
|
+
nodeId: id,
|
|
418
|
+
kind,
|
|
419
|
+
message: `widget.source is missing a non-empty string "type" or "version" — either on the source itself or a degradedFrom entry (which must be an array)`,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
return {
|
|
425
|
+
ok: findings.every((f) => f.severity !== "error"),
|
|
426
|
+
findings,
|
|
427
|
+
widgets,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which of a catalogue's components this registry can actually render.
|
|
3
|
+
*
|
|
4
|
+
* Use it as a test in any package that ships a renderer, so adding a kind to the
|
|
5
|
+
* vocabulary without a renderer fails the build rather than the viewer.
|
|
6
|
+
*/
|
|
7
|
+
export function catalogCoverage(catalog, registry) {
|
|
8
|
+
const registered = new Set(registry.kinds());
|
|
9
|
+
const missing = [];
|
|
10
|
+
const declared = new Set();
|
|
11
|
+
for (const entry of catalog.entries) {
|
|
12
|
+
declared.add(entry.kind);
|
|
13
|
+
if (!registered.has(entry.kind))
|
|
14
|
+
missing.push(entry.kind);
|
|
15
|
+
}
|
|
16
|
+
const extra = [...registered].filter((kind) => !declared.has(kind));
|
|
17
|
+
return { ok: missing.length === 0, missing, extra };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/coverage.ts"],"names":[],"mappings":"AA6BA;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAsB,EACtB,QAAmC;IAEnC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAEpE,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function stableId(kind, props) {
|
|
2
|
+
const str = kind + JSON.stringify(props, Object.keys(props).sort());
|
|
3
|
+
let hash = 0;
|
|
4
|
+
for (let i = 0; i < str.length; i++) {
|
|
5
|
+
hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0;
|
|
6
|
+
}
|
|
7
|
+
return `${kind}-${(hash >>> 0).toString(16)}`;
|
|
8
|
+
}
|
|
9
|
+
// Builder for user-defined widget kinds that live outside the WidgetKind union.
|
|
10
|
+
// Register a matching renderer in your RendererRegistry under the same `kind` string.
|
|
11
|
+
//
|
|
12
|
+
// const HeatmapWidget = customWidget$({
|
|
13
|
+
// kind: "Heatmap",
|
|
14
|
+
// title: "CPU Heatmap",
|
|
15
|
+
// binding: { projectionId: "metrics", path: "cpu.history" },
|
|
16
|
+
// props: { buckets: 20 },
|
|
17
|
+
// });
|
|
18
|
+
// registry.register("Heatmap", HeatmapComponent);
|
|
19
|
+
export function customWidget$(config) {
|
|
20
|
+
const propsRecord = config.props ?? {};
|
|
21
|
+
return {
|
|
22
|
+
_tag: "Widget",
|
|
23
|
+
id: config.id ??
|
|
24
|
+
stableId(config.kind, { title: config.title, binding: config.binding, ...propsRecord }),
|
|
25
|
+
kind: config.kind,
|
|
26
|
+
...(config.title !== undefined && { title: config.title }),
|
|
27
|
+
props: propsRecord,
|
|
28
|
+
...(config.binding !== undefined && { binding: config.binding }),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=customWidget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customWidget.js","sourceRoot":"","sources":["../../src/customWidget.ts"],"names":[],"mappings":"AAEA,SAAS,QAAQ,CAAC,IAAY,EAAE,KAAa;IAC3C,MAAM,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/F,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AAChD,CAAC;AAUD,gFAAgF;AAChF,sFAAsF;AACtF,EAAE;AACF,0CAA0C;AAC1C,uBAAuB;AACvB,4BAA4B;AAC5B,iEAAiE;AACjE,8BAA8B;AAC9B,QAAQ;AACR,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,MAA0B;IACtD,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IACvC,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,EAAE,EACA,MAAM,CAAC,EAAE;YACT,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,WAAW,EAAE,CAAC;QACzF,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QAC1D,KAAK,EAAE,WAAW;QAClB,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;KACjE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { hasLayoutId } from "./layout";
|
|
2
|
+
import { asCell } from "./normalize";
|
|
3
|
+
/**
|
|
4
|
+
* The one total, iterative, pre-order walk behind every id list below. `onWidget` sees each
|
|
5
|
+
* widget id; `onLayout` each WELL-FORMED layout id (IR 2.4.0, ADR 0101) — an absent or malformed
|
|
6
|
+
* one is skipped, exactly as a renderer treats it as absent.
|
|
7
|
+
*/
|
|
8
|
+
function walkNodeIds(blueprint, onWidget, onLayout) {
|
|
9
|
+
// Iterative, matching `eachNode` in catalog.ts: a deep composed tree must not be able
|
|
10
|
+
// to blow the call stack.
|
|
11
|
+
//
|
|
12
|
+
// Length-driven, not `pop() !== undefined`: a blueprint is data an agent or a composer
|
|
13
|
+
// may have authored, so a layout child CAN legitimately be `undefined` (a malformed
|
|
14
|
+
// `cell(6, undefined)`, a hole from a revision) — and `stack.pop()` returns `undefined`
|
|
15
|
+
// both for "the stack is empty" and for "the next element IS `undefined`". A sentinel
|
|
16
|
+
// loop conflates the two and stops early, silently dropping every id not yet visited.
|
|
17
|
+
// Rejecting the hole is `validateBlueprint`'s job (`malformed-node`); this walk stays
|
|
18
|
+
// total and skips only the one non-object child.
|
|
19
|
+
const stack = [blueprint.layout];
|
|
20
|
+
while (stack.length > 0) {
|
|
21
|
+
const node = stack.pop();
|
|
22
|
+
if (typeof node !== "object" || node === null)
|
|
23
|
+
continue;
|
|
24
|
+
if ("_tag" in node && node._tag === "Widget") {
|
|
25
|
+
onWidget?.(node.id);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const layout = node;
|
|
29
|
+
if (!Array.isArray(layout.children))
|
|
30
|
+
continue;
|
|
31
|
+
if (onLayout !== undefined && hasLayoutId(layout))
|
|
32
|
+
onLayout(layout.id);
|
|
33
|
+
for (let i = layout.children.length - 1; i >= 0; i--) {
|
|
34
|
+
stack.push(layout.type === "grid"
|
|
35
|
+
? asCell(layout.children[i]).node
|
|
36
|
+
: layout.children[i]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Every WIDGET id in a blueprint, in pre-order. Duplicates are `validateBlueprint`'s job.
|
|
42
|
+
*
|
|
43
|
+
* Widget-only by contract, also after IR 2.4.0: callers read its output as widget ids (a
|
|
44
|
+
* renderer's remount report, the a2ui bridge's `identityDiff`). Layout ids are
|
|
45
|
+
* `blueprintLayoutIds`; `preservesBlueprintNodes` compares both.
|
|
46
|
+
*/
|
|
47
|
+
export function blueprintNodeIds(blueprint) {
|
|
48
|
+
const ids = [];
|
|
49
|
+
walkNodeIds(blueprint, (id) => ids.push(id), undefined);
|
|
50
|
+
return ids;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Every well-formed LAYOUT id in a blueprint (IR 2.4.0, ADR 0101), in pre-order, the root
|
|
54
|
+
* included. Absent and malformed ids are skipped. Duplicates are `validateBlueprint`'s job.
|
|
55
|
+
*/
|
|
56
|
+
export function blueprintLayoutIds(blueprint) {
|
|
57
|
+
const ids = [];
|
|
58
|
+
walkNodeIds(blueprint, undefined, (id) => ids.push(id));
|
|
59
|
+
return ids;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Which of `before`'s node ids survive into `after`.
|
|
63
|
+
*
|
|
64
|
+
* Two readings, one function:
|
|
65
|
+
* - *"did this revision keep the nodes it kept?"* — `before` is the previous tree;
|
|
66
|
+
* - *"does this variant contain the floor?"* — `before` is the floor's ids, and any finding
|
|
67
|
+
* is a floor violation. Nodes above the floor land in `added`, which is exactly right:
|
|
68
|
+
* a variant may add, never subtract.
|
|
69
|
+
*
|
|
70
|
+
* One pass over each side, `Set`-backed: O(|before| + |after|).
|
|
71
|
+
*/
|
|
72
|
+
export function preservesNodes(before, after) {
|
|
73
|
+
const afterIds = new Set(after);
|
|
74
|
+
const findings = [];
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
for (const id of before) {
|
|
77
|
+
// A repeated id in `before` is one node's worth of identity, so report it once —
|
|
78
|
+
// otherwise a duplicated id inflates the finding count for a single lost node.
|
|
79
|
+
if (seen.has(id))
|
|
80
|
+
continue;
|
|
81
|
+
seen.add(id);
|
|
82
|
+
if (!afterIds.has(id)) {
|
|
83
|
+
findings.push({
|
|
84
|
+
code: "dropped-node-id",
|
|
85
|
+
severity: "error",
|
|
86
|
+
nodeId: id,
|
|
87
|
+
message: `node "${id}" is present before the change and absent after — either it was removed, or a prop edit re-identified it (ids are derived from kind + props unless supplied explicitly)`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const added = [];
|
|
92
|
+
for (const id of afterIds) {
|
|
93
|
+
if (!seen.has(id))
|
|
94
|
+
added.push(id);
|
|
95
|
+
}
|
|
96
|
+
return { ok: findings.length === 0, findings, added, compared: seen.size };
|
|
97
|
+
}
|
|
98
|
+
/** Widget ids and layout ids together, in one pre-order (document) walk. */
|
|
99
|
+
function allNodeIds(blueprint) {
|
|
100
|
+
const ids = [];
|
|
101
|
+
const push = (id) => {
|
|
102
|
+
ids.push(id);
|
|
103
|
+
};
|
|
104
|
+
walkNodeIds(blueprint, push, push);
|
|
105
|
+
return ids;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* `preservesNodes` over two blueprints — the common case, spelled once. Compares widget ids AND
|
|
109
|
+
* layout ids (IR 2.4.0, ADR 0101): they are one identity axis, so a re-identified container is a
|
|
110
|
+
* `dropped-node-id` finding rather than a silent loss of the state under it. A `before` with no
|
|
111
|
+
* layout ids (any pre-2.4.0 artifact) contributes none, so upgrading only adds to `added`.
|
|
112
|
+
*/
|
|
113
|
+
export function preservesBlueprintNodes(before, after) {
|
|
114
|
+
return preservesNodes(allNodeIds(before), allNodeIds(after));
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { blueprint$ } from "./Blueprint";
|
|
2
|
+
export { grid, flex, stack, isLayoutId, isLayoutRole } from "./layout";
|
|
3
|
+
export { asCell, cell, upgradeBlueprint, blueprintIrVersion, isBindingStatus, isWidget, resolveWidgetStatus, IR_VERSION_LEGACY, } from "./normalize";
|
|
4
|
+
export { MetricCard, Timeline, ApprovalCard, StatusBadge, ProgressBar, DataTable, AlertCard, StreamingText, } from "./widgets";
|
|
5
|
+
export { customWidget$ } from "./customWidget";
|
|
6
|
+
export { BLUEPRINT_IR_SHAPE, BLUEPRINT_IR_VERSION } from "./irVersion";
|
|
7
|
+
export { validateBlueprint, defineWidgetCatalog, builtinCatalog, BUILTIN_CATALOG_ID, } from "./catalog";
|
|
8
|
+
export { catalogCoverage } from "./coverage";
|
|
9
|
+
export { blueprintLayoutIds, blueprintNodeIds, preservesBlueprintNodes, preservesNodes, } from "./identity";
|
|
10
|
+
export { blueprintPins, checkBlueprintPins } from "./pins";
|
|
11
|
+
export { RendererRegistry } from "./RendererRegistry";
|
|
12
|
+
export { traverseBlueprint, resolveWidgetValue, resolvePath } from "./traverseBlueprint";
|
|
13
|
+
export { describeWidgetFrame, describeWidgetStatus } from "./widgetStatus";
|
|
14
|
+
export { traverseBlueprintAsync } from "./traverseBlueprintAsync";
|
|
15
|
+
//# sourceMappingURL=index.js.map
|