dsh-motion-ui 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +55 -1
  3. package/VERIFICATION.md +46 -0
  4. package/client.js +2136 -1095
  5. package/examples/mechanism-assembly.json +50 -0
  6. package/examples/mechanism-crank.json +103 -0
  7. package/examples/mechanism-gears.json +103 -0
  8. package/index.js +132 -106
  9. package/lib/assets/standalone-runtime.js +3 -1
  10. package/lib/assets/visual.js +3 -1
  11. package/lib/author-expression.js +9 -8
  12. package/lib/authoring-guide.js +3 -16
  13. package/lib/build-info.js +1 -1
  14. package/lib/canvas-design.js +2 -2
  15. package/lib/canvas-system.js +1 -1
  16. package/lib/catalog.js +5 -6
  17. package/lib/components.js +2 -2
  18. package/lib/delivery.js +1 -1
  19. package/lib/export-runtime-bundle.js +1 -1
  20. package/lib/expressive-examples.js +6 -0
  21. package/lib/fence-feedback.js +8 -2
  22. package/lib/freeform.js +4 -9
  23. package/lib/inspection.js +11 -6
  24. package/lib/mechanism-catalog.js +24 -0
  25. package/lib/mechanism-css.js +13 -0
  26. package/lib/mechanism-entry.js +20 -0
  27. package/lib/mechanism-geometry.js +62 -0
  28. package/lib/mechanism-render.js +24 -0
  29. package/lib/mechanism-runtime-bundle.js +2 -0
  30. package/lib/mechanism-scenes.js +20 -0
  31. package/lib/motion-library.js +17 -0
  32. package/lib/normalize-authoring.js +15 -2
  33. package/lib/prompt-contract.js +22 -0
  34. package/lib/protocol.js +26 -11
  35. package/lib/refinement.js +3 -2
  36. package/lib/sandbox.js +2 -0
  37. package/lib/scene-entry.js +41 -0
  38. package/lib/style-system.js +28 -0
  39. package/lib/text-guide.js +9 -13
  40. package/lib/third-party/anatomy-LICENSE.txt +21 -0
  41. package/lib/third-party/anatomy-NOTICE.md +13 -0
  42. package/lib/third-party/anatomy-iso.mjs +674 -0
  43. package/lib/third-party/lucian-uxui-LICENSE.txt +21 -0
  44. package/lib/third-party/lucian-uxui-NOTICE.md +1 -0
  45. package/lib/third-party/taste-skill-NOTICE.md +10 -0
  46. package/lib/tool-schema.js +9 -9
  47. package/lib/ui-intent.js +4 -15
  48. package/lib/ui-skill.js +19 -25
  49. package/lib/visual-audit.js +15 -4
  50. package/lib/visual-styles.js +134 -22
  51. package/package.json +92 -92
  52. package/skills/intelligent-ui/SKILL.md +22 -102
package/lib/catalog.js CHANGED
@@ -1,21 +1,20 @@
1
1
  import { UI_ICONS, UI_TONES } from './presentation.js';
2
2
  import { componentGuide } from './components.js';
3
3
  import { AUTHORING_GUIDE } from './authoring-guide.js';
4
- import {VISUAL_GUIDE} from './visual.js';
5
4
  export { sceneExamples } from './scenes.js';
6
5
  export { bicycleExample, oscillatorExample } from './canvas-example.js';
7
- export const PROTOCOL_GUIDE = VISUAL_GUIDE+'\n\n'+AUTHORING_GUIDE+`\n\nDSH UI v1. Call render_ui({document: ...}) directly when visualization helps. Do not call it inside run_code/PTC in this version.
8
- Formula shorthand: {expr:"state.V * state.V / state.R"} compiles to the same validated interpreter. Supports explicit state.x/data.x/derived.x/item.x references, + - * /, == != < <= > >=, parentheses, condition ? yes : no, min/max/round/abs, double-quoted strings and booleans. Use it in derived, node text/value/max/rows or map.fields. No arbitrary functions, property chains, statements or side effects; complex animation belongs in render_canvas. Example derived:{power:{expr:"state.V * state.V / state.R"}}, metric.value:{ref:"derived.power"}.
6
+ export const PROTOCOL_GUIDE = AUTHORING_GUIDE+`\n\nDSH UI v1. Call render_ui({document: ...}) directly when visualization helps. Do not call it inside run_code/PTC in this version.
7
+ Formula shorthand: {expr:"state.V * state.V / state.R"} compiles to the same validated interpreter. Supports explicit state.x/data.x/derived.x/item.x references, + - * /, == != < <= > >=, parentheses, condition ? yes : no, min/max/round/ceil/floor/abs, double-quoted strings and booleans. Use it in derived, node text/value/max/rows or map.fields. No arbitrary functions, property chains, statements or side effects; complex animation belongs in render_canvas. Example derived:{power:{expr:"state.V * state.V / state.R"}}, metric.value:{ref:"derived.power"}.
9
8
  Document: {version:1,title:string,fallback:string,presentation?:{layout,accent,density,preset},state?:{},data?:{},derived?:{},nodes:[],actions?:{}}.
