dsh-motion-ui 0.5.5 → 0.5.6
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/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/VERIFICATION.md +8 -0
- package/client.js +169 -242
- package/index.js +1 -1
- package/lib/authoring-guide.js +7 -1
- package/lib/build-info.js +1 -1
- package/lib/export-runtime-bundle.js +1 -1
- package/lib/freeform.js +1 -1
- package/lib/normalize-authoring.js +68 -0
- package/lib/protocol.js +28 -19
- package/lib/stream.js +1 -1
- package/lib/tool-schema.js +2 -2
- package/package.json +1 -1
- package/skills/intelligent-ui/SKILL.md +4 -0
package/client.js
CHANGED
|
@@ -9280,8 +9280,8 @@ var require_react_dom_development = __commonJS({
|
|
|
9280
9280
|
pushTreeId(workInProgress2, numberOfForks, slotIndex);
|
|
9281
9281
|
}
|
|
9282
9282
|
}
|
|
9283
|
-
function getBitLength(
|
|
9284
|
-
return 32 - clz32(
|
|
9283
|
+
function getBitLength(number2) {
|
|
9284
|
+
return 32 - clz32(number2);
|
|
9285
9285
|
}
|
|
9286
9286
|
function getLeadingBit(id) {
|
|
9287
9287
|
return 1 << getBitLength(id) - 1;
|
|
@@ -9745,11 +9745,11 @@ var require_react_dom_development = __commonJS({
|
|
|
9745
9745
|
return maybeStrictRoot;
|
|
9746
9746
|
};
|
|
9747
9747
|
var setToSortedString = function(set2) {
|
|
9748
|
-
var
|
|
9748
|
+
var array2 = [];
|
|
9749
9749
|
set2.forEach(function(value) {
|
|
9750
|
-
|
|
9750
|
+
array2.push(value);
|
|
9751
9751
|
});
|
|
9752
|
-
return
|
|
9752
|
+
return array2.sort().join(", ");
|
|
9753
9753
|
};
|
|
9754
9754
|
var pendingComponentWillMountWarnings = [];
|
|
9755
9755
|
var pendingUNSAFE_ComponentWillMountWarnings = [];
|
|
@@ -21925,22 +21925,22 @@ function compileExpression(source) {
|
|
|
21925
21925
|
return result;
|
|
21926
21926
|
}
|
|
21927
21927
|
function expandExpressions(doc) {
|
|
21928
|
-
const
|
|
21928
|
+
const expression2 = (e) => {
|
|
21929
21929
|
if (!e || typeof e !== "object" || Array.isArray(e)) return e;
|
|
21930
21930
|
if (Object.hasOwn(e, "expr")) {
|
|
21931
21931
|
if (Object.keys(e).length !== 1) throw new Error("DSH UI: expr must be the only formula field");
|
|
21932
21932
|
return compileExpression(e.expr);
|
|
21933
21933
|
}
|
|
21934
|
-
if (Array.isArray(e.args)) e.args = e.args.map(
|
|
21935
|
-
if (e.map?.fields) for (const k of Object.keys(e.map.fields)) e.map.fields[k] =
|
|
21934
|
+
if (Array.isArray(e.args)) e.args = e.args.map(expression2);
|
|
21935
|
+
if (e.map?.fields) for (const k of Object.keys(e.map.fields)) e.map.fields[k] = expression2(e.map.fields[k]);
|
|
21936
21936
|
return e;
|
|
21937
21937
|
};
|
|
21938
|
-
if (doc.derived && typeof doc.derived === "object") for (const k of Object.keys(doc.derived)) doc.derived[k] =
|
|
21938
|
+
if (doc.derived && typeof doc.derived === "object") for (const k of Object.keys(doc.derived)) doc.derived[k] = expression2(doc.derived[k]);
|
|
21939
21939
|
const walk = (nodes) => {
|
|
21940
21940
|
if (!Array.isArray(nodes)) return;
|
|
21941
21941
|
for (const node of nodes) {
|
|
21942
21942
|
if (!node || typeof node !== "object") continue;
|
|
21943
|
-
for (const field of ["text", "value", "max", "rows"]) if (Object.hasOwn(node, field)) node[field] =
|
|
21943
|
+
for (const field of ["text", "value", "max", "rows"]) if (Object.hasOwn(node, field)) node[field] = expression2(node[field]);
|
|
21944
21944
|
walk(node.children);
|
|
21945
21945
|
}
|
|
21946
21946
|
};
|
|
@@ -21948,20 +21948,93 @@ function expandExpressions(doc) {
|
|
|
21948
21948
|
return doc;
|
|
21949
21949
|
}
|
|
21950
21950
|
|
|
21951
|
+
// lib/normalize-authoring.js
|
|
21952
|
+
var object = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
21953
|
+
var own = (v, k) => Object.hasOwn(v, k);
|
|
21954
|
+
var singleItem = (v) => object(v) && Object.keys(v).length === 1 && own(v, "item");
|
|
21955
|
+
var array = (v) => singleItem(v) && Array.isArray(v.item) ? v.item : v;
|
|
21956
|
+
var numericOps = /* @__PURE__ */ new Set(["add", "sub", "mul", "div", "min", "max", "round", "abs", "lt", "lte", "gt", "gte"]);
|
|
21957
|
+
function number(v) {
|
|
21958
|
+
if (typeof v !== "string" || !/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(v.trim())) return v;
|
|
21959
|
+
const n = Number(v);
|
|
21960
|
+
return Number.isFinite(n) && (!Number.isInteger(n) || Number.isSafeInteger(n)) ? n : v;
|
|
21961
|
+
}
|
|
21962
|
+
var boolean = (v) => v === "true" ? true : v === "false" ? false : v;
|
|
21963
|
+
function expression(e, numeric = false, logical = false) {
|
|
21964
|
+
if (!object(e)) return e;
|
|
21965
|
+
if (own(e, "const")) {
|
|
21966
|
+
if (numeric) e.const = number(e.const);
|
|
21967
|
+
if (logical) e.const = boolean(e.const);
|
|
21968
|
+
}
|
|
21969
|
+
if (own(e, "args")) e.args = array(e.args);
|
|
21970
|
+
if (Array.isArray(e.args)) e.args.forEach((arg, i) => expression(arg, numericOps.has(e.op) || e.op === "if" && i > 0 && numeric, e.op === "if" && i === 0));
|
|
21971
|
+
if (object(e.map?.fields)) Object.values(e.map.fields).forEach((e2) => expression(e2));
|
|
21972
|
+
return e;
|
|
21973
|
+
}
|
|
21974
|
+
function normalizeAuthoring(doc) {
|
|
21975
|
+
if (!object(doc)) return doc;
|
|
21976
|
+
if (own(doc, "version")) doc.version = number(doc.version);
|
|
21977
|
+
if (object(doc.state)) for (const k of Object.keys(doc.state)) {
|
|
21978
|
+
let s = doc.state[k];
|
|
21979
|
+
if (typeof s === "string" && s.trim().startsWith("{")) {
|
|
21980
|
+
try {
|
|
21981
|
+
const parsed = JSON.parse(s);
|
|
21982
|
+
if (object(parsed)) s = parsed;
|
|
21983
|
+
} catch {
|
|
21984
|
+
}
|
|
21985
|
+
}
|
|
21986
|
+
if (singleItem(s) && object(s.item)) s = s.item;
|
|
21987
|
+
doc.state[k] = s;
|
|
21988
|
+
if (!object(s)) continue;
|
|
21989
|
+
if (own(s, "options")) s.options = array(s.options);
|
|
21990
|
+
if (s.type === "number") {
|
|
21991
|
+
for (const field of ["default", "min", "max", "step"]) if (own(s, field)) s[field] = number(s[field]);
|
|
21992
|
+
if (Array.isArray(s.options)) s.options = s.options.map(number);
|
|
21993
|
+
} else if (s.type === "boolean") {
|
|
21994
|
+
if (own(s, "default")) s.default = boolean(s.default);
|
|
21995
|
+
if (Array.isArray(s.options)) s.options = s.options.map(boolean);
|
|
21996
|
+
}
|
|
21997
|
+
}
|
|
21998
|
+
if (object(doc.derived)) Object.values(doc.derived).forEach((e) => expression(e));
|
|
21999
|
+
if (object(doc.actions)) {
|
|
22000
|
+
for (const action of Object.values(doc.actions)) if (object(action) && own(action, "stateKeys")) action.stateKeys = array(action.stateKeys);
|
|
22001
|
+
}
|
|
22002
|
+
function nodes(list) {
|
|
22003
|
+
list = array(list);
|
|
22004
|
+
if (!Array.isArray(list)) return list;
|
|
22005
|
+
for (const node of list) {
|
|
22006
|
+
if (!object(node)) continue;
|
|
22007
|
+
if (node.type === "sandbox") {
|
|
22008
|
+
if (!own(node, "title")) node.title = doc.title;
|
|
22009
|
+
if (!own(node, "fallback")) node.fallback = doc.fallback;
|
|
22010
|
+
}
|
|
22011
|
+
for (const field of ["children", "items", "columns", "stateKeys", "dataKeys", "derivedKeys"]) if (own(node, field)) node[field] = array(node[field]);
|
|
22012
|
+
if (["table", "chart"].includes(node.type) && own(node, "rows")) node.rows = array(node.rows);
|
|
22013
|
+
for (const field of ["decimals", "height"]) if (own(node, field)) node[field] = number(node[field]);
|
|
22014
|
+
if (node.type === "details" && own(node, "open")) node.open = boolean(node.open);
|
|
22015
|
+
for (const field of ["text", "value", "max", "rows"]) if (own(node, field)) expression(node[field], node.type === "progress");
|
|
22016
|
+
if (["row", "column", "card", "details"].includes(node.type) && own(node, "children")) node.children = nodes(node.children);
|
|
22017
|
+
}
|
|
22018
|
+
return list;
|
|
22019
|
+
}
|
|
22020
|
+
if (own(doc, "nodes")) doc.nodes = nodes(doc.nodes);
|
|
22021
|
+
return doc;
|
|
22022
|
+
}
|
|
22023
|
+
|
|
21951
22024
|
// lib/protocol.js
|
|
21952
22025
|
var LIMITS = Object.freeze({ bytes: 131072, nodes: 160, depth: 12, rows: 500, expressions: 128, steps: 4096 });
|
|
21953
|
-
var
|
|
22026
|
+
var own2 = (o, k) => Object.hasOwn(o, k);
|
|
21954
22027
|
var fail = (message) => {
|
|
21955
22028
|
throw new Error(`DSH UI: ${message}`);
|
|
21956
22029
|
};
|
|
21957
|
-
var
|
|
22030
|
+
var object2 = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
|
|
21958
22031
|
var finite = (x) => typeof x === "number" && Number.isFinite(x);
|
|
21959
22032
|
var scalar = (x) => typeof x === "string" || typeof x === "boolean" || finite(x);
|
|
21960
22033
|
var text = (x, at) => {
|
|
21961
22034
|
if (typeof x !== "string" || x.length > 8192) fail(`${at} must be a string of at most 8192 characters`);
|
|
21962
22035
|
};
|
|
21963
22036
|
function fields(value, allowed, at) {
|
|
21964
|
-
if (!
|
|
22037
|
+
if (!object2(value)) fail(`${at} must be an object`);
|
|
21965
22038
|
for (const k of Object.keys(value)) if (!allowed.includes(k)) {
|
|
21966
22039
|
const hint = at === "document" ? " Controls and visual components belong in nodes as registered typed nodes, not extra document fields." : "";
|
|
21967
22040
|
fail(`unknown field ${at}.${k}; allowed fields at ${at}: ${allowed.join(", ")}.${hint}`);
|
|
@@ -21992,17 +22065,17 @@ function initialState(doc) {
|
|
|
21992
22065
|
return Object.fromEntries(Object.entries(doc.state).map(([k, spec]) => [k, spec.default]));
|
|
21993
22066
|
}
|
|
21994
22067
|
function validateState(doc, state) {
|
|
21995
|
-
if (!
|
|
22068
|
+
if (!object2(state) || Object.keys(state).length !== Object.keys(doc.state).length) fail("state keys do not match the document");
|
|
21996
22069
|
const result = {};
|
|
21997
22070
|
for (const [k, s] of Object.entries(doc.state)) {
|
|
21998
|
-
if (!
|
|
22071
|
+
if (!own2(state, k)) fail(`missing state.${k}`);
|
|
21999
22072
|
const v = state[k];
|
|
22000
22073
|
if (s.type === "number") {
|
|
22001
22074
|
if (!finite(v) || v < s.min || v > s.max) fail(`state.${k} is outside its numeric range`);
|
|
22002
22075
|
const offset = (v - s.min) / s.step;
|
|
22003
22076
|
if (Math.abs(offset - Math.round(offset)) > 1e-6) fail(`state.${k} does not match its step`);
|
|
22004
22077
|
} else if (s.type === "json") {
|
|
22005
|
-
if (!
|
|
22078
|
+
if (!object2(v) && !Array.isArray(v)) fail(`state.${k} must be an array or object for type json`);
|
|
22006
22079
|
safeJson(v);
|
|
22007
22080
|
if (new TextEncoder().encode(JSON.stringify(v)).length > 16384) fail(`state.${k} JSON exceeds 16 KiB`);
|
|
22008
22081
|
} else if (s.type === "string") text(v, `state.${k}`);
|
|
@@ -22016,7 +22089,7 @@ var ARITY = { add: [1, 8], sub: [2, 2], mul: [1, 8], div: [2, 2], min: [1, 8], m
|
|
|
22016
22089
|
function reference(doc, ref) {
|
|
22017
22090
|
if (typeof ref !== "string" || !["state", "data", "derived"].includes(ref.split(".")[0]) || ref.split(".").length !== 2 || !validIdentifier(ref.split(".")[1])) fail(`invalid reference ${JSON.stringify(ref)}; use state.key, data.key or derived.key with a letter/underscore identifier`);
|
|
22018
22091
|
const [group, k] = ref.split(".");
|
|
22019
|
-
if (!
|
|
22092
|
+
if (!own2(doc[group], k)) fail(`unknown reference ${ref}`);
|
|
22020
22093
|
return [group, k];
|
|
22021
22094
|
}
|
|
22022
22095
|
function evaluate(doc, state) {
|
|
@@ -22029,40 +22102,40 @@ function evaluate(doc, state) {
|
|
|
22029
22102
|
budget();
|
|
22030
22103
|
const [group, k] = reference(doc, ref);
|
|
22031
22104
|
if (group !== "derived") return (group === "state" ? state : doc.data)[k];
|
|
22032
|
-
if (
|
|
22105
|
+
if (own2(derived, k)) return derived[k];
|
|
22033
22106
|
if (active.has(k)) fail(`cyclic calculation ${k}`);
|
|
22034
22107
|
active.add(k);
|
|
22035
|
-
const result2 =
|
|
22108
|
+
const result2 = expression2(doc.derived[k]);
|
|
22036
22109
|
active.delete(k);
|
|
22037
22110
|
derived[k] = result2;
|
|
22038
22111
|
return result2;
|
|
22039
22112
|
};
|
|
22040
|
-
const
|
|
22113
|
+
const expression2 = (e, item) => {
|
|
22041
22114
|
budget();
|
|
22042
|
-
if (
|
|
22115
|
+
if (own2(e, "map")) {
|
|
22043
22116
|
const rows = resolve(e.map.source);
|
|
22044
22117
|
if ((generatedRows += rows.length) > LIMITS.rows) fail("generated row limit exceeded");
|
|
22045
22118
|
return rows.map((row) => Object.fromEntries(Object.entries(e.map.fields).map(([k, value]) => {
|
|
22046
|
-
const out2 =
|
|
22119
|
+
const out2 = expression2(value, row);
|
|
22047
22120
|
if (!scalar(out2)) fail("generated cells must be scalar");
|
|
22048
22121
|
return [k, out2];
|
|
22049
22122
|
})));
|
|
22050
22123
|
}
|
|
22051
|
-
if (
|
|
22052
|
-
if (
|
|
22124
|
+
if (own2(e, "const")) return e.const;
|
|
22125
|
+
if (own2(e, "ref")) {
|
|
22053
22126
|
if (e.ref.startsWith("item.")) {
|
|
22054
22127
|
const k = e.ref.slice(5);
|
|
22055
|
-
if (!item || !
|
|
22128
|
+
if (!item || !own2(item, k) || !scalar(item[k])) fail("invalid item reference");
|
|
22056
22129
|
return item[k];
|
|
22057
22130
|
}
|
|
22058
22131
|
return resolve(e.ref);
|
|
22059
22132
|
}
|
|
22060
22133
|
if (e.op === "if") {
|
|
22061
|
-
const condition =
|
|
22134
|
+
const condition = expression2(e.args[0], item);
|
|
22062
22135
|
if (typeof condition !== "boolean") fail("if condition must be boolean");
|
|
22063
|
-
return
|
|
22136
|
+
return expression2(e.args[condition ? 1 : 2], item);
|
|
22064
22137
|
}
|
|
22065
|
-
const a = e.args.map((arg) =>
|
|
22138
|
+
const a = e.args.map((arg) => expression2(arg, item));
|
|
22066
22139
|
if (e.op === "eq") return a[0] === a[1];
|
|
22067
22140
|
if (e.op === "neq") return a[0] !== a[1];
|
|
22068
22141
|
if (!a.every(finite)) fail(`operator ${e.op} requires finite numbers`);
|
|
@@ -22110,8 +22183,8 @@ function evaluate(doc, state) {
|
|
|
22110
22183
|
for (const k of Object.keys(doc.derived)) resolve(`derived.${k}`);
|
|
22111
22184
|
if (new TextEncoder().encode(JSON.stringify(derived)).length > LIMITS.bytes) fail("derived output exceeds 128 KiB");
|
|
22112
22185
|
const result = { derived, resolveValue: (value) => {
|
|
22113
|
-
if (!
|
|
22114
|
-
return
|
|
22186
|
+
if (!object2(value)) return value;
|
|
22187
|
+
return expression2(value);
|
|
22115
22188
|
} };
|
|
22116
22189
|
checkViewValues(doc, result);
|
|
22117
22190
|
return result;
|
|
@@ -22121,8 +22194,8 @@ function checkViewValues(doc, evaluated) {
|
|
|
22121
22194
|
for (const n of list) {
|
|
22122
22195
|
if (n.type === "table" || n.type === "chart") {
|
|
22123
22196
|
const rows = evaluated.resolveValue(n.rows);
|
|
22124
|
-
if (!Array.isArray(rows) || rows.length > LIMITS.rows || !rows.every(
|
|
22125
|
-
if (n.type === "chart" && rows.some((r) => !
|
|
22197
|
+
if (!Array.isArray(rows) || rows.length > LIMITS.rows || !rows.every(object2)) fail("table/chart rows must reference an array of objects");
|
|
22198
|
+
if (n.type === "chart" && rows.some((r) => !own2(r, n.labelKey) || !scalar(r[n.labelKey]) || !own2(r, n.valueKey) || !finite(r[n.valueKey]))) fail("chart requires labels and finite numeric values");
|
|
22126
22199
|
if (n.type === "chart" && n.xScale === "linear" && rows.some((r) => !finite(r[n.labelKey]))) fail("linear chart requires numeric x values");
|
|
22127
22200
|
}
|
|
22128
22201
|
if (n.type === "metric" && !scalar(evaluated.resolveValue(n.value))) fail(`node.${n.id}.value must resolve to a number, string or boolean; use table for rows or a canvas for structured data`);
|
|
@@ -22139,7 +22212,9 @@ function validateDocument(input) {
|
|
|
22139
22212
|
safeJson(input);
|
|
22140
22213
|
const json = JSON.stringify(input);
|
|
22141
22214
|
if (new TextEncoder().encode(json).length > LIMITS.bytes) fail("document exceeds 128 KiB");
|
|
22142
|
-
const
|
|
22215
|
+
const normalized = normalizeAuthoring(JSON.parse(json));
|
|
22216
|
+
safeJson(normalized);
|
|
22217
|
+
const doc = expandExpressions(normalized);
|
|
22143
22218
|
fields(doc, ["version", "title", "fallback", "state", "data", "derived", "nodes", "actions", "presentation"], "document");
|
|
22144
22219
|
if (doc.presentation !== void 0) {
|
|
22145
22220
|
fields(doc.presentation, ["layout", "accent", "density"], "presentation");
|
|
@@ -22152,60 +22227,61 @@ function validateDocument(input) {
|
|
|
22152
22227
|
if (!doc.title.trim() || !doc.fallback.trim()) fail("title and fallback cannot be empty");
|
|
22153
22228
|
for (const group of ["state", "data", "derived", "actions"]) {
|
|
22154
22229
|
doc[group] ??= {};
|
|
22155
|
-
if (!
|
|
22230
|
+
if (!object2(doc[group]) || Object.keys(doc[group]).length > 64) fail(`invalid ${group}`);
|
|
22156
22231
|
for (const k of Object.keys(doc[group])) if (!validIdentifier(k)) fail(`invalid key in ${group}: ${JSON.stringify(k)}; use 1..64 letters, numbers or underscores, starting with a letter/underscore (Chinese letters supported); no spaces, dots or hyphens. Put display labels in label/text`);
|
|
22157
22232
|
}
|
|
22158
22233
|
for (const [k, s] of Object.entries(doc.state)) {
|
|
22159
22234
|
fields(s, ["type", "default", "min", "max", "step", "options"], `state.${k}`);
|
|
22160
|
-
if (s.type === "string" && (Array.isArray(s.default) ||
|
|
22235
|
+
if (s.type === "string" && (Array.isArray(s.default) || object2(s.default)) && !own2(s, "options")) s.type = "json";
|
|
22161
22236
|
if (!["number", "string", "boolean", "json"].includes(s.type)) fail(`unsupported state type ${k}; use number, string, boolean or json (arrays/objects)`);
|
|
22162
|
-
if (s.type === "json" &&
|
|
22237
|
+
if (s.type === "json" && own2(s, "options")) fail(`state.${k}: json state does not support select options; use scalar options or a canvas`);
|
|
22163
22238
|
if (s.type === "number") {
|
|
22164
|
-
|
|
22239
|
+
for (const field of ["default", "min", "max", "step"]) if (!finite(s[field])) fail(`state.${k}.${field} must be a finite JSON number; received ${s[field] === null ? "null" : typeof s[field]}. Supply {type:"number",default:2,min:1,max:12,step:1}; do not omit limits or include units in numeric fields`);
|
|
22240
|
+
if (s.min >= s.max || s.step <= 0) fail(`invalid numeric limits for state.${k}: require min < max and step > 0; received min=${s.min}, max=${s.max}, step=${s.step}`);
|
|
22165
22241
|
if ((s.max - s.min) / s.step > 1e9) fail(`numeric step too small for ${k}`);
|
|
22166
|
-
} else if (["min", "max", "step"].some((p) =>
|
|
22167
|
-
if (s.options && (!Array.isArray(s.options) || !s.options.length || s.options.length > 64 || !s.options.every(scalar))) fail(`
|
|
22242
|
+
} else if (["min", "max", "step"].some((p) => own2(s, p))) fail(`numeric limits on non-number ${k}`);
|
|
22243
|
+
if (s.options !== void 0 && (!Array.isArray(s.options) || !s.options.length || s.options.length > 64 || !s.options.every(scalar))) fail(`state.${k}.options must be a direct array of 1..64 scalar values, e.g. options:["3","5","7"], not {item:[...]}`);
|
|
22168
22244
|
}
|
|
22169
22245
|
const defaults = validateState(doc, initialState(doc));
|
|
22170
22246
|
let expressions = 0;
|
|
22171
22247
|
for (const [k, spec] of Object.entries(doc.state))
|
|
22172
22248
|
for (const option of spec.options ?? []) validateState(doc, { ...defaults, [k]: option });
|
|
22173
|
-
function validateExpression(e, depth = 0, itemRows) {
|
|
22249
|
+
function validateExpression(e, depth = 0, itemRows, at = "expression") {
|
|
22174
22250
|
if (++expressions > LIMITS.expressions || depth > LIMITS.depth) fail("expression limit exceeded");
|
|
22175
|
-
if (!
|
|
22176
|
-
if (
|
|
22251
|
+
if (!object2(e)) fail(`${at}: expression must be an object`);
|
|
22252
|
+
if (own2(e, "map")) {
|
|
22177
22253
|
if (depth !== 0) fail("map is only allowed at the root of a derived value");
|
|
22178
22254
|
fields(e, ["map"], "expression");
|
|
22179
22255
|
fields(e.map, ["source", "fields"], "map");
|
|
22180
22256
|
if (typeof e.map.source !== "string" || !e.map.source.startsWith("data.")) fail('map source must reference static data using a string such as "data.samples"; put object rows in document.data.samples, not inline in map.source');
|
|
22181
22257
|
const [, k] = reference(doc, e.map.source), rows = doc.data[k];
|
|
22182
|
-
if (!Array.isArray(rows) || rows.length > LIMITS.rows || !rows.every(
|
|
22183
|
-
if (!
|
|
22258
|
+
if (!Array.isArray(rows) || rows.length > LIMITS.rows || !rows.every(object2)) fail("map source must be object rows");
|
|
22259
|
+
if (!object2(e.map.fields) || !Object.keys(e.map.fields).length || Object.keys(e.map.fields).length > 16) fail("map requires 1..16 fields");
|
|
22184
22260
|
for (const [k2, value2] of Object.entries(e.map.fields)) {
|
|
22185
22261
|
if (!validIdentifier(k2)) fail(`invalid map field ${JSON.stringify(k2).slice(0, 100)}; use a letter/underscore identifier such as powerW or \u529F\u7387, without spaces, dots or hyphens`);
|
|
22186
|
-
validateExpression(value2, depth + 1, rows);
|
|
22262
|
+
validateExpression(value2, depth + 1, rows, `${at}.map.fields.${k2}`);
|
|
22187
22263
|
}
|
|
22188
22264
|
return;
|
|
22189
22265
|
}
|
|
22190
|
-
if (
|
|
22266
|
+
if (own2(e, "const")) {
|
|
22191
22267
|
fields(e, ["const"], "expression");
|
|
22192
22268
|
if (!scalar(e.const)) fail("constant must be scalar");
|
|
22193
22269
|
return;
|
|
22194
22270
|
}
|
|
22195
|
-
if (
|
|
22271
|
+
if (own2(e, "ref")) {
|
|
22196
22272
|
fields(e, ["ref"], "expression");
|
|
22197
22273
|
if (typeof e.ref === "string" && e.ref.startsWith("item.")) {
|
|
22198
22274
|
const k = e.ref.slice(5);
|
|
22199
|
-
if (!itemRows || !validIdentifier(k) || itemRows.some((row) => !
|
|
22275
|
+
if (!itemRows || !validIdentifier(k) || itemRows.some((row) => !own2(row, k) || !scalar(row[k]))) fail("invalid or unscoped item reference");
|
|
22200
22276
|
} else reference(doc, e.ref);
|
|
22201
22277
|
return;
|
|
22202
22278
|
}
|
|
22203
22279
|
fields(e, ["op", "args"], "expression");
|
|
22204
22280
|
const arity = ARITY[e.op];
|
|
22205
|
-
if (!arity || !Array.isArray(e.args) || e.args.length < arity[0] || e.args.length > arity[1]) fail(
|
|
22206
|
-
e.args.forEach((a) => validateExpression(a, depth + 1, itemRows));
|
|
22281
|
+
if (!arity || !Array.isArray(e.args) || e.args.length < arity[0] || e.args.length > arity[1]) fail(`${at}: invalid operator or argument count for ${JSON.stringify(e.op)}; args must be a direct array${arity ? ` with ${arity[0]}..${arity[1]} expressions` : " and op must be registered"}. Received ${Array.isArray(e.args) ? e.args.length + " arguments" : typeof e.args}. Prefer {expr:"state.people * state.days"} for arithmetic; missing operands cannot be inferred`);
|
|
22282
|
+
e.args.forEach((a, i) => validateExpression(a, depth + 1, itemRows, `${at}.args[${i}]`));
|
|
22207
22283
|
}
|
|
22208
|
-
Object.
|
|
22284
|
+
Object.entries(doc.derived).forEach(([k, e]) => validateExpression(e, 0, void 0, `derived.${k}`));
|
|
22209
22285
|
const visited = /* @__PURE__ */ new Set(), active = /* @__PURE__ */ new Set();
|
|
22210
22286
|
function visit(k) {
|
|
22211
22287
|
if (active.has(k)) fail(`cyclic calculation ${k}`);
|
|
@@ -22225,32 +22301,38 @@ function validateDocument(input) {
|
|
|
22225
22301
|
fields(a, ["type", "intent", "stateKeys"], `actions.${k}`);
|
|
22226
22302
|
if (a.type !== "agent.followup") fail("only agent.followup actions are supported");
|
|
22227
22303
|
text(a.intent, "action intent");
|
|
22228
|
-
if (!a.intent.trim() || !Array.isArray(a.stateKeys) || a.stateKeys.length > 64 || new Set(a.stateKeys).size !== a.stateKeys.length || a.stateKeys.some((s) => !
|
|
22304
|
+
if (!a.intent.trim() || !Array.isArray(a.stateKeys) || a.stateKeys.length > 64 || new Set(a.stateKeys).size !== a.stateKeys.length || a.stateKeys.some((s) => !own2(doc.state, s))) fail("invalid action stateKeys");
|
|
22229
22305
|
}
|
|
22230
|
-
const ids = /* @__PURE__ */ new
|
|
22306
|
+
const ids = /* @__PURE__ */ new Map();
|
|
22231
22307
|
let count = 0;
|
|
22232
22308
|
const value = (v, at) => {
|
|
22233
|
-
if (
|
|
22234
|
-
if (
|
|
22309
|
+
if (object2(v)) {
|
|
22310
|
+
if (own2(v, "map")) fail("map belongs in derived");
|
|
22235
22311
|
validateExpression(v);
|
|
22236
22312
|
} else if (!scalar(v)) fail(`invalid value at ${at}`);
|
|
22237
22313
|
};
|
|
22238
|
-
function nodes(list, depth = 0) {
|
|
22314
|
+
function nodes(list, depth = 0, path = "nodes") {
|
|
22239
22315
|
if (!Array.isArray(list) || depth > LIMITS.depth) fail("invalid node children or depth");
|
|
22240
|
-
for (const n of list) {
|
|
22316
|
+
for (const [index, n] of list.entries()) {
|
|
22317
|
+
const at = `${path}[${index}]`;
|
|
22241
22318
|
if (++count > LIMITS.nodes) fail("too many nodes");
|
|
22242
|
-
if (!
|
|
22243
|
-
|
|
22319
|
+
if (!object2(n)) fail(`${at} must be a node object`);
|
|
22320
|
+
if (!own2(n, "id")) fail(`${at}.id is missing (type=${JSON.stringify(n.type)}). Every node, including sandbox, needs its own id; add e.g. id:"canvas". JSON field order does not matter`);
|
|
22321
|
+
if (!validIdentifier(n.id)) fail(`${at}.id is invalid: ${JSON.stringify(n.id)}. Use 1..64 letters/numbers/underscores starting with a letter/underscore`);
|
|
22322
|
+
if (ids.has(n.id)) fail(`${at}.id duplicates ${JSON.stringify(n.id)} first declared at ${ids.get(n.id)}. Give each node a unique id; state keys and node ids are separate namespaces`);
|
|
22323
|
+
ids.set(n.id, `${at}.id`);
|
|
22244
22324
|
if (!Object.hasOwn(COMPONENTS, n.type)) fail("unsupported component " + n.type);
|
|
22245
22325
|
if (["table", "chart"].includes(n.type) && typeof n.rows === "string" && /^(data|derived)\./.test(n.rows)) n.rows = { ref: n.rows };
|
|
22246
22326
|
switch (n.type) {
|
|
22247
22327
|
case "sandbox": {
|
|
22248
|
-
text(n.title,
|
|
22249
|
-
text(n.fallback,
|
|
22250
|
-
if (!n.title.trim()
|
|
22328
|
+
text(n.title, `${at}.title (canvas title)`);
|
|
22329
|
+
text(n.fallback, `${at}.fallback (canvas fallback)`);
|
|
22330
|
+
if (!n.title.trim()) fail(`${at}.title must not be empty`);
|
|
22331
|
+
if (!n.fallback.trim()) fail(`${at}.fallback must not be empty`);
|
|
22332
|
+
if (typeof n.html !== "string" || !n.html.trim()) fail(`${at}.html must be a non-empty HTML string; script/css/fallback belong beside html on the sandbox node, not inside it`);
|
|
22251
22333
|
for (const field of ["html", "css", "script"]) if (n[field] !== void 0 && (typeof n[field] !== "string" || n[field].length > SANDBOX_LIMITS.source)) fail("canvas source exceeds 48 Ki characters");
|
|
22252
22334
|
if (n.height !== void 0 && (!Number.isInteger(n.height) || n.height < 160 || n.height > SANDBOX_LIMITS.height)) fail("canvas height must be 160..1000");
|
|
22253
|
-
for (const [field, group] of [["stateKeys", "state"], ["dataKeys", "data"], ["derivedKeys", "derived"]]) if (n[field] !== void 0 && (!Array.isArray(n[field]) || n[field].length > 64 || new Set(n[field]).size !== n[field].length || n[field].some((k) => !
|
|
22335
|
+
for (const [field, group] of [["stateKeys", "state"], ["dataKeys", "data"], ["derivedKeys", "derived"]]) if (n[field] !== void 0 && (!Array.isArray(n[field]) || n[field].length > 64 || new Set(n[field]).size !== n[field].length || n[field].some((k) => !own2(doc[group], k)))) fail("invalid canvas " + field);
|
|
22254
22336
|
break;
|
|
22255
22337
|
}
|
|
22256
22338
|
case "row":
|
|
@@ -22275,12 +22357,11 @@ function validateDocument(input) {
|
|
|
22275
22357
|
if (n.unit !== void 0) text(n.unit, "progress unit");
|
|
22276
22358
|
break;
|
|
22277
22359
|
case "steps": {
|
|
22278
|
-
|
|
22279
|
-
|
|
22280
|
-
if (!
|
|
22281
|
-
if (!n.items.length || n.items.length > 12) fail(`${at} requires 1..12 items; received ${n.items.length}. Split a longer guide into separate steps nodes`);
|
|
22360
|
+
const at2 = `node.${n.id}.items`;
|
|
22361
|
+
if (!Array.isArray(n.items)) fail(`${at2} must be an array of 1..12 step objects, received ${n.items === null ? "null" : typeof n.items}. Use items:[{"title":"First step","description":"One sentence"}], not items:{item:[...]}. status is optional for a static guide`);
|
|
22362
|
+
if (!n.items.length || n.items.length > 12) fail(`${at2} requires 1..12 items; received ${n.items.length}. Split a longer guide into separate steps nodes`);
|
|
22282
22363
|
for (const [i, item] of n.items.entries()) {
|
|
22283
|
-
const stepAt = `${
|
|
22364
|
+
const stepAt = `${at2}[${i}]`;
|
|
22284
22365
|
fields(item, ["title", "description", "status"], stepAt);
|
|
22285
22366
|
text(item.title, `${stepAt}.title`);
|
|
22286
22367
|
if (item.description !== void 0) text(item.description, `${stepAt}.description`);
|
|
@@ -22314,7 +22395,7 @@ function validateDocument(input) {
|
|
|
22314
22395
|
}
|
|
22315
22396
|
case "button":
|
|
22316
22397
|
text(n.text, "button text");
|
|
22317
|
-
if (!
|
|
22398
|
+
if (!own2(doc.actions, n.action)) fail("unknown action");
|
|
22318
22399
|
break;
|
|
22319
22400
|
case "table": {
|
|
22320
22401
|
if (n.label !== void 0) text(n.label, "table label");
|
|
@@ -22347,7 +22428,7 @@ function validateDocument(input) {
|
|
|
22347
22428
|
}
|
|
22348
22429
|
}
|
|
22349
22430
|
fields(n, componentFields(n.type), `node.${n.id}`);
|
|
22350
|
-
if (["row", "column", "card", "details"].includes(n.type)) nodes(n.children, depth + 1);
|
|
22431
|
+
if (["row", "column", "card", "details"].includes(n.type)) nodes(n.children, depth + 1, `${at}.children`);
|
|
22351
22432
|
}
|
|
22352
22433
|
}
|
|
22353
22434
|
nodes(doc.nodes);
|
|
@@ -22556,156 +22637,6 @@ function useSavedState(record, sessionId, preview = false) {
|
|
|
22556
22637
|
return { state: preview ? initialState(doc) : state, setState, ready: !preview && ready, status: preview ? "\u751F\u6210\u9884\u89C8 \xB7 \u5B8C\u6210\u6821\u9A8C\u540E\u53EF\u64CD\u4F5C" : status, issue, epoch, retry, useRemote: () => load(), keepLocal: () => load(true) };
|
|
22557
22638
|
}
|
|
22558
22639
|
|
|
22559
|
-
// lib/tool-json.js
|
|
22560
|
-
function clean(source) {
|
|
22561
|
-
let input = source.trim().replace(/^\uFEFF/, "");
|
|
22562
|
-
const fence = input.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i);
|
|
22563
|
-
if (fence) input = fence[1];
|
|
22564
|
-
let output = "", quoted = false, escaped = false;
|
|
22565
|
-
for (let i = 0; i < input.length; i++) {
|
|
22566
|
-
const c = input[i];
|
|
22567
|
-
if (quoted) {
|
|
22568
|
-
output += c;
|
|
22569
|
-
if (escaped) escaped = false;
|
|
22570
|
-
else if (c === "\\") escaped = true;
|
|
22571
|
-
else if (c === '"') quoted = false;
|
|
22572
|
-
continue;
|
|
22573
|
-
}
|
|
22574
|
-
if (c === '"') quoted = true;
|
|
22575
|
-
if (c === "," && /^\s*[}\]]/.test(input.slice(i + 1))) continue;
|
|
22576
|
-
output += c;
|
|
22577
|
-
}
|
|
22578
|
-
return output;
|
|
22579
|
-
}
|
|
22580
|
-
function toolJson(value, field, maxBytes = 131072) {
|
|
22581
|
-
if (typeof value !== "string") return value;
|
|
22582
|
-
if (new TextEncoder().encode(value).length > maxBytes) throw new Error(`DSH UI: ${field} JSON exceeds size limit`);
|
|
22583
|
-
const source = clean(value);
|
|
22584
|
-
try {
|
|
22585
|
-
return JSON.parse(source);
|
|
22586
|
-
} catch (error) {
|
|
22587
|
-
const position = error.message.match(/position (\d+)/)?.[1];
|
|
22588
|
-
const location = position === void 0 ? "" : ` at line ${source.slice(0, Number(position)).split("\n").length}, column ${Number(position) - source.lastIndexOf("\n", Number(position) - 1)}`;
|
|
22589
|
-
throw new Error(`DSH UI: ${field} must contain valid JSON${location}. Pass an object directly, not a manually escaped JSON string. Do not use single-quoted keys or raw newlines inside strings. For HTML/SVG use render_canvas with separate html/css/script fields. No partial document was saved.`);
|
|
22590
|
-
}
|
|
22591
|
-
}
|
|
22592
|
-
|
|
22593
|
-
// lib/stream.js
|
|
22594
|
-
function parseToolArguments(raw) {
|
|
22595
|
-
if (typeof raw !== "string" || raw.length > LIMITS.bytes || new TextEncoder().encode(raw).length > LIMITS.bytes) return null;
|
|
22596
|
-
let i = 0;
|
|
22597
|
-
const ws = () => {
|
|
22598
|
-
while (/\s/.test(raw[i] ?? "") && i < raw.length) i++;
|
|
22599
|
-
};
|
|
22600
|
-
function parse(depth = 0) {
|
|
22601
|
-
if (depth > 48) throw Error("depth");
|
|
22602
|
-
ws();
|
|
22603
|
-
const start = i, c = raw[i];
|
|
22604
|
-
if (c === "{" || c === "[") {
|
|
22605
|
-
const array = c === "[", value = array ? [] : {}, members = /* @__PURE__ */ new Map(), close = array ? "]" : "}";
|
|
22606
|
-
i++;
|
|
22607
|
-
ws();
|
|
22608
|
-
if (raw[i] === close) {
|
|
22609
|
-
i++;
|
|
22610
|
-
return { value, complete: true, members };
|
|
22611
|
-
}
|
|
22612
|
-
let count = 0;
|
|
22613
|
-
while (i < raw.length) {
|
|
22614
|
-
if (++count > 500) throw Error("items");
|
|
22615
|
-
let k;
|
|
22616
|
-
if (!array) {
|
|
22617
|
-
const name = parse(depth + 1);
|
|
22618
|
-
if (!name.complete) return { value, complete: false, members };
|
|
22619
|
-
if (typeof name.value !== "string" || Object.hasOwn(value, name.value) || ["__proto__", "constructor", "prototype"].includes(name.value)) throw Error("key");
|
|
22620
|
-
k = name.value;
|
|
22621
|
-
ws();
|
|
22622
|
-
if (i === raw.length) return { value, complete: false, members };
|
|
22623
|
-
if (raw[i++] !== ":") throw Error("colon");
|
|
22624
|
-
}
|
|
22625
|
-
const child = parse(depth + 1);
|
|
22626
|
-
if (array) {
|
|
22627
|
-
if (child.complete) value.push(child.value);
|
|
22628
|
-
} else {
|
|
22629
|
-
members.set(k, child);
|
|
22630
|
-
if (child.complete) value[k] = child.value;
|
|
22631
|
-
}
|
|
22632
|
-
if (!child.complete) return { value, complete: false, members };
|
|
22633
|
-
ws();
|
|
22634
|
-
if (i === raw.length) return { value, complete: false, members };
|
|
22635
|
-
const separator = raw[i++];
|
|
22636
|
-
if (separator === close) return { value, complete: true, members };
|
|
22637
|
-
if (separator !== ",") throw Error("separator");
|
|
22638
|
-
ws();
|
|
22639
|
-
if (raw[i] === close) throw Error("trailing comma");
|
|
22640
|
-
}
|
|
22641
|
-
return { value, complete: false, members };
|
|
22642
|
-
}
|
|
22643
|
-
if (c === '"') {
|
|
22644
|
-
i++;
|
|
22645
|
-
let safe = i;
|
|
22646
|
-
while (i < raw.length) {
|
|
22647
|
-
const ch = raw[i++];
|
|
22648
|
-
if (ch === '"') return { value: JSON.parse(raw.slice(start, i)), complete: true };
|
|
22649
|
-
if (ch === "\\") {
|
|
22650
|
-
if (i === raw.length) break;
|
|
22651
|
-
const escaped = raw[i++];
|
|
22652
|
-
if (escaped === "u") {
|
|
22653
|
-
if (raw.length - i < 4) {
|
|
22654
|
-
i = raw.length;
|
|
22655
|
-
break;
|
|
22656
|
-
}
|
|
22657
|
-
if (!/^[0-9a-f]{4}$/i.test(raw.slice(i, i + 4))) throw Error("escape");
|
|
22658
|
-
i += 4;
|
|
22659
|
-
} else if (!'"\\/bfnrt'.includes(escaped)) throw Error("escape");
|
|
22660
|
-
} else if (ch.charCodeAt(0) < 32) throw Error("string");
|
|
22661
|
-
safe = i;
|
|
22662
|
-
}
|
|
22663
|
-
return { value: JSON.parse(raw.slice(start, safe) + '"'), complete: false };
|
|
22664
|
-
}
|
|
22665
|
-
while (i < raw.length && !/[\s,}\]]/.test(raw[i])) i++;
|
|
22666
|
-
const token = raw.slice(start, i);
|
|
22667
|
-
if (i === raw.length) return { complete: false };
|
|
22668
|
-
return { value: JSON.parse(token), complete: true };
|
|
22669
|
-
}
|
|
22670
|
-
try {
|
|
22671
|
-
const root = parse();
|
|
22672
|
-
ws();
|
|
22673
|
-
if (i !== raw.length) return null;
|
|
22674
|
-
return root;
|
|
22675
|
-
} catch {
|
|
22676
|
-
return null;
|
|
22677
|
-
}
|
|
22678
|
-
}
|
|
22679
|
-
function previewDocument(raw) {
|
|
22680
|
-
try {
|
|
22681
|
-
const root = parseToolArguments(raw);
|
|
22682
|
-
if (!root) return null;
|
|
22683
|
-
const document2 = root.members?.get("document");
|
|
22684
|
-
if (document2?.complete && typeof document2.value === "string" && root.members.size === 1) return validateDocument(toolJson(document2.value, "document"));
|
|
22685
|
-
if (!document2?.members) return null;
|
|
22686
|
-
if ([...root.members.keys()].some((k) => k !== "document")) return null;
|
|
22687
|
-
const doc = { ...document2.value }, nodes = document2.members.get("nodes");
|
|
22688
|
-
if (!nodes || !Array.isArray(nodes.value) || !nodes.value.length) return null;
|
|
22689
|
-
doc.nodes = nodes.value;
|
|
22690
|
-
for (let n = doc.nodes.length; n > 0; n--) {
|
|
22691
|
-
try {
|
|
22692
|
-
return validateDocument({ ...doc, nodes: doc.nodes.slice(0, n) });
|
|
22693
|
-
} catch {
|
|
22694
|
-
}
|
|
22695
|
-
}
|
|
22696
|
-
} catch {
|
|
22697
|
-
}
|
|
22698
|
-
return null;
|
|
22699
|
-
}
|
|
22700
|
-
function previewCanvas(raw) {
|
|
22701
|
-
const root = parseToolArguments(raw);
|
|
22702
|
-
if (!root?.members) return null;
|
|
22703
|
-
const get = (k) => root.members.get(k), html = get("html"), css = get("css");
|
|
22704
|
-
if (!get("title")?.complete || !get("fallback")?.complete || typeof html?.value !== "string" || !html.value || !css?.complete || typeof css.value !== "string") return null;
|
|
22705
|
-
if (html.value.length > 49152 || css.value.length > 49152) return null;
|
|
22706
|
-
return { id: "canvas-preview", title: String(get("title").value).slice(0, 160), fallback: String(get("fallback").value).slice(0, 2e3), html: html.value, css: css.value, height: Number.isFinite(get("height")?.value) ? Math.max(160, Math.min(1e3, get("height").value)) : 420 };
|
|
22707
|
-
}
|
|
22708
|
-
|
|
22709
22640
|
// src/canvas-preview.js
|
|
22710
22641
|
var import_react3 = __toESM(require("react"), 1);
|
|
22711
22642
|
|
|
@@ -22796,7 +22727,7 @@ function CanvasPreview({ node }) {
|
|
|
22796
22727
|
var import_react6 = __toESM(require("react"), 1);
|
|
22797
22728
|
|
|
22798
22729
|
// lib/export-runtime-bundle.js
|
|
22799
|
-
var EXPORT_RUNTIME = '(() => {\n // lib/presentation.js\n var UI_ICONS = Object.freeze(["sparkles", "sliders", "layers", "chart", "gauge", "cpu", "database", "info", "check", "warning", "arrow", "target"]);\n var UI_TONES = Object.freeze(["neutral", "accent", "success", "warning", "danger"]);\n var UI_LAYOUTS = Object.freeze(["dashboard", "workbench", "comparison", "guide", "status", "report"]);\n var UI_ACCENTS = Object.freeze(["blue", "teal", "violet", "amber", "rose"]);\n var UI_SURFACES = Object.freeze(["outline", "soft", "featured"]);\n\n // lib/visual-audit.js\n function measureVisual(root, nodeId = "") {\n const issues = [], seen = /* @__PURE__ */ new Set(), coverage = { nodeId, elements: 0, truncated: false, contrastChecked: 0, contrastUncovered: 0 };\n const add = (code, value, el2) => {\n const id = el2?.closest("[data-ui-node]")?.getAttribute("data-ui-node") ?? nodeId;\n const k = id + ":" + code + ":" + Math.round(value ?? 0);\n if (!seen.has(k) && issues.length < 40) {\n seen.add(k);\n issues.push({ code, nodeId: id, ...value === void 0 ? {} : { value: Number(value.toFixed(2)) } });\n }\n };\n const color = (s) => {\n let m = s.match(/^rgba?\\(([^)]+)\\)/);\n if (m) {\n const a = m[1].split(/[\\s,\\/]+/).map(Number);\n return [a[0] / 255, a[1] / 255, a[2] / 255, a[3] ?? 1];\n }\n m = s.match(/^color\\(srgb ([^)]+)\\)/);\n if (m) {\n const a = m[1].split(/[\\s\\/]+/).map(Number);\n return [a[0], a[1], a[2], a[3] ?? 1];\n }\n return null;\n };\n const luminance = (c) => c.slice(0, 3).map((v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4).reduce((s, v, i) => s + v * [0.2126, 0.7152, 0.0722][i], 0);\n const widthRoot = root.id === "canvas" ? document.documentElement : root;\n if (widthRoot.scrollWidth > widthRoot.clientWidth + 2) add("overflow", widthRoot.scrollWidth - widthRoot.clientWidth);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);\n let el;\n while (el = walker.nextNode()) {\n if (coverage.elements >= 2e3) {\n coverage.truncated = true;\n break;\n }\n coverage.elements++;\n if (el.matches("[data-canvas-phase=error]")) add("runtimeError", void 0, el);\n const css = getComputedStyle(el), box = el.getBoundingClientRect();\n if (!box.width || !box.height || css.visibility === "hidden" || css.display === "none") continue;\n const text2 = [...el.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim());\n if (text2) {\n let size = parseFloat(css.fontSize);\n if (el instanceof SVGElement && el.getScreenCTM) {\n const m = el.getScreenCTM();\n if (m) size *= Math.hypot(m.c, m.d);\n }\n if (size < 11.9) add("smallText", size, el);\n const fg = color(css.color);\n let ancestor = el, bg = null, depth = 0;\n while (ancestor && depth++ < 64) {\n const style = getComputedStyle(ancestor);\n if (style.backgroundImage !== "none") break;\n const c = color(style.backgroundColor);\n if (c?.[3] === 1) {\n bg = c;\n break;\n }\n if (c?.[3] > 0) break;\n ancestor = ancestor.parentElement;\n }\n if (!(el instanceof SVGElement) && fg?.[3] === 1 && bg) {\n coverage.contrastChecked++;\n const a = luminance(fg), b = luminance(bg), ratio = (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);\n if (ratio < (size >= 24 || size >= 18.66 && parseFloat(css.fontWeight) >= 700 ? 3 : 4.5)) add("contrast", ratio, el);\n } else coverage.contrastUncovered++;\n }\n if (el.matches("input:not([type=hidden]),select,textarea,button,[role=button]") && !el.getAttribute("aria-label") && !el.getAttribute("aria-labelledby") && !el.labels?.length && !el.textContent.trim() && !el.getAttribute("title")) add("missingLabel", void 0, el);\n }\n Object.defineProperty(issues, "coverage", { value: coverage });\n return issues;\n }\n\n // lib/format-number.js\n function formatNumber(value, decimals = 2) {\n if (typeof value !== "number" || !Number.isFinite(value)) throw new Error("DSH UI: formatNumber requires a finite number");\n if (!Number.isInteger(decimals) || decimals < 0 || decimals > 6) throw new Error("DSH UI: formatNumber decimals must be an integer from 0 to 6");\n return value.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals });\n }\n\n // lib/canvas-system.js\n var CANVAS_SYSTEM_CSS = `\n:root{--canvas-radius:20px;--canvas-gap:16px;--canvas-positive:#167456;--canvas-negative:#b13b3b;--canvas-warning:#875b15}\n:root[data-canvas-scheme=dark]{--canvas-positive:#78d9b4;--canvas-negative:#ffaaaa;--canvas-warning:#edc87e}\n#canvas{display:flow-root}html,body{margin:0;min-width:0;background:var(--canvas-bg,#fff);color:var(--canvas-ink,#182537);font:14px/1.6 system-ui,"Microsoft YaHei",sans-serif}*{box-sizing:border-box}svg{max-width:100%;height:auto}button,input,select,textarea{font:inherit;color:inherit}button,input,select,textarea{border:1px solid var(--canvas-line,#ced4df);border-radius:8px;background:var(--canvas-bg,#fff);padding:8px 12px;max-width:100%}button{cursor:pointer}button:disabled{cursor:default;opacity:.55}input[type=range]{padding:0;accent-color:var(--canvas-accent,#3e60d3)}input[type=checkbox],input[type=radio]{accent-color:var(--canvas-accent,#3e60d3)}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--canvas-accent,#3e60d3);outline-offset:3px}\n.ui-stack{display:grid;gap:var(--canvas-gap)}.ui-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:var(--canvas-gap)}.ui-surface{padding:20px;border:1px solid var(--canvas-line,#ced4df);border-radius:var(--canvas-radius);background:var(--canvas-soft,#f3f5f8)}.ui-title{margin:0;font-size:clamp(20px,3vw,28px);line-height:1.3;letter-spacing:-.02em}.ui-caption{color:var(--canvas-muted,#526176);margin:8px 0;font-size:14px}.ui-value{font-size:clamp(24px,4vw,36px);font-variant-numeric:tabular-nums;line-height:1.2}.ui-controls{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.ui-field{display:grid;gap:6px;min-width:0}.ui-legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px}.ui-status{min-height:1.6em}.ui-error{color:var(--canvas-negative)}.ui-primary{background:var(--canvas-accent,#3e60d3);color:var(--canvas-accent-ink,#fff);border-color:transparent}\nsvg .ui-node{fill:var(--canvas-soft,#f3f5f8);stroke:var(--canvas-line,#ced4df);stroke-width:1}svg .ui-edge{fill:none;stroke:var(--canvas-muted,#526176);stroke-width:1.5}svg .ui-label{fill:var(--canvas-ink,#182537);font:500 14px system-ui,sans-serif}svg .ui-note{fill:var(--canvas-muted,#526176);font:400 13px system-ui,sans-serif}svg .ui-leader{fill:none;stroke:var(--canvas-muted,#526176);stroke-dasharray:3 4}svg .ui-accent{fill:color-mix(in srgb,var(--canvas-accent,#3e60d3) 12%,var(--canvas-bg,#fff));stroke:var(--canvas-accent,#3e60d3)}\n.ui-flow{display:flex;align-items:center;justify-content:center;gap:16px;padding:24px 8px;flex-wrap:wrap}.ui-step{display:grid;gap:6px;min-width:130px;max-width:240px;padding:16px 20px;border:1px solid var(--canvas-line);border-radius:6px;background:var(--canvas-bg)}.ui-step strong{font-size:16px;font-weight:600}.ui-step span{font-size:14px;color:var(--canvas-muted)}.ui-arrow{color:var(--canvas-muted);font-size:22px}.ui-callout{border-inline-start:3px solid var(--canvas-accent);padding:8px 16px;color:var(--canvas-ink)}\n.ui-surface,.ui-step{border-radius:var(--canvas-radius);background:linear-gradient(135deg,color-mix(in srgb,var(--canvas-accent,#3e60d3) 9%,var(--canvas-bg,#fff)),var(--canvas-soft,#f3f5f8))}button,input:not([type=checkbox]):not([type=radio]):not([type=range]),select,textarea{border-radius:14px}.ui-primary{background:linear-gradient(120deg,var(--canvas-accent,#3e60d3),color-mix(in srgb,var(--canvas-accent,#3e60d3) 85%,#152647))}.ui-callout{border-radius:14px;background:linear-gradient(120deg,color-mix(in srgb,var(--canvas-accent,#3e60d3) 7%,var(--canvas-bg,#fff)),var(--canvas-bg,#fff))}\n@media(max-width:480px){.ui-flow{flex-direction:column;padding:16px 8px;gap:12px}.ui-step{width:100%;max-width:none}.ui-arrow{transform:rotate(90deg)}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}`;\n\n // lib/sandbox.js\n var SANDBOX_LIMITS = Object.freeze({ source: 49152, height: 1e3, message: 16384, rate: 30 });\n function canvasSnapshot(node2, state2, data, derived, disabled, theme) {\n const pick = (keys, values) => Object.fromEntries((keys ?? []).map((k) => [k, values[k]]));\n return { state: pick(node2.stateKeys, state2), data: pick(node2.dataKeys, data), derived: pick(node2.derivedKeys, derived), disabled, theme };\n }\n function canvasPatch(message, node2) {\n if (!message || typeof message !== "object" || Array.isArray(message)) throw new Error("Invalid canvas patch");\n const keys = Object.keys(message);\n if (!keys.length || keys.length > 64 || keys.some((k) => !(node2.stateKeys ?? []).includes(k))) throw new Error("Canvas state key is not declared");\n if (JSON.stringify(message).length > SANDBOX_LIMITS.message) throw new Error("Canvas patch is too large");\n return message;\n }\n function sandboxDocument(node2, channel2, nonce2) {\n const json = (value) => JSON.stringify(value).replace(/</g, "\\\\u003c").replace(/\\u2028/g, "\\\\u2028").replace(/\\u2029/g, "\\\\u2029");\n return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src \'none\'; script-src \'nonce-${nonce2}\'; style-src \'unsafe-inline\'; img-src data:; font-src \'none\'; connect-src \'none\'; media-src \'none\'; object-src \'none\'; frame-src \'none\'; worker-src \'none\'; base-uri \'none\'; form-action \'none\'"><meta name="viewport" content="width=device-width,initial-scale=1"><style>${CANVAS_SYSTEM_CSS}</style></head><body><main id="canvas"></main><script nonce="${nonce2}">(()=>{\n const channel=${json(channel2)}, source=${json({ html: node2.html, css: node2.css ?? "", script: node2.script ?? "" })};\n let snapshot={state:{},data:{},derived:{},disabled:true,theme:{}}, started=false;\n const listeners=new Set(), send=(type,payload)=>parent.postMessage({dshCanvas:1,channel,type,payload},\'*\');\n const report=e=>send(\'error\',String(e?.message??e).slice(0,1000));\n const audit=${measureVisual.toString()};let auditTimer;const scheduleAudit=()=>{clearTimeout(auditTimer);auditTimer=setTimeout(()=>{const issues=audit(document.getElementById(\'canvas\'),${json(node2.id ?? "")});send(\'audit\',{issues,coverage:issues.coverage})},400)};\n addEventListener(\'resize\',scheduleAudit);\n addEventListener(\'error\',e=>report(e.error??e.message));addEventListener(\'unhandledrejection\',e=>report(e.reason));\n const animations=new Set(), motion=matchMedia(\'(prefers-reduced-motion: reduce)\');\n let raf=0,last=0,time=0;\n const visible=()=>snapshot.visible!==false&&!document.hidden;\n const canAnimate=()=>visible()&&!snapshot.disabled&&!motion.matches;\n const draw=delta=>{for(const fn of animations)try{fn({time,delta,reducedMotion:motion.matches,visible:visible()})}catch(e){animations.delete(fn);report(e)}};\n const tick=t=>{raf=0;if(!canAnimate()||!animations.size){last=0;draw(0);return}const delta=last?Math.min(.05,(t-last)/1000):0;last=t;time+=delta;draw(delta);if(animations.size)raf=requestAnimationFrame(tick)};\n const refresh=()=>{draw(0);if(canAnimate()&&animations.size){if(!raf){last=0;raf=requestAnimationFrame(tick)}}else{cancelAnimationFrame(raf);raf=0;last=0}};\n motion.addEventListener(\'change\',refresh);document.addEventListener(\'visibilitychange\',refresh);\n const formatNumber=${formatNumber.toString()};\n const api={formatNumber,get snapshot(){return structuredClone(snapshot)},subscribe(fn){if(typeof fn!==\'function\')throw Error(\'Listener required\');listeners.add(fn);fn(api.snapshot);return()=>listeners.delete(fn)},setState(patch){if(!snapshot.disabled)send(\'patch\',patch)},animate(fn){if(typeof fn!==\'function\')throw Error(\'Animation callback required\');animations.add(fn);refresh();return()=>{animations.delete(fn);if(!animations.size){cancelAnimationFrame(raf);raf=0;last=0}}}};\n Object.defineProperty(window,\'DSHUI\',{value:Object.freeze(api),writable:false});\n for(const type of [\'click\',\'input\',\'change\',\'keydown\',\'pointerdown\'])document.addEventListener(type,e=>{if(snapshot.disabled){e.preventDefault();e.stopImmediatePropagation()}},true);\n addEventListener(\'message\',event=>{\n const m=event.data;if(event.source!==parent||m?.dshCanvas!==1||m.channel!==channel||m.type!==\'snapshot\')return;\n snapshot=m.payload;for(const [k,v] of Object.entries(snapshot.theme??{}))document.documentElement.style.setProperty(\'--canvas-\'+k,v);\n document.documentElement.style.colorScheme=snapshot.theme?.scheme??\'light\';document.documentElement.dataset.canvasScheme=snapshot.theme?.scheme??\'light\';\n const wasStarted=started;\n if(!started){started=true;document.getElementById(\'canvas\').innerHTML=source.html;const style=document.createElement(\'style\');style.textContent=source.css;document.head.append(style);const script=document.createElement(\'script\');script.nonce=${json(nonce2)};script.textContent=source.script;document.body.append(script)}\n if(wasStarted)for(const fn of listeners)try{fn(api.snapshot)}catch(e){report(e)}\n refresh();if(!wasStarted)send(\'mounted\');scheduleAudit();\n });\n let resizePending=false;new ResizeObserver(()=>{if(!started||resizePending)return;resizePending=true;requestAnimationFrame(()=>{resizePending=false;send(\'height\',Math.ceil(document.getElementById(\'canvas\').getBoundingClientRect().height))})}).observe(document.getElementById(\'canvas\'));\n send(\'ready\');\n})();<\\/script></body></html>`;\n }\n\n // lib/components.js\n var define = (fields2, purpose, presentation = "") => Object.freeze({ fields: Object.freeze(fields2), purpose, presentation });\n var COMPONENTS = Object.freeze({\n heading: define(["text"], "Section heading", "tone"),\n text: define(["text"], "Readable explanation or bound text", "tone"),\n row: define(["children"], "Wrapping parallel content"),\n column: define(["children"], "Ordered vertical content"),\n card: define(["title", "children", "surface"], "Meaningful grouped section", "tone"),\n details: define(["title", "children", "open"], "Collapsible supporting content", "tone"),\n badge: define(["text"], "Short explicit status label", "tone"),\n progress: define(["label", "value", "max", "unit"], "Declared numeric progress snapshot", "tone"),\n steps: define(["items"], "Ordered declared steps"),\n metric: define(["label", "value", "unit", "decimals"], "Number with units, or a literal/bound status string", "tone"),\n input: define(["label", "bind"], "String or numeric state input"),\n slider: define(["label", "bind"], "Numeric state control"),\n select: define(["label", "bind"], "Selection from declared state options"),\n toggle: define(["label", "bind"], "Boolean state control"),\n button: define(["text", "action"], "Declared Agent follow-up", "icon"),\n table: define(["label", "rows", "columns"], "Supplied or derived values; inline rows or data/derived reference"),\n chart: define(["kind", "rows", "labelKey", "valueKey", "label", "unit", "xScale"], "Bar/line comparison with accessible data"),\n sandbox: define(["title", "html", "css", "script", "fallback", "height", "stateKeys", "dataKeys", "derivedKeys"], "Freeform isolated HTML/SVG/CSS/JavaScript visualization")\n });\n function componentFields(type) {\n const c = Object.hasOwn(COMPONENTS, type) ? COMPONENTS[type] : void 0;\n return c && ["id", "type", ...c.fields, ...c.presentation ? ["icon"] : [], ...c.presentation === "tone" ? ["tone"] : []];\n }\n\n // lib/identifiers.js\n var IDENTIFIER_SOURCE = "[\\\\p{L}_][\\\\p{L}\\\\p{M}\\\\p{N}_]{0,63}";\n var pattern = new RegExp(`^${IDENTIFIER_SOURCE}$`, "u");\n var validIdentifier = (value) => typeof value === "string" && pattern.test(value) && !["constructor", "prototype", "__proto__"].includes(value);\n\n // lib/author-expression.js\n function compileExpression(source) {\n const fail2 = () => {\n throw new Error("DSH UI: invalid formula; use state.x, data.x, derived.x, item.x, numbers, strings, + - * /, comparisons, ?: and min/max/round/abs");\n };\n if (typeof source !== "string" || !source.trim() || source.length > 2048) fail2();\n const tokens = [];\n let offset = 0;\n const token2 = new RegExp(String.raw`\\s*(?:(\\d+(?:\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?|("(?:[^"\\\\]|\\\\.)*")|(${IDENTIFIER_SOURCE}(?:\\.${IDENTIFIER_SOURCE})?)|(===|!==|<=|>=|==|!=|[+*/()?,:<>-]))`, "uy");\n while (offset < source.length) {\n if (!source.slice(offset).trim()) break;\n token2.lastIndex = offset;\n const m = token2.exec(source);\n if (!m) fail2();\n offset = token2.lastIndex;\n tokens.push(m[1] ? { number: Number(m[1] + (m[2] ?? "")) } : m[3] ? { string: JSON.parse(m[3]) } : m[4] ?? m[5]);\n if (tokens.length > 256) fail2();\n }\n let pos = 0;\n const operators = { "==": ["eq", 1], "===": ["eq", 1], "!=": ["neq", 1], "!==": ["neq", 1], "<": ["lt", 2], "<=": ["lte", 2], ">": ["gt", 2], ">=": ["gte", 2], "+": ["add", 3], "-": ["sub", 3], "*": ["mul", 4], "/": ["div", 4] };\n const take = (t) => {\n if (tokens[pos++] !== t) fail2();\n };\n function parse(min = 0, depth = 0) {\n if (depth > 12) fail2();\n const t = tokens[pos++];\n let out;\n if (t && typeof t === "object") out = { const: Object.hasOwn(t, "number") ? t.number : t.string };\n else if (t === "true" || t === "false") out = { const: t === "true" };\n else if (t === "(") {\n out = parse(0, depth + 1);\n take(")");\n } else if (t === "-" || t === "+") {\n const arg = parse(5, depth + 1);\n out = t === "-" ? { op: "sub", args: [{ const: 0 }, arg] } : arg;\n } else if (typeof t === "string" && t.split(".").length === 2 && ["state", "data", "derived", "item"].includes(t.split(".")[0]) && validIdentifier(t.split(".")[1])) out = { ref: t };\n else if (["min", "max", "round", "abs"].includes(t)) {\n take("(");\n const args = [parse(0, depth + 1)];\n while (tokens[pos] === ",") {\n pos++;\n args.push(parse(0, depth + 1));\n if (args.length > 8) fail2();\n }\n take(")");\n out = { op: t, args };\n } else fail2();\n while (typeof tokens[pos] === "string" && Object.hasOwn(operators, tokens[pos]) && operators[tokens[pos]][1] >= min) {\n const [op, precedence] = operators[tokens[pos++]];\n out = { op, args: [out, parse(precedence + 1, depth + 1)] };\n }\n if (min === 0 && tokens[pos] === "?") {\n pos++;\n const yes = parse(0, depth + 1);\n take(":");\n out = { op: "if", args: [out, yes, parse(0, depth + 1)] };\n }\n return out;\n }\n const result = parse();\n if (pos !== tokens.length) fail2();\n return result;\n }\n function expandExpressions(doc2) {\n const expression = (e) => {\n if (!e || typeof e !== "object" || Array.isArray(e)) return e;\n if (Object.hasOwn(e, "expr")) {\n if (Object.keys(e).length !== 1) throw new Error("DSH UI: expr must be the only formula field");\n return compileExpression(e.expr);\n }\n if (Array.isArray(e.args)) e.args = e.args.map(expression);\n if (e.map?.fields) for (const k of Object.keys(e.map.fields)) e.map.fields[k] = expression(e.map.fields[k]);\n return e;\n };\n if (doc2.derived && typeof doc2.derived === "object") for (const k of Object.keys(doc2.derived)) doc2.derived[k] = expression(doc2.derived[k]);\n const walk = (nodes) => {\n if (!Array.isArray(nodes)) return;\n for (const node2 of nodes) {\n if (!node2 || typeof node2 !== "object") continue;\n for (const field of ["text", "value", "max", "rows"]) if (Object.hasOwn(node2, field)) node2[field] = expression(node2[field]);\n walk(node2.children);\n }\n };\n walk(doc2.nodes);\n return doc2;\n }\n\n // lib/protocol.js\n var LIMITS = Object.freeze({ bytes: 131072, nodes: 160, depth: 12, rows: 500, expressions: 128, steps: 4096 });\n var own = (o, k) => Object.hasOwn(o, k);\n var fail = (message) => {\n throw new Error(`DSH UI: ${message}`);\n };\n var object = (x) => x !== null && typeof x === "object" && !Array.isArray(x);\n var finite = (x) => typeof x === "number" && Number.isFinite(x);\n var scalar = (x) => typeof x === "string" || typeof x === "boolean" || finite(x);\n var text = (x, at) => {\n if (typeof x !== "string" || x.length > 8192) fail(`${at} must be a string of at most 8192 characters`);\n };\n function fields(value, allowed, at) {\n if (!object(value)) fail(`${at} must be an object`);\n for (const k of Object.keys(value)) if (!allowed.includes(k)) {\n const hint = at === "document" ? " Controls and visual components belong in nodes as registered typed nodes, not extra document fields." : "";\n fail(`unknown field ${at}.${k}; allowed fields at ${at}: ${allowed.join(", ")}.${hint}`);\n }\n }\n function safeJson(value, depth = 0, source = false) {\n if (depth > 48) fail("JSON nesting is too deep");\n if (value === null || typeof value === "boolean") return;\n if (typeof value === "string") {\n if (source) {\n if (value.length > SANDBOX_LIMITS.source) fail("canvas source exceeds 48 Ki characters");\n return;\n }\n return text(value, "value");\n }\n if (typeof value === "number") {\n if (!finite(value)) fail("numbers must be finite");\n return;\n }\n if (typeof value !== "object") fail("only JSON values are accepted");\n if (Array.isArray(value) && value.length > LIMITS.rows) fail("too many array items");\n for (const k of Object.keys(value)) {\n if (["__proto__", "prototype", "constructor"].includes(k)) fail("unsafe property name");\n safeJson(value[k], depth + 1, value.type === "sandbox" && ["html", "css", "script"].includes(k));\n }\n }\n function initialState(doc2) {\n return Object.fromEntries(Object.entries(doc2.state).map(([k, spec]) => [k, spec.default]));\n }\n function validateState(doc2, state2) {\n if (!object(state2) || Object.keys(state2).length !== Object.keys(doc2.state).length) fail("state keys do not match the document");\n const result = {};\n for (const [k, s] of Object.entries(doc2.state)) {\n if (!own(state2, k)) fail(`missing state.${k}`);\n const v = state2[k];\n if (s.type === "number") {\n if (!finite(v) || v < s.min || v > s.max) fail(`state.${k} is outside its numeric range`);\n const offset = (v - s.min) / s.step;\n if (Math.abs(offset - Math.round(offset)) > 1e-6) fail(`state.${k} does not match its step`);\n } else if (s.type === "json") {\n if (!object(v) && !Array.isArray(v)) fail(`state.${k} must be an array or object for type json`);\n safeJson(v);\n if (new TextEncoder().encode(JSON.stringify(v)).length > 16384) fail(`state.${k} JSON exceeds 16 KiB`);\n } else if (s.type === "string") text(v, `state.${k}`);\n else if (typeof v !== "boolean") fail(`state.${k} must be boolean`);\n if (s.options && !s.options.includes(v)) fail(`invalid option for state.${k}`);\n result[k] = v;\n }\n return result;\n }\n var ARITY = { add: [1, 8], sub: [2, 2], mul: [1, 8], div: [2, 2], min: [1, 8], max: [1, 8], round: [1, 1], abs: [1, 1], eq: [2, 2], neq: [2, 2], lt: [2, 2], lte: [2, 2], gt: [2, 2], gte: [2, 2], if: [3, 3] };\n function reference(doc2, ref) {\n if (typeof ref !== "string" || !["state", "data", "derived"].includes(ref.split(".")[0]) || ref.split(".").length !== 2 || !validIdentifier(ref.split(".")[1])) fail(`invalid reference ${JSON.stringify(ref)}; use state.key, data.key or derived.key with a letter/underscore identifier`);\n const [group, k] = ref.split(".");\n if (!own(doc2[group], k)) fail(`unknown reference ${ref}`);\n return [group, k];\n }\n function evaluate(doc2, state2) {\n const derived = {}, active = /* @__PURE__ */ new Set();\n let steps = 0, generatedRows = 0;\n const budget = () => {\n if (++steps > LIMITS.steps) fail("calculation budget exceeded");\n };\n const resolve = (ref) => {\n budget();\n const [group, k] = reference(doc2, ref);\n if (group !== "derived") return (group === "state" ? state2 : doc2.data)[k];\n if (own(derived, k)) return derived[k];\n if (active.has(k)) fail(`cyclic calculation ${k}`);\n active.add(k);\n const result2 = expression(doc2.derived[k]);\n active.delete(k);\n derived[k] = result2;\n return result2;\n };\n const expression = (e, item) => {\n budget();\n if (own(e, "map")) {\n const rows = resolve(e.map.source);\n if ((generatedRows += rows.length) > LIMITS.rows) fail("generated row limit exceeded");\n return rows.map((row) => Object.fromEntries(Object.entries(e.map.fields).map(([k, value]) => {\n const out2 = expression(value, row);\n if (!scalar(out2)) fail("generated cells must be scalar");\n return [k, out2];\n })));\n }\n if (own(e, "const")) return e.const;\n if (own(e, "ref")) {\n if (e.ref.startsWith("item.")) {\n const k = e.ref.slice(5);\n if (!item || !own(item, k) || !scalar(item[k])) fail("invalid item reference");\n return item[k];\n }\n return resolve(e.ref);\n }\n if (e.op === "if") {\n const condition = expression(e.args[0], item);\n if (typeof condition !== "boolean") fail("if condition must be boolean");\n return expression(e.args[condition ? 1 : 2], item);\n }\n const a = e.args.map((arg) => expression(arg, item));\n if (e.op === "eq") return a[0] === a[1];\n if (e.op === "neq") return a[0] !== a[1];\n if (!a.every(finite)) fail(`operator ${e.op} requires finite numbers`);\n let out;\n switch (e.op) {\n case "add":\n out = a.reduce((x, y) => x + y, 0);\n break;\n case "sub":\n out = a[0] - a[1];\n break;\n case "mul":\n out = a.reduce((x, y) => x * y, 1);\n break;\n case "div":\n if (a[1] === 0) fail("division by zero");\n out = a[0] / a[1];\n break;\n case "min":\n out = Math.min(...a);\n break;\n case "max":\n out = Math.max(...a);\n break;\n case "round":\n out = Math.round(a[0]);\n break;\n case "abs":\n out = Math.abs(a[0]);\n break;\n case "lt":\n return a[0] < a[1];\n case "lte":\n return a[0] <= a[1];\n case "gt":\n return a[0] > a[1];\n case "gte":\n return a[0] >= a[1];\n default:\n fail(`unsupported operator ${e.op}`);\n }\n if (!finite(out)) fail("non-finite calculation result");\n return out;\n };\n for (const k of Object.keys(doc2.derived)) resolve(`derived.${k}`);\n if (new TextEncoder().encode(JSON.stringify(derived)).length > LIMITS.bytes) fail("derived output exceeds 128 KiB");\n const result = { derived, resolveValue: (value) => {\n if (!object(value)) return value;\n return expression(value);\n } };\n checkViewValues(doc2, result);\n return result;\n }\n function checkViewValues(doc2, evaluated) {\n function walk(list) {\n for (const n of list) {\n if (n.type === "table" || n.type === "chart") {\n const rows = evaluated.resolveValue(n.rows);\n if (!Array.isArray(rows) || rows.length > LIMITS.rows || !rows.every(object)) fail("table/chart rows must reference an array of objects");\n if (n.type === "chart" && rows.some((r) => !own(r, n.labelKey) || !scalar(r[n.labelKey]) || !own(r, n.valueKey) || !finite(r[n.valueKey]))) fail("chart requires labels and finite numeric values");\n if (n.type === "chart" && n.xScale === "linear" && rows.some((r) => !finite(r[n.labelKey]))) fail("linear chart requires numeric x values");\n }\n if (n.type === "metric" && !scalar(evaluated.resolveValue(n.value))) fail(`node.${n.id}.value must resolve to a number, string or boolean; use table for rows or a canvas for structured data`);\n if (n.type === "progress") {\n const v = evaluated.resolveValue(n.value), max = evaluated.resolveValue(n.max);\n if (!finite(v) || !finite(max) || max <= 0 || v < 0 || v > max) fail("progress requires 0 <= value <= positive max");\n }\n if (n.children) walk(n.children);\n }\n }\n walk(doc2.nodes);\n }\n function validateDocument(input) {\n safeJson(input);\n const json = JSON.stringify(input);\n if (new TextEncoder().encode(json).length > LIMITS.bytes) fail("document exceeds 128 KiB");\n const doc2 = expandExpressions(JSON.parse(json));\n fields(doc2, ["version", "title", "fallback", "state", "data", "derived", "nodes", "actions", "presentation"], "document");\n if (doc2.presentation !== void 0) {\n fields(doc2.presentation, ["layout", "accent", "density"], "presentation");\n for (const [k, allowed] of Object.entries({ layout: UI_LAYOUTS, accent: UI_ACCENTS, density: ["comfortable", "compact"] }))\n if (doc2.presentation[k] !== void 0 && !allowed.includes(doc2.presentation[k])) fail(`unknown presentation ${k}`);\n }\n if (doc2.version !== 1) fail("unsupported protocol version");\n text(doc2.title, "title");\n text(doc2.fallback, "fallback");\n if (!doc2.title.trim() || !doc2.fallback.trim()) fail("title and fallback cannot be empty");\n for (const group of ["state", "data", "derived", "actions"]) {\n doc2[group] ??= {};\n if (!object(doc2[group]) || Object.keys(doc2[group]).length > 64) fail(`invalid ${group}`);\n for (const k of Object.keys(doc2[group])) if (!validIdentifier(k)) fail(`invalid key in ${group}: ${JSON.stringify(k)}; use 1..64 letters, numbers or underscores, starting with a letter/underscore (Chinese letters supported); no spaces, dots or hyphens. Put display labels in label/text`);\n }\n for (const [k, s] of Object.entries(doc2.state)) {\n fields(s, ["type", "default", "min", "max", "step", "options"], `state.${k}`);\n if (s.type === "string" && (Array.isArray(s.default) || object(s.default)) && !own(s, "options")) s.type = "json";\n if (!["number", "string", "boolean", "json"].includes(s.type)) fail(`unsupported state type ${k}; use number, string, boolean or json (arrays/objects)`);\n if (s.type === "json" && own(s, "options")) fail(`state.${k}: json state does not support select options; use scalar options or a canvas`);\n if (s.type === "number") {\n if (![s.min, s.max, s.step].every(finite) || s.min >= s.max || s.step <= 0) fail(`invalid numeric limits for ${k}`);\n if ((s.max - s.min) / s.step > 1e9) fail(`numeric step too small for ${k}`);\n } else if (["min", "max", "step"].some((p) => own(s, p))) fail(`numeric limits on non-number ${k}`);\n if (s.options && (!Array.isArray(s.options) || !s.options.length || s.options.length > 64 || !s.options.every(scalar))) fail(`invalid options for ${k}`);\n }\n const defaults = validateState(doc2, initialState(doc2));\n let expressions = 0;\n for (const [k, spec] of Object.entries(doc2.state))\n for (const option of spec.options ?? []) validateState(doc2, { ...defaults, [k]: option });\n function validateExpression(e, depth = 0, itemRows) {\n if (++expressions > LIMITS.expressions || depth > LIMITS.depth) fail("expression limit exceeded");\n if (!object(e)) fail("expression must be an object");\n if (own(e, "map")) {\n if (depth !== 0) fail("map is only allowed at the root of a derived value");\n fields(e, ["map"], "expression");\n fields(e.map, ["source", "fields"], "map");\n if (typeof e.map.source !== "string" || !e.map.source.startsWith("data.")) fail(\'map source must reference static data using a string such as "data.samples"; put object rows in document.data.samples, not inline in map.source\');\n const [, k] = reference(doc2, e.map.source), rows = doc2.data[k];\n if (!Array.isArray(rows) || rows.length > LIMITS.rows || !rows.every(object)) fail("map source must be object rows");\n if (!object(e.map.fields) || !Object.keys(e.map.fields).length || Object.keys(e.map.fields).length > 16) fail("map requires 1..16 fields");\n for (const [k2, value2] of Object.entries(e.map.fields)) {\n if (!validIdentifier(k2)) fail(`invalid map field ${JSON.stringify(k2).slice(0, 100)}; use a letter/underscore identifier such as powerW or \\u529F\\u7387, without spaces, dots or hyphens`);\n validateExpression(value2, depth + 1, rows);\n }\n return;\n }\n if (own(e, "const")) {\n fields(e, ["const"], "expression");\n if (!scalar(e.const)) fail("constant must be scalar");\n return;\n }\n if (own(e, "ref")) {\n fields(e, ["ref"], "expression");\n if (typeof e.ref === "string" && e.ref.startsWith("item.")) {\n const k = e.ref.slice(5);\n if (!itemRows || !validIdentifier(k) || itemRows.some((row) => !own(row, k) || !scalar(row[k]))) fail("invalid or unscoped item reference");\n } else reference(doc2, e.ref);\n return;\n }\n fields(e, ["op", "args"], "expression");\n const arity = ARITY[e.op];\n if (!arity || !Array.isArray(e.args) || e.args.length < arity[0] || e.args.length > arity[1]) fail("invalid operator or argument count");\n e.args.forEach((a) => validateExpression(a, depth + 1, itemRows));\n }\n Object.values(doc2.derived).forEach((e) => validateExpression(e));\n const visited = /* @__PURE__ */ new Set(), active = /* @__PURE__ */ new Set();\n function visit(k) {\n if (active.has(k)) fail(`cyclic calculation ${k}`);\n if (visited.has(k)) return;\n active.add(k);\n function walk(e) {\n if (e.ref?.startsWith("derived.")) visit(e.ref.split(".")[1]);\n for (const a of e.args ?? []) walk(a);\n for (const value2 of Object.values(e.map?.fields ?? {})) walk(value2);\n }\n walk(doc2.derived[k]);\n active.delete(k);\n visited.add(k);\n }\n Object.keys(doc2.derived).forEach(visit);\n for (const [k, a] of Object.entries(doc2.actions)) {\n fields(a, ["type", "intent", "stateKeys"], `actions.${k}`);\n if (a.type !== "agent.followup") fail("only agent.followup actions are supported");\n text(a.intent, "action intent");\n if (!a.intent.trim() || !Array.isArray(a.stateKeys) || a.stateKeys.length > 64 || new Set(a.stateKeys).size !== a.stateKeys.length || a.stateKeys.some((s) => !own(doc2.state, s))) fail("invalid action stateKeys");\n }\n const ids = /* @__PURE__ */ new Set();\n let count2 = 0;\n const value = (v, at) => {\n if (object(v)) {\n if (own(v, "map")) fail("map belongs in derived");\n validateExpression(v);\n } else if (!scalar(v)) fail(`invalid value at ${at}`);\n };\n function nodes(list, depth = 0) {\n if (!Array.isArray(list) || depth > LIMITS.depth) fail("invalid node children or depth");\n for (const n of list) {\n if (++count2 > LIMITS.nodes) fail("too many nodes");\n if (!object(n) || !validIdentifier(n.id) || ids.has(n.id)) fail("node ids must be unique identifiers");\n ids.add(n.id);\n if (!Object.hasOwn(COMPONENTS, n.type)) fail("unsupported component " + n.type);\n if (["table", "chart"].includes(n.type) && typeof n.rows === "string" && /^(data|derived)\\./.test(n.rows)) n.rows = { ref: n.rows };\n switch (n.type) {\n case "sandbox": {\n text(n.title, "canvas title");\n text(n.fallback, "canvas fallback");\n if (!n.title.trim() || !n.fallback.trim() || typeof n.html !== "string" || !n.html.trim()) fail("canvas requires title, HTML and fallback");\n for (const field of ["html", "css", "script"]) if (n[field] !== void 0 && (typeof n[field] !== "string" || n[field].length > SANDBOX_LIMITS.source)) fail("canvas source exceeds 48 Ki characters");\n if (n.height !== void 0 && (!Number.isInteger(n.height) || n.height < 160 || n.height > SANDBOX_LIMITS.height)) fail("canvas height must be 160..1000");\n for (const [field, group] of [["stateKeys", "state"], ["dataKeys", "data"], ["derivedKeys", "derived"]]) if (n[field] !== void 0 && (!Array.isArray(n[field]) || n[field].length > 64 || new Set(n[field]).size !== n[field].length || n[field].some((k) => !own(doc2[group], k)))) fail("invalid canvas " + field);\n break;\n }\n case "row":\n case "column":\n break;\n case "card":\n if (n.title !== void 0) text(n.title, "card title");\n if (n.surface !== void 0 && !UI_SURFACES.includes(n.surface)) fail("unknown card surface");\n break;\n case "details":\n text(n.title, "details title");\n if (n.open === "true" || n.open === "false") n.open = n.open === "true";\n if (n.open !== void 0 && typeof n.open !== "boolean") fail(`node.${n.id}.open must be boolean true or false`);\n break;\n case "badge":\n value(n.text, "badge text");\n break;\n case "progress":\n text(n.label, "progress label");\n value(n.value, "progress value");\n value(n.max, "progress max");\n if (n.unit !== void 0) text(n.unit, "progress unit");\n break;\n case "steps": {\n if (object(n.items) && Object.keys(n.items).length === 1 && Array.isArray(n.items.item)) n.items = n.items.item;\n const at = `node.${n.id}.items`;\n if (!Array.isArray(n.items)) fail(`${at} must be an array of 1..12 step objects, received ${n.items === null ? "null" : typeof n.items}. Use items:[{"title":"First step","description":"One sentence"}], not items:{item:[...]}. status is optional for a static guide`);\n if (!n.items.length || n.items.length > 12) fail(`${at} requires 1..12 items; received ${n.items.length}. Split a longer guide into separate steps nodes`);\n for (const [i, item] of n.items.entries()) {\n const stepAt = `${at}[${i}]`;\n fields(item, ["title", "description", "status"], stepAt);\n text(item.title, `${stepAt}.title`);\n if (item.description !== void 0) text(item.description, `${stepAt}.description`);\n if (item.status !== void 0 && !["pending", "active", "done", "error"].includes(item.status)) fail(`${stepAt}.status must be pending, active, done or error; omit status for a static guide`);\n }\n break;\n }\n case "heading":\n case "text":\n value(n.text, "text");\n break;\n case "metric":\n text(n.label, "metric label");\n value(n.value, "metric value");\n if (n.unit !== void 0) text(n.unit, "unit");\n if (n.decimals !== void 0 && (!Number.isInteger(n.decimals) || n.decimals < 0 || n.decimals > 6)) fail("invalid decimals");\n break;\n case "input":\n case "slider":\n case "select":\n case "toggle": {\n text(n.label, "control label");\n if (typeof n.bind !== "string" || !n.bind.startsWith("state.")) fail("controls must bind state");\n const [, k] = reference(doc2, n.bind), spec = doc2.state[k];\n if (n.type === "slider" && spec.type !== "number") fail("slider requires numeric state");\n if (n.type === "toggle" && spec.type !== "boolean") fail("toggle requires boolean state");\n if (["input", "select"].includes(n.type) && spec.type === "json") fail(`node.${n.id}: structured state requires a sandbox with DSHUI.setState; use separate boolean states and toggle nodes for a native checklist`);\n if (n.type === "input" && spec.type === "boolean") fail("use toggle for boolean state");\n if (n.type === "select" && !spec.options) fail("select requires options");\n break;\n }\n case "button":\n text(n.text, "button text");\n if (!own(doc2.actions, n.action)) fail("unknown action");\n break;\n case "table": {\n if (n.label !== void 0) text(n.label, "table label");\n if (!Array.isArray(n.rows)) value(n.rows, "table rows");\n if (!Array.isArray(n.columns) || !n.columns.length || n.columns.length > 16) fail("invalid table columns");\n const cols = /* @__PURE__ */ new Set();\n for (const c of n.columns) {\n fields(c, ["key", "label", "unit"], "column");\n if (!validIdentifier(c.key) || cols.has(c.key)) fail("invalid or duplicate column key");\n cols.add(c.key);\n text(c.label, "column label");\n if (c.unit !== void 0) text(c.unit, "column unit");\n }\n break;\n }\n case "chart":\n if (!Array.isArray(n.rows)) value(n.rows, "chart rows");\n if (!["bar", "line"].includes(n.kind) || !validIdentifier(n.labelKey) || !validIdentifier(n.valueKey)) fail("invalid chart");\n text(n.label, "chart label");\n if (n.unit !== void 0) text(n.unit, "chart unit");\n if (n.xScale !== void 0 && (!["category", "linear"].includes(n.xScale) || n.xScale === "linear" && n.kind !== "line")) fail("invalid xScale");\n break;\n default:\n fail(`unsupported component ${n.type}`);\n }\n if (["heading", "text", "card", "metric", "button", "badge", "progress", "details"].includes(n.type)) {\n if (n.icon !== void 0 && !UI_ICONS.includes(n.icon)) fail("unknown UI icon");\n if (n.type !== "button") {\n if (n.tone !== void 0 && !UI_TONES.includes(n.tone)) fail("unknown UI tone");\n }\n }\n fields(n, componentFields(n.type), `node.${n.id}`);\n if (["row", "column", "card", "details"].includes(n.type)) nodes(n.children, depth + 1);\n }\n }\n nodes(doc2.nodes);\n if (!count2) fail("at least one node is required");\n evaluate(doc2, defaults);\n return doc2;\n }\n\n // src/export-runtime.js\n var payload = JSON.parse(document.getElementById("dsh-ui-export").textContent);\n var doc = validateDocument(payload.document);\n var node = doc.nodes.flatMap(function flatten(n) {\n return [n, ...(n.children ?? []).flatMap(flatten)];\n }).find((n) => n.id === payload.nodeId && n.type === "sandbox");\n if (!node) throw Error("\\u5BFC\\u51FA\\u7684\\u753B\\u5E03\\u4E0D\\u5B58\\u5728");\n var state = validateState(doc, payload.state);\n var connected = false;\n var start = Date.now();\n var count = 0;\n var token = () => Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("");\n var channel = token();\n var nonce = payload.nonce;\n var frame = document.createElement("iframe");\n frame.title = node.title;\n frame.sandbox = "allow-scripts";\n frame.referrerPolicy = "no-referrer";\n frame.style.cssText = "display:block;width:100%;height:" + (node.height ?? 420) + "px;border:0";\n var status = document.getElementById("status");\n var publish = () => {\n try {\n frame.contentWindow.postMessage({ dshCanvas: 1, channel, type: "snapshot", payload: { ...canvasSnapshot(node, state, doc.data, evaluate(doc, state).derived, false, payload.theme), visible: !document.hidden } }, "*");\n } catch (e) {\n status.textContent = "\\u8BA1\\u7B97\\u5931\\u8D25\\uFF1A" + e.message;\n }\n };\n addEventListener("message", (event) => {\n const m = event.data;\n if (event.source !== frame.contentWindow || m?.channel !== channel || m.dshCanvas !== 1) return;\n if (Date.now() - start > 1e3) {\n start = Date.now();\n count = 0;\n }\n if (++count > SANDBOX_LIMITS.rate) return;\n if (m.type === "ready") {\n connected = true;\n publish();\n } else if (m.type === "patch" && connected) {\n try {\n const next = validateState(doc, { ...state, ...canvasPatch(m.payload, node) });\n evaluate(doc, next);\n state = next;\n status.textContent = "\\u672C\\u5730\\u53C2\\u6570\\u5DF2\\u66F4\\u65B0\\uFF1B\\u5237\\u65B0\\u6587\\u4EF6\\u4F1A\\u56DE\\u5230\\u5BFC\\u51FA\\u65F6\\u7684\\u53C2\\u6570\\u3002";\n } catch (e) {\n status.textContent = "\\u53C2\\u6570\\u65E0\\u6548\\uFF1A" + e.message;\n }\n publish();\n } else if (m.type === "height" && Number.isFinite(m.payload) && m.payload >= 0) frame.style.height = Math.max(32, Math.min(1e3, Math.ceil(m.payload))) + "px";\n else if (m.type === "error") status.textContent = "\\u753B\\u5E03\\u8FD0\\u884C\\u5F02\\u5E38\\uFF1A" + String(m.payload).slice(0, 1e3);\n });\n document.addEventListener("visibilitychange", () => {\n if (connected) publish();\n });\n document.getElementById("surface").append(frame);\n frame.srcdoc = sandboxDocument(node, channel, nonce);\n})();\n';
|
|
22730
|
+
var EXPORT_RUNTIME = '(() => {\n // lib/presentation.js\n var UI_ICONS = Object.freeze(["sparkles", "sliders", "layers", "chart", "gauge", "cpu", "database", "info", "check", "warning", "arrow", "target"]);\n var UI_TONES = Object.freeze(["neutral", "accent", "success", "warning", "danger"]);\n var UI_LAYOUTS = Object.freeze(["dashboard", "workbench", "comparison", "guide", "status", "report"]);\n var UI_ACCENTS = Object.freeze(["blue", "teal", "violet", "amber", "rose"]);\n var UI_SURFACES = Object.freeze(["outline", "soft", "featured"]);\n\n // lib/visual-audit.js\n function measureVisual(root, nodeId = "") {\n const issues = [], seen = /* @__PURE__ */ new Set(), coverage = { nodeId, elements: 0, truncated: false, contrastChecked: 0, contrastUncovered: 0 };\n const add = (code, value, el2) => {\n const id = el2?.closest("[data-ui-node]")?.getAttribute("data-ui-node") ?? nodeId;\n const k = id + ":" + code + ":" + Math.round(value ?? 0);\n if (!seen.has(k) && issues.length < 40) {\n seen.add(k);\n issues.push({ code, nodeId: id, ...value === void 0 ? {} : { value: Number(value.toFixed(2)) } });\n }\n };\n const color = (s) => {\n let m = s.match(/^rgba?\\(([^)]+)\\)/);\n if (m) {\n const a = m[1].split(/[\\s,\\/]+/).map(Number);\n return [a[0] / 255, a[1] / 255, a[2] / 255, a[3] ?? 1];\n }\n m = s.match(/^color\\(srgb ([^)]+)\\)/);\n if (m) {\n const a = m[1].split(/[\\s\\/]+/).map(Number);\n return [a[0], a[1], a[2], a[3] ?? 1];\n }\n return null;\n };\n const luminance = (c) => c.slice(0, 3).map((v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4).reduce((s, v, i) => s + v * [0.2126, 0.7152, 0.0722][i], 0);\n const widthRoot = root.id === "canvas" ? document.documentElement : root;\n if (widthRoot.scrollWidth > widthRoot.clientWidth + 2) add("overflow", widthRoot.scrollWidth - widthRoot.clientWidth);\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);\n let el;\n while (el = walker.nextNode()) {\n if (coverage.elements >= 2e3) {\n coverage.truncated = true;\n break;\n }\n coverage.elements++;\n if (el.matches("[data-canvas-phase=error]")) add("runtimeError", void 0, el);\n const css = getComputedStyle(el), box = el.getBoundingClientRect();\n if (!box.width || !box.height || css.visibility === "hidden" || css.display === "none") continue;\n const text2 = [...el.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim());\n if (text2) {\n let size = parseFloat(css.fontSize);\n if (el instanceof SVGElement && el.getScreenCTM) {\n const m = el.getScreenCTM();\n if (m) size *= Math.hypot(m.c, m.d);\n }\n if (size < 11.9) add("smallText", size, el);\n const fg = color(css.color);\n let ancestor = el, bg = null, depth = 0;\n while (ancestor && depth++ < 64) {\n const style = getComputedStyle(ancestor);\n if (style.backgroundImage !== "none") break;\n const c = color(style.backgroundColor);\n if (c?.[3] === 1) {\n bg = c;\n break;\n }\n if (c?.[3] > 0) break;\n ancestor = ancestor.parentElement;\n }\n if (!(el instanceof SVGElement) && fg?.[3] === 1 && bg) {\n coverage.contrastChecked++;\n const a = luminance(fg), b = luminance(bg), ratio = (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);\n if (ratio < (size >= 24 || size >= 18.66 && parseFloat(css.fontWeight) >= 700 ? 3 : 4.5)) add("contrast", ratio, el);\n } else coverage.contrastUncovered++;\n }\n if (el.matches("input:not([type=hidden]),select,textarea,button,[role=button]") && !el.getAttribute("aria-label") && !el.getAttribute("aria-labelledby") && !el.labels?.length && !el.textContent.trim() && !el.getAttribute("title")) add("missingLabel", void 0, el);\n }\n Object.defineProperty(issues, "coverage", { value: coverage });\n return issues;\n }\n\n // lib/format-number.js\n function formatNumber(value, decimals = 2) {\n if (typeof value !== "number" || !Number.isFinite(value)) throw new Error("DSH UI: formatNumber requires a finite number");\n if (!Number.isInteger(decimals) || decimals < 0 || decimals > 6) throw new Error("DSH UI: formatNumber decimals must be an integer from 0 to 6");\n return value.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals });\n }\n\n // lib/canvas-system.js\n var CANVAS_SYSTEM_CSS = `\n:root{--canvas-radius:20px;--canvas-gap:16px;--canvas-positive:#167456;--canvas-negative:#b13b3b;--canvas-warning:#875b15}\n:root[data-canvas-scheme=dark]{--canvas-positive:#78d9b4;--canvas-negative:#ffaaaa;--canvas-warning:#edc87e}\n#canvas{display:flow-root}html,body{margin:0;min-width:0;background:var(--canvas-bg,#fff);color:var(--canvas-ink,#182537);font:14px/1.6 system-ui,"Microsoft YaHei",sans-serif}*{box-sizing:border-box}svg{max-width:100%;height:auto}button,input,select,textarea{font:inherit;color:inherit}button,input,select,textarea{border:1px solid var(--canvas-line,#ced4df);border-radius:8px;background:var(--canvas-bg,#fff);padding:8px 12px;max-width:100%}button{cursor:pointer}button:disabled{cursor:default;opacity:.55}input[type=range]{padding:0;accent-color:var(--canvas-accent,#3e60d3)}input[type=checkbox],input[type=radio]{accent-color:var(--canvas-accent,#3e60d3)}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--canvas-accent,#3e60d3);outline-offset:3px}\n.ui-stack{display:grid;gap:var(--canvas-gap)}.ui-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:var(--canvas-gap)}.ui-surface{padding:20px;border:1px solid var(--canvas-line,#ced4df);border-radius:var(--canvas-radius);background:var(--canvas-soft,#f3f5f8)}.ui-title{margin:0;font-size:clamp(20px,3vw,28px);line-height:1.3;letter-spacing:-.02em}.ui-caption{color:var(--canvas-muted,#526176);margin:8px 0;font-size:14px}.ui-value{font-size:clamp(24px,4vw,36px);font-variant-numeric:tabular-nums;line-height:1.2}.ui-controls{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.ui-field{display:grid;gap:6px;min-width:0}.ui-legend{display:flex;gap:16px;flex-wrap:wrap;font-size:13px}.ui-status{min-height:1.6em}.ui-error{color:var(--canvas-negative)}.ui-primary{background:var(--canvas-accent,#3e60d3);color:var(--canvas-accent-ink,#fff);border-color:transparent}\nsvg .ui-node{fill:var(--canvas-soft,#f3f5f8);stroke:var(--canvas-line,#ced4df);stroke-width:1}svg .ui-edge{fill:none;stroke:var(--canvas-muted,#526176);stroke-width:1.5}svg .ui-label{fill:var(--canvas-ink,#182537);font:500 14px system-ui,sans-serif}svg .ui-note{fill:var(--canvas-muted,#526176);font:400 13px system-ui,sans-serif}svg .ui-leader{fill:none;stroke:var(--canvas-muted,#526176);stroke-dasharray:3 4}svg .ui-accent{fill:color-mix(in srgb,var(--canvas-accent,#3e60d3) 12%,var(--canvas-bg,#fff));stroke:var(--canvas-accent,#3e60d3)}\n.ui-flow{display:flex;align-items:center;justify-content:center;gap:16px;padding:24px 8px;flex-wrap:wrap}.ui-step{display:grid;gap:6px;min-width:130px;max-width:240px;padding:16px 20px;border:1px solid var(--canvas-line);border-radius:6px;background:var(--canvas-bg)}.ui-step strong{font-size:16px;font-weight:600}.ui-step span{font-size:14px;color:var(--canvas-muted)}.ui-arrow{color:var(--canvas-muted);font-size:22px}.ui-callout{border-inline-start:3px solid var(--canvas-accent);padding:8px 16px;color:var(--canvas-ink)}\n.ui-surface,.ui-step{border-radius:var(--canvas-radius);background:linear-gradient(135deg,color-mix(in srgb,var(--canvas-accent,#3e60d3) 9%,var(--canvas-bg,#fff)),var(--canvas-soft,#f3f5f8))}button,input:not([type=checkbox]):not([type=radio]):not([type=range]),select,textarea{border-radius:14px}.ui-primary{background:linear-gradient(120deg,var(--canvas-accent,#3e60d3),color-mix(in srgb,var(--canvas-accent,#3e60d3) 85%,#152647))}.ui-callout{border-radius:14px;background:linear-gradient(120deg,color-mix(in srgb,var(--canvas-accent,#3e60d3) 7%,var(--canvas-bg,#fff)),var(--canvas-bg,#fff))}\n@media(max-width:480px){.ui-flow{flex-direction:column;padding:16px 8px;gap:12px}.ui-step{width:100%;max-width:none}.ui-arrow{transform:rotate(90deg)}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}`;\n\n // lib/sandbox.js\n var SANDBOX_LIMITS = Object.freeze({ source: 49152, height: 1e3, message: 16384, rate: 30 });\n function canvasSnapshot(node2, state2, data, derived, disabled, theme) {\n const pick = (keys, values) => Object.fromEntries((keys ?? []).map((k) => [k, values[k]]));\n return { state: pick(node2.stateKeys, state2), data: pick(node2.dataKeys, data), derived: pick(node2.derivedKeys, derived), disabled, theme };\n }\n function canvasPatch(message, node2) {\n if (!message || typeof message !== "object" || Array.isArray(message)) throw new Error("Invalid canvas patch");\n const keys = Object.keys(message);\n if (!keys.length || keys.length > 64 || keys.some((k) => !(node2.stateKeys ?? []).includes(k))) throw new Error("Canvas state key is not declared");\n if (JSON.stringify(message).length > SANDBOX_LIMITS.message) throw new Error("Canvas patch is too large");\n return message;\n }\n function sandboxDocument(node2, channel2, nonce2) {\n const json = (value) => JSON.stringify(value).replace(/</g, "\\\\u003c").replace(/\\u2028/g, "\\\\u2028").replace(/\\u2029/g, "\\\\u2029");\n return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src \'none\'; script-src \'nonce-${nonce2}\'; style-src \'unsafe-inline\'; img-src data:; font-src \'none\'; connect-src \'none\'; media-src \'none\'; object-src \'none\'; frame-src \'none\'; worker-src \'none\'; base-uri \'none\'; form-action \'none\'"><meta name="viewport" content="width=device-width,initial-scale=1"><style>${CANVAS_SYSTEM_CSS}</style></head><body><main id="canvas"></main><script nonce="${nonce2}">(()=>{\n const channel=${json(channel2)}, source=${json({ html: node2.html, css: node2.css ?? "", script: node2.script ?? "" })};\n let snapshot={state:{},data:{},derived:{},disabled:true,theme:{}}, started=false;\n const listeners=new Set(), send=(type,payload)=>parent.postMessage({dshCanvas:1,channel,type,payload},\'*\');\n const report=e=>send(\'error\',String(e?.message??e).slice(0,1000));\n const audit=${measureVisual.toString()};let auditTimer;const scheduleAudit=()=>{clearTimeout(auditTimer);auditTimer=setTimeout(()=>{const issues=audit(document.getElementById(\'canvas\'),${json(node2.id ?? "")});send(\'audit\',{issues,coverage:issues.coverage})},400)};\n addEventListener(\'resize\',scheduleAudit);\n addEventListener(\'error\',e=>report(e.error??e.message));addEventListener(\'unhandledrejection\',e=>report(e.reason));\n const animations=new Set(), motion=matchMedia(\'(prefers-reduced-motion: reduce)\');\n let raf=0,last=0,time=0;\n const visible=()=>snapshot.visible!==false&&!document.hidden;\n const canAnimate=()=>visible()&&!snapshot.disabled&&!motion.matches;\n const draw=delta=>{for(const fn of animations)try{fn({time,delta,reducedMotion:motion.matches,visible:visible()})}catch(e){animations.delete(fn);report(e)}};\n const tick=t=>{raf=0;if(!canAnimate()||!animations.size){last=0;draw(0);return}const delta=last?Math.min(.05,(t-last)/1000):0;last=t;time+=delta;draw(delta);if(animations.size)raf=requestAnimationFrame(tick)};\n const refresh=()=>{draw(0);if(canAnimate()&&animations.size){if(!raf){last=0;raf=requestAnimationFrame(tick)}}else{cancelAnimationFrame(raf);raf=0;last=0}};\n motion.addEventListener(\'change\',refresh);document.addEventListener(\'visibilitychange\',refresh);\n const formatNumber=${formatNumber.toString()};\n const api={formatNumber,get snapshot(){return structuredClone(snapshot)},subscribe(fn){if(typeof fn!==\'function\')throw Error(\'Listener required\');listeners.add(fn);fn(api.snapshot);return()=>listeners.delete(fn)},setState(patch){if(!snapshot.disabled)send(\'patch\',patch)},animate(fn){if(typeof fn!==\'function\')throw Error(\'Animation callback required\');animations.add(fn);refresh();return()=>{animations.delete(fn);if(!animations.size){cancelAnimationFrame(raf);raf=0;last=0}}}};\n Object.defineProperty(window,\'DSHUI\',{value:Object.freeze(api),writable:false});\n for(const type of [\'click\',\'input\',\'change\',\'keydown\',\'pointerdown\'])document.addEventListener(type,e=>{if(snapshot.disabled){e.preventDefault();e.stopImmediatePropagation()}},true);\n addEventListener(\'message\',event=>{\n const m=event.data;if(event.source!==parent||m?.dshCanvas!==1||m.channel!==channel||m.type!==\'snapshot\')return;\n snapshot=m.payload;for(const [k,v] of Object.entries(snapshot.theme??{}))document.documentElement.style.setProperty(\'--canvas-\'+k,v);\n document.documentElement.style.colorScheme=snapshot.theme?.scheme??\'light\';document.documentElement.dataset.canvasScheme=snapshot.theme?.scheme??\'light\';\n const wasStarted=started;\n if(!started){started=true;document.getElementById(\'canvas\').innerHTML=source.html;const style=document.createElement(\'style\');style.textContent=source.css;document.head.append(style);const script=document.createElement(\'script\');script.nonce=${json(nonce2)};script.textContent=source.script;document.body.append(script)}\n if(wasStarted)for(const fn of listeners)try{fn(api.snapshot)}catch(e){report(e)}\n refresh();if(!wasStarted)send(\'mounted\');scheduleAudit();\n });\n let resizePending=false;new ResizeObserver(()=>{if(!started||resizePending)return;resizePending=true;requestAnimationFrame(()=>{resizePending=false;send(\'height\',Math.ceil(document.getElementById(\'canvas\').getBoundingClientRect().height))})}).observe(document.getElementById(\'canvas\'));\n send(\'ready\');\n})();<\\/script></body></html>`;\n }\n\n // lib/components.js\n var define = (fields2, purpose, presentation = "") => Object.freeze({ fields: Object.freeze(fields2), purpose, presentation });\n var COMPONENTS = Object.freeze({\n heading: define(["text"], "Section heading", "tone"),\n text: define(["text"], "Readable explanation or bound text", "tone"),\n row: define(["children"], "Wrapping parallel content"),\n column: define(["children"], "Ordered vertical content"),\n card: define(["title", "children", "surface"], "Meaningful grouped section", "tone"),\n details: define(["title", "children", "open"], "Collapsible supporting content", "tone"),\n badge: define(["text"], "Short explicit status label", "tone"),\n progress: define(["label", "value", "max", "unit"], "Declared numeric progress snapshot", "tone"),\n steps: define(["items"], "Ordered declared steps"),\n metric: define(["label", "value", "unit", "decimals"], "Number with units, or a literal/bound status string", "tone"),\n input: define(["label", "bind"], "String or numeric state input"),\n slider: define(["label", "bind"], "Numeric state control"),\n select: define(["label", "bind"], "Selection from declared state options"),\n toggle: define(["label", "bind"], "Boolean state control"),\n button: define(["text", "action"], "Declared Agent follow-up", "icon"),\n table: define(["label", "rows", "columns"], "Supplied or derived values; inline rows or data/derived reference"),\n chart: define(["kind", "rows", "labelKey", "valueKey", "label", "unit", "xScale"], "Bar/line comparison with accessible data"),\n sandbox: define(["title", "html", "css", "script", "fallback", "height", "stateKeys", "dataKeys", "derivedKeys"], "Freeform isolated HTML/SVG/CSS/JavaScript visualization")\n });\n function componentFields(type) {\n const c = Object.hasOwn(COMPONENTS, type) ? COMPONENTS[type] : void 0;\n return c && ["id", "type", ...c.fields, ...c.presentation ? ["icon"] : [], ...c.presentation === "tone" ? ["tone"] : []];\n }\n\n // lib/identifiers.js\n var IDENTIFIER_SOURCE = "[\\\\p{L}_][\\\\p{L}\\\\p{M}\\\\p{N}_]{0,63}";\n var pattern = new RegExp(`^${IDENTIFIER_SOURCE}$`, "u");\n var validIdentifier = (value) => typeof value === "string" && pattern.test(value) && !["constructor", "prototype", "__proto__"].includes(value);\n\n // lib/author-expression.js\n function compileExpression(source) {\n const fail2 = () => {\n throw new Error("DSH UI: invalid formula; use state.x, data.x, derived.x, item.x, numbers, strings, + - * /, comparisons, ?: and min/max/round/abs");\n };\n if (typeof source !== "string" || !source.trim() || source.length > 2048) fail2();\n const tokens = [];\n let offset = 0;\n const token2 = new RegExp(String.raw`\\s*(?:(\\d+(?:\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?|("(?:[^"\\\\]|\\\\.)*")|(${IDENTIFIER_SOURCE}(?:\\.${IDENTIFIER_SOURCE})?)|(===|!==|<=|>=|==|!=|[+*/()?,:<>-]))`, "uy");\n while (offset < source.length) {\n if (!source.slice(offset).trim()) break;\n token2.lastIndex = offset;\n const m = token2.exec(source);\n if (!m) fail2();\n offset = token2.lastIndex;\n tokens.push(m[1] ? { number: Number(m[1] + (m[2] ?? "")) } : m[3] ? { string: JSON.parse(m[3]) } : m[4] ?? m[5]);\n if (tokens.length > 256) fail2();\n }\n let pos = 0;\n const operators = { "==": ["eq", 1], "===": ["eq", 1], "!=": ["neq", 1], "!==": ["neq", 1], "<": ["lt", 2], "<=": ["lte", 2], ">": ["gt", 2], ">=": ["gte", 2], "+": ["add", 3], "-": ["sub", 3], "*": ["mul", 4], "/": ["div", 4] };\n const take = (t) => {\n if (tokens[pos++] !== t) fail2();\n };\n function parse(min = 0, depth = 0) {\n if (depth > 12) fail2();\n const t = tokens[pos++];\n let out;\n if (t && typeof t === "object") out = { const: Object.hasOwn(t, "number") ? t.number : t.string };\n else if (t === "true" || t === "false") out = { const: t === "true" };\n else if (t === "(") {\n out = parse(0, depth + 1);\n take(")");\n } else if (t === "-" || t === "+") {\n const arg = parse(5, depth + 1);\n out = t === "-" ? { op: "sub", args: [{ const: 0 }, arg] } : arg;\n } else if (typeof t === "string" && t.split(".").length === 2 && ["state", "data", "derived", "item"].includes(t.split(".")[0]) && validIdentifier(t.split(".")[1])) out = { ref: t };\n else if (["min", "max", "round", "abs"].includes(t)) {\n take("(");\n const args = [parse(0, depth + 1)];\n while (tokens[pos] === ",") {\n pos++;\n args.push(parse(0, depth + 1));\n if (args.length > 8) fail2();\n }\n take(")");\n out = { op: t, args };\n } else fail2();\n while (typeof tokens[pos] === "string" && Object.hasOwn(operators, tokens[pos]) && operators[tokens[pos]][1] >= min) {\n const [op, precedence] = operators[tokens[pos++]];\n out = { op, args: [out, parse(precedence + 1, depth + 1)] };\n }\n if (min === 0 && tokens[pos] === "?") {\n pos++;\n const yes = parse(0, depth + 1);\n take(":");\n out = { op: "if", args: [out, yes, parse(0, depth + 1)] };\n }\n return out;\n }\n const result = parse();\n if (pos !== tokens.length) fail2();\n return result;\n }\n function expandExpressions(doc2) {\n const expression2 = (e) => {\n if (!e || typeof e !== "object" || Array.isArray(e)) return e;\n if (Object.hasOwn(e, "expr")) {\n if (Object.keys(e).length !== 1) throw new Error("DSH UI: expr must be the only formula field");\n return compileExpression(e.expr);\n }\n if (Array.isArray(e.args)) e.args = e.args.map(expression2);\n if (e.map?.fields) for (const k of Object.keys(e.map.fields)) e.map.fields[k] = expression2(e.map.fields[k]);\n return e;\n };\n if (doc2.derived && typeof doc2.derived === "object") for (const k of Object.keys(doc2.derived)) doc2.derived[k] = expression2(doc2.derived[k]);\n const walk = (nodes) => {\n if (!Array.isArray(nodes)) return;\n for (const node2 of nodes) {\n if (!node2 || typeof node2 !== "object") continue;\n for (const field of ["text", "value", "max", "rows"]) if (Object.hasOwn(node2, field)) node2[field] = expression2(node2[field]);\n walk(node2.children);\n }\n };\n walk(doc2.nodes);\n return doc2;\n }\n\n // lib/normalize-authoring.js\n var object = (v) => v !== null && typeof v === "object" && !Array.isArray(v);\n var own = (v, k) => Object.hasOwn(v, k);\n var singleItem = (v) => object(v) && Object.keys(v).length === 1 && own(v, "item");\n var array = (v) => singleItem(v) && Array.isArray(v.item) ? v.item : v;\n var numericOps = /* @__PURE__ */ new Set(["add", "sub", "mul", "div", "min", "max", "round", "abs", "lt", "lte", "gt", "gte"]);\n function number(v) {\n if (typeof v !== "string" || !/^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:e[+-]?\\d+)?$/i.test(v.trim())) return v;\n const n = Number(v);\n return Number.isFinite(n) && (!Number.isInteger(n) || Number.isSafeInteger(n)) ? n : v;\n }\n var boolean = (v) => v === "true" ? true : v === "false" ? false : v;\n function expression(e, numeric = false, logical = false) {\n if (!object(e)) return e;\n if (own(e, "const")) {\n if (numeric) e.const = number(e.const);\n if (logical) e.const = boolean(e.const);\n }\n if (own(e, "args")) e.args = array(e.args);\n if (Array.isArray(e.args)) e.args.forEach((arg, i) => expression(arg, numericOps.has(e.op) || e.op === "if" && i > 0 && numeric, e.op === "if" && i === 0));\n if (object(e.map?.fields)) Object.values(e.map.fields).forEach((e2) => expression(e2));\n return e;\n }\n function normalizeAuthoring(doc2) {\n if (!object(doc2)) return doc2;\n if (own(doc2, "version")) doc2.version = number(doc2.version);\n if (object(doc2.state)) for (const k of Object.keys(doc2.state)) {\n let s = doc2.state[k];\n if (typeof s === "string" && s.trim().startsWith("{")) {\n try {\n const parsed = JSON.parse(s);\n if (object(parsed)) s = parsed;\n } catch {\n }\n }\n if (singleItem(s) && object(s.item)) s = s.item;\n doc2.state[k] = s;\n if (!object(s)) continue;\n if (own(s, "options")) s.options = array(s.options);\n if (s.type === "number") {\n for (const field of ["default", "min", "max", "step"]) if (own(s, field)) s[field] = number(s[field]);\n if (Array.isArray(s.options)) s.options = s.options.map(number);\n } else if (s.type === "boolean") {\n if (own(s, "default")) s.default = boolean(s.default);\n if (Array.isArray(s.options)) s.options = s.options.map(boolean);\n }\n }\n if (object(doc2.derived)) Object.values(doc2.derived).forEach((e) => expression(e));\n if (object(doc2.actions)) {\n for (const action of Object.values(doc2.actions)) if (object(action) && own(action, "stateKeys")) action.stateKeys = array(action.stateKeys);\n }\n function nodes(list) {\n list = array(list);\n if (!Array.isArray(list)) return list;\n for (const node2 of list) {\n if (!object(node2)) continue;\n if (node2.type === "sandbox") {\n if (!own(node2, "title")) node2.title = doc2.title;\n if (!own(node2, "fallback")) node2.fallback = doc2.fallback;\n }\n for (const field of ["children", "items", "columns", "stateKeys", "dataKeys", "derivedKeys"]) if (own(node2, field)) node2[field] = array(node2[field]);\n if (["table", "chart"].includes(node2.type) && own(node2, "rows")) node2.rows = array(node2.rows);\n for (const field of ["decimals", "height"]) if (own(node2, field)) node2[field] = number(node2[field]);\n if (node2.type === "details" && own(node2, "open")) node2.open = boolean(node2.open);\n for (const field of ["text", "value", "max", "rows"]) if (own(node2, field)) expression(node2[field], node2.type === "progress");\n if (["row", "column", "card", "details"].includes(node2.type) && own(node2, "children")) node2.children = nodes(node2.children);\n }\n return list;\n }\n if (own(doc2, "nodes")) doc2.nodes = nodes(doc2.nodes);\n return doc2;\n }\n\n // lib/protocol.js\n var LIMITS = Object.freeze({ bytes: 131072, nodes: 160, depth: 12, rows: 500, expressions: 128, steps: 4096 });\n var own2 = (o, k) => Object.hasOwn(o, k);\n var fail = (message) => {\n throw new Error(`DSH UI: ${message}`);\n };\n var object2 = (x) => x !== null && typeof x === "object" && !Array.isArray(x);\n var finite = (x) => typeof x === "number" && Number.isFinite(x);\n var scalar = (x) => typeof x === "string" || typeof x === "boolean" || finite(x);\n var text = (x, at) => {\n if (typeof x !== "string" || x.length > 8192) fail(`${at} must be a string of at most 8192 characters`);\n };\n function fields(value, allowed, at) {\n if (!object2(value)) fail(`${at} must be an object`);\n for (const k of Object.keys(value)) if (!allowed.includes(k)) {\n const hint = at === "document" ? " Controls and visual components belong in nodes as registered typed nodes, not extra document fields." : "";\n fail(`unknown field ${at}.${k}; allowed fields at ${at}: ${allowed.join(", ")}.${hint}`);\n }\n }\n function safeJson(value, depth = 0, source = false) {\n if (depth > 48) fail("JSON nesting is too deep");\n if (value === null || typeof value === "boolean") return;\n if (typeof value === "string") {\n if (source) {\n if (value.length > SANDBOX_LIMITS.source) fail("canvas source exceeds 48 Ki characters");\n return;\n }\n return text(value, "value");\n }\n if (typeof value === "number") {\n if (!finite(value)) fail("numbers must be finite");\n return;\n }\n if (typeof value !== "object") fail("only JSON values are accepted");\n if (Array.isArray(value) && value.length > LIMITS.rows) fail("too many array items");\n for (const k of Object.keys(value)) {\n if (["__proto__", "prototype", "constructor"].includes(k)) fail("unsafe property name");\n safeJson(value[k], depth + 1, value.type === "sandbox" && ["html", "css", "script"].includes(k));\n }\n }\n function initialState(doc2) {\n return Object.fromEntries(Object.entries(doc2.state).map(([k, spec]) => [k, spec.default]));\n }\n function validateState(doc2, state2) {\n if (!object2(state2) || Object.keys(state2).length !== Object.keys(doc2.state).length) fail("state keys do not match the document");\n const result = {};\n for (const [k, s] of Object.entries(doc2.state)) {\n if (!own2(state2, k)) fail(`missing state.${k}`);\n const v = state2[k];\n if (s.type === "number") {\n if (!finite(v) || v < s.min || v > s.max) fail(`state.${k} is outside its numeric range`);\n const offset = (v - s.min) / s.step;\n if (Math.abs(offset - Math.round(offset)) > 1e-6) fail(`state.${k} does not match its step`);\n } else if (s.type === "json") {\n if (!object2(v) && !Array.isArray(v)) fail(`state.${k} must be an array or object for type json`);\n safeJson(v);\n if (new TextEncoder().encode(JSON.stringify(v)).length > 16384) fail(`state.${k} JSON exceeds 16 KiB`);\n } else if (s.type === "string") text(v, `state.${k}`);\n else if (typeof v !== "boolean") fail(`state.${k} must be boolean`);\n if (s.options && !s.options.includes(v)) fail(`invalid option for state.${k}`);\n result[k] = v;\n }\n return result;\n }\n var ARITY = { add: [1, 8], sub: [2, 2], mul: [1, 8], div: [2, 2], min: [1, 8], max: [1, 8], round: [1, 1], abs: [1, 1], eq: [2, 2], neq: [2, 2], lt: [2, 2], lte: [2, 2], gt: [2, 2], gte: [2, 2], if: [3, 3] };\n function reference(doc2, ref) {\n if (typeof ref !== "string" || !["state", "data", "derived"].includes(ref.split(".")[0]) || ref.split(".").length !== 2 || !validIdentifier(ref.split(".")[1])) fail(`invalid reference ${JSON.stringify(ref)}; use state.key, data.key or derived.key with a letter/underscore identifier`);\n const [group, k] = ref.split(".");\n if (!own2(doc2[group], k)) fail(`unknown reference ${ref}`);\n return [group, k];\n }\n function evaluate(doc2, state2) {\n const derived = {}, active = /* @__PURE__ */ new Set();\n let steps = 0, generatedRows = 0;\n const budget = () => {\n if (++steps > LIMITS.steps) fail("calculation budget exceeded");\n };\n const resolve = (ref) => {\n budget();\n const [group, k] = reference(doc2, ref);\n if (group !== "derived") return (group === "state" ? state2 : doc2.data)[k];\n if (own2(derived, k)) return derived[k];\n if (active.has(k)) fail(`cyclic calculation ${k}`);\n active.add(k);\n const result2 = expression2(doc2.derived[k]);\n active.delete(k);\n derived[k] = result2;\n return result2;\n };\n const expression2 = (e, item) => {\n budget();\n if (own2(e, "map")) {\n const rows = resolve(e.map.source);\n if ((generatedRows += rows.length) > LIMITS.rows) fail("generated row limit exceeded");\n return rows.map((row) => Object.fromEntries(Object.entries(e.map.fields).map(([k, value]) => {\n const out2 = expression2(value, row);\n if (!scalar(out2)) fail("generated cells must be scalar");\n return [k, out2];\n })));\n }\n if (own2(e, "const")) return e.const;\n if (own2(e, "ref")) {\n if (e.ref.startsWith("item.")) {\n const k = e.ref.slice(5);\n if (!item || !own2(item, k) || !scalar(item[k])) fail("invalid item reference");\n return item[k];\n }\n return resolve(e.ref);\n }\n if (e.op === "if") {\n const condition = expression2(e.args[0], item);\n if (typeof condition !== "boolean") fail("if condition must be boolean");\n return expression2(e.args[condition ? 1 : 2], item);\n }\n const a = e.args.map((arg) => expression2(arg, item));\n if (e.op === "eq") return a[0] === a[1];\n if (e.op === "neq") return a[0] !== a[1];\n if (!a.every(finite)) fail(`operator ${e.op} requires finite numbers`);\n let out;\n switch (e.op) {\n case "add":\n out = a.reduce((x, y) => x + y, 0);\n break;\n case "sub":\n out = a[0] - a[1];\n break;\n case "mul":\n out = a.reduce((x, y) => x * y, 1);\n break;\n case "div":\n if (a[1] === 0) fail("division by zero");\n out = a[0] / a[1];\n break;\n case "min":\n out = Math.min(...a);\n break;\n case "max":\n out = Math.max(...a);\n break;\n case "round":\n out = Math.round(a[0]);\n break;\n case "abs":\n out = Math.abs(a[0]);\n break;\n case "lt":\n return a[0] < a[1];\n case "lte":\n return a[0] <= a[1];\n case "gt":\n return a[0] > a[1];\n case "gte":\n return a[0] >= a[1];\n default:\n fail(`unsupported operator ${e.op}`);\n }\n if (!finite(out)) fail("non-finite calculation result");\n return out;\n };\n for (const k of Object.keys(doc2.derived)) resolve(`derived.${k}`);\n if (new TextEncoder().encode(JSON.stringify(derived)).length > LIMITS.bytes) fail("derived output exceeds 128 KiB");\n const result = { derived, resolveValue: (value) => {\n if (!object2(value)) return value;\n return expression2(value);\n } };\n checkViewValues(doc2, result);\n return result;\n }\n function checkViewValues(doc2, evaluated) {\n function walk(list) {\n for (const n of list) {\n if (n.type === "table" || n.type === "chart") {\n const rows = evaluated.resolveValue(n.rows);\n if (!Array.isArray(rows) || rows.length > LIMITS.rows || !rows.every(object2)) fail("table/chart rows must reference an array of objects");\n if (n.type === "chart" && rows.some((r) => !own2(r, n.labelKey) || !scalar(r[n.labelKey]) || !own2(r, n.valueKey) || !finite(r[n.valueKey]))) fail("chart requires labels and finite numeric values");\n if (n.type === "chart" && n.xScale === "linear" && rows.some((r) => !finite(r[n.labelKey]))) fail("linear chart requires numeric x values");\n }\n if (n.type === "metric" && !scalar(evaluated.resolveValue(n.value))) fail(`node.${n.id}.value must resolve to a number, string or boolean; use table for rows or a canvas for structured data`);\n if (n.type === "progress") {\n const v = evaluated.resolveValue(n.value), max = evaluated.resolveValue(n.max);\n if (!finite(v) || !finite(max) || max <= 0 || v < 0 || v > max) fail("progress requires 0 <= value <= positive max");\n }\n if (n.children) walk(n.children);\n }\n }\n walk(doc2.nodes);\n }\n function validateDocument(input) {\n safeJson(input);\n const json = JSON.stringify(input);\n if (new TextEncoder().encode(json).length > LIMITS.bytes) fail("document exceeds 128 KiB");\n const normalized = normalizeAuthoring(JSON.parse(json));\n safeJson(normalized);\n const doc2 = expandExpressions(normalized);\n fields(doc2, ["version", "title", "fallback", "state", "data", "derived", "nodes", "actions", "presentation"], "document");\n if (doc2.presentation !== void 0) {\n fields(doc2.presentation, ["layout", "accent", "density"], "presentation");\n for (const [k, allowed] of Object.entries({ layout: UI_LAYOUTS, accent: UI_ACCENTS, density: ["comfortable", "compact"] }))\n if (doc2.presentation[k] !== void 0 && !allowed.includes(doc2.presentation[k])) fail(`unknown presentation ${k}`);\n }\n if (doc2.version !== 1) fail("unsupported protocol version");\n text(doc2.title, "title");\n text(doc2.fallback, "fallback");\n if (!doc2.title.trim() || !doc2.fallback.trim()) fail("title and fallback cannot be empty");\n for (const group of ["state", "data", "derived", "actions"]) {\n doc2[group] ??= {};\n if (!object2(doc2[group]) || Object.keys(doc2[group]).length > 64) fail(`invalid ${group}`);\n for (const k of Object.keys(doc2[group])) if (!validIdentifier(k)) fail(`invalid key in ${group}: ${JSON.stringify(k)}; use 1..64 letters, numbers or underscores, starting with a letter/underscore (Chinese letters supported); no spaces, dots or hyphens. Put display labels in label/text`);\n }\n for (const [k, s] of Object.entries(doc2.state)) {\n fields(s, ["type", "default", "min", "max", "step", "options"], `state.${k}`);\n if (s.type === "string" && (Array.isArray(s.default) || object2(s.default)) && !own2(s, "options")) s.type = "json";\n if (!["number", "string", "boolean", "json"].includes(s.type)) fail(`unsupported state type ${k}; use number, string, boolean or json (arrays/objects)`);\n if (s.type === "json" && own2(s, "options")) fail(`state.${k}: json state does not support select options; use scalar options or a canvas`);\n if (s.type === "number") {\n for (const field of ["default", "min", "max", "step"]) if (!finite(s[field])) fail(`state.${k}.${field} must be a finite JSON number; received ${s[field] === null ? "null" : typeof s[field]}. Supply {type:"number",default:2,min:1,max:12,step:1}; do not omit limits or include units in numeric fields`);\n if (s.min >= s.max || s.step <= 0) fail(`invalid numeric limits for state.${k}: require min < max and step > 0; received min=${s.min}, max=${s.max}, step=${s.step}`);\n if ((s.max - s.min) / s.step > 1e9) fail(`numeric step too small for ${k}`);\n } else if (["min", "max", "step"].some((p) => own2(s, p))) fail(`numeric limits on non-number ${k}`);\n if (s.options !== void 0 && (!Array.isArray(s.options) || !s.options.length || s.options.length > 64 || !s.options.every(scalar))) fail(`state.${k}.options must be a direct array of 1..64 scalar values, e.g. options:["3","5","7"], not {item:[...]}`);\n }\n const defaults = validateState(doc2, initialState(doc2));\n let expressions = 0;\n for (const [k, spec] of Object.entries(doc2.state))\n for (const option of spec.options ?? []) validateState(doc2, { ...defaults, [k]: option });\n function validateExpression(e, depth = 0, itemRows, at = "expression") {\n if (++expressions > LIMITS.expressions || depth > LIMITS.depth) fail("expression limit exceeded");\n if (!object2(e)) fail(`${at}: expression must be an object`);\n if (own2(e, "map")) {\n if (depth !== 0) fail("map is only allowed at the root of a derived value");\n fields(e, ["map"], "expression");\n fields(e.map, ["source", "fields"], "map");\n if (typeof e.map.source !== "string" || !e.map.source.startsWith("data.")) fail(\'map source must reference static data using a string such as "data.samples"; put object rows in document.data.samples, not inline in map.source\');\n const [, k] = reference(doc2, e.map.source), rows = doc2.data[k];\n if (!Array.isArray(rows) || rows.length > LIMITS.rows || !rows.every(object2)) fail("map source must be object rows");\n if (!object2(e.map.fields) || !Object.keys(e.map.fields).length || Object.keys(e.map.fields).length > 16) fail("map requires 1..16 fields");\n for (const [k2, value2] of Object.entries(e.map.fields)) {\n if (!validIdentifier(k2)) fail(`invalid map field ${JSON.stringify(k2).slice(0, 100)}; use a letter/underscore identifier such as powerW or \\u529F\\u7387, without spaces, dots or hyphens`);\n validateExpression(value2, depth + 1, rows, `${at}.map.fields.${k2}`);\n }\n return;\n }\n if (own2(e, "const")) {\n fields(e, ["const"], "expression");\n if (!scalar(e.const)) fail("constant must be scalar");\n return;\n }\n if (own2(e, "ref")) {\n fields(e, ["ref"], "expression");\n if (typeof e.ref === "string" && e.ref.startsWith("item.")) {\n const k = e.ref.slice(5);\n if (!itemRows || !validIdentifier(k) || itemRows.some((row) => !own2(row, k) || !scalar(row[k]))) fail("invalid or unscoped item reference");\n } else reference(doc2, e.ref);\n return;\n }\n fields(e, ["op", "args"], "expression");\n const arity = ARITY[e.op];\n if (!arity || !Array.isArray(e.args) || e.args.length < arity[0] || e.args.length > arity[1]) fail(`${at}: invalid operator or argument count for ${JSON.stringify(e.op)}; args must be a direct array${arity ? ` with ${arity[0]}..${arity[1]} expressions` : " and op must be registered"}. Received ${Array.isArray(e.args) ? e.args.length + " arguments" : typeof e.args}. Prefer {expr:"state.people * state.days"} for arithmetic; missing operands cannot be inferred`);\n e.args.forEach((a, i) => validateExpression(a, depth + 1, itemRows, `${at}.args[${i}]`));\n }\n Object.entries(doc2.derived).forEach(([k, e]) => validateExpression(e, 0, void 0, `derived.${k}`));\n const visited = /* @__PURE__ */ new Set(), active = /* @__PURE__ */ new Set();\n function visit(k) {\n if (active.has(k)) fail(`cyclic calculation ${k}`);\n if (visited.has(k)) return;\n active.add(k);\n function walk(e) {\n if (e.ref?.startsWith("derived.")) visit(e.ref.split(".")[1]);\n for (const a of e.args ?? []) walk(a);\n for (const value2 of Object.values(e.map?.fields ?? {})) walk(value2);\n }\n walk(doc2.derived[k]);\n active.delete(k);\n visited.add(k);\n }\n Object.keys(doc2.derived).forEach(visit);\n for (const [k, a] of Object.entries(doc2.actions)) {\n fields(a, ["type", "intent", "stateKeys"], `actions.${k}`);\n if (a.type !== "agent.followup") fail("only agent.followup actions are supported");\n text(a.intent, "action intent");\n if (!a.intent.trim() || !Array.isArray(a.stateKeys) || a.stateKeys.length > 64 || new Set(a.stateKeys).size !== a.stateKeys.length || a.stateKeys.some((s) => !own2(doc2.state, s))) fail("invalid action stateKeys");\n }\n const ids = /* @__PURE__ */ new Map();\n let count2 = 0;\n const value = (v, at) => {\n if (object2(v)) {\n if (own2(v, "map")) fail("map belongs in derived");\n validateExpression(v);\n } else if (!scalar(v)) fail(`invalid value at ${at}`);\n };\n function nodes(list, depth = 0, path = "nodes") {\n if (!Array.isArray(list) || depth > LIMITS.depth) fail("invalid node children or depth");\n for (const [index, n] of list.entries()) {\n const at = `${path}[${index}]`;\n if (++count2 > LIMITS.nodes) fail("too many nodes");\n if (!object2(n)) fail(`${at} must be a node object`);\n if (!own2(n, "id")) fail(`${at}.id is missing (type=${JSON.stringify(n.type)}). Every node, including sandbox, needs its own id; add e.g. id:"canvas". JSON field order does not matter`);\n if (!validIdentifier(n.id)) fail(`${at}.id is invalid: ${JSON.stringify(n.id)}. Use 1..64 letters/numbers/underscores starting with a letter/underscore`);\n if (ids.has(n.id)) fail(`${at}.id duplicates ${JSON.stringify(n.id)} first declared at ${ids.get(n.id)}. Give each node a unique id; state keys and node ids are separate namespaces`);\n ids.set(n.id, `${at}.id`);\n if (!Object.hasOwn(COMPONENTS, n.type)) fail("unsupported component " + n.type);\n if (["table", "chart"].includes(n.type) && typeof n.rows === "string" && /^(data|derived)\\./.test(n.rows)) n.rows = { ref: n.rows };\n switch (n.type) {\n case "sandbox": {\n text(n.title, `${at}.title (canvas title)`);\n text(n.fallback, `${at}.fallback (canvas fallback)`);\n if (!n.title.trim()) fail(`${at}.title must not be empty`);\n if (!n.fallback.trim()) fail(`${at}.fallback must not be empty`);\n if (typeof n.html !== "string" || !n.html.trim()) fail(`${at}.html must be a non-empty HTML string; script/css/fallback belong beside html on the sandbox node, not inside it`);\n for (const field of ["html", "css", "script"]) if (n[field] !== void 0 && (typeof n[field] !== "string" || n[field].length > SANDBOX_LIMITS.source)) fail("canvas source exceeds 48 Ki characters");\n if (n.height !== void 0 && (!Number.isInteger(n.height) || n.height < 160 || n.height > SANDBOX_LIMITS.height)) fail("canvas height must be 160..1000");\n for (const [field, group] of [["stateKeys", "state"], ["dataKeys", "data"], ["derivedKeys", "derived"]]) if (n[field] !== void 0 && (!Array.isArray(n[field]) || n[field].length > 64 || new Set(n[field]).size !== n[field].length || n[field].some((k) => !own2(doc2[group], k)))) fail("invalid canvas " + field);\n break;\n }\n case "row":\n case "column":\n break;\n case "card":\n if (n.title !== void 0) text(n.title, "card title");\n if (n.surface !== void 0 && !UI_SURFACES.includes(n.surface)) fail("unknown card surface");\n break;\n case "details":\n text(n.title, "details title");\n if (n.open === "true" || n.open === "false") n.open = n.open === "true";\n if (n.open !== void 0 && typeof n.open !== "boolean") fail(`node.${n.id}.open must be boolean true or false`);\n break;\n case "badge":\n value(n.text, "badge text");\n break;\n case "progress":\n text(n.label, "progress label");\n value(n.value, "progress value");\n value(n.max, "progress max");\n if (n.unit !== void 0) text(n.unit, "progress unit");\n break;\n case "steps": {\n const at2 = `node.${n.id}.items`;\n if (!Array.isArray(n.items)) fail(`${at2} must be an array of 1..12 step objects, received ${n.items === null ? "null" : typeof n.items}. Use items:[{"title":"First step","description":"One sentence"}], not items:{item:[...]}. status is optional for a static guide`);\n if (!n.items.length || n.items.length > 12) fail(`${at2} requires 1..12 items; received ${n.items.length}. Split a longer guide into separate steps nodes`);\n for (const [i, item] of n.items.entries()) {\n const stepAt = `${at2}[${i}]`;\n fields(item, ["title", "description", "status"], stepAt);\n text(item.title, `${stepAt}.title`);\n if (item.description !== void 0) text(item.description, `${stepAt}.description`);\n if (item.status !== void 0 && !["pending", "active", "done", "error"].includes(item.status)) fail(`${stepAt}.status must be pending, active, done or error; omit status for a static guide`);\n }\n break;\n }\n case "heading":\n case "text":\n value(n.text, "text");\n break;\n case "metric":\n text(n.label, "metric label");\n value(n.value, "metric value");\n if (n.unit !== void 0) text(n.unit, "unit");\n if (n.decimals !== void 0 && (!Number.isInteger(n.decimals) || n.decimals < 0 || n.decimals > 6)) fail("invalid decimals");\n break;\n case "input":\n case "slider":\n case "select":\n case "toggle": {\n text(n.label, "control label");\n if (typeof n.bind !== "string" || !n.bind.startsWith("state.")) fail("controls must bind state");\n const [, k] = reference(doc2, n.bind), spec = doc2.state[k];\n if (n.type === "slider" && spec.type !== "number") fail("slider requires numeric state");\n if (n.type === "toggle" && spec.type !== "boolean") fail("toggle requires boolean state");\n if (["input", "select"].includes(n.type) && spec.type === "json") fail(`node.${n.id}: structured state requires a sandbox with DSHUI.setState; use separate boolean states and toggle nodes for a native checklist`);\n if (n.type === "input" && spec.type === "boolean") fail("use toggle for boolean state");\n if (n.type === "select" && !spec.options) fail("select requires options");\n break;\n }\n case "button":\n text(n.text, "button text");\n if (!own2(doc2.actions, n.action)) fail("unknown action");\n break;\n case "table": {\n if (n.label !== void 0) text(n.label, "table label");\n if (!Array.isArray(n.rows)) value(n.rows, "table rows");\n if (!Array.isArray(n.columns) || !n.columns.length || n.columns.length > 16) fail("invalid table columns");\n const cols = /* @__PURE__ */ new Set();\n for (const c of n.columns) {\n fields(c, ["key", "label", "unit"], "column");\n if (!validIdentifier(c.key) || cols.has(c.key)) fail("invalid or duplicate column key");\n cols.add(c.key);\n text(c.label, "column label");\n if (c.unit !== void 0) text(c.unit, "column unit");\n }\n break;\n }\n case "chart":\n if (!Array.isArray(n.rows)) value(n.rows, "chart rows");\n if (!["bar", "line"].includes(n.kind) || !validIdentifier(n.labelKey) || !validIdentifier(n.valueKey)) fail("invalid chart");\n text(n.label, "chart label");\n if (n.unit !== void 0) text(n.unit, "chart unit");\n if (n.xScale !== void 0 && (!["category", "linear"].includes(n.xScale) || n.xScale === "linear" && n.kind !== "line")) fail("invalid xScale");\n break;\n default:\n fail(`unsupported component ${n.type}`);\n }\n if (["heading", "text", "card", "metric", "button", "badge", "progress", "details"].includes(n.type)) {\n if (n.icon !== void 0 && !UI_ICONS.includes(n.icon)) fail("unknown UI icon");\n if (n.type !== "button") {\n if (n.tone !== void 0 && !UI_TONES.includes(n.tone)) fail("unknown UI tone");\n }\n }\n fields(n, componentFields(n.type), `node.${n.id}`);\n if (["row", "column", "card", "details"].includes(n.type)) nodes(n.children, depth + 1, `${at}.children`);\n }\n }\n nodes(doc2.nodes);\n if (!count2) fail("at least one node is required");\n evaluate(doc2, defaults);\n return doc2;\n }\n\n // src/export-runtime.js\n var payload = JSON.parse(document.getElementById("dsh-ui-export").textContent);\n var doc = validateDocument(payload.document);\n var node = doc.nodes.flatMap(function flatten(n) {\n return [n, ...(n.children ?? []).flatMap(flatten)];\n }).find((n) => n.id === payload.nodeId && n.type === "sandbox");\n if (!node) throw Error("\\u5BFC\\u51FA\\u7684\\u753B\\u5E03\\u4E0D\\u5B58\\u5728");\n var state = validateState(doc, payload.state);\n var connected = false;\n var start = Date.now();\n var count = 0;\n var token = () => Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("");\n var channel = token();\n var nonce = payload.nonce;\n var frame = document.createElement("iframe");\n frame.title = node.title;\n frame.sandbox = "allow-scripts";\n frame.referrerPolicy = "no-referrer";\n frame.style.cssText = "display:block;width:100%;height:" + (node.height ?? 420) + "px;border:0";\n var status = document.getElementById("status");\n var publish = () => {\n try {\n frame.contentWindow.postMessage({ dshCanvas: 1, channel, type: "snapshot", payload: { ...canvasSnapshot(node, state, doc.data, evaluate(doc, state).derived, false, payload.theme), visible: !document.hidden } }, "*");\n } catch (e) {\n status.textContent = "\\u8BA1\\u7B97\\u5931\\u8D25\\uFF1A" + e.message;\n }\n };\n addEventListener("message", (event) => {\n const m = event.data;\n if (event.source !== frame.contentWindow || m?.channel !== channel || m.dshCanvas !== 1) return;\n if (Date.now() - start > 1e3) {\n start = Date.now();\n count = 0;\n }\n if (++count > SANDBOX_LIMITS.rate) return;\n if (m.type === "ready") {\n connected = true;\n publish();\n } else if (m.type === "patch" && connected) {\n try {\n const next = validateState(doc, { ...state, ...canvasPatch(m.payload, node) });\n evaluate(doc, next);\n state = next;\n status.textContent = "\\u672C\\u5730\\u53C2\\u6570\\u5DF2\\u66F4\\u65B0\\uFF1B\\u5237\\u65B0\\u6587\\u4EF6\\u4F1A\\u56DE\\u5230\\u5BFC\\u51FA\\u65F6\\u7684\\u53C2\\u6570\\u3002";\n } catch (e) {\n status.textContent = "\\u53C2\\u6570\\u65E0\\u6548\\uFF1A" + e.message;\n }\n publish();\n } else if (m.type === "height" && Number.isFinite(m.payload) && m.payload >= 0) frame.style.height = Math.max(32, Math.min(1e3, Math.ceil(m.payload))) + "px";\n else if (m.type === "error") status.textContent = "\\u753B\\u5E03\\u8FD0\\u884C\\u5F02\\u5E38\\uFF1A" + String(m.payload).slice(0, 1e3);\n });\n document.addEventListener("visibilitychange", () => {\n if (connected) publish();\n });\n document.getElementById("surface").append(frame);\n frame.srcdoc = sandboxDocument(node, channel, nonce);\n})();\n';
|
|
22800
22731
|
|
|
22801
22732
|
// lib/canvas-export.js
|
|
22802
22733
|
function standaloneCanvas(document2, node, state, theme = {}) {
|
|
@@ -23006,7 +22937,7 @@ function Sandbox({ node, document: doc, state, data, derived, disabled, preview,
|
|
|
23006
22937
|
}
|
|
23007
22938
|
|
|
23008
22939
|
// lib/build-info.js
|
|
23009
|
-
var BUILD_INFO = Object.freeze({ "version": "0.5.
|
|
22940
|
+
var BUILD_INFO = Object.freeze({ "version": "0.5.6", "id": "0.5.6+1a92fbfce0c2", "sourceHash": "1a92fbfce0c23812d59332a0de554fd7fd914d44fd715ab8e056ca0f19193bcf" });
|
|
23010
22941
|
|
|
23011
22942
|
// lib/refinement.js
|
|
23012
22943
|
var AUDIT_MESSAGES = Object.freeze({ overflow: "\u5185\u5BB9\u8D85\u51FA\u89C6\u53E3\u5BBD\u5EA6", smallText: "\u6587\u5B57\u5B9E\u9645\u663E\u793A\u5C3A\u5BF8\u5C0F\u4E8E 12px", contrast: "\u6587\u5B57\u4E0E\u80CC\u666F\u5BF9\u6BD4\u5EA6\u504F\u4F4E", missingLabel: "\u4EA4\u4E92\u63A7\u4EF6\u7F3A\u5C11\u53EF\u8BBF\u95EE\u540D\u79F0", runtimeError: "\u753B\u5E03\u62A5\u544A\u8FD0\u884C\u5F02\u5E38" });
|
|
@@ -23596,30 +23527,21 @@ var UiBoundary = class extends import_react12.default.Component {
|
|
|
23596
23527
|
}
|
|
23597
23528
|
};
|
|
23598
23529
|
function ToolView(props) {
|
|
23599
|
-
if (props.phase === "preparing") return h6(
|
|
23600
|
-
if (props.phase === "start") return h6(StreamingPreview, { raw: props.block?.argsRaw ?? "", callId: props.callId, sessionId: props.sessionId });
|
|
23530
|
+
if (props.phase === "preparing" || props.phase === "start") return h6("p", { className: "dgui-pending", role: "status" }, "\u6B63\u5728\u751F\u6210\u754C\u9762\uFF0C\u56DE\u7B54\u7ED3\u675F\u540E\u663E\u793A\u3002");
|
|
23601
23531
|
const fallback = (props.block.content ?? []).filter((x) => x.type === "text").map((x) => x.text).join("\n");
|
|
23602
23532
|
if (props.block.isError) return h6("div", { className: "dgui-tool-error", role: "alert" }, h6(Icon, { name: "warning" }), h6("span", null, fallback || "\u754C\u9762\u672A\u751F\u6210\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u53C2\u6570\u3002"));
|
|
23603
23533
|
const raw = props.block.meta?.dshGenerativeUi;
|
|
23604
|
-
if (raw?.version === 1)
|
|
23534
|
+
if (raw?.version === 1) {
|
|
23535
|
+
try {
|
|
23536
|
+
validateDocument(raw.document);
|
|
23537
|
+
return h6("p", { className: "dgui-pending", role: "status" }, "\u754C\u9762\u5DF2\u751F\u6210\uFF0C\u6574\u8F6E\u56DE\u7B54\u7ED3\u675F\u540E\u5728\u4E0B\u65B9\u663E\u793A\u3002");
|
|
23538
|
+
} catch {
|
|
23539
|
+
}
|
|
23540
|
+
}
|
|
23605
23541
|
return h6(ValidatedView, { ...props, raw, fallback });
|
|
23606
23542
|
}
|
|
23607
|
-
function StreamingToolView(props) {
|
|
23608
|
-
const raw = props.useToolCallArgumentsPartial ? props.useToolCallArgumentsPartial() : "";
|
|
23609
|
-
return h6(StreamingPreview, { raw, callId: props.callId, sessionId: props.sessionId });
|
|
23610
|
-
}
|
|
23611
|
-
function StreamingPreview({ raw, callId = "stream", sessionId }) {
|
|
23612
|
-
const committed = useUiRegistry().some((r) => r.sessionId === sessionId && r.raw.callId === callId);
|
|
23613
|
-
const deferred = import_react12.default.useDeferredValue(raw);
|
|
23614
|
-
const doc = (0, import_react12.useMemo)(() => previewDocument(deferred), [deferred]);
|
|
23615
|
-
const canvas = (0, import_react12.useMemo)(() => doc ? null : previewCanvas(deferred), [doc, deferred]);
|
|
23616
|
-
if (committed) return h6("p", { className: "dgui-copy" }, "\u754C\u9762\u5DF2\u751F\u6210\u3002");
|
|
23617
|
-
if (canvas) return h6("div", { className: "dgui-root", "data-preview": true }, h6(CanvasPreview, { node: canvas }));
|
|
23618
|
-
if (!doc) return h6("div", { className: "dgui-pending", role: "status" }, "\u6B63\u5728\u7EC4\u7EC7\u5E76\u6821\u9A8C\u4EA4\u4E92\u754C\u9762\u2026");
|
|
23619
|
-
return h6(UiBoundary, { key: callId, fallback: doc.fallback }, h6(UiView, { record: { document: doc, documentId: `preview-${callId}` }, sessionId, preview: true }));
|
|
23620
|
-
}
|
|
23621
23543
|
function TurnUiView(props) {
|
|
23622
|
-
return (props.turn.data.get("dshGenerativeUi") ?? []).filter((r) => r.
|
|
23544
|
+
return (props.turn.data.get("dshGenerativeUi") ?? []).filter((r) => r.displayReady === true).map((r) => r.isPatch ? h6(PatchNotice, { key: `patch-${r.seq}`, raw: r.raw }) : h6(PresentationView, { key: r.raw.documentId, ...r, sessionId: props.sessionId, sendPrompt: props.sendPrompt, scope: props.presentationScope }));
|
|
23623
23545
|
}
|
|
23624
23546
|
function PatchNotice({ raw }) {
|
|
23625
23547
|
(0, import_react12.useEffect)(() => {
|
|
@@ -23655,20 +23577,25 @@ var turnUiDefinition = {
|
|
|
23655
23577
|
kind: "dshGenerativeUi",
|
|
23656
23578
|
match(event) {
|
|
23657
23579
|
if (event.type === "turn/start") return { id: String(event.data.turn), role: "start" };
|
|
23580
|
+
if (event.type === "turn/end") return { id: String(event.data.turn), role: "update" };
|
|
23658
23581
|
if (event.type === "tool/result" && event.surfaceOp === "append" && !event.data.message.isError && (event.data.meta?.dshGenerativeUi || event.data.meta?.dshGenerativeUiPatch))
|
|
23659
23582
|
return { id: String(event.data.turn), role: "update" };
|
|
23660
23583
|
return null;
|
|
23661
23584
|
},
|
|
23662
23585
|
start(_context, match) {
|
|
23663
|
-
return { turn: match.event.data.turn, records: [] };
|
|
23586
|
+
return { turn: match.event.data.turn, settled: false, records: [] };
|
|
23664
23587
|
},
|
|
23665
23588
|
update(context, match) {
|
|
23666
23589
|
const event = match.event;
|
|
23590
|
+
if (event.type === "turn/end") {
|
|
23591
|
+
if (context.state.settled) return context.state;
|
|
23592
|
+
return { ...context.state, settled: true, records: context.state.records.map((r) => ({ ...r, displayReady: true })) };
|
|
23593
|
+
}
|
|
23667
23594
|
if (event.type !== "tool/result") return context.state;
|
|
23668
23595
|
const isPatch = !!event.data.meta.dshGenerativeUiPatch, raw = event.data.meta.dshGenerativeUi ?? event.data.meta.dshGenerativeUiPatch;
|
|
23669
23596
|
if (context.state.records.some((r) => r.raw.documentId === raw.documentId && (r.raw.documentRevision ?? 0) >= (raw.documentRevision ?? 0))) return context.state;
|
|
23670
23597
|
const fallback = (event.data.message.content ?? []).filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
23671
|
-
return { ...context.state, records: [...context.state.records, { seq: event.seq, raw, fallback, ...isPatch ? { isPatch: true } : {} }] };
|
|
23598
|
+
return { ...context.state, records: [...context.state.records, { seq: event.seq, raw, fallback, displayReady: context.state.settled === true, ...isPatch ? { isPatch: true } : {} }] };
|
|
23672
23599
|
},
|
|
23673
23600
|
buildLocationData(context, scope2, previous) {
|
|
23674
23601
|
if (scope2 !== "turn" || !context.state) return null;
|