dsh-motion-ui 0.5.2 → 0.5.3

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/client.js CHANGED
@@ -21678,10 +21678,10 @@ __export(client_exports, {
21678
21678
  inject: () => inject
21679
21679
  });
21680
21680
  module.exports = __toCommonJS(client_exports);
21681
- var import_react11 = __toESM(require("react"), 1);
21681
+ var import_react12 = __toESM(require("react"), 1);
21682
21682
 
21683
21683
  // src/view.js
21684
- var import_react9 = __toESM(require("react"), 1);
21684
+ var import_react10 = __toESM(require("react"), 1);
21685
21685
 
21686
21686
  // lib/presentation.js
21687
21687
  var UI_ICONS = Object.freeze(["sparkles", "sliders", "layers", "chart", "gauge", "cpu", "database", "info", "check", "warning", "arrow", "target"]);
@@ -21775,6 +21775,8 @@ var CANVAS_SYSTEM_CSS = `
21775
21775
  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}
21776
21776
  .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}
21777
21777
  svg .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)}
21778
+ .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)}
21779
+ @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)}}
21778
21780
  @media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}`;
21779
21781
 
21780
21782
  // lib/sandbox.js
@@ -22297,6 +22299,7 @@ function validateDocument(input) {
22297
22299
  if (!own(doc.actions, n.action)) fail("unknown action");
22298
22300
  break;
22299
22301
  case "table": {
22302
+ if (Array.isArray(n.rows)) fail('table.rows must use {ref:"data.rows"}; move the array into document.data.rows, not inline in the table');
22300
22303
  value(n.rows, "table rows");
22301
22304
  if (!Array.isArray(n.columns) || !n.columns.length || n.columns.length > 16) fail("invalid table columns");
22302
22305
  const cols = /* @__PURE__ */ new Set();
@@ -22495,10 +22498,10 @@ function useSavedState(record, sessionId, preview = false) {
22495
22498
  const timer = setTimeout(async () => {
22496
22499
  r.saving = true;
22497
22500
  if (r.alive) setStatus("\u6B63\u5728\u4FDD\u5B58\u53C2\u6570\u2026");
22498
- const revision = r.revision;
22501
+ const revision2 = r.revision;
22499
22502
  try {
22500
- const saved = checked(await request(STATE_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ documentId: record.documentId, sessionId, revision, state }) }));
22501
- if (saved.revision !== revision + 1 || JSON.stringify(saved.state) !== signature) throw new Error("Unexpected save response");
22503
+ const saved = checked(await request(STATE_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ documentId: record.documentId, sessionId, revision: revision2, state }) }));
22504
+ if (saved.revision !== revision2 + 1 || JSON.stringify(saved.state) !== signature) throw new Error("Unexpected save response");
22502
22505
  r.revision = saved.revision;
22503
22506
  r.saved = signature;
22504
22507
  broadcastState(record.documentId, sessionId, saved, receive.current);