10
9
  Scene layouts: dashboard (metrics/trends), workbench (parameter controls/results), comparison (parallel choice cards), guide (ordered instructions), status (observed task snapshot), report (reading and evidence). Accent: blue/teal/violet/amber/rose; density: comfortable/compact. Omit to keep the standard design. Choose based on the task, not at random. Card surface: outline/soft/featured. Featured indicates discussion focus, not an automatic recommendation.
11
10
  Additional nodes: badge {id,type,text,icon?,tone?}; progress {id,type,label,value,max,unit?,icon?,tone?} where values may be bindings and 0 <= value <= positive max; steps {id,type,items:[{title,description?,status?:"pending"|"active"|"done"|"error"}]} (direct array of 1..12 items, not {item:[...]}; omit status for static guides); details {id,type,title,children,open?:boolean,icon?,tone?}. Progress and steps are declared snapshots, not a live host task subscription. Only claim real progress/completion from actual tool evidence; mark demo data visibly. Details keeps the content available in text fallback.
12
11
  Display lifecycle: submit native render_text/render_ui/render_canvas directly. Several independent UI documents may be created in one answer. Complete validated native blocks appear progressively as read-only previews. Keep previews visible through later reasoning and text; durable turn/end activates controls and scripts. Incomplete/failed tools never become interactive. Use small meaningful documents; no separate ui.begin/append calls.
13
12
  All IDs and dictionary keys: Unicode letters (including Chinese) or underscore, followed by letters/marks/digits/underscore, max64; unique node IDs. Standard components reject HTML, JS, arbitrary styles and callbacks. link/button href explicitly supports http/https URLs; path uses the host file opener on user click. The sandbox component below is the explicit exception; unlisted fields are always rejected.
14
- Freeform canvas: {id,type:"sandbox",title,html,css?:string,script?:string,height?:160..1000,fallback,stateKeys?:string[],dataKeys?:string[],derivedKeys?:string[]}. Use it for custom SVG diagrams, animation, simulations and bespoke interfaces that fixed components cannot express. HTML is a body fragment. Put CSS in css and ALL JavaScript in script (not inline handlers, script tags or remote imports). Inline SVG is supported. Each source field allows up to 49152 characters; the total document remains 128KiB. The iframe has no same-origin privilege, remote resources/network APIs are blocked by CSP, and no host tools are exposed. Never put credentials or private unrelated data in a canvas. It is browser origin isolation, not a CPU/memory hard sandbox.
13
+ Freeform canvas: {id,type:"sandbox",title,html,css?:string,script?:string,height?:160..1000,fallback,stateKeys?:string[],dataKeys?:string[],derivedKeys?:string[],libraries?:["mechanism"]}. For internal assemblies and mechanical relations, ui_catalog topic=mechanism provides an opt-in local drawing/kinematics helper and three complete examples (assembly/gears/crank). Use it for custom SVG diagrams, animation, simulations and bespoke interfaces that fixed components cannot express. HTML is a body fragment. Put CSS in css and ALL JavaScript in script (not inline handlers, script tags or remote imports). Inline SVG is supported. Each source field allows up to 49152 characters; the total document remains 128KiB. The iframe has no same-origin privilege, remote resources/network APIs are blocked by CSP, and no host tools are exposed. Never put credentials or private unrelated data in a canvas. It is browser origin isolation, not a CPU/memory hard sandbox.
15
14
  Canvas API: window.DSHUI.snapshot -> {state,data,derived,disabled,theme,visible}; DSHUI.animate(fn) returns cleanup and calls fn({time,delta,reducedMotion,visible}), time/delta in seconds, delta capped at 0.05. It pauses offscreen/background and supplies a static delta=0 callback for reduced motion; integrate delta for pause/resume without jumps. DSHUI.subscribe(snapshot=>{...}) immediately supplies current selected values and returns unsubscribe; DSHUI.setState({key:value,...}) requests an atomic Host-validated update of declared stateKeys. Use bare dictionary keys in stateKeys/dataKeys/derivedKeys. Only declared keys are exposed; state types/ranges/options remain enforced. Subscribe to drive DOM/SVG from host state; do not send the same state back from subscribe (feedback loop). Native controls and canvas share the saved state. Local animation may use requestAnimationFrame without saving every frame. No Agent/tool action API in this release. theme exposes bg,soft,ink,muted,line,accent,scheme; CSS variables --canvas-bg/soft/ink/muted/line/accent update automatically. Honor reduced motion, provide accessible labels and responsive layout. Canvas scripts activate only after successful tool commit and state restoration; previews show inert completed HTML/CSS. Use a complete useful fallback per canvas. Code or reset changes restart the frame, state changes do not. All transient internal animation state is reset on reload; declare important parameters in document.state.
