pmx-canvas 0.8.0 → 0.8.2
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/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/CHANGELOG.md +17 -0
- package/Readme.md +2 -2
- package/dist/canvas/global.css +6 -5
- package/dist/canvas/index.js +12 -12
- package/dist/canvas/mcp-app.html +22 -21
- package/dist/json-render/index.css +81 -1
- package/dist/json-render/index.js +1 -1
- package/dist/types/client/nodes/HtmlNode.d.ts +1 -0
- package/docs/design/Chrome.dc.html +3 -0
- package/docs/design.md +6 -3
- package/docs/http-api.md +6 -1
- package/docs/mcp.md +4 -1
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/skills/pmx-canvas/SKILL.md +20 -3
- package/skills/pmx-canvas/evals/evals.json +62 -0
- package/skills/pmx-canvas/references/ax-html-control-surface.md +6 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/src/cli/commands/nodes.ts +3 -2
- package/src/cli/index.ts +2 -1
- package/src/client/nodes/HtmlNode.tsx +12 -2
- package/src/client/theme/global.css +6 -5
- package/src/json-render/renderer/index.css +6 -1
- package/src/json-render/renderer/index.tsx +0 -1
- package/src/server/canvas-operations.ts +1 -1
- package/src/server/context-reads.ts +1 -0
- package/src/server/index.ts +22 -2
- package/src/server/operations/mcp.ts +6 -0
- package/src/server/operations/ops/json-render.ts +5 -1
- package/src/server/operations/registry.ts +3 -1
|
@@ -920,4 +920,4 @@ html[data-jr-devtools-picking="true"] * {
|
|
|
920
920
|
<path d="M16 21h1a2 2 0 0 0 2-2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1"/>
|
|
921
921
|
</svg>`;function wte(){return{id:$2e,label:"Spec",badge(e){let t=e.getSpec();if(!t)return;return Object.keys(t.elements).length||void 0},mount(e,t){return q2e(e,t)}}}function q2e(e,t){let r=new Set,n=te("div",{className:"jr-split-left",role:"tree",tabindex:"0"}),a=te("div",{className:"jr-generations","aria-label":"Spec generation"}),o=null,i=null,s=te("nav",{className:"jr-breadcrumbs","aria-label":"Selected element ancestors"}),l=te("div",{className:"jr-breadcrumbs-trail"}),u=te("div",{className:"jr-breadcrumbs-tools",role:"group","aria-label":"Spec view"}),c=te("button",{className:"jr-view-btn",type:"button",title:"Tree view","aria-label":"Tree view","data-active":"true",onClick:()=>m("tree")});c.innerHTML=U2e;let f=te("button",{className:"jr-view-btn",type:"button",title:"Raw JSON view","aria-label":"Raw JSON view","data-active":"false",onClick:()=>m("raw")});f.innerHTML=V2e,u.append(c,f),s.append(l,u);let d=te("div",{className:"jr-tree-rows"});n.append(a,s,d);let p=te("div",{className:"jr-split-right"});e.appendChild(te("div",{className:"jr-split"},n,p));let g="tree";function m(z){if(g===z)return;g=z,c.dataset.active=String(g==="tree"),f.dataset.active=String(g==="raw"),j()}let y=t.selection.subscribe(()=>{let z=t.selection.get();if(z){let{spec:_,generations:O}=T();if(_&&!_.elements[z]&&O.length>1){let k=O.find((E)=>E.spec.elements[z]);if(k)o=k.id}}j()}),h=()=>oo(null);n.addEventListener("mouseleave",h);let v=[];function x(z){if(r.has(z))r.delete(z);else r.add(z);j()}function S(){n.querySelector('.jr-tree-row[data-selected="true"]')?.scrollIntoView({block:"nearest"})}function C(z){if(!z)return;t.selection.set(z),oo(z),S()}function P(z){if(g==="raw")return;if(z.metaKey||z.ctrlKey||z.altKey)return;let _=z.key;if(_!=="ArrowUp"&&_!=="ArrowDown"&&_!=="ArrowLeft"&&_!=="ArrowRight"&&_!=="Enter"&&_!==" "&&_!=="Home"&&_!=="End")return;let{spec:O}=T();if(!O||v.length===0)return;let k=t.selection.get(),E=k?v.indexOf(k):-1;if(z.preventDefault(),_==="ArrowDown"){if(E<0)C(v[0]);else C(v[Math.min(E+1,v.length-1)]);return}if(_==="ArrowUp"){if(E<0)C(v[0]);else C(v[Math.max(E-1,0)]);return}if(_==="Home"){C(v[0]);return}if(_==="End"){C(v[v.length-1]);return}if(_==="ArrowRight"){if(!k){C(v[0]);return}let D=O.elements[k],W=Ql(D);if(!(W.length>0))return;if(!r.has(k))r.add(k),j(),S();else C(W[0]);return}if(_==="ArrowLeft"){if(!k)return;let D=O.elements[k];if(Ql(D).length>0&&r.has(k))r.delete(k),j(),S();else{let K=K2e(O,k);if(K)C(K)}return}if(!k){C(v[0]);return}let L=O.elements[k];if(Ql(L).length>0)x(k),S()}let A=()=>{let z=t.selection.get();if(z)oo(z)},I=(z)=>{let _=z.relatedTarget;if(!_||!n.contains(_))oo(null)},R=()=>{try{n.focus({preventScroll:!0})}catch{}};n.addEventListener("mousedown",R),n.addEventListener("keydown",P),n.addEventListener("focusin",A),n.addEventListener("focusout",I);function T(){let z=t.getSpecs?.()??[];if(z.length===0)return{spec:t.getSpec(),generations:z};let _=z[z.length-1];if(o==null||!z.some((k)=>k.id===o))o=_.id;else if(i!=null&&o===i&&i!==_.id)o=_.id;return i=_.id,{spec:z.find((k)=>k.id===o).spec,generations:z}}function N(z){if(z.length<2){zt(a,null);return}let _=z[z.length-1].id,O=te("select",{className:"jr-generation-select","aria-label":"Inspect generation",onChange:(E)=>{o=E.target.value,j()}},...z.map((E,L)=>te("option",{value:E.id,selected:E.id===o?"true":void 0},E.id===_?`Gen ${L+1} (latest)`:`Gen ${L+1}`))),k=te("span",{className:"jr-generations-count"},`${z.length} generation${z.length===1?"":"s"}`);zt(a,O,k)}function j(){let{spec:z,generations:_}=T();if(N(_),!z||!z.root||Object.keys(z.elements).length===0){v=[],Z(null,null),zt(d,te("div",{className:"jr-empty"},"No spec attached.")),zt(p,null);return}let O=G2e(z),k=t.selection.get(),E=k?Z2e(z,k):[];for(let L of E)r.add(L);if(v=H2e(z,r),Z(z,E),g==="raw")q(z);else zt(d,Cte(z,z.root,0,r,k,O,t,x,null));if(k&&z.elements[k])Y2e(p,z,k,O);else zt(p,te("div",{className:"jr-empty"},"Select an element to inspect."))}function q(z){let _;try{_=JSON.stringify(z,null,2)}catch{_=String(z)}zt(d,te("pre",{className:"jr-raw"},_))}function Z(z,_){if(!z||!_||_.length===0){zt(l,te("span",{className:"jr-breadcrumbs-empty"},"No element selected"));return}let O=[..._].reverse(),k=[];O.forEach((E,L)=>{let W=z.elements[E]?.type??"<missing>",K=L===O.length-1,Y=te("button",{className:"jr-breadcrumb",type:"button","data-current":String(K),title:`#${E}`,onClick:(ae)=>{ae.stopPropagation(),t.selection.set(E),oo(E)},onMouseEnter:()=>oo(E),onMouseLeave:()=>oo(null)},W);if(k.push(Y),!K)k.push(te("span",{className:"jr-breadcrumb-sep"},"▸"))}),zt(l,k)}return j(),{update:j,destroy(){y(),n.removeEventListener("mouseleave",h),n.removeEventListener("mousedown",R),n.removeEventListener("keydown",P),n.removeEventListener("focusin",A),n.removeEventListener("focusout",I),oo(null)}}}function H2e(e,t){let r=[];function n(a){r.push(a);let o=e.elements[a],i=Ql(o);if(i.length===0)return;if(!t.has(a))return;for(let s of i)n(s)}return n(e.root),r}function K2e(e,t){for(let[r,n]of Object.entries(e.elements))if(Ql(n).includes(t))return r;return null}function Ql(e){if(!e)return[];return[...e.children??[],...Object.values(e.slots??{}).flat()]}function G2e(e){let t=$j(e,{checkOrphans:!0}),r=new Map;for(let n of t.issues){let a=n.elementKey??"__spec__",o=r.get(a)??[];o.push(n),r.set(a,o)}return{all:t.issues,byKey:r}}function Z2e(e,t){let r=[];function n(a){if(a===t)return r.push(a),!0;let o=e.elements[a];for(let i of Ql(o))if(n(i))return r.push(a),!0;return!1}return n(e.root),r}function Cte(e,t,r,n,a,o,i,s,l){let u=e.elements[t];if(!u)return te("div",{className:"jr-tree-row",style:{paddingLeft:`${12+r*14}px`}},te("span",{className:"jr-tree-toggle"}," "),te("span",{className:"jr-tree-type"},"<missing>"),te("span",{className:"jr-tree-key"},t));let c=Ql(u),f=c.length>0,d=f&&n.has(t),p=a===t,g=o.byKey.get(t)??[],m=g.some((S)=>S.severity==="error"),y=W2e(u),h=u.visible!==void 0?null:l,v=te("div",{className:`jr-tree-row${h===!0?" jr-tree-hidden":""}`,"data-selected":String(p),style:{paddingLeft:`${8+r*14}px`},onClick:(S)=>{S.stopPropagation(),i.selection.set(t)},onMouseEnter:()=>{oo(t)},onMouseLeave:()=>{oo(null)}},te("span",{className:"jr-tree-toggle",onClick:(S)=>{if(S.stopPropagation(),f)s(t)}},f?d?"▾":"▸":" "),te("span",{className:"jr-tree-type"},u.type),te("span",{className:"jr-tree-key"},`#${t}`),y?te("span",{className:"jr-tree-summary"},y):null,u.visible!==void 0?te("span",{className:"jr-badge","data-tone":"amber"},"visible"):null,u.repeat?te("span",{className:"jr-badge","data-tone":"accent"},"repeat"):null,m?te("span",{className:"jr-badge","data-tone":"red",title:g.map((S)=>S.message).join(`
|
|
922
922
|
`)},`${g.length}`):null),x=te("div",null,v);if(d&&f)for(let S of c){let C=Cte(e,S,r+1,n,a,o,i,s,h);if(C)x.appendChild(C)}return x}function W2e(e){let t=e.props;if(!t)return null;let r=["title","label","text","name","value","status"];for(let n of r){let a=t[n];if(typeof a==="string"&&a.length>0)return ste(a,60);if(typeof a==="number"||typeof a==="boolean")return String(a)}for(let[,n]of Object.entries(t)){if(typeof n==="string"&&n.length>0)return ste(n,60);if(typeof n==="number"||typeof n==="boolean")return String(n)}return null}function ste(e,t){if(e.length<=t)return e;return e.slice(0,t-1)+"…"}function Y2e(e,t,r,n){let a=t.elements[r];if(!a){zt(e,te("div",{className:"jr-empty"},`Element '${r}' not found.`));return}let o=n.byKey.get(r)??[],i=Ql(a).length;zt(e,te("div",{className:"jr-detail"},te("h4",null,"Element"),te("div",{style:{marginBottom:"10px"}},te("span",{className:"jr-tree-type"},a.type)," ",te("span",{className:"jr-tree-key"},`#${r}`)," ",te("span",{className:"jr-tree-summary"},`${i} child${i===1?"":"ren"}`)),o.length>0?[te("h4",null,"Issues"),...o.map((s)=>te("div",{className:"jr-badge","data-tone":s.severity==="error"?"red":"amber",style:{marginBottom:"6px",display:"block",padding:"4px 8px"}},`${s.severity.toUpperCase()}: ${s.message}`))]:null,a.visible!==void 0?[te("h4",null,"Visibility"),te("pre",null,tf(a.visible,2000))]:null,a.on&&Object.keys(a.on).length>0?[te("h4",null,"Events"),te("pre",null,tf(a.on,2000))]:null,a.watch&&Object.keys(a.watch).length>0?[te("h4",null,"Watchers"),te("pre",null,tf(a.watch,2000))]:null,a.repeat?[te("h4",null,"Repeat"),te("pre",null,tf(a.repeat,2000))]:null,te("h4",null,"Props"),te("pre",null,JSON.stringify(a.props,null,2))))}var X2e="state";function Ate(){return{id:X2e,label:"State",badge(e){let t=e.getStateStore();if(!t)return;try{let r=Ns(t.getSnapshot());return Object.keys(r).length||void 0}catch{return}},mount(e,t){return J2e(e,t)}}}function J2e(e,t){let r=te("div",{style:{height:"100%",display:"flex",flexDirection:"column"}}),n=te("div",{className:"jr-toolbar"}),a=te("div",{style:{overflow:"auto",flex:"1",minHeight:"0"}});r.appendChild(n),r.appendChild(a),e.appendChild(r);let o="",i=new Set,s=te("input",{className:"jr-search",type:"text",placeholder:"Filter paths…",onInput:(c)=>{o=(c.target.value||"").toLowerCase(),u()}});function l(){zt(n,s,te("button",{className:"jr-icon-btn",title:"Refresh state listing",onClick:()=>u()},"refresh"))}function u(){let c=t.getStateStore();if(!c){zt(a,te("div",{className:"jr-empty"},"No StateStore attached. Devtools sees state once a provider mounts."));return}let f={};try{f=Ns(c.getSnapshot())}catch(p){zt(a,te("div",{className:"jr-empty"},`State read failed: ${String(p)}`));return}let d=Object.entries(f).filter(([p])=>!o||p.toLowerCase().includes(o)).sort(([p],[g])=>p.localeCompare(g));if(d.length===0){zt(a,te("div",{className:"jr-empty"},o?"No paths match the filter.":"State is empty."));return}zt(a,d.map(([p,g])=>Q2e(p,g,c.set,i)))}return l(),u(),{update(){if(i.size===0)u()}}}function Q2e(e,t,r,n){let a=te("input",{className:"jr-state-value",type:"text",value:lte(t),spellcheck:"false",onFocus:()=>{n.add(e)},onBlur:(o)=>{n.delete(e);let i=o.target.value;try{let s=i===""?"":JSON.parse(i);r(e,s),a.dataset.dirty="false"}catch{r(e,i),a.dataset.dirty="false"}},onInput:()=>{a.dataset.dirty="true"},onKeyDown:(o)=>{if(o.key==="Enter")o.preventDefault(),o.target.blur();if(o.key==="Escape")a.value=lte(t),a.dataset.dirty="false",o.target.blur()}});return te("div",{className:"jr-state-row",title:`${e}
|
|
923
|
-
${tf(t,1000)}`},te("span",{className:"jr-state-path"},e),a)}function lte(e){if(typeof e==="string")return JSON.stringify(e);if(e===null)return"null";if(e===void 0)return"";if(typeof e==="number"||typeof e==="boolean")return String(e);try{return JSON.stringify(e)}catch{return String(e)}}var eze="actions";function Pte(){return{id:eze,label:"Actions",badge(e){return Ite(e.events.snapshot()).length||void 0},mount(e,t){return tze(e,t)}}}function tze(e,t){let r=te("div",{style:{height:"100%",display:"flex",flexDirection:"column"}}),n=te("div",{className:"jr-toolbar"}),a=te("div",{style:{overflow:"auto",flex:"1",minHeight:"0"}});r.appendChild(n),r.appendChild(a),e.appendChild(r);let o="",i=new Set,s=te("input",{className:"jr-search",type:"text",placeholder:"Filter by action name…",onInput:(f)=>{o=(f.target.value||"").toLowerCase(),c()}}),l=te("span",{className:"jr-badge"},"0 actions"),u=te("button",{className:"jr-icon-btn",title:"Clear action log",onClick:()=>{let f=t.events.snapshot().filter((d)=>d.kind!=="action-dispatched"&&d.kind!=="action-settled");t.events.clear();for(let d of f)t.events.push(d)}},"clear");n.append(s,l,u);function c(){let f=Ite(t.events.snapshot()).filter((d)=>!o||d.name.toLowerCase().includes(o)).reverse();if(l.textContent=`${f.length} action${f.length===1?"":"s"}`,f.length===0){zt(a,te("div",{className:"jr-empty"},o?"No actions match the filter.":"No actions dispatched yet."));return}zt(a,f.map((d)=>rze(d,i,c)))}return c(),{update:c}}function Ite(e){let t=new Map,r=[];for(let n of e)if(n.kind==="action-dispatched"){if(t.has(n.id))continue;t.set(n.id,{id:n.id,name:n.name,params:n.params,dispatchedAt:n.at}),r.push(n.id)}else if(n.kind==="action-settled"){let a=t.get(n.id);if(!a)continue;a.settledAt=n.at,a.durationMs=n.durationMs,a.ok=n.ok,a.result=n.result,a.error=n.error}return r.map((n)=>t.get(n)).filter(Boolean)}function rze(e,t,r){let n=t.has(e.id),a=e.settledAt===void 0,o=a?"amber":e.ok?"green":"red",i=a?"pending":e.ok?"ok":"error",s=e.durationMs!==void 0?`${e.durationMs.toFixed(0)}ms`:"",l=te("div",{className:"jr-row",onClick:()=>{if(n)t.delete(e.id);else t.add(e.id);r()}},te("span",{className:"jr-row-time"},mv(e.dispatchedAt)),te("span",{className:"jr-row-main"},te("strong",null,e.name),e.params!==void 0?te("span",{className:"jr-tree-summary",style:{marginLeft:"8px"}},tf(e.params,80)):null),te("span",{className:"jr-row-meta"},te("span",{className:"jr-badge","data-tone":o},i),s?te("span",{style:{marginLeft:"6px"}},s):null));if(!n)return l;let u=te("div",null,l);return u.appendChild(te("div",{className:"jr-row-expanded"},te("h4",null,"Params"),te("pre",null,JSON.stringify(e.params??null,null,2)),e.ok===!1?[te("h4",null,"Error"),te("pre",null,e.error??"(unknown error)")]:null,e.result!==void 0&&e.ok!==!1?[te("h4",null,"Result"),te("pre",null,JSON.stringify(e.result,null,2))]:null)),u}var nze="stream";function _te(){return{id:nze,label:"Stream",badge(e){return Ete(e.events.snapshot()).length||void 0},mount(e,t){return aze(e,t)}}}function aze(e,t){let r=te("div",{style:{height:"100%",display:"flex",flexDirection:"column"}}),n=te("div",{className:"jr-toolbar"}),a=te("div",{style:{overflow:"auto",flex:"1",minHeight:"0"}});r.appendChild(n),r.appendChild(a),e.appendChild(r);function o(){let i=t.events.snapshot(),s=Ete(i),l=oze(i);if(zt(n,te("span",{className:"jr-badge"},`${s.length} generation${s.length===1?"":"s"}`)),s.length===0&&l.length===0){zt(a,te("div",{className:"jr-empty"},"No stream events yet. Tap a stream or pass messages to capture patches."));return}let u=[];if(l.length>0)u.push(ute({startedAt:l[0]?.at??Date.now(),events:l},"live",!0));for(let c=s.length-1;c>=0;c--){let f=s[c];u.push(ute(f,`gen-${c}`,c===s.length-1))}zt(a,u)}return o(),{update:o}}function Ete(e){let t=[],r=null;for(let n of e){if(n.kind==="stream-lifecycle"){if(n.phase==="start"){if(r)t.push(r);r={startedAt:n.at,events:[]}}else{if(!r)r={startedAt:n.at,events:[]};r.endedAt=n.at,r.ok=n.ok,t.push(r),r=null}continue}if(n.kind==="stream-patch"||n.kind==="stream-text"||n.kind==="stream-usage"){if(!r)r={startedAt:n.at,events:[]};if(r.events.push(n),n.kind==="stream-usage")r.usage=n.usage}}if(r)t.push(r);return t}function oze(e){if(e.some((r)=>r.kind==="stream-lifecycle"))return[];return e.filter((r)=>r.kind==="stream-patch"||r.kind==="stream-text"||r.kind==="stream-usage")}function ute(e,t,r){let n=e.events.filter((c)=>c.kind==="stream-patch").length,a=e.events.filter((c)=>c.kind==="stream-text").length,o=e.endedAt!==void 0?e.endedAt-e.startedAt:void 0,i=e.endedAt===void 0?"amber":e.ok===!1?"red":"green",s=e.endedAt===void 0?"live":e.ok===!1?"error":"done",l=te("div",{style:{borderTop:"1px solid var(--jr-border)",background:"var(--jr-bg-soft)",display:r?"block":"none"}},...e.events.map(ize)),u=te("div",{className:"jr-row",style:{cursor:"pointer"},onClick:()=>{l.style.display=l.style.display==="none"?"block":"none"}},te("span",{className:"jr-row-time"},mv(e.startedAt)),te("span",{className:"jr-row-main"},te("strong",null,`generation #${t}`),te("span",{className:"jr-tree-summary",style:{marginLeft:"8px"}},`${n} patch${n===1?"":"es"}`,a>0?`, ${a} text chunk${a===1?"":"s"}`:"")),te("span",{className:"jr-row-meta"},te("span",{className:"jr-badge","data-tone":i},s),o!==void 0?te("span",{style:{marginLeft:"6px"}},`${o}ms`):null,e.usage?te("span",{style:{marginLeft:"6px"}},`${TC(e.usage.totalTokens)} tok`):null));return te("div",null,u,l)}function ize(e){if(e.kind==="stream-patch")return te("div",{className:"jr-row"},te("span",{className:"jr-row-time"},mv(e.at)),te("span",{className:"jr-row-main"},te("span",{className:"jr-badge","data-tone":"accent"},e.patch.op),te("span",{className:"jr-tree-summary",style:{marginLeft:"8px"}},e.patch.path,e.patch.value!==void 0?` = ${tf(e.patch.value,80)}`:"")),te("span",{className:"jr-row-meta"},e.source));if(e.kind==="stream-text")return te("div",{className:"jr-row"},te("span",{className:"jr-row-time"},mv(e.at)),te("span",{className:"jr-row-main",style:{whiteSpace:"pre-wrap"}},e.text),te("span",{className:"jr-row-meta"},te("span",{className:"jr-badge"},"text")));if(e.kind==="stream-usage")return te("div",{className:"jr-row"},te("span",{className:"jr-row-time"},mv(e.at)),te("span",{className:"jr-row-main"},`prompt ${TC(e.usage.promptTokens)} / completion ${TC(e.usage.completionTokens)} / total ${TC(e.usage.totalTokens)}`),te("span",{className:"jr-row-meta"},te("span",{className:"jr-badge","data-tone":"accent"},"usage")));return te("div")}function TC(e){if(e>=1000)return`${(e/1000).toFixed(1).replace(/\.0$/,"")}k`;return String(e)}var sze="catalog";function Ote(){return{id:sze,label:"Catalog",badge(e){let t=e.getCatalog();if(!t)return;return t.componentNames.length+t.actionNames.length||void 0},mount(e,t){return lze(e,t)}}}function lze(e,t){let r=te("div",{style:{height:"100%",display:"flex",flexDirection:"column"}}),n=te("div",{className:"jr-toolbar"}),a=te("div",{style:{overflow:"auto",flex:"1",minHeight:"0"}});r.appendChild(n),r.appendChild(a),e.appendChild(r);let o="components",i="",s=te("button",{className:"jr-icon-btn","data-active":"true",onClick:()=>{if(o==="components")return;o="components",c(),f()}},"components"),l=te("button",{className:"jr-icon-btn","data-active":"false",onClick:()=>{if(o==="actions")return;o="actions",c(),f()}},"actions"),u=te("input",{className:"jr-search",type:"text",placeholder:"Filter by name…",onInput:(p)=>{i=(p.target.value||"").toLowerCase(),f()}});n.append(s,l,u);function c(){let p=t.getCatalog(),g=p?cte(p.data):null,m=g?.components.length??0,y=g?.actions.length??0;s.textContent=`components (${m})`,l.textContent=`actions (${y})`,s.dataset.active=String(o==="components"),l.dataset.active=String(o==="actions")}function f(){let p=t.getCatalog();if(!p){zt(a,te("div",{className:"jr-empty"},"No catalog attached. Pass `catalog` to `<JsonRenderDevtools>` to browse it here."));return}let g=cte(p.data);if(o==="components"){let m=g.components.filter((y)=>!i||y.name.toLowerCase().includes(i));if(m.length===0){zt(a,te("div",{className:"jr-empty"},"No components match."));return}zt(a,m.map(uze))}else{let m=g.actions.filter((y)=>!i||y.name.toLowerCase().includes(i));if(m.length===0){zt(a,te("div",{className:"jr-empty"},"No actions match."));return}zt(a,m.map(cze))}}function d(){c(),f()}return d(),{update:d}}function uze(e){return te("div",{className:"jr-catalog-item"},te("div",{className:"jr-catalog-name"},e.name),e.description?te("div",{className:"jr-catalog-desc"},e.description):null,e.props.length>0||e.events.length>0?te("div",{className:"jr-chips"},...e.props.map((t)=>te("span",{className:"jr-chip","data-tone":"prop"},`${t.name}: ${t.type}`)),...e.events.map((t)=>te("span",{className:"jr-chip","data-tone":"event"},`on.${t}`))):null)}function cze(e){return te("div",{className:"jr-catalog-item"},te("div",{className:"jr-catalog-name"},e.name),e.description?te("div",{className:"jr-catalog-desc"},e.description):null,e.params.length>0?te("div",{className:"jr-chips"},...e.params.map((t)=>te("span",{className:"jr-chip","data-tone":"param"},`${t.name}: ${t.type}`))):null)}function cte(e){let t=e??{},r=t.components??{},n=t.actions??{},a=Object.entries(r).map(([i,s])=>{let l=s??{};return{name:i,description:l.description??"",props:fte(l.props),slots:Array.isArray(l.slots)?l.slots:[],events:Array.isArray(l.events)?l.events:[]}}).sort((i,s)=>i.name.localeCompare(s.name)),o=Object.entries(n).map(([i,s])=>{let l=s??{};return{name:i,description:l.description??"",params:fte(l.params)}}).sort((i,s)=>i.name.localeCompare(s.name));return{components:a,actions:o}}function fte(e){if(!e)return[];try{let t=e,r=typeof t.shape==="object"&&t.shape!==null?t.shape:typeof t._def?.shape==="function"?t._def.shape():typeof t._def?.shape==="object"?t._def.shape:null;if(!r)return[];return Object.entries(r).map(([n,a])=>{let o="unknown";try{let i=a,s=i?._zod?.def?.type??i?._def?.typeName??"";if(s.includes("string"))o="string";else if(s.includes("number"))o="number";else if(s.includes("boolean"))o="boolean";else if(s.includes("array"))o="array";else if(s.includes("enum")){let l=i?._zod?.def?.values??i?._def?.values;o=Array.isArray(l)?l.join(" | "):"enum"}else if(s.includes("union"))o="union";else if(s.includes("nullable")){let l=i?._zod?.def?.innerType??i?._def?.innerType,u=l?._zod?.def?.type??l?._def?.typeName??"";if(u.includes("string"))o="string?";else if(u.includes("number"))o="number?";else if(u.includes("boolean"))o="boolean?";else if(u.includes("array"))o="array?";else if(u.includes("enum")){let c=l?._zod?.def?.values??l?._def?.values;o=Array.isArray(c)?`(${c.join(" | ")})?`:"enum?"}else o="optional"}}catch{}return{name:n,type:o}})}catch{return[]}}function Rte(e,t,r){if(!e)return;for(let n of e){if(!n||typeof n!=="object")continue;if(r.has(n))continue;if(r.add(n),n.type===Hp){let a=n.data;if(!a)continue;if(a.type==="patch")t.push({kind:"stream-patch",at:Date.now(),patch:a.patch,source:"json"})}}}function Lte(e){if(!e)return null;let t={root:"",elements:{}},r=!1;for(let n of e){if(!n||typeof n!=="object")continue;if(n.type!==Hp)continue;let a=n.data;if(!a)continue;if(a.type==="patch")r=!0,Kv(t,a.patch);else if(a.type==="flat")r=!0,Object.assign(t,a.spec);else if(a.type==="nested")r=!0,Object.assign(t,Gv(a.spec))}return r?t:null}function fze(e,t){let r=[];if(e){let n=0;for(let a of e){let o=Lte(a?.parts);if(!o||Object.keys(o.elements).length===0)continue;n+=1,r.push({id:a?.id??`gen-${n}`,label:`Generation ${n}`,spec:o})}}if(r.length===0&&t)r.push({id:"spec",label:"Current spec",spec:t});return r}var kte=null;function Tte(e){if(pte())return null;let t=M(e.spec??null);t.current=e.spec??null;let r=M(e.catalog??null);r.current=e.catalog??null;let n=M(e.messages);n.current=e.messages;let a=Gi(),o=M(null);o.current={get:a.get,set:a.set,update:a.update,getSnapshot:a.getSnapshot,subscribe:()=>()=>{}};let i=he(()=>dte({bufferSize:e.bufferSize}),[e.bufferSize]);G(()=>{if(!e.onEvent)return;return i.subscribe(()=>{let u=i.snapshot(),c=u[u.length-1];if(c)e.onEvent?.(c)})},[i,e.onEvent]),G(()=>Tj({onDispatch(u){i.push({kind:"action-dispatched",at:u.at,id:u.id,name:u.name,params:u.params})},onSettle(u){i.push({kind:"action-settled",at:u.at,id:u.id,ok:u.ok,durationMs:u.durationMs,result:u.result,error:u.error!==void 0?String(u.error):void 0})}}),[i]),G(()=>{l.current?.refresh()});let s=M(new WeakSet);G(()=>{if(!e.messages)return;for(let u of e.messages)if(u?.parts)Rte(u.parts,i,s.current)},[e.messages,i]);let l=M(null);return G(()=>{if(l.current)return;let u=Ste(),f=bte({context:{events:i,getSpec:()=>t.current,getSpecs:()=>fze(n.current,t.current),getCatalog:()=>r.current,getStateStore:()=>o.current,startPicker:(g)=>gte(g),selection:u,activateTab:()=>{}},tabs:[wte(),Ate(),Pte(),_te(),Ote()],initialOpen:e.initialOpen,position:e.position,hotkey:e.hotkey,reserveSpace:e.reserveSpace,allowDockToggle:e.allowDockToggle});l.current=f;let d=u.subscribe((g)=>{if(g)vte(g)});kte={open:()=>f.open(),close:()=>f.close(),toggle:()=>f.toggle(),recordEvent:(g)=>i.push(g),clear:()=>i.clear()};let p=jj();return()=>{d(),p(),f.destroy(),l.current=null,kte=null}},[i,e.initialOpen,e.position,e.hotkey,e.reserveSpace,e.allowDockToggle]),null}function dze({props:e}){let r=e.variant??"default";return b("span",{"data-slot":"badge","data-variant":r,className:`pmx-badge pmx-badge--${r}`,children:e.text})}function pze({props:e,emit:t}){let r=e.variant??"primary";return b("button",{type:"button","data-slot":"button","data-variant":r,className:`pmx-button pmx-button--${r}`,disabled:e.disabled??!1,onClick:()=>t("press"),children:e.label})}function mze({props:e,children:t,slots:r,...n}){let a=r?.header,o=r?.footer;return le(PO.Card,{...n,props:a?{...e,title:null,description:null}:e,children:[a&&b("header",{"data-pmx-card-slot":"header",children:a}),t,o&&b("footer",{"data-pmx-card-slot":"footer",className:"flex flex-wrap items-center gap-3",children:o})]})}var{registry:Dte}=nU(d5,{components:{...PO,Card:mze,Badge:dze,Button:pze,...Qee,...ete,...tte}}),hze=["ax.event.record","ax.steer","ax.work.create","ax.work.update","ax.evidence.add","ax.approval.request","ax.review.add","ax.focus.set","ax.elicitation.request","ax.mode.request","ax.command.invoke"];function gze(){let{__PMX_CANVAS_JSON_RENDER_NODE_ID__:e,__PMX_CANVAS_AX_TOKEN__:t}=window,r={};if(!e||!t)return r;for(let n of hze)r[n]=(a)=>{window.parent.postMessage({source:qj,token:t,nodeId:e,interaction:{type:n,payload:a&&typeof a==="object"?a:{}}},"*")};return r}function vze(){let e=window.__PMX_CANVAS_JSON_RENDER_THEME__;if(e){Mte(e);return}let t=window.matchMedia?.("(prefers-color-scheme: dark)").matches??!1;Mte(t?"dark":"light")}function Mte(e){if(e!=="dark"&&e!=="light"&&e!=="high-contrast")return;document.documentElement.setAttribute("data-theme",e),document.documentElement.classList.toggle("dark",e==="dark"||e==="high-contrast"),document.documentElement.style.colorScheme=e==="light"?"light":"dark"}function yze({store:e}){let t=window.__PMX_CANVAS_JSON_RENDER_SPEC__;if(!t)return b("div",{style:{height:"100vh",display:"flex",alignItems:"center",justifyContent:"center",color:"var(--muted-foreground, #666)",fontFamily:"system-ui"},children:"Waiting for UI spec..."});let r=window.__PMX_CANVAS_JSON_RENDER_DISPLAY__==="site";return b("div",{style:r?{display:"flex",flexDirection:"column",height:"100dvh",minHeight:"100dvh",padding:0,boxSizing:"border-box"}:{minHeight:"100vh",padding:16,boxSizing:"border-box"},children:le(rU,{registry:Dte,store:e,directives:IO,handlers:gze(),children:[b("div",{style:r?{flex:1,minHeight:0}:void 0,children:b(tU,{spec:t,registry:Dte,loading:!1})}),window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__?b(Tte,{position:"right"}):null]})})}var Nte=document.getElementById("root");if(Nte){vze();let e=window.__PMX_CANVAS_UI_STATE_TOKEN__,t,r=(n)=>{if(t)return;let a=n??window.__PMX_CANVAS_JSON_RENDER_SPEC__?.state??{},o=window.__PMX_CANVAS_AX_STATE__;if(t=af(o!=null?{...a,ax:o}:a),e)t.subscribe(()=>{let{ax:i,...s}=t.getSnapshot();window.parent.postMessage({source:kA,type:"snapshot",token:e,nodeId:window.__PMX_CANVAS_JSON_RENDER_NODE_ID__,state:s},"*")});S_(Nte).render(b(yze,{store:t}))};if(window.addEventListener("message",(n)=>{if(n.source!==window.parent)return;let a=n.data;if(!a)return;if(a.source===kA&&a.type==="restore"&&e&&a.token===e){if(a.state===null||typeof a.state==="object"&&!Array.isArray(a.state))r(a.state)}if(a.source===Hj&&a.type==="ax-update"&&window.__PMX_CANVAS_AX_TOKEN__&&a.token===window.__PMX_CANVAS_AX_TOKEN__)window.__PMX_CANVAS_AX_STATE__=a.state,t?.set("/ax",a.state)}),!e)r(null)}
|
|
923
|
+
${tf(t,1000)}`},te("span",{className:"jr-state-path"},e),a)}function lte(e){if(typeof e==="string")return JSON.stringify(e);if(e===null)return"null";if(e===void 0)return"";if(typeof e==="number"||typeof e==="boolean")return String(e);try{return JSON.stringify(e)}catch{return String(e)}}var eze="actions";function Pte(){return{id:eze,label:"Actions",badge(e){return Ite(e.events.snapshot()).length||void 0},mount(e,t){return tze(e,t)}}}function tze(e,t){let r=te("div",{style:{height:"100%",display:"flex",flexDirection:"column"}}),n=te("div",{className:"jr-toolbar"}),a=te("div",{style:{overflow:"auto",flex:"1",minHeight:"0"}});r.appendChild(n),r.appendChild(a),e.appendChild(r);let o="",i=new Set,s=te("input",{className:"jr-search",type:"text",placeholder:"Filter by action name…",onInput:(f)=>{o=(f.target.value||"").toLowerCase(),c()}}),l=te("span",{className:"jr-badge"},"0 actions"),u=te("button",{className:"jr-icon-btn",title:"Clear action log",onClick:()=>{let f=t.events.snapshot().filter((d)=>d.kind!=="action-dispatched"&&d.kind!=="action-settled");t.events.clear();for(let d of f)t.events.push(d)}},"clear");n.append(s,l,u);function c(){let f=Ite(t.events.snapshot()).filter((d)=>!o||d.name.toLowerCase().includes(o)).reverse();if(l.textContent=`${f.length} action${f.length===1?"":"s"}`,f.length===0){zt(a,te("div",{className:"jr-empty"},o?"No actions match the filter.":"No actions dispatched yet."));return}zt(a,f.map((d)=>rze(d,i,c)))}return c(),{update:c}}function Ite(e){let t=new Map,r=[];for(let n of e)if(n.kind==="action-dispatched"){if(t.has(n.id))continue;t.set(n.id,{id:n.id,name:n.name,params:n.params,dispatchedAt:n.at}),r.push(n.id)}else if(n.kind==="action-settled"){let a=t.get(n.id);if(!a)continue;a.settledAt=n.at,a.durationMs=n.durationMs,a.ok=n.ok,a.result=n.result,a.error=n.error}return r.map((n)=>t.get(n)).filter(Boolean)}function rze(e,t,r){let n=t.has(e.id),a=e.settledAt===void 0,o=a?"amber":e.ok?"green":"red",i=a?"pending":e.ok?"ok":"error",s=e.durationMs!==void 0?`${e.durationMs.toFixed(0)}ms`:"",l=te("div",{className:"jr-row",onClick:()=>{if(n)t.delete(e.id);else t.add(e.id);r()}},te("span",{className:"jr-row-time"},mv(e.dispatchedAt)),te("span",{className:"jr-row-main"},te("strong",null,e.name),e.params!==void 0?te("span",{className:"jr-tree-summary",style:{marginLeft:"8px"}},tf(e.params,80)):null),te("span",{className:"jr-row-meta"},te("span",{className:"jr-badge","data-tone":o},i),s?te("span",{style:{marginLeft:"6px"}},s):null));if(!n)return l;let u=te("div",null,l);return u.appendChild(te("div",{className:"jr-row-expanded"},te("h4",null,"Params"),te("pre",null,JSON.stringify(e.params??null,null,2)),e.ok===!1?[te("h4",null,"Error"),te("pre",null,e.error??"(unknown error)")]:null,e.result!==void 0&&e.ok!==!1?[te("h4",null,"Result"),te("pre",null,JSON.stringify(e.result,null,2))]:null)),u}var nze="stream";function _te(){return{id:nze,label:"Stream",badge(e){return Ete(e.events.snapshot()).length||void 0},mount(e,t){return aze(e,t)}}}function aze(e,t){let r=te("div",{style:{height:"100%",display:"flex",flexDirection:"column"}}),n=te("div",{className:"jr-toolbar"}),a=te("div",{style:{overflow:"auto",flex:"1",minHeight:"0"}});r.appendChild(n),r.appendChild(a),e.appendChild(r);function o(){let i=t.events.snapshot(),s=Ete(i),l=oze(i);if(zt(n,te("span",{className:"jr-badge"},`${s.length} generation${s.length===1?"":"s"}`)),s.length===0&&l.length===0){zt(a,te("div",{className:"jr-empty"},"No stream events yet. Tap a stream or pass messages to capture patches."));return}let u=[];if(l.length>0)u.push(ute({startedAt:l[0]?.at??Date.now(),events:l},"live",!0));for(let c=s.length-1;c>=0;c--){let f=s[c];u.push(ute(f,`gen-${c}`,c===s.length-1))}zt(a,u)}return o(),{update:o}}function Ete(e){let t=[],r=null;for(let n of e){if(n.kind==="stream-lifecycle"){if(n.phase==="start"){if(r)t.push(r);r={startedAt:n.at,events:[]}}else{if(!r)r={startedAt:n.at,events:[]};r.endedAt=n.at,r.ok=n.ok,t.push(r),r=null}continue}if(n.kind==="stream-patch"||n.kind==="stream-text"||n.kind==="stream-usage"){if(!r)r={startedAt:n.at,events:[]};if(r.events.push(n),n.kind==="stream-usage")r.usage=n.usage}}if(r)t.push(r);return t}function oze(e){if(e.some((r)=>r.kind==="stream-lifecycle"))return[];return e.filter((r)=>r.kind==="stream-patch"||r.kind==="stream-text"||r.kind==="stream-usage")}function ute(e,t,r){let n=e.events.filter((c)=>c.kind==="stream-patch").length,a=e.events.filter((c)=>c.kind==="stream-text").length,o=e.endedAt!==void 0?e.endedAt-e.startedAt:void 0,i=e.endedAt===void 0?"amber":e.ok===!1?"red":"green",s=e.endedAt===void 0?"live":e.ok===!1?"error":"done",l=te("div",{style:{borderTop:"1px solid var(--jr-border)",background:"var(--jr-bg-soft)",display:r?"block":"none"}},...e.events.map(ize)),u=te("div",{className:"jr-row",style:{cursor:"pointer"},onClick:()=>{l.style.display=l.style.display==="none"?"block":"none"}},te("span",{className:"jr-row-time"},mv(e.startedAt)),te("span",{className:"jr-row-main"},te("strong",null,`generation #${t}`),te("span",{className:"jr-tree-summary",style:{marginLeft:"8px"}},`${n} patch${n===1?"":"es"}`,a>0?`, ${a} text chunk${a===1?"":"s"}`:"")),te("span",{className:"jr-row-meta"},te("span",{className:"jr-badge","data-tone":i},s),o!==void 0?te("span",{style:{marginLeft:"6px"}},`${o}ms`):null,e.usage?te("span",{style:{marginLeft:"6px"}},`${TC(e.usage.totalTokens)} tok`):null));return te("div",null,u,l)}function ize(e){if(e.kind==="stream-patch")return te("div",{className:"jr-row"},te("span",{className:"jr-row-time"},mv(e.at)),te("span",{className:"jr-row-main"},te("span",{className:"jr-badge","data-tone":"accent"},e.patch.op),te("span",{className:"jr-tree-summary",style:{marginLeft:"8px"}},e.patch.path,e.patch.value!==void 0?` = ${tf(e.patch.value,80)}`:"")),te("span",{className:"jr-row-meta"},e.source));if(e.kind==="stream-text")return te("div",{className:"jr-row"},te("span",{className:"jr-row-time"},mv(e.at)),te("span",{className:"jr-row-main",style:{whiteSpace:"pre-wrap"}},e.text),te("span",{className:"jr-row-meta"},te("span",{className:"jr-badge"},"text")));if(e.kind==="stream-usage")return te("div",{className:"jr-row"},te("span",{className:"jr-row-time"},mv(e.at)),te("span",{className:"jr-row-main"},`prompt ${TC(e.usage.promptTokens)} / completion ${TC(e.usage.completionTokens)} / total ${TC(e.usage.totalTokens)}`),te("span",{className:"jr-row-meta"},te("span",{className:"jr-badge","data-tone":"accent"},"usage")));return te("div")}function TC(e){if(e>=1000)return`${(e/1000).toFixed(1).replace(/\.0$/,"")}k`;return String(e)}var sze="catalog";function Ote(){return{id:sze,label:"Catalog",badge(e){let t=e.getCatalog();if(!t)return;return t.componentNames.length+t.actionNames.length||void 0},mount(e,t){return lze(e,t)}}}function lze(e,t){let r=te("div",{style:{height:"100%",display:"flex",flexDirection:"column"}}),n=te("div",{className:"jr-toolbar"}),a=te("div",{style:{overflow:"auto",flex:"1",minHeight:"0"}});r.appendChild(n),r.appendChild(a),e.appendChild(r);let o="components",i="",s=te("button",{className:"jr-icon-btn","data-active":"true",onClick:()=>{if(o==="components")return;o="components",c(),f()}},"components"),l=te("button",{className:"jr-icon-btn","data-active":"false",onClick:()=>{if(o==="actions")return;o="actions",c(),f()}},"actions"),u=te("input",{className:"jr-search",type:"text",placeholder:"Filter by name…",onInput:(p)=>{i=(p.target.value||"").toLowerCase(),f()}});n.append(s,l,u);function c(){let p=t.getCatalog(),g=p?cte(p.data):null,m=g?.components.length??0,y=g?.actions.length??0;s.textContent=`components (${m})`,l.textContent=`actions (${y})`,s.dataset.active=String(o==="components"),l.dataset.active=String(o==="actions")}function f(){let p=t.getCatalog();if(!p){zt(a,te("div",{className:"jr-empty"},"No catalog attached. Pass `catalog` to `<JsonRenderDevtools>` to browse it here."));return}let g=cte(p.data);if(o==="components"){let m=g.components.filter((y)=>!i||y.name.toLowerCase().includes(i));if(m.length===0){zt(a,te("div",{className:"jr-empty"},"No components match."));return}zt(a,m.map(uze))}else{let m=g.actions.filter((y)=>!i||y.name.toLowerCase().includes(i));if(m.length===0){zt(a,te("div",{className:"jr-empty"},"No actions match."));return}zt(a,m.map(cze))}}function d(){c(),f()}return d(),{update:d}}function uze(e){return te("div",{className:"jr-catalog-item"},te("div",{className:"jr-catalog-name"},e.name),e.description?te("div",{className:"jr-catalog-desc"},e.description):null,e.props.length>0||e.events.length>0?te("div",{className:"jr-chips"},...e.props.map((t)=>te("span",{className:"jr-chip","data-tone":"prop"},`${t.name}: ${t.type}`)),...e.events.map((t)=>te("span",{className:"jr-chip","data-tone":"event"},`on.${t}`))):null)}function cze(e){return te("div",{className:"jr-catalog-item"},te("div",{className:"jr-catalog-name"},e.name),e.description?te("div",{className:"jr-catalog-desc"},e.description):null,e.params.length>0?te("div",{className:"jr-chips"},...e.params.map((t)=>te("span",{className:"jr-chip","data-tone":"param"},`${t.name}: ${t.type}`))):null)}function cte(e){let t=e??{},r=t.components??{},n=t.actions??{},a=Object.entries(r).map(([i,s])=>{let l=s??{};return{name:i,description:l.description??"",props:fte(l.props),slots:Array.isArray(l.slots)?l.slots:[],events:Array.isArray(l.events)?l.events:[]}}).sort((i,s)=>i.name.localeCompare(s.name)),o=Object.entries(n).map(([i,s])=>{let l=s??{};return{name:i,description:l.description??"",params:fte(l.params)}}).sort((i,s)=>i.name.localeCompare(s.name));return{components:a,actions:o}}function fte(e){if(!e)return[];try{let t=e,r=typeof t.shape==="object"&&t.shape!==null?t.shape:typeof t._def?.shape==="function"?t._def.shape():typeof t._def?.shape==="object"?t._def.shape:null;if(!r)return[];return Object.entries(r).map(([n,a])=>{let o="unknown";try{let i=a,s=i?._zod?.def?.type??i?._def?.typeName??"";if(s.includes("string"))o="string";else if(s.includes("number"))o="number";else if(s.includes("boolean"))o="boolean";else if(s.includes("array"))o="array";else if(s.includes("enum")){let l=i?._zod?.def?.values??i?._def?.values;o=Array.isArray(l)?l.join(" | "):"enum"}else if(s.includes("union"))o="union";else if(s.includes("nullable")){let l=i?._zod?.def?.innerType??i?._def?.innerType,u=l?._zod?.def?.type??l?._def?.typeName??"";if(u.includes("string"))o="string?";else if(u.includes("number"))o="number?";else if(u.includes("boolean"))o="boolean?";else if(u.includes("array"))o="array?";else if(u.includes("enum")){let c=l?._zod?.def?.values??l?._def?.values;o=Array.isArray(c)?`(${c.join(" | ")})?`:"enum?"}else o="optional"}}catch{}return{name:n,type:o}})}catch{return[]}}function Rte(e,t,r){if(!e)return;for(let n of e){if(!n||typeof n!=="object")continue;if(r.has(n))continue;if(r.add(n),n.type===Hp){let a=n.data;if(!a)continue;if(a.type==="patch")t.push({kind:"stream-patch",at:Date.now(),patch:a.patch,source:"json"})}}}function Lte(e){if(!e)return null;let t={root:"",elements:{}},r=!1;for(let n of e){if(!n||typeof n!=="object")continue;if(n.type!==Hp)continue;let a=n.data;if(!a)continue;if(a.type==="patch")r=!0,Kv(t,a.patch);else if(a.type==="flat")r=!0,Object.assign(t,a.spec);else if(a.type==="nested")r=!0,Object.assign(t,Gv(a.spec))}return r?t:null}function fze(e,t){let r=[];if(e){let n=0;for(let a of e){let o=Lte(a?.parts);if(!o||Object.keys(o.elements).length===0)continue;n+=1,r.push({id:a?.id??`gen-${n}`,label:`Generation ${n}`,spec:o})}}if(r.length===0&&t)r.push({id:"spec",label:"Current spec",spec:t});return r}var kte=null;function Tte(e){if(pte())return null;let t=M(e.spec??null);t.current=e.spec??null;let r=M(e.catalog??null);r.current=e.catalog??null;let n=M(e.messages);n.current=e.messages;let a=Gi(),o=M(null);o.current={get:a.get,set:a.set,update:a.update,getSnapshot:a.getSnapshot,subscribe:()=>()=>{}};let i=he(()=>dte({bufferSize:e.bufferSize}),[e.bufferSize]);G(()=>{if(!e.onEvent)return;return i.subscribe(()=>{let u=i.snapshot(),c=u[u.length-1];if(c)e.onEvent?.(c)})},[i,e.onEvent]),G(()=>Tj({onDispatch(u){i.push({kind:"action-dispatched",at:u.at,id:u.id,name:u.name,params:u.params})},onSettle(u){i.push({kind:"action-settled",at:u.at,id:u.id,ok:u.ok,durationMs:u.durationMs,result:u.result,error:u.error!==void 0?String(u.error):void 0})}}),[i]),G(()=>{l.current?.refresh()});let s=M(new WeakSet);G(()=>{if(!e.messages)return;for(let u of e.messages)if(u?.parts)Rte(u.parts,i,s.current)},[e.messages,i]);let l=M(null);return G(()=>{if(l.current)return;let u=Ste(),f=bte({context:{events:i,getSpec:()=>t.current,getSpecs:()=>fze(n.current,t.current),getCatalog:()=>r.current,getStateStore:()=>o.current,startPicker:(g)=>gte(g),selection:u,activateTab:()=>{}},tabs:[wte(),Ate(),Pte(),_te(),Ote()],initialOpen:e.initialOpen,position:e.position,hotkey:e.hotkey,reserveSpace:e.reserveSpace,allowDockToggle:e.allowDockToggle});l.current=f;let d=u.subscribe((g)=>{if(g)vte(g)});kte={open:()=>f.open(),close:()=>f.close(),toggle:()=>f.toggle(),recordEvent:(g)=>i.push(g),clear:()=>i.clear()};let p=jj();return()=>{d(),p(),f.destroy(),l.current=null,kte=null}},[i,e.initialOpen,e.position,e.hotkey,e.reserveSpace,e.allowDockToggle]),null}function dze({props:e}){let r=e.variant??"default";return b("span",{"data-slot":"badge","data-variant":r,className:`pmx-badge pmx-badge--${r}`,children:e.text})}function pze({props:e,emit:t}){let r=e.variant??"primary";return b("button",{type:"button","data-slot":"button","data-variant":r,className:`pmx-button pmx-button--${r}`,disabled:e.disabled??!1,onClick:()=>t("press"),children:e.label})}function mze({props:e,children:t,slots:r,...n}){let a=r?.header,o=r?.footer;return le(PO.Card,{...n,props:a?{...e,title:null,description:null}:e,children:[a&&b("header",{"data-pmx-card-slot":"header",children:a}),t,o&&b("footer",{"data-pmx-card-slot":"footer",className:"flex flex-wrap items-center gap-3",children:o})]})}var{registry:Dte}=nU(d5,{components:{...PO,Card:mze,Badge:dze,Button:pze,...Qee,...ete,...tte}}),hze=["ax.event.record","ax.steer","ax.work.create","ax.work.update","ax.evidence.add","ax.approval.request","ax.review.add","ax.focus.set","ax.elicitation.request","ax.mode.request","ax.command.invoke"];function gze(){let{__PMX_CANVAS_JSON_RENDER_NODE_ID__:e,__PMX_CANVAS_AX_TOKEN__:t}=window,r={};if(!e||!t)return r;for(let n of hze)r[n]=(a)=>{window.parent.postMessage({source:qj,token:t,nodeId:e,interaction:{type:n,payload:a&&typeof a==="object"?a:{}}},"*")};return r}function vze(){let e=window.__PMX_CANVAS_JSON_RENDER_THEME__;if(e){Mte(e);return}let t=window.matchMedia?.("(prefers-color-scheme: dark)").matches??!1;Mte(t?"dark":"light")}function Mte(e){if(e!=="dark"&&e!=="light"&&e!=="high-contrast")return;document.documentElement.setAttribute("data-theme",e),document.documentElement.classList.toggle("dark",e==="dark"||e==="high-contrast"),document.documentElement.style.colorScheme=e==="light"?"light":"dark"}function yze({store:e}){let t=window.__PMX_CANVAS_JSON_RENDER_SPEC__;if(!t)return b("div",{style:{height:"100vh",display:"flex",alignItems:"center",justifyContent:"center",color:"var(--muted-foreground, #666)"},children:"Waiting for UI spec..."});let r=window.__PMX_CANVAS_JSON_RENDER_DISPLAY__==="site";return b("div",{style:r?{display:"flex",flexDirection:"column",height:"100dvh",minHeight:"100dvh",padding:0,boxSizing:"border-box"}:{minHeight:"100vh",padding:16,boxSizing:"border-box"},children:le(rU,{registry:Dte,store:e,directives:IO,handlers:gze(),children:[b("div",{style:r?{flex:1,minHeight:0}:void 0,children:b(tU,{spec:t,registry:Dte,loading:!1})}),window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__?b(Tte,{position:"right"}):null]})})}var Nte=document.getElementById("root");if(Nte){vze();let e=window.__PMX_CANVAS_UI_STATE_TOKEN__,t,r=(n)=>{if(t)return;let a=n??window.__PMX_CANVAS_JSON_RENDER_SPEC__?.state??{},o=window.__PMX_CANVAS_AX_STATE__;if(t=af(o!=null?{...a,ax:o}:a),e)t.subscribe(()=>{let{ax:i,...s}=t.getSnapshot();window.parent.postMessage({source:kA,type:"snapshot",token:e,nodeId:window.__PMX_CANVAS_JSON_RENDER_NODE_ID__,state:s},"*")});S_(Nte).render(b(yze,{store:t}))};if(window.addEventListener("message",(n)=>{if(n.source!==window.parent)return;let a=n.data;if(!a)return;if(a.source===kA&&a.type==="restore"&&e&&a.token===e){if(a.state===null||typeof a.state==="object"&&!Array.isArray(a.state))r(a.state)}if(a.source===Hj&&a.type==="ax-update"&&window.__PMX_CANVAS_AX_TOKEN__&&a.token===window.__PMX_CANVAS_AX_TOKEN__)window.__PMX_CANVAS_AX_STATE__=a.state,t?.set("/ax",a.state)}),!e)r(null)}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CanvasNodeState } from '../types';
|
|
2
2
|
export declare function shouldShowPresentationControls(node: CanvasNodeState): boolean;
|
|
3
|
+
export declare function htmlAxCapabilitySignature(value: unknown): string;
|
|
3
4
|
export declare function HtmlNode({ node, expanded, presentation, presentationExitToken, autoFocus, }: {
|
|
4
5
|
node: CanvasNodeState;
|
|
5
6
|
expanded?: boolean;
|
|
@@ -76,6 +76,9 @@ body{margin:0}
|
|
|
76
76
|
<span style="flex: 1; font-size: 12px; font-weight: 600">PMX Canvas — guided tour</span>
|
|
77
77
|
<button aria-label="Expand (Enter)" style="width: 22px; height: 22px; border: 0; border-radius: 6px; padding: 0; background: transparent; color: #b9c6d6; display: flex; align-items: center; justify-content: center; flex-shrink: 0"><svg width="13" height="13" viewBox="0 0 24 24" style="fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round"><path d="M14 4h6v6M20 4l-7 7M10 20H4v-6M4 20l7-7"></path></svg></button>
|
|
78
78
|
<button aria-label="Add to context (P)" style="width: 18px; height: 18px; box-sizing: border-box; border: 1.5px solid #6b7d94; border-radius: 999px; padding: 0; background: transparent; color: #b9c6d6; display: flex; align-items: center; justify-content: center; flex-shrink: 0"><svg width="10" height="10" viewBox="0 0 24 24" style="fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round"><path d="M9 4h6l-.8 5.5 2.8 3H7l2.8-3zM12 12.5V20"></path></svg></button>
|
|
79
|
+
<!-- Reserve the hover-action slots: revealing More/Close must not move the pin. -->
|
|
80
|
+
<span aria-hidden="true" style="width: 24px; height: 24px; flex-shrink: 0"></span>
|
|
81
|
+
<span aria-hidden="true" style="width: 24px; height: 24px; flex-shrink: 0"></span>
|
|
79
82
|
</div>
|
|
80
83
|
<div style="height: 44px; padding: 10px 12px; box-sizing: border-box; font-size: 12px; color: #b9c6d6">An infinite spatial canvas that agents drive over MCP, HTTP or the Bun SDK.</div>
|
|
81
84
|
</div>
|
package/docs/design.md
CHANGED
|
@@ -152,7 +152,8 @@ the same change.
|
|
|
152
152
|
- **Every mark has a glyph and a word**, so it reads without colour, and explains
|
|
153
153
|
itself on hover.
|
|
154
154
|
- **One font and one scale.** IBM Plex Sans and Mono, bundled with the app (no CDN);
|
|
155
|
-
every control inherits the font
|
|
155
|
+
every control inherits the font, including embedded JSON forms. Self-contained
|
|
156
|
+
viewers embed the font bytes for hosted and offline use. Type 11/12/13/15/20; radii 6/10/14/pill; two
|
|
156
157
|
shadow levels. No glow is used as elevation.
|
|
157
158
|
- **Quiet chrome, with expand and the context pin always in reach.** Every node header
|
|
158
159
|
shows its kind, title, expand (`⤢`, Enter — on the types that expand: markdown,
|
|
@@ -160,8 +161,10 @@ the same change.
|
|
|
160
161
|
html) and context pin: an outline pin in a circle when the node is not in context
|
|
161
162
|
(click or `P` to add), the filled blue badge when it is (click to remove), always in
|
|
162
163
|
the same place. The other controls (ask agent, open in new tab, collapse, set as
|
|
163
|
-
README, close) appear on hover under ⋯ and ×.
|
|
164
|
-
|
|
164
|
+
README, close) appear on hover under ⋯ and ×. Their slots stay reserved while
|
|
165
|
+
hidden, so hover or keyboard focus cannot move the pin onto another action.
|
|
166
|
+
Section labels are a heading display of the markdown node. Rarely used tools
|
|
167
|
+
live in one settings menu on the rail.
|
|
165
168
|
- **Relations are the edge layer, not node types.** Design work adds no node type;
|
|
166
169
|
the vision's own `ask` node (move 3) is separate.
|
|
167
170
|
- **Any size, from a side pane to a full window.** The workbench lives in narrow agent-host
|
package/docs/http-api.md
CHANGED
|
@@ -294,6 +294,9 @@ against the node's top-level `contentRevision`; stale content returns `409`
|
|
|
294
294
|
without changing the node. Conditional edits cannot request webpage refresh.
|
|
295
295
|
The private MCP App uses these guards to reject stale board actions and note drafts.
|
|
296
296
|
|
|
297
|
+
Replacing a JSON-render node's `spec` preserves its current title; include `title`
|
|
298
|
+
to rename it explicitly. A new authored spec still resets the viewer's local drafts.
|
|
299
|
+
|
|
297
300
|
Mermaid nodes accept `data: { "fit": "contain" | "none" }` on both
|
|
298
301
|
`POST /api/canvas/node` and `PATCH /api/canvas/node/:id`. The default, `contain`,
|
|
299
302
|
shrinks overflowing diagrams without changing aspect ratio; `none` keeps 100%
|
|
@@ -504,7 +507,9 @@ curl "http://localhost:4313/api/canvas/ax/timeline?limit=50"
|
|
|
504
507
|
# nodes were in what came back (newest first, default 50, max 500; the per-consumer
|
|
505
508
|
# summary covers the whole retained log of 5,000 reads). Agent reads of pinned
|
|
506
509
|
# context, AX context, AX state, summary, spatial context and layout are recorded
|
|
507
|
-
# automatically
|
|
510
|
+
# automatically, as are individual GET /api/canvas/node/:id reads (only the
|
|
511
|
+
# delivered node). The workbench's own reads are not. A projecting client may
|
|
512
|
+
# pass contextDelivery=false on node GET to avoid marking unseen content read.
|
|
508
513
|
curl "http://localhost:4313/api/canvas/ax/context-reads?limit=50"
|
|
509
514
|
|
|
510
515
|
# Read status — per node on a board (default: the open board), the latest agent
|
package/docs/mcp.md
CHANGED
|
@@ -491,7 +491,10 @@ wanted. Omitting both `consumer` and `since` starts from the beginning; explicit
|
|
|
491
491
|
`since` does not update a cursor. The budget is UTF-16 code units (default
|
|
492
492
|
16,000; maximum 100,000), not model tokens. Pins are prioritized. If an entry is
|
|
493
493
|
truncated, follow its source board/card IDs with `canvas_board get` and
|
|
494
|
-
`canvas_node get` rather than inferring the missing text.
|
|
494
|
+
`canvas_node { action: "get", board, id, full: true }` rather than inferring the missing text.
|
|
495
|
+
Full node reads record only that node's delivered content revision; compact metadata
|
|
496
|
+
does not. Inspect `canvas_ax_timeline { action: "read-status", board }` and re-read
|
|
497
|
+
stale cards before acting. Read marks prove delivery, not understanding or approval.
|
|
495
498
|
|
|
496
499
|
### Skills extension (SEP-2640)
|
|
497
500
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "pmx-canvas",
|
|
4
4
|
"description": "PMX Canvas spatial workbench and MCP tools, with a native canvas panel in the GitHub Copilot app.",
|
|
5
|
-
"version": "0.8.
|
|
5
|
+
"version": "0.8.2",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "pskoett",
|
|
8
8
|
"url": "https://github.com/pskoett"
|
|
@@ -22,7 +22,18 @@ falling back to broader resources. The budget is UTF-16 code units, not tokens.
|
|
|
22
22
|
Use one stable consumer ID per agent/adapter only when durable deltas are wanted;
|
|
23
23
|
omit it for a fresh read. Pins are highest priority, then the cards near each pin
|
|
24
24
|
(reason `near`, title + short summary only). If a source card is clipped or near,
|
|
25
|
-
pull that card explicitly
|
|
25
|
+
pull that card explicitly with `canvas_node { action: "get", board, id, full: true }`.
|
|
26
|
+
|
|
27
|
+
**Read → check → refresh:** after reading context, inspect `canvas_ax_timeline {
|
|
28
|
+
action: "read-status", board }`. No entry means no recorded content delivery;
|
|
29
|
+
`readRevision < contentRevision` on the current card means changed since read.
|
|
30
|
+
Re-read that card with `full: true`, then check status again before acting on it.
|
|
31
|
+
Full HTTP/CLI node reads and SDK `getNode` also record the delivered revision;
|
|
32
|
+
compact MCP metadata, CLI `--summary`/`--field`, and workbench reads do not.
|
|
33
|
+
Compact MCP responses that contain text (such as Markdown) do count as delivery.
|
|
34
|
+
In 0.8.0, direct node gets were untracked; use a context read on that version.
|
|
35
|
+
A read mark proves delivery, not understanding or approval. Agent attribution
|
|
36
|
+
describes who acted; it never grants permission or wakes an idle agent.
|
|
26
37
|
|
|
27
38
|
When authoring a board, establish its README without waiting for the human to
|
|
28
39
|
click a button. Read `canvas_board { action: "get" }`; if `readmeNodeId` is null,
|
|
@@ -44,7 +55,7 @@ for the authoritative stop, viewport, persistence, reset, group-order, and captu
|
|
|
44
55
|
|
|
45
56
|
## Runtime prerequisites
|
|
46
57
|
|
|
47
|
-
PMX Canvas 0.8.
|
|
58
|
+
PMX Canvas 0.8.2 requires **Bun >=1.4.2**, including when installed through npm.
|
|
48
59
|
Check `bun --version` and ensure Bun is on the MCP host's PATH, not just your shell's;
|
|
49
60
|
use an absolute executable path if needed. Install/configure only when requested.
|
|
50
61
|
For pinned installation, MCP configuration, managed services, and disposable verification,
|
|
@@ -382,6 +393,12 @@ What the session asks of you:
|
|
|
382
393
|
`agentId` values are for actual independent subagents, not steps in one chat. Attaching over a
|
|
383
394
|
non-empty board saved a `Before session · …` snapshot; detaching emits the receipt (items
|
|
384
395
|
done / vetoed, a diff against that snapshot, one-click restore) — an idle timeout delays it.
|
|
396
|
+
- **Inspect receipt touch rows:** Read lists delivered content, Pinned lists pins, and
|
|
397
|
+
Created/Edited come from the session snapshot diff. Creating then editing one card puts
|
|
398
|
+
it in Created, not both rows. A stale warning means the pin changed after its read.
|
|
399
|
+
For example, after reading A, editing existing B and creating C, use **Dim untouched
|
|
400
|
+
nodes** to fade an untouched D to 0.35 opacity; A/B/C remain visible. The lens changes
|
|
401
|
+
presentation, not content, pin membership, approval or delivery.
|
|
385
402
|
- **403 = outside the scope fence.** The human may fence you to a region (`policy.scope`): writes
|
|
386
403
|
outside it are refused with a reason naming the node or position (a fenced group frame
|
|
387
404
|
grants its members too). Read `policy.scope` in `canvas://ax-context`, ask the human to widen
|
|
@@ -443,7 +460,7 @@ Use the visible workbench when the human is actively curating layout:
|
|
|
443
460
|
carries the tools (Select V, Pan Space, Connect C), Markdown note M, Attach files, webpage W,
|
|
444
461
|
group G, and separate Draw A, Text note and Eraser buttons for board feedback,
|
|
445
462
|
and utilities: search & commands (Cmd+K — actions with shortcuts, then jump-to-node), arrange,
|
|
446
|
-
trace, minimap, history, the theme picker (nine themes:
|
|
463
|
+
trace, minimap, history, the theme picker (nine themes: harbor, daylight, high-contrast, midnight,
|
|
447
464
|
sepia, arctic, ember, forest, volt), and shortcuts (?). The top bar holds the connection dot,
|
|
448
465
|
workspace title, the session chip / gate badge / context meter while a session is attached
|
|
449
466
|
(or the external-writers indicator and *Start agent session* otherwise), and the zoom cluster
|
|
@@ -772,6 +772,68 @@
|
|
|
772
772
|
"type": "output_check"
|
|
773
773
|
}
|
|
774
774
|
]
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
"id": 32,
|
|
778
|
+
"name": "read-refresh-and-receipt-loop",
|
|
779
|
+
"prompt": "On a disposable board, pin A and B. Read A, then let the human edit it. Refresh only A before acting. Read nearby card C's source too. End your self-owned session after editing existing D and creating then editing E. Explain the receipt and dim untouched F. Does a read badge mean you understood or approved the content?",
|
|
780
|
+
"expected_output": "Uses a stable consumer for context and checks read-status. Fetches stale A and nearby C with canvas_node get full:true; compares recorded and current content revisions, leaving B unread unless actually delivered. Explains compact metadata/CLI projections do not record full delivery and 0.8.0 direct gets were untracked. Receipt Read/Pinned are descriptive, D is Edited, E is Created only; untouched F dims. Does not equate attribution or delivery with approval, understanding or idle wakeup.",
|
|
781
|
+
"assertions": [
|
|
782
|
+
{
|
|
783
|
+
"name": "refreshes-exact-stale-source",
|
|
784
|
+
"description": "Checks revisions, fetches full A/C by board and node ID, verifies refreshed status, and does not falsely claim unrelated B was read",
|
|
785
|
+
"type": "output_check"
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
"name": "explains-receipt-without-authority",
|
|
789
|
+
"description": "Distinguishes Created from Edited, verifies an untouched lens control, and calls reads delivery evidence rather than understanding, approval or wakeup",
|
|
790
|
+
"type": "output_check"
|
|
791
|
+
}
|
|
792
|
+
]
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
"id": 33,
|
|
796
|
+
"name": "first-entry-header-targets",
|
|
797
|
+
"prompt": "Verify Expand and Add to context on a disposable card by moving directly from outside to each at-rest button coordinate. A prior release shifted controls on hover and sometimes deleted cards. Is a successful keyboard Enter enough to close this bug?",
|
|
798
|
+
"expected_output": "Measures at-rest targets before hover, clicks those original coordinates on first entry, and checks expansion/pin state plus retained node identity. Includes keyboard activation only as a control, not pointer proof. Uses Undo if a test-owned card is accidentally deleted and retains failure evidence.",
|
|
799
|
+
"assertions": [
|
|
800
|
+
{
|
|
801
|
+
"name": "tests-real-first-entry-pointer",
|
|
802
|
+
"description": "Uses original at-rest coordinates, confirms intended action and no deletion, and does not substitute pre-hover or keyboard-only success",
|
|
803
|
+
"type": "output_check"
|
|
804
|
+
}
|
|
805
|
+
]
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
"id": 34,
|
|
809
|
+
"name": "spec-only-title-preservation",
|
|
810
|
+
"prompt": "Create a JSON card titled Protected custom title whose spec heading is Different heading. Replace only its spec with a New heading, then explicitly rename it. What should happen to its title and local form draft?",
|
|
811
|
+
"expected_output": "Verifies spec-only replacement preserves Protected custom title, explicit rename changes it, and authored spec replacement resets local runtime state. On old 0.8.0, includes the existing title as a workaround rather than silently accepting inferred renaming.",
|
|
812
|
+
"assertions": [
|
|
813
|
+
{
|
|
814
|
+
"name": "checks-independent-title-and-spec",
|
|
815
|
+
"description": "Uses distinct custom/inferred names, checks spec-only preservation and explicit rename, and distinguishes runtime reset from title loss",
|
|
816
|
+
"type": "output_check"
|
|
817
|
+
}
|
|
818
|
+
]
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
"id": 35,
|
|
822
|
+
"name": "late-html-ax-enable",
|
|
823
|
+
"prompt": "Mount an HTML form with AX disabled, then enable ax.work.create without changing its HTML. Exercise enable, disable, re-enable and allowed-action changes in src and srcdoc. Can we preserve unsaved iframe drafts or claim that a queued steer wakes an idle agent?",
|
|
824
|
+
"expected_output": "Saves important local data before capability changes, which reload the HTML document. Waits for bridge bootstrap, exercises the action and verifies acknowledgement plus persisted work, and checks denial while disabled or not allowed. Tests both modes rather than using focus-mode remount as proof. Describes 0.8.0's workaround separately; no idle-wakeup claim.",
|
|
825
|
+
"assertions": [
|
|
826
|
+
{
|
|
827
|
+
"name": "verifies-mounted-capability-lifecycle",
|
|
828
|
+
"description": "Tests both embed modes on the mounted card, checks permitted and forbidden effects, and verifies real persistence rather than just bridge presence",
|
|
829
|
+
"type": "output_check"
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
"name": "respects-draft-and-delivery-boundaries",
|
|
833
|
+
"description": "Warns capability changes reload local drafts, preserves important data beforehand, and distinguishes queued steering from host delivery",
|
|
834
|
+
"type": "output_check"
|
|
835
|
+
}
|
|
836
|
+
]
|
|
775
837
|
}
|
|
776
838
|
]
|
|
777
839
|
}
|
|
@@ -24,6 +24,12 @@ Without `axCapabilities.enabled = true`, `window.PMX_AX` is **not** injected —
|
|
|
24
24
|
renders but can't emit. `allowed` narrows what it may emit (never escalates the type's
|
|
25
25
|
ceiling). Flip an existing node on with `canvas_node({ action: "update", id, axCapabilities: { enabled: true, allowed: [...] } })`.
|
|
26
26
|
|
|
27
|
+
Changing effective capabilities reloads the mounted HTML document in both `src` and
|
|
28
|
+
`srcdoc` modes, including disable/re-enable and allowed-action changes. Unsaved iframe-local
|
|
29
|
+
drafts reset: save important state through durable AX primitives before changing capabilities.
|
|
30
|
+
Wait for the new document, invoke the action, and verify both its acknowledgement and persisted
|
|
31
|
+
result. On 0.8.0, late enabling required a deliberate remount; upgrading fixes that missing bridge.
|
|
32
|
+
|
|
27
33
|
## Three footguns (this is why a hand-rolled node looks "inert")
|
|
28
34
|
|
|
29
35
|
1. **The iframe is sandboxed opaque-origin** (no `allow-same-origin`). `localStorage`,
|
|
@@ -4,7 +4,7 @@ Use this reference when the `pmx-canvas` skill is installed but the `pmx-canvas`
|
|
|
4
4
|
|
|
5
5
|
## Prerequisites and version
|
|
6
6
|
|
|
7
|
-
The examples pin **PMX Canvas 0.8.
|
|
7
|
+
The examples pin **PMX Canvas 0.8.2**, which requires **Bun >=1.4.2** even when
|
|
8
8
|
installed through npm: the CLI runs through Bun, not Node. Check `bun --version`
|
|
9
9
|
first. If missing or older, follow the [Bun installation instructions](https://bun.sh/docs/installation)
|
|
10
10
|
with the user's authorization. Ensure Bun is on the MCP host's PATH as well as the
|
|
@@ -17,11 +17,11 @@ in `SKILL.md`. Do not pair newer checkout guidance with an older runtime unknowi
|
|
|
17
17
|
## Install from npm
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install -g pmx-canvas@0.8.
|
|
20
|
+
npm install -g pmx-canvas@0.8.2
|
|
21
21
|
pmx-canvas --version
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Without a global install, use `bunx pmx-canvas@0.8.
|
|
24
|
+
Without a global install, use `bunx pmx-canvas@0.8.2` in place of `pmx-canvas` in
|
|
25
25
|
the commands below. Do not use unpinned `bunx pmx-canvas` for a version-specific check.
|
|
26
26
|
|
|
27
27
|
## Choose the workspace and target
|
|
@@ -91,7 +91,7 @@ discards empty env values, remove `PMX_CANVAS_URL` from its launch environment i
|
|
|
91
91
|
"mcpServers": {
|
|
92
92
|
"canvas": {
|
|
93
93
|
"command": "bunx",
|
|
94
|
-
"args": ["pmx-canvas@0.8.
|
|
94
|
+
"args": ["pmx-canvas@0.8.2", "--mcp"],
|
|
95
95
|
"env": {
|
|
96
96
|
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
97
97
|
"PMX_CANVAS_PORT": "14313",
|
|
@@ -135,7 +135,7 @@ Amp executes, not only on the user's laptop. Merge this into that workspace's
|
|
|
135
135
|
"amp.mcpServers": {
|
|
136
136
|
"canvas": {
|
|
137
137
|
"command": "bunx",
|
|
138
|
-
"args": ["pmx-canvas@0.8.
|
|
138
|
+
"args": ["pmx-canvas@0.8.2", "--mcp"],
|
|
139
139
|
"includeTools": ["canvas_*"],
|
|
140
140
|
"env": {
|
|
141
141
|
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
@@ -169,7 +169,7 @@ The agent must not run this approval itself or move the command to global settin
|
|
|
169
169
|
`--mcp-config` to bypass workspace trust. Approval authorizes execution of this MCP
|
|
170
170
|
command; it is not a new Canvas import consent or permission to mutate arbitrary boards.
|
|
171
171
|
After approval, reload MCP connections (`reload_mcp` in Amp) and require `connected`.
|
|
172
|
-
Discover `canvas` tools with `tool_search`; for the full unfiltered 0.8.
|
|
172
|
+
Discover `canvas` tools with `tool_search`; for the full unfiltered 0.8.2 server there
|
|
173
173
|
are 24 tools. If still absent, inspect the server status, command/PATH and target instead
|
|
174
174
|
of reinstalling blindly. See [Amp MCP documentation](https://ampcode.com/docs/customize/mcp).
|
|
175
175
|
|
|
@@ -796,11 +796,12 @@ cmd(
|
|
|
796
796
|
const id = positional[0];
|
|
797
797
|
if (!id) die('Missing node ID', 'pmx-canvas node get <node-id>');
|
|
798
798
|
|
|
799
|
+
const requestedFields = collectRequestedFields(args, flags);
|
|
799
800
|
const result = (await invokeOperation('node.get', {
|
|
800
801
|
id,
|
|
801
802
|
board: typeof flags.board === 'string' ? flags.board : undefined,
|
|
803
|
+
contextDelivery: !flags.summary && !flags.compact && requestedFields.length === 0,
|
|
802
804
|
})) as Record<string, unknown>;
|
|
803
|
-
const requestedFields = collectRequestedFields(args, flags);
|
|
804
805
|
if (requestedFields.length > 0) {
|
|
805
806
|
const picked = Object.fromEntries(requestedFields.map((field) => [field, resolveNodeFieldValue(result, field)]));
|
|
806
807
|
const missing = requestedFields.filter((field) => picked[field] === undefined);
|
|
@@ -893,7 +894,7 @@ cmd(
|
|
|
893
894
|
frameHeight !== undefined ||
|
|
894
895
|
arrangeLocked !== undefined
|
|
895
896
|
) {
|
|
896
|
-
const existing = (await invokeOperation('node.get', { id })) as {
|
|
897
|
+
const existing = (await invokeOperation('node.get', { id, contextDelivery: false })) as {
|
|
897
898
|
position: { x: number; y: number };
|
|
898
899
|
size: { width: number; height: number };
|
|
899
900
|
data: Record<string, unknown>;
|
package/src/cli/index.ts
CHANGED
|
@@ -316,7 +316,8 @@ Agent CLI (works against running server):
|
|
|
316
316
|
diagram add Add an Excalidraw diagram node
|
|
317
317
|
html primitive add|schema Add or inspect HTML communication primitives
|
|
318
318
|
pin <ids...> | --list | --clear Manage context pins
|
|
319
|
-
board list|create|rename|category
|
|
319
|
+
board list|create|open|rename|category
|
|
320
|
+
Manage boards and open the target before writing
|
|
320
321
|
backup [status|schedule] Back up every board, now or on a schedule
|
|
321
322
|
restore <backup-file> Replace every board with a backup
|
|
322
323
|
export [--board id] [--out file] Export a board as one HTML file anyone can open
|
|
@@ -12,6 +12,14 @@ export function shouldShowPresentationControls(node: CanvasNodeState): boolean {
|
|
|
12
12
|
return node.type === 'html' && node.data.presentation === true;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export function htmlAxCapabilitySignature(value: unknown): string {
|
|
16
|
+
const capabilities = value as { enabled?: unknown; allowed?: unknown } | undefined;
|
|
17
|
+
if (capabilities?.enabled !== true) return 'disabled';
|
|
18
|
+
if (!Array.isArray(capabilities.allowed)) return 'enabled:default';
|
|
19
|
+
const allowed = [...new Set(capabilities.allowed.filter((item): item is string => typeof item === 'string'))].sort();
|
|
20
|
+
return allowed.length > 0 ? `enabled:${allowed.join(',')}` : 'disabled';
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
export function HtmlNode({
|
|
16
24
|
node,
|
|
17
25
|
expanded = false,
|
|
@@ -27,10 +35,12 @@ export function HtmlNode({
|
|
|
27
35
|
}) {
|
|
28
36
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
29
37
|
const theme = canvasTheme.value;
|
|
38
|
+
const axCapabilitySignature = htmlAxCapabilitySignature(node.data.axCapabilities);
|
|
30
39
|
// Stable per-mount nonce that authorizes parent → iframe theme-update messages.
|
|
31
40
|
const themeToken = useMemo(() => `theme-${crypto.randomUUID()}`, []);
|
|
32
|
-
//
|
|
33
|
-
|
|
41
|
+
// Rotate the nonce when capabilities change: both the URL and injected srcdoc
|
|
42
|
+
// change, rebootstrapping the bridge and rejecting emits from the old document.
|
|
43
|
+
const axToken = useMemo(() => `ax-${crypto.randomUUID()}`, [axCapabilitySignature]);
|
|
34
44
|
// Per-mount nonce for the content-height reporter (node grows to fit content).
|
|
35
45
|
const frameToken = useMemo(() => `frame-${crypto.randomUUID()}`, []);
|
|
36
46
|
const html =
|
|
@@ -636,8 +636,8 @@ body,
|
|
|
636
636
|
}
|
|
637
637
|
.node-port::after {
|
|
638
638
|
content: '';
|
|
639
|
-
width: calc(
|
|
640
|
-
height: calc(
|
|
639
|
+
width: calc(4px * var(--node-port-scale, 1));
|
|
640
|
+
height: calc(4px * var(--node-port-scale, 1));
|
|
641
641
|
border-radius: 50%;
|
|
642
642
|
background: var(--c-accent);
|
|
643
643
|
border: calc(2px * var(--node-port-scale, 1)) solid var(--c-panel);
|
|
@@ -775,14 +775,15 @@ body,
|
|
|
775
775
|
}
|
|
776
776
|
|
|
777
777
|
/* ⋯ and × appear on hover or keyboard focus; expand and the context pin
|
|
778
|
-
always show (docs/design/Chrome.dc.html).
|
|
778
|
+
always show (docs/design/Chrome.dc.html). Reserve their slots so revealing
|
|
779
|
+
them cannot move the pin out from under the pointer onto Close. */
|
|
779
780
|
.canvas-node .node-controls .node-hover-control {
|
|
780
|
-
|
|
781
|
+
visibility: hidden;
|
|
781
782
|
}
|
|
782
783
|
|
|
783
784
|
.canvas-node:hover .node-controls .node-hover-control,
|
|
784
785
|
.canvas-node:focus-within .node-controls .node-hover-control {
|
|
785
|
-
|
|
786
|
+
visibility: visible;
|
|
786
787
|
}
|
|
787
788
|
|
|
788
789
|
.canvas-node .node-body {
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
@custom-variant dark (&:is([data-theme="dark"] *));
|
|
7
7
|
|
|
8
|
+
@theme {
|
|
9
|
+
--font-sans: "IBM Plex Sans", system-ui, sans-serif;
|
|
10
|
+
--font-mono: "IBM Plex Mono", monospace;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
:root {
|
|
9
14
|
--radius: 0.5rem;
|
|
10
15
|
--background: oklch(1 0 0);
|
|
@@ -135,7 +140,7 @@
|
|
|
135
140
|
body {
|
|
136
141
|
@apply bg-background text-foreground antialiased;
|
|
137
142
|
margin: 0;
|
|
138
|
-
font-family:
|
|
143
|
+
font-family: var(--font-sans);
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
|
|
@@ -394,7 +394,7 @@ export function buildJsonRenderNodeUpdate(
|
|
|
394
394
|
): { data: Record<string, unknown>; spec: JsonRenderSpec } {
|
|
395
395
|
if (node.type !== 'json-render') throw new Error(`Node "${node.id}" is not a json-render node.`);
|
|
396
396
|
const spec = normalizeAndValidateJsonRenderSpec(input.spec);
|
|
397
|
-
const title = input.title?.trim() || inferJsonRenderNodeTitle(spec);
|
|
397
|
+
const title = input.title?.trim() || pickString(node.data, 'title') || inferJsonRenderNodeTitle(spec);
|
|
398
398
|
// Bump specVersion so the viewer URL changes: the iframe src is keyed on
|
|
399
399
|
// nodeId + ?v=specVersion, and the viewer only reads the spec at document
|
|
400
400
|
// load. Without the bump the src is byte-identical after a content update,
|
|
@@ -17,6 +17,7 @@ export type ContextReadChannel = (typeof CONTEXT_READ_CHANNELS)[number];
|
|
|
17
17
|
|
|
18
18
|
/** Registry reads that hand an agent canvas context; recorded in executeOperation. */
|
|
19
19
|
export const CONTEXT_READ_OPS = new Set([
|
|
20
|
+
'node.get',
|
|
20
21
|
'pinned-context.get',
|
|
21
22
|
'ax.context.get',
|
|
22
23
|
'ax.get',
|
package/src/server/index.ts
CHANGED
|
@@ -83,6 +83,7 @@ import { executeOperation, runCanvasBatchOperation, type OpenMcpAppCoreResult }
|
|
|
83
83
|
import { validateCanvasLayout } from './canvas-validation.js';
|
|
84
84
|
import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
|
|
85
85
|
import { serializeCanvasNode, type SerializedCanvasNode } from './canvas-serialization.js';
|
|
86
|
+
import { contextReadFromPayload } from './context-reads.js';
|
|
86
87
|
import {
|
|
87
88
|
buildHtmlPrimitive,
|
|
88
89
|
getHtmlPrimitiveSemanticMetadata,
|
|
@@ -1253,10 +1254,29 @@ export class PmxCanvas extends EventEmitter {
|
|
|
1253
1254
|
}
|
|
1254
1255
|
|
|
1255
1256
|
getNode(id: string, options?: { board?: string }): SdkCanvasNode | undefined {
|
|
1257
|
+
const boardId = options?.board ?? canvasState.activeBoardId;
|
|
1258
|
+
const board = options?.board ? canvasState.readBoard(options.board) : null;
|
|
1259
|
+
if (options?.board && !board) throw new Error(`Board "${options.board}" not found.`);
|
|
1256
1260
|
const node = options?.board
|
|
1257
|
-
?
|
|
1261
|
+
? board?.layout.nodes.find((candidate) => candidate.id === id)
|
|
1258
1262
|
: canvasState.getNode(id);
|
|
1259
|
-
|
|
1263
|
+
if (!node) return undefined;
|
|
1264
|
+
const result = toSdkNode(node);
|
|
1265
|
+
canvasState.recordContextRead(
|
|
1266
|
+
contextReadFromPayload(
|
|
1267
|
+
{
|
|
1268
|
+
channel: 'operation',
|
|
1269
|
+
resource: 'node.get',
|
|
1270
|
+
source: 'sdk',
|
|
1271
|
+
consumer: null,
|
|
1272
|
+
agentId: null,
|
|
1273
|
+
pinnedNodeIds: options?.board ? (board?.state.contextPins ?? []) : [...canvasState.contextPinnedNodeIds],
|
|
1274
|
+
},
|
|
1275
|
+
result,
|
|
1276
|
+
),
|
|
1277
|
+
boardId,
|
|
1278
|
+
);
|
|
1279
|
+
return result;
|
|
1260
1280
|
}
|
|
1261
1281
|
|
|
1262
1282
|
search(query: string): ReturnType<typeof searchNodes> {
|