@@ -22732,7 +22735,7 @@ function CanvasPreview({ node }) {
22732
22735
  var import_react4 = __toESM(require("react"), 1);
22733
22736
 
22734
22737
  // lib/export-runtime-bundle.js
22735
- 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:12px;--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}\nhtml,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@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"], "Numeric result with units", "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(["rows", "columns"], "Exact supplied or derived values"),\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/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 = /\\s*(?:(\\d+(?:\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?|("(?:[^"\\\\]|\\\\.)*")|([a-zA-Z][a-zA-Z0-9_]*(?:\\.[a-zA-Z][a-zA-Z0-9_]*)?)|(===|!==|<=|>=|==|!=|[+*/()?,:<>-]))/y;\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" && /^(state|data|derived|item)\\.[a-zA-Z][a-zA-Z0-9_]*$/.test(t)) 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 key = (x) => typeof x === "string" && /^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(x) && !["constructor", "prototype", "__proto__"].includes(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 === "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)\\.[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(ref)) fail("invalid reference");\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" && !finite(evaluated.resolveValue(n.value))) fail("metric requires a numeric value");\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 (!key(k)) fail(`invalid key in ${group}`);\n }\n for (const [k, s] of Object.entries(doc2.state)) {\n fields(s, ["type", "default", "min", "max", "step", "options"], `state.${k}`);\n if (!["number", "string", "boolean"].includes(s.type)) fail(`unsupported state type ${k}`);\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 (!key(k2)) fail(`invalid map field ${JSON.stringify(k2).slice(0, 100)}; use an ASCII identifier matching [a-zA-Z][a-zA-Z0-9_]{0,63}, such as powerW. Put Chinese display text in column/chart labels, not internal field keys`);\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 || !key(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) || !key(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 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 !== void 0 && typeof n.open !== "boolean") fail("details open must be boolean");\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 (!Array.isArray(n.items) || !n.items.length || n.items.length > 12) fail("steps require 1..12 items");\n for (const item of n.items) {\n fields(item, ["title", "description", "status"], "step");\n text(item.title, "step title");\n if (item.description !== void 0) text(item.description, "step description");\n if (!["pending", "active", "done", "error"].includes(item.status)) fail("unknown step status");\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 (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 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 (!key(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 value(n.rows, "chart rows");\n if (!["bar", "line"].includes(n.kind) || !key(n.labelKey) || !key(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)) frame.style.height = Math.max(160, 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';
22738
+ 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:12px;--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}\nhtml,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@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"], "Numeric result with units", "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(["rows", "columns"], "Exact supplied or derived values"),\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/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 = /\\s*(?:(\\d+(?:\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?|("(?:[^"\\\\]|\\\\.)*")|([a-zA-Z][a-zA-Z0-9_]*(?:\\.[a-zA-Z][a-zA-Z0-9_]*)?)|(===|!==|<=|>=|==|!=|[+*/()?,:<>-]))/y;\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" && /^(state|data|derived|item)\\.[a-zA-Z][a-zA-Z0-9_]*$/.test(t)) 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 key = (x) => typeof x === "string" && /^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(x) && !["constructor", "prototype", "__proto__"].includes(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 === "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)\\.[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(ref)) fail("invalid reference");\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" && !finite(evaluated.resolveValue(n.value))) fail("metric requires a numeric value");\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 (!key(k)) fail(`invalid key in ${group}`);\n }\n for (const [k, s] of Object.entries(doc2.state)) {\n fields(s, ["type", "default", "min", "max", "step", "options"], `state.${k}`);\n if (!["number", "string", "boolean"].includes(s.type)) fail(`unsupported state type ${k}`);\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 (!key(k2)) fail(`invalid map field ${JSON.stringify(k2).slice(0, 100)}; use an ASCII identifier matching [a-zA-Z][a-zA-Z0-9_]{0,63}, such as powerW. Put Chinese display text in column/chart labels, not internal field keys`);\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 || !key(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) || !key(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 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 !== void 0 && typeof n.open !== "boolean") fail("details open must be boolean");\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 (!Array.isArray(n.items) || !n.items.length || n.items.length > 12) fail("steps require 1..12 items");\n for (const item of n.items) {\n fields(item, ["title", "description", "status"], "step");\n text(item.title, "step title");\n if (item.description !== void 0) text(item.description, "step description");\n if (!["pending", "active", "done", "error"].includes(item.status)) fail("unknown step status");\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 (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 (Array.isArray(n.rows)) fail(\'table.rows must use {ref:"data.rows"}; move the array into document.data.rows, not inline in the table\');\n 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 (!key(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 value(n.rows, "chart rows");\n if (!["bar", "line"].includes(n.kind) || !key(n.labelKey) || !key(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)) frame.style.height = Math.max(160, 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';
22736
22739
 
22737
22740
  // lib/canvas-export.js
22738
22741
  function standaloneCanvas(document2, node, state, theme = {}) {
@@ -22888,7 +22891,7 @@ function Sandbox({ node, document: doc, state, data, derived, disabled, preview,
22888
22891
  }
22889
22892
 
22890
22893
  // lib/build-info.js
22891
- var BUILD_INFO = Object.freeze({ "version": "0.5.2", "id": "0.5.2+af2abef3c448", "sourceHash": "af2abef3c4483e0ce89fb9438a032ad133c7039193778388acb821ce9d353449" });
22894
+ var BUILD_INFO = Object.freeze({ "version": "0.5.3", "id": "0.5.3+634080c7e02a", "sourceHash": "634080c7e02ae0667c17a2ce4f94d8999270f1827b7735058deee989caa7777a" });
22892
22895
 
22893
22896
  // lib/refinement.js
22894
22897
  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" });
@@ -22954,11 +22957,11 @@ function HistoryView({ record, sessionId }) {
22954
22957
  setBusy(false);
22955
22958
  }
22956
22959
  }
22957
- async function restore(revision) {
22960
+ async function restore(revision2) {
22958
22961
  setBusy(true);
22959
22962
  setError("");
22960
22963
  try {
22961
- const next = await request2({ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ documentRevision: record.documentRevision ?? 0, targetRevision: revision }) });
22964
+ const next = await request2({ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ documentRevision: record.documentRevision ?? 0, targetRevision: revision2 }) });
22962
22965
  publishDocument(next);
22963
22966
  setPreview(null);
22964
22967
  setVersions(await request2());
@@ -22968,11 +22971,11 @@ function HistoryView({ record, sessionId }) {
22968
22971
  setBusy(false);
22969
22972
  }
22970
22973
  }
22971
- async function inspect(revision) {
22974
+ async function inspect(revision2) {
22972
22975
  setBusy(true);
22973
22976
  setError("");
22974
22977
  try {
22975
- const r = await fetch(url + "&revision=" + revision, { credentials: "same-origin" }), v = await r.json();
22978
+ const r = await fetch(url + "&revision=" + revision2, { credentials: "same-origin" }), v = await r.json();
22976
22979
  if (!r.ok) throw new Error(v.error ?? "\u5386\u53F2\u8BFB\u53D6\u5931\u8D25");
22977
22980
  setPreview({ ...v, document: validateDocument(v.document) });
22978
22981
  } catch (e) {
@@ -23003,7 +23006,7 @@ function HistoryView({ record, sessionId }) {
23003
23006
  }
23004
23007
 
23005
23008
  // src/presentation.js
23006
- var import_react8 = __toESM(require("react"), 1);
23009
+ var import_react9 = __toESM(require("react"), 1);
23007
23010
  var import_react_dom = __toESM(require_react_dom(), 1);
23008
23011
 
23009
23012
  // src/presentation-store.js
@@ -23012,7 +23015,11 @@ var records = /* @__PURE__ */ new Map();
23012
23015
  var listeners3 = /* @__PURE__ */ new Set();
23013
23016
  var locations = /* @__PURE__ */ new Map();
23014
23017
  var opener;
23015
- var emit = () => listeners3.forEach((fn) => fn());
23018
+ var revision = 0;
23019
+ var emit = () => {
23020
+ revision++;
23021
+ listeners3.forEach((fn) => fn());
23022
+ };
23016
23023
  var notifyUi = emit;
23017
23024
  function rememberUi(raw, sessionId, sendPrompt) {
23018
23025
  if (raw?.version !== 1 || raw.sessionId !== sessionId) return;
@@ -23028,8 +23035,7 @@ var subscribeUi = (fn) => {
23028
23035
  return () => listeners3.delete(fn);
23029
23036
  };
23030
23037
  function useUiRegistry() {
23031
- const [, wake] = (0, import_react7.useState)(0);
23032
- (0, import_react7.useEffect)(() => subscribeUi(() => wake((v) => v + 1)), []);
23038
+ (0, import_react7.useSyncExternalStore)(subscribeUi, () => revision, () => 0);
23033
23039
  return uiRecords();
23034
23040
  }
23035
23041
  function setPanelOpener(fn) {
@@ -23120,16 +23126,58 @@ function startUiMarkers(root, sessionId) {
23120
23126
  };
23121
23127
  }
23122
23128
 
23129
+ // src/presentation-owner.js
23130
+ var import_react8 = require("react");
23131
+ var roots = /* @__PURE__ */ new WeakMap();
23132
+ function usePresentationOwner(anchor, documentId, sessionId) {
23133
+ const [owner, setOwner] = (0, import_react8.useState)(false);
23134
+ (0, import_react8.useEffect)(() => {
23135
+ const element = anchor.current;
23136
+ if (!element) return;
23137
+ const root = element.closest("[data-conversation-scroll]") ?? element.parentElement;
23138
+ if (!root) return;
23139
+ let documents = roots.get(root);
23140
+ if (!documents) {
23141
+ documents = /* @__PURE__ */ new Map();
23142
+ roots.set(root, documents);
23143
+ }
23144
+ const key3 = sessionId + ":" + documentId;
23145
+ let entries = documents.get(key3);
23146
+ if (!entries) {
23147
+ entries = /* @__PURE__ */ new Set();
23148
+ documents.set(key3, entries);
23149
+ }
23150
+ const entry = { element, update: setOwner };
23151
+ entries.add(entry);
23152
+ const elect = () => {
23153
+ let winner;
23154
+ for (const candidate of entries) {
23155
+ if (!candidate.element.isConnected) continue;
23156
+ if (!winner || winner.element.compareDocumentPosition(candidate.element) & 4) winner = candidate;
23157
+ }
23158
+ for (const candidate of entries) candidate.update(candidate === winner);
23159
+ };
23160
+ elect();
23161
+ return () => {
23162
+ entries.delete(entry);
23163
+ elect();
23164
+ if (!entries.size) documents.delete(key3);
23165
+ };
23166
+ }, [anchor, documentId, sessionId]);
23167
+ return owner;
23168
+ }
23169
+
23123
23170
  // src/presentation.js
23124
- var h5 = import_react8.default.createElement;
23171
+ var h5 = import_react9.default.createElement;
23125
23172
  var scanners = /* @__PURE__ */ new WeakMap();
23126
23173
  var focused = /* @__PURE__ */ new Map();
23127
23174
  function PresentationView({ raw, sessionId, sendPrompt, scope }) {
23128
- const anchor = (0, import_react8.useRef)(null), records2 = useUiRegistry(), mode = usePresentation(scope), host = markerLocation(raw.documentId);
23129
- (0, import_react8.useEffect)(() => {
23175
+ const anchor = (0, import_react9.useRef)(null), records2 = useUiRegistry(), mode = usePresentation(scope), host = markerLocation(raw.documentId);
23176
+ const owner = usePresentationOwner(anchor, raw.documentId, sessionId);
23177
+ (0, import_react9.useEffect)(() => {
23130
23178
  rememberUi(raw, sessionId, sendPrompt);
23131
23179
  }, [raw, sessionId, sendPrompt]);
23132
- (0, import_react8.useEffect)(() => {
23180
+ (0, import_react9.useEffect)(() => {
23133
23181
  const root = anchor.current?.closest("[data-conversation-scroll]");
23134
23182
  if (!root) return;
23135
23183
  let entry = scanners.get(root);
@@ -23150,31 +23198,31 @@ function PresentationView({ raw, sessionId, sendPrompt, scope }) {
23150
23198
  }
23151
23199
  };
23152
23200
  }, [sessionId]);
23153
- (0, import_react8.useEffect)(() => {
23201
+ (0, import_react9.useEffect)(() => {
23154
23202
  const block = host?.uiMarkerBlock;
23155
- if (!block) return;
23203
+ if (!block || !owner) return;
23156
23204
  const previous = block.style.display;
23157
23205
  block.style.display = mode === "inline" ? "none" : previous;
23158
23206
  return () => {
23159
23207
  block.style.display = previous;
23160
23208
  };
23161
- }, [host, mode]);
23209
+ }, [host, mode, owner]);
23162
23210
  const available = panelAvailable();
23163
- (0, import_react8.useEffect)(() => {
23164
- if (mode === "sidebar" && available) openUiPanel(raw.documentId, sessionId);
23165
- }, [mode, available, sessionId, raw.documentId]);
23211
+ (0, import_react9.useEffect)(() => {
23212
+ if (owner && mode === "sidebar" && available) openUiPanel(raw.documentId, sessionId);
23213
+ }, [mode, available, sessionId, raw.documentId, owner]);
23166
23214
  const content = h5(ValidatedView, { raw, fallback: raw.document.fallback, sessionId, sendPrompt });
23167
23215
  return h5(
23168
23216
  "div",
23169
23217
  { ref: anchor, "data-dgui-presentation": raw.documentId },
23170
- available && h5("button", { type: "button", className: "dgui-secondary", onClick: () => openUiPanel(raw.documentId, sessionId) }, "\u5728\u4FA7\u680F\u6253\u5F00"),
23171
- mode === "sidebar" && available ? h5("p", { className: "dgui-copy" }, `${raw.document.title} \xB7 \u5728\u4FA7\u680F\u4E2D\u64CD\u4F5C`) : mode === "inline" && host ? (0, import_react_dom.createPortal)(content, host) : content,
23172
- mode === "inline" && !host && h5("small", { className: "dgui-copy" }, "\u6B63\u6587\u6807\u8BB0\u5C1A\u672A\u51FA\u73B0\uFF0C\u6682\u5728\u56DE\u7B54\u4E0B\u65B9\u663E\u793A\u3002")
23218
+ owner && available && h5("button", { type: "button", className: "dgui-secondary", onClick: () => openUiPanel(raw.documentId, sessionId) }, "\u5728\u4FA7\u680F\u6253\u5F00"),
23219
+ owner && (mode === "sidebar" && available ? h5("p", { className: "dgui-copy" }, `${raw.document.title} \xB7 \u5728\u4FA7\u680F\u4E2D\u64CD\u4F5C`) : mode === "inline" && host ? (0, import_react_dom.createPortal)(content, host) : content),
23220
+ owner && mode === "inline" && !host && h5("small", { className: "dgui-copy" }, "\u672A\u63D0\u4F9B\u6B63\u6587\u6807\u8BB0\uFF0C\u754C\u9762\u663E\u793A\u5728\u56DE\u7B54\u4E0B\u65B9\u3002")
23173
23221
  );
23174
23222
  }
23175
23223
  function UiPanel({ sessionId, sendPrompt }) {
23176
- const records2 = useUiRegistry().filter((r) => r.sessionId === sessionId), [selected, setSelected] = (0, import_react8.useState)(focused.get(sessionId));
23177
- (0, import_react8.useEffect)(() => subscribeUi(() => setSelected(focused.get(sessionId))), [sessionId]);
23224
+ const records2 = useUiRegistry().filter((r) => r.sessionId === sessionId), [selected, setSelected] = (0, import_react9.useState)(focused.get(sessionId));
23225
+ (0, import_react9.useEffect)(() => subscribeUi(() => setSelected(focused.get(sessionId))), [sessionId]);
23178
23226
  const record = records2.find((r) => r.raw.documentId === selected) ?? records2.at(-1);
23179
23227
  return h5(
23180
23228
  "div",
@@ -23192,11 +23240,11 @@ function focusUi(id, sessionId) {
23192
23240
  }
23193
23241
 
23194
23242
  // src/view.js
23195
- var h6 = import_react9.default.createElement;
23243
+ var h6 = import_react10.default.createElement;
23196
23244
  var display = (value) => value === void 0 || value === null ? "\u2014" : typeof value === "object" ? JSON.stringify(value) : String(value);
23197
23245
  function NumericInput({ id, spec, value, onChange, invalidChanged, disabled }) {
23198
- const [draft, setDraft] = (0, import_react9.useState)(String(value)), [invalid, setInvalid] = (0, import_react9.useState)(false);
23199
- (0, import_react9.useEffect)(() => {
23246
+ const [draft, setDraft] = (0, import_react10.useState)(String(value)), [invalid, setInvalid] = (0, import_react10.useState)(false);
23247
+ (0, import_react10.useEffect)(() => {
23200
23248
  setDraft(String(value));
23201
23249
  setInvalid(false);
23202
23250
  invalidChanged(id, false);
@@ -23263,9 +23311,9 @@ function Chart({ node, rows }) {
23263
23311
  }
23264
23312
  function UiView({ record, sessionId, sendPrompt, preview = false }) {
23265
23313
  const doc = record.document;
23266
- const viewId = import_react9.default.useId();
23267
- const root = (0, import_react9.useRef)(null), canvasAudits = (0, import_react9.useRef)(/* @__PURE__ */ new Map()), [audit, setAudit] = (0, import_react9.useState)(null), auditPending = (0, import_react9.useRef)(0);
23268
- const collectAudit = import_react9.default.useCallback(() => {
23314
+ const viewId = import_react10.default.useId();
23315
+ const root = (0, import_react10.useRef)(null), canvasAudits = (0, import_react10.useRef)(/* @__PURE__ */ new Map()), [audit, setAudit] = (0, import_react10.useState)(null), auditPending = (0, import_react10.useRef)(0);
23316
+ const collectAudit = import_react10.default.useCallback(() => {
23269
23317
  clearTimeout(auditPending.current);
23270
23318
  auditPending.current = setTimeout(() => {
23271
23319
  if (!root.current || preview) return;
@@ -23277,7 +23325,7 @@ function UiView({ record, sessionId, sendPrompt, preview = false }) {
23277
23325
  });
23278
23326
  }, 250);
23279
23327
  }, [record.documentId, record.documentRevision, sessionId, preview]);
23280
- (0, import_react9.useEffect)(() => {
23328
+ (0, import_react10.useEffect)(() => {
23281
23329
  canvasAudits.current.clear();
23282
23330
  collectAudit();
23283
23331
  window.addEventListener("resize", collectAudit);
@@ -23295,22 +23343,22 @@ function UiView({ record, sessionId, sendPrompt, preview = false }) {
23295
23343
  collectAudit();
23296
23344
  };
23297
23345
  const { state, setState, ready, status, issue, epoch, retry, useRemote, keepLocal } = useSavedState(record, sessionId, preview);
23298
- const [error, setError] = (0, import_react9.useState)(""), [actionStatus, setActionStatus] = (0, import_react9.useState)("");
23299
- const [busy, setBusy] = (0, import_react9.useState)(false), [invalidFields, setInvalidFields] = (0, import_react9.useState)({});
23300
- const pending = (0, import_react9.useRef)(false);
23301
- (0, import_react9.useEffect)(() => {
23346
+ const [error, setError] = (0, import_react10.useState)(""), [actionStatus, setActionStatus] = (0, import_react10.useState)("");
23347
+ const [busy, setBusy] = (0, import_react10.useState)(false), [invalidFields, setInvalidFields] = (0, import_react10.useState)({});
23348
+ const pending = (0, import_react10.useRef)(false);
23349
+ (0, import_react10.useEffect)(() => {
23302
23350
  setInvalidFields({});
23303
23351
  }, [epoch]);
23304
- const invalidChanged = import_react9.default.useCallback((id, invalid) => setInvalidFields((previous) => previous[id] === invalid ? previous : { ...previous, [id]: invalid }), []);
23352
+ const invalidChanged = import_react10.default.useCallback((id, invalid) => setInvalidFields((previous) => previous[id] === invalid ? previous : { ...previous, [id]: invalid }), []);
23305
23353
  let computed;
23306
23354
  try {
23307
23355
  computed = evaluate(doc, state);
23308
23356
  } catch (e) {
23309
23357
  computed = { error: e.message };
23310
23358
  }
23311
- const currentState = (0, import_react9.useRef)(state);
23359
+ const currentState = (0, import_react10.useRef)(state);
23312
23360
  currentState.current = state;
23313
- (0, import_react9.useEffect)(() => {
23361
+ (0, import_react10.useEffect)(() => {
23314
23362
  collectAudit();
23315
23363
  }, [state, ready, collectAudit]);
23316
23364
  const updatePatch = (patch) => {
@@ -23382,7 +23430,7 @@ function UiView({ record, sessionId, sendPrompt, preview = false }) {
23382
23430
  let control;
23383
23431
  if (n.type === "select") control = h6("select", { id, value: spec.options.indexOf(v), disabled: !ready, onChange: (e) => update(k, spec.options[Number(e.target.value)]) }, spec.options.map((o, i) => h6("option", { key: i, value: i }, display(o))));
23384
23432
  else if (n.type === "toggle") control = h6("input", { id, className: "dgui-toggle", type: "checkbox", checked: v, disabled: !ready, onChange: (e) => update(k, e.target.checked) });
23385
- else if (n.type === "slider") control = h6(import_react9.default.Fragment, null, h6("input", { id, type: "range", min: spec.min, max: spec.max, step: spec.step, value: v, disabled: !ready, onChange: (e) => update(k, Number(e.target.value)) }), h6("output", { htmlFor: id }, display(v)));
23433
+ else if (n.type === "slider") control = h6(import_react10.default.Fragment, null, h6("input", { id, type: "range", min: spec.min, max: spec.max, step: spec.step, value: v, disabled: !ready, onChange: (e) => update(k, Number(e.target.value)) }), h6("output", { htmlFor: id }, display(v)));
23386
23434
  else if (spec.type === "number") control = h6(NumericInput, { id, spec, value: v, disabled: !ready, invalidChanged, onChange: (value) => update(k, value) });
23387
23435
  else control = h6("input", { id, type: "text", value: v, maxLength: 8192, disabled: !ready, onChange: (e) => update(k, e.target.value) });
23388
23436
  return h6("div", { ...props, className: "dgui-control" }, h6("label", { htmlFor: id }, h6(Icon, { name: n.type === "toggle" ? "layers" : n.type === "select" ? "target" : "sliders" }), h6("span", null, n.label)), control);
@@ -23401,14 +23449,14 @@ function UiView({ record, sessionId, sendPrompt, preview = false }) {
23401
23449
  return h6(
23402
23450
  "section",
23403
23451
  { ref: root, className: "dgui-root", "data-document-revision": record.documentRevision ?? 0, "data-dgui-build": BUILD_INFO.id, "data-dsh-generative-ui": record.documentId, "data-layout": doc.presentation?.layout, "data-accent": doc.presentation?.accent, "data-density": doc.presentation?.density, "data-preview": preview || void 0, "aria-label": doc.title, "aria-busy": preview || void 0 },
23404
- h6("header", { className: "dgui-header" }, h6("span", { className: "dgui-heading-icon" }, h6(Icon, { name: sceneIcons[doc.presentation?.layout] ?? "sliders" })), h6("strong", { className: "dgui-header-title" }, doc.title), h6("span", { className: "dgui-badge" }, h6(Icon, { name: "sparkles" }), preview ? "\u751F\u6210\u4E2D" : "\u4EA4\u4E92\u754C\u9762")),
23452
+ h6("header", { className: "dgui-header" }, h6("span", { className: "dgui-heading-icon" }, h6(Icon, { name: sceneIcons[doc.presentation?.layout] ?? (Object.keys(doc.state).length ? "sliders" : "layers") })), h6("strong", { className: "dgui-header-title" }, doc.title), h6("span", { className: "dgui-badge" }, h6(Icon, { name: "sparkles" }), preview ? "\u751F\u6210\u4E2D" : "\u53EF\u89C6\u5316")),
23405
23453
  h6("div", { key: epoch, className: "dgui-content" }, doc.nodes.map(renderNode)),
23406
- h6("div", { className: "dgui-status", role: "status", "data-status": /失败|冲突/.test(status) ? "error" : /正在|等待/.test(status) ? "pending" : "saved" }, h6(Icon, { name: /失败|冲突/.test(status) ? "warning" : /正在|等待/.test(status) ? "database" : "check" }), h6("span", null, status), actionStatus && h6("span", null, ` \xB7 ${actionStatus}`)),
23454
+ (Object.keys(doc.state).length > 0 || issue || actionStatus) && h6("div", { className: "dgui-status", role: "status", "data-status": /失败|冲突/.test(status) ? "error" : /正在|等待/.test(status) ? "pending" : "saved" }, h6(Icon, { name: /失败|冲突/.test(status) ? "warning" : /正在|等待/.test(status) ? "database" : "check" }), h6("span", null, status), actionStatus && h6("span", null, ` \xB7 ${actionStatus}`)),
23407
23455
  issue && h6(
23408
23456
  "div",
23409
23457
  { className: "dgui-recovery" },
23410
23458
  issue.kind === "conflict" ? h6(
23411
- import_react9.default.Fragment,
23459
+ import_react10.default.Fragment,
23412
23460
  null,
23413
23461
  h6("button", { type: "button", className: "dgui-secondary", disabled: status === "\u6B63\u5728\u6062\u590D\u53C2\u6570\u2026", onClick: useRemote }, "\u4F7F\u7528\u5DF2\u4FDD\u5B58\u53C2\u6570"),
23414
23462
  h6("button", { type: "button", className: "dgui-secondary", disabled: status === "\u6B63\u5728\u6062\u590D\u53C2\u6570\u2026", onClick: keepLocal }, "\u4FDD\u7559\u672C\u9875\u53C2\u6570\u5E76\u91CD\u8BD5")
@@ -23421,7 +23469,7 @@ function UiView({ record, sessionId, sendPrompt, preview = false }) {
23421
23469
  h6("details", { className: "dgui-fallback" }, h6("summary", null, Object.keys(doc.state).length ? "\u539F\u59CB\u6587\u5B57\u8BF4\u660E\u4E0E\u521D\u59CB\u53C2\u6570" : "\u539F\u59CB\u6587\u5B57\u8BF4\u660E"), h6("p", null, doc.fallback), Object.keys(doc.state).length > 0 && h6("pre", null, JSON.stringify(initialState(doc), null, 2)))
23422
23470
  );
23423
23471
  }
23424
- var UiBoundary = class extends import_react9.default.Component {
23472
+ var UiBoundary = class extends import_react10.default.Component {
23425
23473
  state = { failed: false };
23426
23474
  static getDerivedStateFromError() {
23427
23475
  return { failed: true };
@@ -23444,9 +23492,11 @@ function StreamingToolView(props) {
23444
23492
  return h6(StreamingPreview, { raw, callId: props.callId, sessionId: props.sessionId });
23445
23493
  }
23446
23494
  function StreamingPreview({ raw, callId = "stream", sessionId }) {
23447
- const deferred = import_react9.default.useDeferredValue(raw);
23448
- const doc = (0, import_react9.useMemo)(() => previewDocument(deferred), [deferred]);
23449
- const canvas = (0, import_react9.useMemo)(() => doc ? null : previewCanvas(deferred), [doc, deferred]);
23495
+ const committed = useUiRegistry().some((r) => r.sessionId === sessionId && r.raw.callId === callId);
23496
+ const deferred = import_react10.default.useDeferredValue(raw);
23497
+ const doc = (0, import_react10.useMemo)(() => previewDocument(deferred), [deferred]);
23498
+ const canvas = (0, import_react10.useMemo)(() => doc ? null : previewCanvas(deferred), [doc, deferred]);
23499
+ if (committed) return h6("p", { className: "dgui-copy" }, "\u754C\u9762\u5DF2\u751F\u6210\u3002");
23450
23500
  if (canvas) return h6("div", { className: "dgui-root", "data-preview": true }, h6(CanvasPreview, { node: canvas }));
23451
23501
  if (!doc) return h6("div", { className: "dgui-pending", role: "status" }, "\u6B63\u5728\u7EC4\u7EC7\u5E76\u6821\u9A8C\u4EA4\u4E92\u754C\u9762\u2026");
23452
23502
  return h6(UiBoundary, { key: callId, fallback: doc.fallback }, h6(UiView, { record: { document: doc, documentId: `preview-${callId}` }, sessionId, preview: true }));
@@ -23455,7 +23505,7 @@ function TurnUiView(props) {
23455
23505
  return (props.turn.data.get("dshGenerativeUi") ?? []).filter((r) => r.seq < props.seq).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 }));
23456
23506
  }
23457
23507
  function PatchNotice({ raw }) {
23458
- (0, import_react9.useEffect)(() => {
23508
+ (0, import_react10.useEffect)(() => {
23459
23509
  try {
23460
23510
  if (raw) publishDocument(raw);
23461
23511
  } catch {
@@ -23465,7 +23515,7 @@ function PatchNotice({ raw }) {
23465
23515
  }
23466
23516
  function ValidatedView({ raw, fallback, sessionId, sendPrompt }) {
23467
23517
  const { record: current2, status, retry } = useCurrentDocument(raw ?? {}, sessionId);
23468
- const result = (0, import_react9.useMemo)(() => {
23518
+ const result = (0, import_react10.useMemo)(() => {
23469
23519
  try {
23470
23520
  if (!current2 || current2.version !== 1 || typeof current2.documentId !== "string" || typeof current2.callId !== "string") return null;
23471
23521
  return { ...current2, document: validateDocument(current2.document) };
@@ -23475,7 +23525,7 @@ function ValidatedView({ raw, fallback, sessionId, sendPrompt }) {
23475
23525
  }, [current2]);
23476
23526
  if (!result) return h6("div", { className: "dgui-root" }, h6("p", null, "\u6587\u5B57\u7ED3\u679C\uFF08\u672A\u63D0\u4F9B\u517C\u5BB9\u7684\u754C\u9762\u6570\u636E\uFF09"), h6("pre", null, fallback));
23477
23527
  return h6(
23478
- import_react9.default.Fragment,
23528
+ import_react10.default.Fragment,
23479
23529
  null,
23480
23530
  status === "error" && h6("div", { className: "dgui-root dgui-document-warning", role: "status" }, h6("p", null, "\u672A\u80FD\u786E\u8BA4\u6700\u65B0\u754C\u9762\u7248\u672C\uFF0C\u5F53\u524D\u663E\u793A\u5DF2\u77E5\u7248\u672C\u3002"), h6("button", { type: "button", className: "dgui-secondary", onClick: retry }, "\u91CD\u65B0\u8BFB\u53D6\u6700\u65B0\u754C\u9762")),
23481
23531
  status === "loading" && h6("p", { className: "dgui-copy", role: "status" }, "\u6B63\u5728\u8BFB\u53D6\u6700\u65B0\u754C\u9762\u7248\u672C\u2026"),
@@ -23565,11 +23615,11 @@ body[data-ds-dark-theme] .dgui-root,.dgui-root[data-preview-theme=dark]{--du-acc
23565
23615
  `;
23566
23616
 
23567
23617
  // src/settings.js
23568
- var import_react10 = __toESM(require("react"), 1);
23569
- var h7 = import_react10.default.createElement;
23618
+ var import_react11 = __toESM(require("react"), 1);
23619
+ var h7 = import_react11.default.createElement;
23570
23620
  function ModeSettings({ scope }) {
23571
- const [snapshot, setSnapshot] = (0, import_react10.useState)(() => scope.getSnapshot()), [busy, setBusy] = (0, import_react10.useState)(false), [message, setMessage] = (0, import_react10.useState)(""), pending = (0, import_react10.useRef)(false);
23572
- (0, import_react10.useEffect)(() => scope.subscribe(() => setSnapshot(scope.getSnapshot())), [scope]);
23621
+ const [snapshot, setSnapshot] = (0, import_react11.useState)(() => scope.getSnapshot()), [busy, setBusy] = (0, import_react11.useState)(false), [message, setMessage] = (0, import_react11.useState)(""), pending = (0, import_react11.useRef)(false);
23622
+ (0, import_react11.useEffect)(() => scope.subscribe(() => setSnapshot(scope.getSnapshot())), [scope]);
23573
23623
  const ready = snapshot.status === "ready" && snapshot.value !== void 0;
23574
23624
  async function change(mode, key3 = "designMode") {
23575
23625
  if (pending.current || !ready) return;
@@ -23611,7 +23661,7 @@ function ModeSettings({ scope }) {
23611
23661
  var inject = ["slots", "sessions", "conversation", "uiConversation", "configForms"];
23612
23662
  function apply(ctx) {
23613
23663
  const scope = ctx.configForms.get("motion-ui");
23614
- ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({ name: "plugins.bundle.config", key: "dsh-motion-ui" }, (props) => import_react11.default.createElement(ModeSettings, { ...props, scope })));
23664
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({ name: "plugins.bundle.config", key: "dsh-motion-ui" }, (props) => import_react12.default.createElement(ModeSettings, { ...props, scope })));
23615
23665
  ctx.uiConversation.events.register(turnUiDefinition);
23616
23666
  ctx.slots.inject("conversation.chat.turnTail", () => ctx.slots.register({
23617
23667
  name: "conversation.chat.turnTail",