16
15
  state: key -> {type:"number",default,min,max,step} or {type:"string",default,options?:scalar[]} or {type:"boolean",default} or {type:"json",default:array/object} (canvas structured state, max16KiB per value). select requires scalar options. Numeric defaults follow step from min.
17
- derived: key -> expression: {const:scalar}, {ref:"state.key"|"data.key"|"derived.key"}, or {op,args:expression[]}. Allowed ops add,mul,min,max (1..8 args); sub,div,eq,neq,lt,lte,gt,gte (2); round,abs (1); if (3, boolean condition). No cycles. Arithmetic requires finite numbers: add does NOT concatenate strings. For formatted labels use a metric's label/unit/decimals, or format text inside a sandbox; omit unused derived fields. if can return strings: {op:"if",args:[{op:"gt",args:[{ref:"derived.weightGiB"},{const:20}]},{const:"超过理论预算"},{const:"在理论预算内"}]} -> bind a text node to this derived result. No separate conditional-rendering field is needed.
18
- Text/metric values are literal scalars or {ref:...}. Table/chart rows accept inline arrays of objects, {ref:"data.rows"}, {ref:"derived.rows"}, or the shorthand string "data.rows"/"derived.rows". Table label is an optional caption. Static table example: data:{rows:[{name:"A",note:"示例"}]}, node:{id:"t",type:"table",rows:{ref:"data.rows"},columns:[{key:"name",label:"方案"},{key:"note",label:"说明"}]}. Dynamic rows: a derived root may be {map:{source:"data.samples",fields:{contextK:{op:"mul",args:[{ref:"state.contextK"},{ref:"item.fraction"}]},value:{ref:"derived.total"}}}}. source must be the string "data.samples", not an inline array; declare those object rows in document.data.samples. Internal field keys support Chinese identifiers such as 功率 and ASCII identifiers such as powerW; spaces/dots/hyphens belong in display labels. item.key is available only inside map fields. Each field is a normal scalar expression. No nested maps/callbacks; 1..16 fields, max500 generated rows across all maps; normal expression and calculation budgets apply. Derived output max128KiB. DSHUI.formatNumber(value, decimals=2) formats finite numbers with 0..6 decimal places and comma grouping, exactly matching native metrics and fallback; use it for shared readouts instead of mixing toFixed rounding. Use this for live charts/tables, never model calls on slider changes.
16
+ derived: key -> expression: {const:scalar}, {ref:"state.key"|"data.key"|"derived.key"}, or {op,args:expression[]}. Allowed ops add,mul,min,max (1..8 args); sub,div,eq,neq,lt,lte,gt,gte (2); round,ceil,floor,abs (1); if (3, boolean condition). No cycles. Arithmetic requires finite numbers: add does NOT concatenate strings. For formatted labels use a metric's label/unit/decimals, or format text inside a sandbox; omit unused derived fields. if can return strings: {op:"if",args:[{op:"gt",args:[{ref:"derived.weightGiB"},{const:20}]},{const:"超过理论预算"},{const:"在理论预算内"}]} -> bind a text node to this derived result. No separate conditional-rendering field is needed.
17
+ Text/metric values are literal scalars or {ref:...}. Table/chart rows accept inline arrays of objects, {ref:"data.rows"}, {ref:"derived.rows"}, or the shorthand string "data.rows"/"derived.rows". Table label is an optional caption. Static table example: data:{rows:[{name:"A",note:"示例"}]}, node:{id:"t",type:"table",rows:{ref:"data.rows"},columns:[{key:"name",label:"方案"},{key:"note",label:"说明"}]}. For a short live breakdown prefer inline formula cells: rows:[{label:"餐饮",value:{ref:"derived.mealTotal"}},{label:"住宿",value:{expr:"ceil(state.people / 2) * state.room"}}], labelKey:"label",valueKey:"value". These cells recalculate locally; data remains literal and never executes formulas. Dynamic rows for many samples: a derived root may be {map:{source:"data.samples",fields:{contextK:{op:"mul",args:[{ref:"state.contextK"},{ref:"item.fraction"}]},value:{ref:"derived.total"}}}}. source must be the string "data.samples", not an inline array; declare those object rows in document.data.samples. Internal field keys support Chinese identifiers such as 功率 and ASCII identifiers such as powerW; spaces/dots/hyphens belong in display labels. item.key is available only inside map fields. Each field is a normal scalar expression. No nested maps/callbacks; 1..16 fields, max500 generated rows across all maps; normal expression and calculation budgets apply. Derived output max128KiB. DSHUI.formatNumber(value, decimals=2) formats finite numbers with 0..6 decimal places and comma grouping, exactly matching native metrics and fallback; use it for shared readouts instead of mixing toFixed rounding. Use this for live charts/tables, never model calls on slider changes.
19
18
  Registered component fields (some optional; required values and types follow the rules above/below): ${componentGuide()}. Controls bind "state.key"; slider numeric, toggle boolean, select requires state options. Metric decimals 0..6. Table columns: [{key,label,unit?}]. Chart kind bar/line, finite numeric values, rows as a data/derived binding; line xScale linear positions numeric labelKey samples by their actual coordinate, category uses equal spacing. Use ascending samples for a curve.
