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.
@@ -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. Type 11/12/13/15/20; radii 6/10/14/pill; two
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 ×. Section labels are a heading
164
- display of the markdown node. Rarely used tools live in one settings menu on the rail.
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; the workbench's own reads are not.
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.0",
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",
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.8.0"
2
+ "version": "0.8.2"
3
3
  }
@@ -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.0",
5
+ "version": "0.8.2",
6
6
  "author": {
7
7
  "name": "pskoett",
8
8
  "url": "https://github.com/pskoett"
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.8.0"
2
+ "version": "0.8.2"
3
3
  }
@@ -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 by its returned board/card IDs.
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.0 requires **Bun >=1.4.2**, including when installed through npm.
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: dark, light, high-contrast, midnight,
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.0**, which requires **Bun >=1.4.2** even when
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.0
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.0` in place of `pmx-canvas` in
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.0", "--mcp"],
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.0", "--mcp"],
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.0 server there
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 Manage boards (the human opens them in the workbench)
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
- // Per-mount nonce authorizing iframe → parent AX emits (Phase 3 HTML bridge).
33
- const axToken = useMemo(() => `ax-${crypto.randomUUID()}`, []);
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(6px * var(--node-port-scale, 1));
640
- height: calc(6px * var(--node-port-scale, 1));
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
- display: none;
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
- display: inline-flex;
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: system-ui, sans-serif;
143
+ font-family: var(--font-sans);
139
144
  }
140
145
  }
141
146
 
@@ -184,7 +184,6 @@ function App({ store }: { store: StateStore }) {
184
184
  alignItems: 'center',
185
185
  justifyContent: 'center',
186
186
  color: 'var(--muted-foreground, #666)',
187
- fontFamily: 'system-ui',
188
187
  }}
189
188
  >
190
189
  Waiting for UI spec...
@@ -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',
@@ -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
- ? this.getLayout(options).nodes.find((candidate) => candidate.id === id)
1261
+ ? board?.layout.nodes.find((candidate) => candidate.id === id)
1258
1262
  : canvasState.getNode(id);
1259
- return node ? toSdkNode(node) : undefined;
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> {