20
19
  actions: key -> {type:"agent.followup",intent:string,stateKeys:string[]}. Button submits current selected parameters as a new conversation request; it does not perform a host action itself. All host actions still use ordinary DSH permissions.
21
20
  Presentation: heading/text/card/metric accept optional tone (${UI_TONES.join(', ')}); heading/text/card/metric/button accept optional icon (${UI_ICONS.join(', ')}). Fixed tokens only, no SVG/HTML/CSS/color strings. Defaults already include icons and themed spacing. Use accent for primary information, warning for assumptions/caveats, success/danger only for explicitly established facts. Tones are static, not conditional; use derived string text to report changing verdicts. Use row for 2-3 related metrics, card for a meaningful section, avoid repeated headings and deep decorative nesting. Do not use color as the only explanation.
package/lib/components.js CHANGED
@@ -13,13 +13,13 @@ export const COMPONENTS = Object.freeze({
13
13
  steps: define(['title','items'], 'Ordered declared steps with an optional heading'),
14
14
  metric: define(['label', 'value', 'unit', 'decimals'], 'Number with units, or a literal/bound status string', 'tone'),
15
15
  input: define(['label', 'bind'], 'String or numeric state input'), slider: define(['label', 'bind'], 'Numeric state control'),
16
- select: define(['label', 'bind'], 'Selection from declared state options'), toggle: define(['label', 'bind'], 'Boolean state control'),
16
+ select: define(['label', 'bind','variant'], 'Selection from declared state options; variant:segmented for 2..8 short options, dropdown otherwise'), toggle: define(['label', 'bind'], 'Boolean state control'),
17
17
  button: define(['text', 'action','href','path','variant'], 'Agent follow-up, web link or host file opener', 'icon'),
18
18
  link: define(['text','href','path','variant'], 'User-clicked webpage or local file button', 'icon'),
19
19
  divider: define(['label'], 'Quiet section divider'),
20
20
  table: define(['label', 'rows', 'columns'], 'Supplied or derived values; inline rows or data/derived reference'),
21
21
  chart: define(['kind', 'rows', 'labelKey', 'valueKey', 'label', 'unit', 'xScale'], 'Bar/line comparison with accessible data'),
22
- sandbox: define(['title', 'html', 'css', 'script', 'fallback', 'height', 'stateKeys', 'dataKeys', 'derivedKeys'], 'Freeform isolated HTML/SVG/CSS/JavaScript visualization')
22
+ sandbox: define(['title', 'html', 'css', 'script', 'fallback', 'height', 'libraries', 'stateKeys', 'dataKeys', 'derivedKeys'], 'Freeform isolated HTML/SVG/CSS/JavaScript visualization')
23
23
  });
24
24
  export function componentFields(type) {
25
25
  const c = Object.hasOwn(COMPONENTS, type) ? COMPONENTS[type] : undefined;
package/lib/delivery.js CHANGED
@@ -6,7 +6,7 @@ export function deliveryDiagnostics(document){
6
6
  return diagnostics;
7
7
  }
8
8
  export function diagnoseDelivery(input){
9
- try{const document=validateDocument(input),diagnostics=deliveryDiagnostics(document);return {valid:true,complete:diagnostics.length===0,document,diagnostics}}catch(e){return {valid:false,complete:false,diagnostics:[{severity:'error',path:'document',message:e.message}]}}
9
+ try{const document=validateDocument(input),diagnostics=deliveryDiagnostics(document);return {valid:true,complete:diagnostics.length===0,document,diagnostics}}catch(e){return {valid:false,complete:false,diagnostics:[{severity:'error',path:e.path??e.message.match(/(?:^DSH UI: |unknown field )((?:node|nodes|derived|state|document|presentation)(?:\.[\p{L}\p{N}_]+|\[\d+\])+)/u)?.[1]??'document',message:e.message}]}}
10
10
  }
11
11
  export function resolveSourceFence(raw,{svg=false}={}){
12
12
  if(typeof raw!=='string'||raw.length>131072)return {valid:false,complete:false,diagnostics:[{severity:'error',path:'source',message:'Source exceeds 128 KiB'}]};