{"schemaVersion":1,"id":"recommended.sm-pipeline","version":"1.0.0","name":"Canvas workflow","description":"An editable production workflow using Canvas v2.2.5.","stages":[{"id":"main","blockId":"sm.pipeline","version":"2.2.5","label":"Canvas"}],"connections":[],"packages":[{"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"sm.pipeline","version":"2.2.5","name":"Canvas","description":"Edit the complete Canvas and prepare exact source-bound context connections for review and Apply, preserving Project Type Steps and existing content.","kind":"workspace","runtime":"javascript","entry":"src/run.js","license":"MIT","provenance":{"notice":"Original source (authorship declared by the packager)\nLicense: MIT\nChanges: Original implementation declared by the packager; no reused repository or dependency code.\nFiles: LICENSE, src/run.js, src/view.json\n\nMIT License\n\nCopyright (c) 2026 StillMade contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","portableSources":[{"kind":"original","license":"MIT","paths":["LICENSE","src/run.js","src/view.json"],"evidence":["upstream/0/LICENSE","upstream/0/src/run.js","upstream/0/src/view.json"],"changes":"Original implementation declared by the packager; no reused repository or dependency code."}]},"inputs":{"canvas":{"type":"pipeline","semantic":"canvas_document","context":"canvas","primary":true,"required":false,"default":{"schemaVersion":1,"nodes":[],"edges":[]},"description":"The complete safe Canvas projection. Native content and layout are retained; executable source and workflow history remain host-owned."},"assets":{"type":"asset[]","semantic":"project_assets","context":"assets","required":false,"description":"Current saved project media available to add to Canvas."},"versions":{"type":"metadata[]","semantic":"asset_versions","context":"versions","required":false,"description":"Saved project version history available for exact take selection."},"updatedCanvas":{"type":"pipeline","semantic":"canvas_document","required":false,"default":{"schemaVersion":1,"nodes":[],"edges":[]},"description":"The complete edited Canvas projection prepared in this workspace."},"commit":{"type":"boolean","semantic":"canvas_commit_intent","required":false,"default":false,"description":"Prepare the edited Canvas for StillMade review and Apply."},"generatedImage":{"type":"image","semantic":"generated_image_version","imageMode":"reference","required":false,"description":"An exact stored image version produced by an approved generation Block or selected from project media."},"addGeneratedImage":{"type":"boolean","semantic":"attach_generated_image","required":false,"default":false,"description":"Add the connected image as a native Canvas media node."},"generatedImageTitle":{"type":"text","semantic":"generated_image_title","required":false,"default":"Generated image","description":"Label for the connected image on Canvas."},"generatedVideo":{"type":"video","semantic":"generated_video_version","required":false,"description":"An exact stored video version produced by an approved generation Block or selected from project media."},"addGeneratedVideo":{"type":"boolean","semantic":"attach_generated_video","required":false,"default":false,"description":"Add the connected video as a native Canvas media node."},"generatedVideoTitle":{"type":"text","semantic":"generated_video_title","required":false,"default":"Generated video","description":"Label for the connected video on Canvas."},"generatedAudio":{"type":"audio","semantic":"generated_audio_version","required":false,"description":"An exact stored narration version produced by an approved Voiceover Block or selected from project media."},"addGeneratedAudio":{"type":"boolean","semantic":"attach_generated_audio","required":false,"default":false,"description":"Add the connected narration as a native Canvas voiceover node."},"generatedAudioTitle":{"type":"text","semantic":"generated_audio_title","required":false,"default":"Voiceover","description":"Label for the connected narration on Canvas."},"connectionProposal":{"type":"json","semantic":"context_connection_proposal","required":false,"description":"A reviewed context proposal containing exact text-only source nodes/edges and new connections. This data does not grant project-write or execution approval.","schema":{"content":{"nodes":[{"id":"string","type":"string","data":{"contextSourceNodeId":"string?","readablePrompt":"string?","connectedPrompt":"string?","shotDescription":"string?","name":"string?","title":"string?","description":"string?","text":"string?","characterRefs":"string[]?","locationRefs":"string[]?","charactersPresent":"string[]?","location":"string?"}}],"edges":[{"source":"string","target":"string"}]},"edges":[{"id":"string","source":"string","target":"string","targetHandle":"string?"}]}},"applyContextConnections":{"type":"boolean","semantic":"context_connection_intent","required":false,"default":false,"description":"Validate the exact current Canvas sources and prepare only the proposed context connections for normal review and Apply."}},"outputs":{"canvas":{"type":"pipeline","semantic":"canvas_document","primary":true,"role":"canvas_document","description":"The complete edited safe Canvas projection."},"edit":{"type":"workspace-edit","semantic":"canvas_edit_proposal","description":"An exact before/after Canvas change reviewed before it updates the project."}},"permissions":{"project":["context.canvas.read","context.assets.read","context.versions.read","asset.read","workspace.canvas.propose"],"network":[],"filesystem":[],"secrets":[]},"ui":[],"platforms":{"phone":{"supported":true},"browser":{"supported":true},"desktop":{"supported":true}},"operations":[{"id":"connect-context","label":"Review context connections","description":"Validate a supplied context proposal against current Canvas text sources and prepare new connections for review. No model, generation or project write runs in this Block.","inputs":{"applyContextConnections":true,"commit":false,"addGeneratedImage":false,"addGeneratedVideo":false,"addGeneratedAudio":false},"outputs":["canvas","edit"]}]},"code":"const copy=value=>JSON.parse(JSON.stringify(value));\nconst source=copy(input.canvas||{schemaVersion:1,nodes:[],edges:[]});\nconst candidate=input.commit?copy(input.updatedCanvas):copy(source);\nif(!candidate||Array.isArray(candidate)||candidate.schemaVersion!==1||!Array.isArray(candidate.nodes)||!Array.isArray(candidate.edges))throw new Error('Keep the complete version 1 Canvas document with nodes and edges.');\nif(input.applyContextConnections){\n if(input.commit||input.addGeneratedImage||input.addGeneratedVideo||input.addGeneratedAudio)throw new Error('Review context connections separately from other Canvas changes.');\n const proposal=input.connectionProposal;\n if(!proposal||Array.isArray(proposal)||Object.keys(proposal).some(key=>!['content','edges'].includes(key))||!proposal.content||!Array.isArray(proposal.edges)||proposal.edges.length>1000)throw new Error('Supply the exact context source and a bounded proposed connection list.');\n const types=['characterReferenceNode','locationReferenceNode','textPromptNode','imageGenNode','videoGenNode','styleNode'],fields=['characterRefs','locationRefs','charactersPresent','location','contextSourceNodeId','readablePrompt','connectedPrompt','shotDescription','name','title','description','text'];\n const nodes=source.nodes.filter(node=>types.includes(node.type)&&!node.data?.hostBlock).map(node=>({id:node.id,type:node.type,data:Object.fromEntries(fields.filter(key=>node.data?.[key]!==undefined).map(key=>[key,node.data[key]]))}));\n const ids=new Set(nodes.map(node=>node.id)),snapshot={nodes,edges:source.edges.filter(edge=>ids.has(edge.source)&&ids.has(edge.target)).map(({source,target})=>({source,target}))};\n const canonical=value=>value===null||typeof value!=='object'?JSON.stringify(value):Array.isArray(value)?'['+value.map(canonical).join(',')+']':'{'+Object.keys(value).sort().map(key=>JSON.stringify(key)+':'+canonical(value[key])).join(',')+'}';\n if(nodes.length>200||snapshot.edges.length>1000||canonical(snapshot)!==canonical(proposal.content))throw new Error('Canvas connection sources changed. Request and review fresh connections.');\n const byId=new Map(source.nodes.map(node=>[node.id,node])),present=new Set(source.edges.map(edge=>JSON.stringify([edge.source,edge.target]))),edgeIds=new Set(source.edges.map(edge=>edge.id));\n for(const edge of proposal.edges){\n  if(!edge||Array.isArray(edge)||typeof edge!=='object'||Object.keys(edge).some(key=>!['id','source','target','targetHandle'].includes(key)))throw new Error('Unsupported connection settings.');\n  const from=byId.get(edge.source),to=byId.get(edge.target),key=JSON.stringify([edge.source,edge.target]);\n  if(!from||!to||from.id===to.id||!ids.has(from.id)||!ids.has(to.id)||!['imageGenNode','videoGenNode'].includes(to.type)||present.has(key)||edge.id!=='e-cc-'+from.id+'-'+to.id||edgeIds.has(edge.id))throw new Error('Unavailable or duplicate context connection.');\n  const allowed=['characterReferenceNode','locationReferenceNode','styleNode','textPromptNode'].includes(from.type)||to.type==='videoGenNode'&&(from.type==='imageGenNode'||to.data.contextSourceNodeId===from.id);\n  if(!allowed||(from.type==='textPromptNode'?edge.targetHandle!=='prompt-in':edge.targetHandle!==undefined))throw new Error('Context connection source and target types do not match.');\n  present.add(key);edgeIds.add(edge.id);candidate.edges.push(copy(edge));\n }\n}\nconst attach=(value,kind,title)=>{\n if(!value||value.kind!==kind)throw new Error('Connect an approved generated '+kind+' before adding it to Canvas.');\n const ref=copy(value),base='connected-'+kind+'-'+ref.assetId+'-'+ref.versionId;\n let id=base,suffix=2;while(candidate.nodes.some(node=>node.id===id)){id=base+'-'+suffix;suffix+=1}\n const position={x:80+candidate.nodes.length*24,y:80+candidate.nodes.length*24};\n if(kind==='image')candidate.nodes.push({id,type:'imageGenNode',position,width:320,height:320,data:{title:title||'Generated image',assetId:ref.assetId,versionId:ref.versionId,url:ref.url,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId}],activeVersion:0,selectedVersion:0,status:'complete',source:'connected-block'}});\n if(kind==='video')candidate.nodes.push({id,type:'videoGenNode',position,width:360,height:260,data:{title:title||'Generated video',assetId:ref.assetId,versionId:ref.versionId,url:ref.url,duration:ref.duration,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId,duration:ref.duration}],currentVersion:0,approvedVersion:0,status:'complete',source:'connected-block'}});\n if(kind==='audio')candidate.nodes.push({id,type:'voiceoverGenNode',position,width:360,height:260,data:{title:title||'Voiceover',assetId:ref.assetId,versionId:ref.versionId,url:ref.url,duration:ref.duration,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId,duration:ref.duration}],currentVersion:0,status:'complete',source:'connected-block'}});\n};\nif(input.addGeneratedImage)attach(input.generatedImage,'image',input.generatedImageTitle);\nif(input.addGeneratedVideo)attach(input.generatedVideo,'video',input.generatedVideoTitle);\nif(input.addGeneratedAudio)attach(input.generatedAudio,'audio',input.generatedAudioTitle);\nif(candidate.nodes.length>5000||candidate.edges.length>10000)throw new Error('Canvas supports up to 5,000 nodes and 10,000 connections.');\nif(!candidate.nodes.every(node=>node&&typeof node==='object'&&!Array.isArray(node)&&typeof node.id==='string'&&node.id&&typeof node.type==='string'&&node.type&&node.position&&Number.isFinite(node.position.x)&&Number.isFinite(node.position.y)))throw new Error('Every Canvas node needs an identity, type, and finite position.');\nif(new Set(candidate.nodes.map(node=>node.id)).size!==candidate.nodes.length)throw new Error('Canvas node identities must be distinct.');\nconst ids=new Set(candidate.nodes.map(node=>node.id));\nif(!candidate.edges.every(edge=>edge&&typeof edge==='object'&&!Array.isArray(edge)&&typeof edge.id==='string'&&edge.id&&ids.has(edge.source)&&ids.has(edge.target)))throw new Error('Every Canvas connection needs an identity and existing source and target nodes.');\nif(new Set(candidate.edges.map(edge=>edge.id)).size!==candidate.edges.length)throw new Error('Canvas connection identities must be distinct.');\nreturn{canvas:candidate,edit:{schemaVersion:1,title:`Update Canvas with ${candidate.nodes.length} node${candidate.nodes.length===1?'':'s'}`,field:'canvas',before:source,after:candidate}};\n","view":{"html":"<main class=\"canvas-workspace\">\n  <header><div><small>Production workspace</small><h1>Canvas</h1><p id=\"summary\">Loading the complete Canvas…</p></div><div class=\"actions\"><button id=\"discard\" type=\"button\" class=\"secondary\">Discard local draft and load shared</button><button id=\"reset\" type=\"button\" class=\"secondary\">Reset changes</button><button id=\"add\" type=\"button\" class=\"secondary\">Add note</button><button id=\"save\" data-stillmade-action=\"run\" type=\"button\">Review changes</button></div></header>\n  <output id=\"status\" role=\"status\" aria-live=\"polite\"></output>\n  <section><div class=\"section-title\"><h2>Connected media</h2></div><div class=\"handoffs\"><div id=\"handoff-image\"></div><div id=\"handoff-video\"></div><div id=\"handoff-audio\"></div></div></section>\n  <section><div class=\"section-title\"><h2>Saved media and history</h2><p class=\"hint\">Choose any exact saved version and add it to Canvas.</p></div><div id=\"library\" class=\"cards\"></div></section>\n  <section><h2>Canvas items</h2><p class=\"hint\">Project Type Steps stay connected and carry their exact installed source. Edit their placement or label here; use the Project Type builder to add, remove, replace, or reconnect Steps.</p><div id=\"nodes\" class=\"cards\"></div></section>\n  <section><h2>Context connections</h2><p class=\"hint\">Request connections in project Chat, review the exact sources there, then supply the proposal to this Canvas Block. Preparing connections does not run generators or save the project.</p><div id=\"contextConnections\" class=\"connections\"></div><button id=\"connectContext\" data-stillmade-action=\"run\" type=\"button\" class=\"secondary\" disabled>Review proposed connections</button></section>\n  <section><h2>Connections</h2><div id=\"edges\" class=\"connections\"></div></section>\n  <details id=\"rawDetails\"><summary>Complete Canvas data</summary><p>Native content, media references, dimensions, and custom fields are retained. Executable source and host bindings are protected when StillMade applies the proposal.</p><label for=\"raw\" class=\"raw-label\">Canvas JSON</label><textarea id=\"raw\" class=\"raw\" spellcheck=\"false\"></textarea><button id=\"applyRaw\" type=\"button\" class=\"secondary\">Apply JSON to workspace</button></details>\n</main>\n","css":".canvas-workspace{width:100%;min-width:0;margin:0;padding:8px;color:var(--fg)}header,.actions,.card-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}header{justify-content:space-between;margin-bottom:14px}header h1{margin:2px 0}header p,header small,details p,output,.hint{color:var(--fg-muted)}section,details{margin:12px 0;padding:16px;border:1px solid var(--border-tok);border-radius:var(--r-3);background:var(--bg-elev-1)}section h2{margin:0 0 8px}.cards,.connections{display:grid;gap:10px}.handoffs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.handoff,.card,.connection{padding:12px;border:1px solid var(--border-tok);border-radius:var(--r-2);background:var(--bg)}.handoff p{min-height:38px}.card-head{justify-content:space-between}.card h3{margin:0;font-size:16px}.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}label{display:grid;gap:5px;margin:7px 0;font-size:13px;font-weight:600}input,textarea{width:100%;background:var(--bg)}textarea{min-height:76px;resize:vertical}.wide{grid-column:1/-1}.secondary{background:transparent;color:var(--fg)}.danger{background:transparent;color:var(--signal)}.raw{min-height:420px;font-family:var(--font-mono);font-size:12px}output{display:block;min-height:24px;margin:8px 2px}.badge{font-size:12px;color:var(--fg-muted)}\n@media(max-width:760px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:520px){.canvas-workspace{padding:0}.grid,.handoffs{grid-template-columns:1fr}.actions{width:100%}.actions button{flex:1}section,details{padding:12px}}\n\n#library .handoff{min-width:0}#library .handoff button{width:100%}\n","javascript":"const status=document.getElementById('status'),summary=document.getElementById('summary'),raw=document.getElementById('raw'),save=document.getElementById('save');\nlet source={schemaVersion:1,nodes:[],edges:[]},draft={schemaVersion:1,nodes:[],edges:[]},draftSource=source,overlay={edits:[]},shared={state:{},outputs:{},canEdit:false};\nlet canEdit=false,busy=false,failed=false,pending=0,queuedBase=null,queuedEdits=null,revision=0,inputReady=false,notice='',rawEditing=false,rawPending=0,rawQueued=null,rawBefore=null,rawFlight=null;\nlet contextProposal=null;\nlet connectedImage=null,connectedVideo=null,connectedAudio=null,titles={image:'Generated image',video:'Generated video',audio:'Voiceover'},library=[];\nconst pages=[],identities=[];let resetBase=null;\nlet rawRenderedDraft=null,rawRenderedRevision=-1;\nconst clone=value=>JSON.parse(JSON.stringify(value));\nconst text=value=>value===undefined||value===null?'':String(value);\nconst managed=node=>node.type==='programmableBlockNode'||!!node.data?.hostBlock;\nconst editable=()=>inputReady&&canEdit&&!busy&&!failed;\nconst keyFor=id=>{let key=identities.indexOf(id);if(key<0){identities.push(id);key=identities.length-1;}return key;};\nconst controlId=(id,purpose)=>{const value=JSON.stringify([id,purpose]),a=value.split('').reduce((hash,char)=>Math.imul(hash^char.charCodeAt(0),16777619),2166136261)>>>0,b=value.split('').reduce((hash,char)=>Math.imul(hash^char.charCodeAt(0),2246822519),3266489917)>>>0;return 'canvas-'+a.toString(36)+'-'+b.toString(36);};\nconst paged=(group,items,render,size)=>{\n let slot=pages.find(item=>item.group===group);if(!slot){slot={group,page:0};pages.push(slot);}\n slot.page=Math.min(slot.page,Math.max(0,Math.ceil(items.length/size)-1));const key=pages.indexOf(slot);\n const tree=items.slice(slot.page*size,slot.page*size+size).map((item,index)=>render(item,slot.page*size+index));\n if(items.length>size)tree.push({tag:'div',className:'actions',children:[{tag:'button',action:'page-back:'+String(key),text:'Previous',disabled:slot.page===0},{tag:'span',text:'Page '+String(slot.page+1)+' of '+String(Math.ceil(items.length/size))},{tag:'button',action:'page-next:'+String(key),text:'Next',disabled:(slot.page+1)*size>=items.length}]});\n return tree;\n};\nconst field=(node,label,kind,value,type='text',wide=false)=>({tag:'label',className:wide?'wide':'',children:[{tag:'span',text:label},{tag:'input',id:controlId(node.id,kind),type,action:kind+':'+String(keyFor(node.id)),value:Number.isFinite(value)?value:text(value),disabled:!editable(),label}]});\nconst nodeCard=node=>({tag:'article',id:controlId(node.id,'card'),className:'card',children:[{tag:'div',className:'card-head',children:[{tag:'h3',text:(text(node.data?.title)||text(node.data?.label)||text(node.data?.name)||text(node.id)).slice(0,240)},{tag:'span',className:'badge',text:managed(node)?'Project Type Step':text(node.type)},{tag:'button',action:'remove:'+String(keyFor(node.id)),text:'Remove',className:'danger',disabled:!editable()||managed(node)}]},{tag:'div',className:'grid',children:[field(node,'Title','title',node.data?.title),field(node,'Label','label',node.data?.label),field(node,'X','x',node.position?.x,'number'),field(node,'Y','y',node.position?.y,'number'),field(node,'Width','width',node.width,'number'),field(node,'Height','height',node.height,'number'),managed(node)?{tag:'p',className:'wide hint',text:'Its executable source and connections are preserved.'}:field(node,'Content','content',node.data?.text===undefined?node.data?.content:node.data.text,'text',true)]}]});\nconst edgeCard=edge=>({tag:'article',id:controlId(edge.id,'edge'),className:'connection',children:[{tag:'strong',text:text(edge.source)+' → '+text(edge.target)},{tag:'p',className:'hint',text:text(edge.sourceHandle)+' '+text(edge.targetHandle)}]});\nconst libraryCard=(value,index)=>({tag:'article',className:'handoff',children:[{tag:'strong',text:(text(value.label)||text(value.assetId)).slice(0,240)},{tag:'p',className:'hint',text:text(value.kind)+' · version '+text(value.versionId)},{tag:'button',action:'library-'+text(value.kind)+':'+String(index),text:'Add to Canvas',className:'secondary',disabled:!editable()}]});\nconst handoff=(kind,value,label)=>value?[{tag:'article',className:'handoff',children:[{tag:'strong',text:label},{tag:'p',className:'hint',text:text(value.assetId)+' · version '+text(value.versionId)},{tag:'button',action:'attach-'+kind,text:'Add '+kind+' to Canvas',className:'secondary',disabled:!editable()}]}]:[{tag:'article',className:'handoff',children:[{tag:'strong',text:label},{tag:'p',className:'hint',text:'Connect a stored '+kind+' output to add it with exact asset and version identity.'}]}];\nconst refreshRaw=()=>{\n if(!rawEditing&&document.getElementById('rawDetails').hasAttribute('open')&&(rawRenderedDraft!==draft||rawRenderedRevision!==revision)){\n  raw.value=JSON.stringify(draft);rawRenderedDraft=draft;rawRenderedRevision=revision;\n }\n};\nconst draw=()=>{\n StillMade.render('handoff-image',handoff('image',connectedImage,'Image'));StillMade.render('handoff-video',handoff('video',connectedVideo,'Video'));StillMade.render('handoff-audio',handoff('audio',connectedAudio,'Narration'));\n StillMade.render('library',library.length?paged('library',library,libraryCard,24):[{tag:'p',text:'No saved media versions yet.'}]);\n StillMade.render('nodes',draft.nodes.length?paged('nodes',draft.nodes,nodeCard,8):[{tag:'p',text:'No Canvas items yet.'}]);StillMade.render('edges',draft.edges.length?paged('edges',draft.edges,edgeCard,24):[{tag:'p',text:'No connections.'}]);\n StillMade.render('contextConnections',contextProposal?.edges?.length?paged('contextConnections',contextProposal.edges,edgeCard,24):[{tag:'p',text:'No context connection proposal supplied.'}]);document.getElementById('connectContext').disabled=!editable()||pending>0||rawPending>0||!contextProposal?.edges?.length;\n refreshRaw();\n summary.textContent=String(draft.nodes.length)+' items · '+String(draft.edges.length)+' connections';\n save.disabled=!editable()||pending>0||!!queuedBase||rawEditing;document.getElementById('reset').disabled=busy||!canEdit||pending>0||rawPending>0||(!failed&&(!!queuedBase||!!rawQueued));document.getElementById('reset').textContent=resetBase?'Confirm reset for everyone':'Reset shared draft to project';document.getElementById('add').disabled=!editable();\n document.getElementById('applyRaw').disabled=!editable()||pending>0||!!queuedBase||rawPending>0||!!rawQueued;raw.disabled=busy||!canEdit;\n document.getElementById('discard').disabled=busy||!canEdit||pending>0||rawPending>0;status.textContent=notice;\n};\nconst validCanvas=(value,edits)=>{\n if(!value||value.schemaVersion!==1||!Array.isArray(value.nodes)||!Array.isArray(value.edges)||value.nodes.length>5000||value.edges.length>10000)throw Error('Use a version 1 Canvas with at most 5,000 nodes and 10,000 connections.');\n const lookup=(items,id)=>items.find(item=>item.id===id);\n edits.forEach(edit=>{\n  const kind=edit.path[0],id=edit.path[1]?.id;\n  if(kind==='nodes'&&id){\n   const old=lookup(draftSource.nodes,id),node=lookup(value.nodes,id);\n   if(old&&managed(old)&&(!node||node.type!==old.type))throw Error('Change executable Steps through the Project Type builder.');\n   if(node){\n    if(typeof node.type!=='string'||!node.type||!node.position||!Number.isFinite(node.position.x)||!Number.isFinite(node.position.y))throw Error('Every Canvas node needs a type and finite position.');\n    if(node.type==='programmableBlockNode'&&(!old||old.type!==node.type||Object.keys(node.data||{}).some(key=>!['title','label'].includes(key))||Object.keys(node).some(key=>!['id','type','position','width','height','data'].includes(key))))throw Error('Executable source cannot be changed through this Canvas draft.');\n    if(JSON.stringify(node.data?.hostBlock||null)!==JSON.stringify(old?.data?.hostBlock||null))throw Error('Host Block bindings are protected.');\n   }\n  }\n  if(kind==='edges'&&id){\n   const old=lookup(draftSource.edges,id),edge=lookup(value.edges,id);\n   const touches=edge=>edge&&[edge.source,edge.target].some(key=>{const item=lookup(draftSource.nodes,key);return item&&managed(item);});\n   if(touches(old)||touches(edge))throw Error('Change Step connections through the Project Type builder.');\n   if(edge&&(!lookup(value.nodes,edge.source)||!lookup(value.nodes,edge.target)))throw Error('Every connection needs existing nodes.');\n  }\n  if((kind==='nodes'||kind==='edges')&&edit.path.length===1&&!edit.order)throw Error('Keep distinct item and connection IDs.');\n });\n if(Object.keys(value).some(key=>!['schemaVersion','nodes','edges','viewport'].includes(key))||JSON.stringify(value.viewport||null)!==JSON.stringify(draftSource.viewport||null))throw Error('Canvas navigation stays local; preserve the supplied viewport and document fields.');\n};\nconst condition=(state,key)=>Object.hasOwn(state,key)?{exists:true,value:clone(key==='canvasDraft'?state.canvasDraft:state.rawDraft)}:{exists:false};\nconst sourceField=(id,path)=>{\n const node=draftSource.nodes.find(item=>item.id===id),key=path.join('.');if(!node)return {exists:false};\n if(key==='data.title')return Object.hasOwn(node.data||{},'title')?{exists:true,value:clone(node.data.title)}:{exists:false};\n if(key==='data.label')return Object.hasOwn(node.data||{},'label')?{exists:true,value:clone(node.data.label)}:{exists:false};\n if(key==='data.text')return Object.hasOwn(node.data||{},'text')?{exists:true,value:clone(node.data.text)}:{exists:false};\n if(key==='data.content')return Object.hasOwn(node.data||{},'content')?{exists:true,value:clone(node.data.content)}:{exists:false};\n if(key==='position.x')return Object.hasOwn(node.position||{},'x')?{exists:true,value:clone(node.position.x)}:{exists:false};\n if(key==='position.y')return Object.hasOwn(node.position||{},'y')?{exists:true,value:clone(node.position.y)}:{exists:false};\n if(key==='width')return Object.hasOwn(node,'width')?{exists:true,value:clone(node.width)}:{exists:false};\n if(key==='height')return Object.hasOwn(node,'height')?{exists:true,value:clone(node.height)}:{exists:false};return {exists:false};\n};\nconst queueFieldEdit=(id,path,value)=>{\n if(!queuedEdits)return;const full=['nodes',{id},...path],key=JSON.stringify(full),before=sourceField(id,path),after={exists:true,value:clone(value)};\n queuedEdits=queuedEdits.filter(edit=>JSON.stringify(edit.path)!==key);\n if(JSON.stringify(before)!==JSON.stringify(after))queuedEdits.push({path:full,before,after});\n};\ndocument.getElementById('rawDetails').addEventListener('toggle',refreshRaw);\nconst fail=error=>{failed=true;notice=error.message+' Your local draft is retained. Copy it before discarding and loading the shared draft.';draw();};\nconst receive=value=>{\n shared=value;canEdit=!!value.canEdit;\n if(inputReady&&!pending&&!queuedBase&&!failed){\n  try{const next=value.state.canvasDraft||{edits:[]};draft=StillMade.applyDocument(source,next.edits);draftSource=source;overlay=clone(next);}\n  catch(error){fail(error);return;}\n }\n if(!rawPending&&!rawQueued&&!failed){rawFlight=null;rawEditing=!!value.state.rawDraft;if(rawEditing)raw.value=value.state.rawDraft.text;}\n if(!busy&&!pending&&!queuedBase&&!failed)notice=value.outputs.edit?'A shared Canvas proposal is ready below. Review before applying.':'Working edits are shared. Review and Apply still govern project changes.';\n draw();\n};\nconst publish=async()=>{\n if(failed)throw Error(notice);if(!queuedBase)return;const before=queuedBase;queuedBase=null;pending+=1;\n try{\n  const edits=(queuedEdits||StillMade.diffDocument(draftSource,draft)).map(edit=>({...edit,id:JSON.stringify(edit.path)}));queuedEdits=null;validCanvas(draft,edits);const next={edits};overlay=clone(next);\n  if(!Object.hasOwn(StillMade.getShared().state,'canvasDraft')){\n   try{await StillMade.updateShared({canvasDraft:{edits:[]}},{canvasDraft:{exists:false}});}\n   catch(error){if(!StillMade.getShared().canEdit||!Object.hasOwn(StillMade.getShared().state,'canvasDraft'))throw error;}\n  }\n  await StillMade.updateShared({canvasDraft:next},{canvasDraft:{exists:true,value:before}},{merge:true});\n }catch(error){fail(error);throw error;}\n finally{pending-=1;if(!pending&&!queuedBase&&!failed)receive(StillMade.getShared());else draw();}\n};\nconst changed=edit=>{if(!queuedBase){queuedBase=clone(overlay);queuedEdits=clone(overlay.edits||[]);}if(edit)queueFieldEdit(edit.id,edit.path,edit.value);else queuedEdits=null;revision+=1;notice='Sharing Canvas edits. Review before applying to the project.';save.disabled=true;StillMade.scheduleSharedEdit('canvasDraft',publish);};\nconst publishRaw=async()=>{\n if(failed)throw Error(notice);if(!rawQueued)return;const value=rawQueued.value,before=rawBefore;rawQueued=null;rawPending+=1;rawFlight={exists:true,value:clone(value)};draw();\n try{await StillMade.updateShared({rawDraft:value},{rawDraft:before});}catch(error){fail(error);throw error;}\n finally{rawPending-=1;if(!rawQueued&&!failed)receive(StillMade.getShared());else draw();}\n};\nconst queueRaw=value=>{if(!rawQueued)rawBefore=rawPending?rawFlight:condition(StillMade.getShared().state,'rawDraft');rawQueued={value};StillMade.scheduleSharedEdit('canvasRaw',publishRaw);};\nraw.addEventListener('input',()=>{if(!editable())return;const base=rawEditing?(rawQueued?.value?.base||rawFlight?.value?.base||shared.state.rawDraft?.base||clone(draft)):clone(draft);rawEditing=true;queueRaw({text:raw.value,base});save.disabled=true;document.getElementById('applyRaw').disabled=true;});\nconst newId=prefix=>prefix+'-'+String(Date.now())+'-'+Math.random().toString(36).slice(2);\nconst attach=(kind,ref,title)=>{\n const id=newId('connected-'+kind),position={x:80+draft.nodes.length*24,y:80+draft.nodes.length*24};let node;\n if(kind==='image')node={id,type:'imageGenNode',position,width:320,height:320,data:{title,assetId:ref.assetId,versionId:ref.versionId,url:ref.url,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId}],activeVersion:0,selectedVersion:0,status:'complete',source:'connected-block'}};\n if(kind==='video')node={id,type:'videoGenNode',position,width:360,height:260,data:{title,assetId:ref.assetId,versionId:ref.versionId,url:ref.url,duration:ref.duration,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId,duration:ref.duration}],currentVersion:0,approvedVersion:0,status:'complete',source:'connected-block'}};\n if(kind==='audio')node={id,type:'voiceoverGenNode',position,width:360,height:260,data:{title,assetId:ref.assetId,versionId:ref.versionId,url:ref.url,duration:ref.duration,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId,duration:ref.duration}],currentVersion:0,status:'complete',source:'connected-block'}};\n if(!node)return;draft.nodes=[...draft.nodes,clone(node)];changed();draw();\n};\nStillMade.onAction(event=>{\n const parts=event.action.split(':'),kind=parts[0],index=Number(parts[1]);if(kind==='page-back'||kind==='page-next'){const slot=pages.at(index);if(slot){slot.page=Math.max(0,slot.page+(kind==='page-next'?1:-1));draw();}return;}\n if(!editable())return;\n if(kind==='attach-image'&&connectedImage){attach('image',connectedImage,titles.image);return;}if(kind==='attach-video'&&connectedVideo){attach('video',connectedVideo,titles.video);return;}if(kind==='attach-audio'&&connectedAudio){attach('audio',connectedAudio,titles.audio);return;}\n if(kind.startsWith('library-')){const value=library.at(index),mediaKind=kind.slice(8);if(value&&value.kind===mediaKind)attach(mediaKind,value,text(value.label)||text(value.assetId));return;}\n const id=identities.at(index),node=draft.nodes.find(item=>item.id===id);if(!node)return;\n if(kind==='remove'&&!managed(node)){draft.nodes=draft.nodes.filter(item=>item.id!==id);draft.edges=draft.edges.filter(edge=>edge.source!==id&&edge.target!==id);changed();draw();return;}\n if(!node.data||Array.isArray(node.data))node.data={};\n let path=null,value=null;if(kind==='title'){node.data.title=event.value;path=['data','title'];value=node.data.title;}if(kind==='label'){node.data.label=event.value;path=['data','label'];value=node.data.label;}if(kind==='x'){node.position.x=Number(event.value)||0;path=['position','x'];value=node.position.x;}if(kind==='y'){node.position.y=Number(event.value)||0;path=['position','y'];value=node.position.y;}if(kind==='width'){node.width=Math.max(1,Number(event.value)||1);path=['width'];value=node.width;}if(kind==='height'){node.height=Math.max(1,Number(event.value)||1);path=['height'];value=node.height;}\n if(kind==='content'&&!managed(node)){if(node.data.text!==undefined){node.data.text=event.value;path=['data','text'];value=node.data.text;}else{node.data.content=event.value;path=['data','content'];value=node.data.content;}}if(path)changed({id,path,value});\n});\ndocument.getElementById('add').addEventListener('click',()=>{if(!editable())return;draft.nodes=[...draft.nodes,{id:newId('portable-note'),type:'stickyNote',position:{x:80+draft.nodes.length*24,y:80+draft.nodes.length*24},width:260,height:180,data:{title:'Note',text:'New note'}}];changed();draw();});\ndocument.getElementById('applyRaw').addEventListener('click',()=>{\n if(!editable()||pending||queuedBase||rawPending||rawQueued)return;\n try{const parsed=JSON.parse(raw.value);validCanvas(parsed,StillMade.diffDocument(draftSource,parsed));const edits=StillMade.diffDocument(shared.state.rawDraft?.base||draft,parsed);draft=StillMade.applyDocument(draft,edits);changed();rawEditing=false;queueRaw(null);draw();}\n catch(error){notice=error.message+' The JSON draft is retained.';draw();}\n});\ndocument.getElementById('reset').addEventListener('click',()=>{\n if(busy||!canEdit||pending||rawPending||(!failed&&(queuedBase||rawQueued)))return;\n if(!resetBase){resetBase={canvasDraft:condition(shared.state,'canvasDraft'),rawDraft:condition(shared.state,'rawDraft')};notice='This resets the shared draft for everyone to the current project. Press Confirm reset for everyone to continue.';draw();return;}\n const before=resetBase;resetBase=null;busy=true;draw();\n StillMade.scheduleSharedEdit('canvasDraft',async()=>{\n  try{await StillMade.updateShared({canvasDraft:{edits:[]},rawDraft:null},before);failed=false;queuedBase=null;queuedEdits=null;rawQueued=null;rawEditing=false;}\n  catch(error){fail(error);throw error;}\n  finally{busy=false;if(!failed)receive(StillMade.getShared());else draw();}\n });\n StillMade.scheduleSharedEdit('canvasRaw',()=>{});\n});\ndocument.getElementById('discard').addEventListener('click',()=>{\n if(busy||!canEdit||pending||rawPending)return;\n StillMade.scheduleSharedEdit('canvasDraft',async()=>{\n  const latest=StillMade.getShared(),value=latest.state.canvasDraft||{edits:[]};\n  try{const restored=StillMade.applyDocument(source,value.edits);await StillMade.updateShared({canvasDraft:value,rawDraft:latest.state.rawDraft||null},{canvasDraft:condition(latest.state,'canvasDraft'),rawDraft:condition(latest.state,'rawDraft')});draft=restored;failed=false;queuedBase=null;queuedEdits=null;rawQueued=null;receive(StillMade.getShared());}\n  catch(error){fail(error);throw error;}\n });\n StillMade.scheduleSharedEdit('canvasRaw',()=>{});\n});\nsave.addEventListener('click',async()=>{\n if(!editable()||pending||queuedBase||rawEditing)return;const started=revision;busy=true;notice='Checking the complete Canvas…';draw();\n try{await StillMade.run({canvas:clone(source),updatedCanvas:clone(draft),commit:true});if(started===revision)notice='Canvas changes are ready below. Review the before and after, then choose Apply.';}\n catch(error){notice=error.message||'Canvas changes could not be prepared.';}finally{busy=false;draw();}\n});\ndocument.getElementById('connectContext').addEventListener('click',async()=>{\n if(!editable()||pending||rawPending||!contextProposal?.edges?.length)return;\n const started=revision;busy=true;notice='Checking the current connection sources…';draw();\n try{await StillMade.run({canvas:clone(source),connectionProposal:clone(contextProposal),applyContextConnections:true,commit:false,addGeneratedImage:false,addGeneratedVideo:false,addGeneratedAudio:false});if(started===revision)notice='Only the proposed connections are ready below. Review the before and after, then choose Apply. Other draft edits are retained.';}\n catch(error){notice=error.message||'Connections could not be prepared.';}finally{busy=false;draw();}\n});\nStillMade.onShared(receive);\nStillMade.onInput(values=>{\n source=values.canvas||source;contextProposal=values.connectionProposal&&Array.isArray(values.connectionProposal.edges)?clone(values.connectionProposal):null;inputReady=true;connectedImage=values.generatedImage?clone(values.generatedImage):null;connectedVideo=values.generatedVideo?clone(values.generatedVideo):null;connectedAudio=values.generatedAudio?clone(values.generatedAudio):null;\n const current=Array.isArray(values.assets)?values.assets:[],historic=(Array.isArray(values.versions)?values.versions:[]).map(item=>item&&item.asset).filter(Boolean);\n const seen=new Set();library=[...current,...historic].filter(item=>{if(!item||!['image','video','audio'].includes(item.kind))return false;const key=JSON.stringify([item.kind,item.assetId,item.versionId]);if(seen.has(key))return false;seen.add(key);return true;}).slice(0,120);\n titles={image:values.generatedImageTitle||'Generated image',video:values.generatedVideoTitle||'Generated video',audio:values.generatedAudioTitle||'Voiceover'};revision+=1;receive(StillMade.getShared());\n});\n"},"tests":[{"name":"Retain native media and protected Steps","input":{"canvas":{"schemaVersion":1,"viewport":{"x":0,"y":0,"zoom":1},"nodes":[{"id":"step","type":"programmableBlockNode","position":{"x":0,"y":0},"width":320,"data":{"title":"Image Panels"}},{"id":"media","type":"assetNode","position":{"x":400,"y":20},"data":{"url":"https://media.example/image.png","assetId":"asset-1","versionId":"version-2","custom":{"keep":true}}}],"edges":[{"id":"connection","source":"step","target":"media","sourceHandle":"out:image","targetHandle":"in:image"}]},"updatedCanvas":{"schemaVersion":1,"nodes":[],"edges":[]},"commit":false},"expected":{"canvas":{"schemaVersion":1,"viewport":{"x":0,"y":0,"zoom":1},"nodes":[{"id":"step","type":"programmableBlockNode","position":{"x":0,"y":0},"width":320,"data":{"title":"Image Panels"}},{"id":"media","type":"assetNode","position":{"x":400,"y":20},"data":{"url":"https://media.example/image.png","assetId":"asset-1","versionId":"version-2","custom":{"keep":true}}}],"edges":[{"id":"connection","source":"step","target":"media","sourceHandle":"out:image","targetHandle":"in:image"}]},"edit":{"schemaVersion":1,"title":"Update Canvas with 2 nodes","field":"canvas","before":{"schemaVersion":1,"viewport":{"x":0,"y":0,"zoom":1},"nodes":[{"id":"step","type":"programmableBlockNode","position":{"x":0,"y":0},"width":320,"data":{"title":"Image Panels"}},{"id":"media","type":"assetNode","position":{"x":400,"y":20},"data":{"url":"https://media.example/image.png","assetId":"asset-1","versionId":"version-2","custom":{"keep":true}}}],"edges":[{"id":"connection","source":"step","target":"media","sourceHandle":"out:image","targetHandle":"in:image"}]},"after":{"schemaVersion":1,"viewport":{"x":0,"y":0,"zoom":1},"nodes":[{"id":"step","type":"programmableBlockNode","position":{"x":0,"y":0},"width":320,"data":{"title":"Image Panels"}},{"id":"media","type":"assetNode","position":{"x":400,"y":20},"data":{"url":"https://media.example/image.png","assetId":"asset-1","versionId":"version-2","custom":{"keep":true}}}],"edges":[{"id":"connection","source":"step","target":"media","sourceHandle":"out:image","targetHandle":"in:image"}]}}}},{"name":"Prepare one exact Canvas update","input":{"canvas":{"schemaVersion":1,"viewport":{"x":0,"y":0,"zoom":1},"nodes":[{"id":"step","type":"programmableBlockNode","position":{"x":0,"y":0},"data":{"title":"Whiteboard"}},{"id":"note","type":"stickyNote","position":{"x":400,"y":20},"data":{"text":"Old"}}],"edges":[]},"updatedCanvas":{"schemaVersion":1,"viewport":{"x":0,"y":0,"zoom":1},"nodes":[{"id":"step","type":"programmableBlockNode","position":{"x":40,"y":30},"data":{"title":"Whiteboard Step"}},{"id":"note","type":"stickyNote","position":{"x":440,"y":60},"data":{"text":"New","media":{"url":"https://media.example/keep.png"}}}],"edges":[]},"commit":true},"expected":{"canvas":{"schemaVersion":1,"viewport":{"x":0,"y":0,"zoom":1},"nodes":[{"id":"step","type":"programmableBlockNode","position":{"x":40,"y":30},"data":{"title":"Whiteboard Step"}},{"id":"note","type":"stickyNote","position":{"x":440,"y":60},"data":{"text":"New","media":{"url":"https://media.example/keep.png"}}}],"edges":[]},"edit":{"schemaVersion":1,"title":"Update Canvas with 2 nodes","field":"canvas","before":{"schemaVersion":1,"viewport":{"x":0,"y":0,"zoom":1},"nodes":[{"id":"step","type":"programmableBlockNode","position":{"x":0,"y":0},"data":{"title":"Whiteboard"}},{"id":"note","type":"stickyNote","position":{"x":400,"y":20},"data":{"text":"Old"}}],"edges":[]},"after":{"schemaVersion":1,"viewport":{"x":0,"y":0,"zoom":1},"nodes":[{"id":"step","type":"programmableBlockNode","position":{"x":40,"y":30},"data":{"title":"Whiteboard Step"}},{"id":"note","type":"stickyNote","position":{"x":440,"y":60},"data":{"text":"New","media":{"url":"https://media.example/keep.png"}}}],"edges":[]}}}},{"name":"Attach exact generated media versions","input":{"canvas":{"schemaVersion":1,"nodes":[],"edges":[]},"updatedCanvas":{"schemaVersion":1,"nodes":[],"edges":[]},"commit":false,"generatedImage":{"kind":"image","assetId":"image-asset","versionId":"image-version","url":"https://media.example/frame.png"},"addGeneratedImage":true,"generatedImageTitle":"Opening frame","generatedVideo":{"kind":"video","assetId":"video-asset","versionId":"video-version","url":"https://media.example/clip.mp4","mimeType":"video/mp4","width":1280,"height":720,"duration":6,"bytes":120000},"addGeneratedVideo":true,"generatedVideoTitle":"Opening motion","generatedAudio":{"kind":"audio","assetId":"audio-asset","versionId":"audio-version","url":"https://media.example/voice.wav","mimeType":"audio/wav","duration":12,"bytes":576044,"sampleRate":24000,"channels":1},"addGeneratedAudio":true,"generatedAudioTitle":"Main narration"},"expected":{"canvas":{"schemaVersion":1,"nodes":[{"id":"connected-image-image-asset-image-version","type":"imageGenNode","position":{"x":80,"y":80},"width":320,"height":320,"data":{"title":"Opening frame","assetId":"image-asset","versionId":"image-version","url":"https://media.example/frame.png","versions":[{"id":"image-version","url":"https://media.example/frame.png","assetId":"image-asset"}],"activeVersion":0,"selectedVersion":0,"status":"complete","source":"connected-block"}},{"id":"connected-video-video-asset-video-version","type":"videoGenNode","position":{"x":104,"y":104},"width":360,"height":260,"data":{"title":"Opening motion","assetId":"video-asset","versionId":"video-version","url":"https://media.example/clip.mp4","duration":6,"versions":[{"id":"video-version","url":"https://media.example/clip.mp4","assetId":"video-asset","duration":6}],"currentVersion":0,"approvedVersion":0,"status":"complete","source":"connected-block"}},{"id":"connected-audio-audio-asset-audio-version","type":"voiceoverGenNode","position":{"x":128,"y":128},"width":360,"height":260,"data":{"title":"Main narration","assetId":"audio-asset","versionId":"audio-version","url":"https://media.example/voice.wav","duration":12,"versions":[{"id":"audio-version","url":"https://media.example/voice.wav","assetId":"audio-asset","duration":12}],"currentVersion":0,"status":"complete","source":"connected-block"}}],"edges":[]},"edit":{"schemaVersion":1,"title":"Update Canvas with 3 nodes","field":"canvas","before":{"schemaVersion":1,"nodes":[],"edges":[]},"after":{"schemaVersion":1,"nodes":[{"id":"connected-image-image-asset-image-version","type":"imageGenNode","position":{"x":80,"y":80},"width":320,"height":320,"data":{"title":"Opening frame","assetId":"image-asset","versionId":"image-version","url":"https://media.example/frame.png","versions":[{"id":"image-version","url":"https://media.example/frame.png","assetId":"image-asset"}],"activeVersion":0,"selectedVersion":0,"status":"complete","source":"connected-block"}},{"id":"connected-video-video-asset-video-version","type":"videoGenNode","position":{"x":104,"y":104},"width":360,"height":260,"data":{"title":"Opening motion","assetId":"video-asset","versionId":"video-version","url":"https://media.example/clip.mp4","duration":6,"versions":[{"id":"video-version","url":"https://media.example/clip.mp4","assetId":"video-asset","duration":6}],"currentVersion":0,"approvedVersion":0,"status":"complete","source":"connected-block"}},{"id":"connected-audio-audio-asset-audio-version","type":"voiceoverGenNode","position":{"x":128,"y":128},"width":360,"height":260,"data":{"title":"Main narration","assetId":"audio-asset","versionId":"audio-version","url":"https://media.example/voice.wav","duration":12,"versions":[{"id":"audio-version","url":"https://media.example/voice.wav","assetId":"audio-asset","duration":12}],"currentVersion":0,"status":"complete","source":"connected-block"}}],"edges":[]}}}},{"name":"Prepare only exact-source context connections","input":{"canvas":{"schemaVersion":1,"nodes":[{"id":"prompt","type":"textPromptNode","position":{"x":0,"y":0},"data":{"text":"Exact prompt"}},{"id":"image","type":"imageGenNode","position":{"x":320,"y":0},"data":{"readablePrompt":"Alice waves","custom":"keep"}}],"edges":[]},"connectionProposal":{"content":{"nodes":[{"id":"prompt","type":"textPromptNode","data":{"text":"Exact prompt"}},{"id":"image","type":"imageGenNode","data":{"readablePrompt":"Alice waves"}}],"edges":[]},"edges":[{"id":"e-cc-prompt-image","source":"prompt","target":"image","targetHandle":"prompt-in"}]},"applyContextConnections":true},"expected":{"canvas":{"schemaVersion":1,"nodes":[{"id":"prompt","type":"textPromptNode","position":{"x":0,"y":0},"data":{"text":"Exact prompt"}},{"id":"image","type":"imageGenNode","position":{"x":320,"y":0},"data":{"readablePrompt":"Alice waves","custom":"keep"}}],"edges":[{"id":"e-cc-prompt-image","source":"prompt","target":"image","targetHandle":"prompt-in"}]},"edit":{"schemaVersion":1,"title":"Update Canvas with 2 nodes","field":"canvas","before":{"schemaVersion":1,"nodes":[{"id":"prompt","type":"textPromptNode","position":{"x":0,"y":0},"data":{"text":"Exact prompt"}},{"id":"image","type":"imageGenNode","position":{"x":320,"y":0},"data":{"readablePrompt":"Alice waves","custom":"keep"}}],"edges":[]},"after":{"schemaVersion":1,"nodes":[{"id":"prompt","type":"textPromptNode","position":{"x":0,"y":0},"data":{"text":"Exact prompt"}},{"id":"image","type":"imageGenNode","position":{"x":320,"y":0},"data":{"readablePrompt":"Alice waves","custom":"keep"}}],"edges":[{"id":"e-cc-prompt-image","source":"prompt","target":"image","targetHandle":"prompt-in"}]}}}}],"portable":{"format":"stillmade-block/1","dependencies":[],"sources":[{"kind":"original","license":"MIT","paths":["LICENSE","src/run.js","src/view.json"],"evidence":["upstream/0/LICENSE","upstream/0/src/run.js","upstream/0/src/view.json"],"changes":"Original implementation declared by the packager; no reused repository or dependency code."}],"files":{"upstream/0/LICENSE":"MIT License\n\nCopyright (c) 2026 StillMade contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","upstream/0/src/run.js":"const copy=value=>JSON.parse(JSON.stringify(value));\nconst source=copy(input.canvas||{schemaVersion:1,nodes:[],edges:[]});\nconst candidate=input.commit?copy(input.updatedCanvas):copy(source);\nif(!candidate||Array.isArray(candidate)||candidate.schemaVersion!==1||!Array.isArray(candidate.nodes)||!Array.isArray(candidate.edges))throw new Error('Keep the complete version 1 Canvas document with nodes and edges.');\nif(input.applyContextConnections){\n if(input.commit||input.addGeneratedImage||input.addGeneratedVideo||input.addGeneratedAudio)throw new Error('Review context connections separately from other Canvas changes.');\n const proposal=input.connectionProposal;\n if(!proposal||Array.isArray(proposal)||Object.keys(proposal).some(key=>!['content','edges'].includes(key))||!proposal.content||!Array.isArray(proposal.edges)||proposal.edges.length>1000)throw new Error('Supply the exact context source and a bounded proposed connection list.');\n const types=['characterReferenceNode','locationReferenceNode','textPromptNode','imageGenNode','videoGenNode','styleNode'],fields=['characterRefs','locationRefs','charactersPresent','location','contextSourceNodeId','readablePrompt','connectedPrompt','shotDescription','name','title','description','text'];\n const nodes=source.nodes.filter(node=>types.includes(node.type)&&!node.data?.hostBlock).map(node=>({id:node.id,type:node.type,data:Object.fromEntries(fields.filter(key=>node.data?.[key]!==undefined).map(key=>[key,node.data[key]]))}));\n const ids=new Set(nodes.map(node=>node.id)),snapshot={nodes,edges:source.edges.filter(edge=>ids.has(edge.source)&&ids.has(edge.target)).map(({source,target})=>({source,target}))};\n const canonical=value=>value===null||typeof value!=='object'?JSON.stringify(value):Array.isArray(value)?'['+value.map(canonical).join(',')+']':'{'+Object.keys(value).sort().map(key=>JSON.stringify(key)+':'+canonical(value[key])).join(',')+'}';\n if(nodes.length>200||snapshot.edges.length>1000||canonical(snapshot)!==canonical(proposal.content))throw new Error('Canvas connection sources changed. Request and review fresh connections.');\n const byId=new Map(source.nodes.map(node=>[node.id,node])),present=new Set(source.edges.map(edge=>JSON.stringify([edge.source,edge.target]))),edgeIds=new Set(source.edges.map(edge=>edge.id));\n for(const edge of proposal.edges){\n  if(!edge||Array.isArray(edge)||typeof edge!=='object'||Object.keys(edge).some(key=>!['id','source','target','targetHandle'].includes(key)))throw new Error('Unsupported connection settings.');\n  const from=byId.get(edge.source),to=byId.get(edge.target),key=JSON.stringify([edge.source,edge.target]);\n  if(!from||!to||from.id===to.id||!ids.has(from.id)||!ids.has(to.id)||!['imageGenNode','videoGenNode'].includes(to.type)||present.has(key)||edge.id!=='e-cc-'+from.id+'-'+to.id||edgeIds.has(edge.id))throw new Error('Unavailable or duplicate context connection.');\n  const allowed=['characterReferenceNode','locationReferenceNode','styleNode','textPromptNode'].includes(from.type)||to.type==='videoGenNode'&&(from.type==='imageGenNode'||to.data.contextSourceNodeId===from.id);\n  if(!allowed||(from.type==='textPromptNode'?edge.targetHandle!=='prompt-in':edge.targetHandle!==undefined))throw new Error('Context connection source and target types do not match.');\n  present.add(key);edgeIds.add(edge.id);candidate.edges.push(copy(edge));\n }\n}\nconst attach=(value,kind,title)=>{\n if(!value||value.kind!==kind)throw new Error('Connect an approved generated '+kind+' before adding it to Canvas.');\n const ref=copy(value),base='connected-'+kind+'-'+ref.assetId+'-'+ref.versionId;\n let id=base,suffix=2;while(candidate.nodes.some(node=>node.id===id)){id=base+'-'+suffix;suffix+=1}\n const position={x:80+candidate.nodes.length*24,y:80+candidate.nodes.length*24};\n if(kind==='image')candidate.nodes.push({id,type:'imageGenNode',position,width:320,height:320,data:{title:title||'Generated image',assetId:ref.assetId,versionId:ref.versionId,url:ref.url,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId}],activeVersion:0,selectedVersion:0,status:'complete',source:'connected-block'}});\n if(kind==='video')candidate.nodes.push({id,type:'videoGenNode',position,width:360,height:260,data:{title:title||'Generated video',assetId:ref.assetId,versionId:ref.versionId,url:ref.url,duration:ref.duration,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId,duration:ref.duration}],currentVersion:0,approvedVersion:0,status:'complete',source:'connected-block'}});\n if(kind==='audio')candidate.nodes.push({id,type:'voiceoverGenNode',position,width:360,height:260,data:{title:title||'Voiceover',assetId:ref.assetId,versionId:ref.versionId,url:ref.url,duration:ref.duration,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId,duration:ref.duration}],currentVersion:0,status:'complete',source:'connected-block'}});\n};\nif(input.addGeneratedImage)attach(input.generatedImage,'image',input.generatedImageTitle);\nif(input.addGeneratedVideo)attach(input.generatedVideo,'video',input.generatedVideoTitle);\nif(input.addGeneratedAudio)attach(input.generatedAudio,'audio',input.generatedAudioTitle);\nif(candidate.nodes.length>5000||candidate.edges.length>10000)throw new Error('Canvas supports up to 5,000 nodes and 10,000 connections.');\nif(!candidate.nodes.every(node=>node&&typeof node==='object'&&!Array.isArray(node)&&typeof node.id==='string'&&node.id&&typeof node.type==='string'&&node.type&&node.position&&Number.isFinite(node.position.x)&&Number.isFinite(node.position.y)))throw new Error('Every Canvas node needs an identity, type, and finite position.');\nif(new Set(candidate.nodes.map(node=>node.id)).size!==candidate.nodes.length)throw new Error('Canvas node identities must be distinct.');\nconst ids=new Set(candidate.nodes.map(node=>node.id));\nif(!candidate.edges.every(edge=>edge&&typeof edge==='object'&&!Array.isArray(edge)&&typeof edge.id==='string'&&edge.id&&ids.has(edge.source)&&ids.has(edge.target)))throw new Error('Every Canvas connection needs an identity and existing source and target nodes.');\nif(new Set(candidate.edges.map(edge=>edge.id)).size!==candidate.edges.length)throw new Error('Canvas connection identities must be distinct.');\nreturn{canvas:candidate,edit:{schemaVersion:1,title:`Update Canvas with ${candidate.nodes.length} node${candidate.nodes.length===1?'':'s'}`,field:'canvas',before:source,after:candidate}};\n","upstream/0/src/view.json":"{\n  \"html\": \"<main class=\\\"canvas-workspace\\\">\\n  <header><div><small>Production workspace</small><h1>Canvas</h1><p id=\\\"summary\\\">Loading the complete Canvas…</p></div><div class=\\\"actions\\\"><button id=\\\"discard\\\" type=\\\"button\\\" class=\\\"secondary\\\">Discard local draft and load shared</button><button id=\\\"reset\\\" type=\\\"button\\\" class=\\\"secondary\\\">Reset changes</button><button id=\\\"add\\\" type=\\\"button\\\" class=\\\"secondary\\\">Add note</button><button id=\\\"save\\\" data-stillmade-action=\\\"run\\\" type=\\\"button\\\">Review changes</button></div></header>\\n  <output id=\\\"status\\\" role=\\\"status\\\" aria-live=\\\"polite\\\"></output>\\n  <section><div class=\\\"section-title\\\"><h2>Connected media</h2></div><div class=\\\"handoffs\\\"><div id=\\\"handoff-image\\\"></div><div id=\\\"handoff-video\\\"></div><div id=\\\"handoff-audio\\\"></div></div></section>\\n  <section><div class=\\\"section-title\\\"><h2>Saved media and history</h2><p class=\\\"hint\\\">Choose any exact saved version and add it to Canvas.</p></div><div id=\\\"library\\\" class=\\\"cards\\\"></div></section>\\n  <section><h2>Canvas items</h2><p class=\\\"hint\\\">Project Type Steps stay connected and carry their exact installed source. Edit their placement or label here; use the Project Type builder to add, remove, replace, or reconnect Steps.</p><div id=\\\"nodes\\\" class=\\\"cards\\\"></div></section>\\n  <section><h2>Context connections</h2><p class=\\\"hint\\\">Request connections in project Chat, review the exact sources there, then supply the proposal to this Canvas Block. Preparing connections does not run generators or save the project.</p><div id=\\\"contextConnections\\\" class=\\\"connections\\\"></div><button id=\\\"connectContext\\\" data-stillmade-action=\\\"run\\\" type=\\\"button\\\" class=\\\"secondary\\\" disabled>Review proposed connections</button></section>\\n  <section><h2>Connections</h2><div id=\\\"edges\\\" class=\\\"connections\\\"></div></section>\\n  <details id=\\\"rawDetails\\\"><summary>Complete Canvas data</summary><p>Native content, media references, dimensions, and custom fields are retained. Executable source and host bindings are protected when StillMade applies the proposal.</p><label for=\\\"raw\\\" class=\\\"raw-label\\\">Canvas JSON</label><textarea id=\\\"raw\\\" class=\\\"raw\\\" spellcheck=\\\"false\\\"></textarea><button id=\\\"applyRaw\\\" type=\\\"button\\\" class=\\\"secondary\\\">Apply JSON to workspace</button></details>\\n</main>\\n\",\n  \"css\": \".canvas-workspace{width:100%;min-width:0;margin:0;padding:8px;color:var(--fg)}header,.actions,.card-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}header{justify-content:space-between;margin-bottom:14px}header h1{margin:2px 0}header p,header small,details p,output,.hint{color:var(--fg-muted)}section,details{margin:12px 0;padding:16px;border:1px solid var(--border-tok);border-radius:var(--r-3);background:var(--bg-elev-1)}section h2{margin:0 0 8px}.cards,.connections{display:grid;gap:10px}.handoffs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.handoff,.card,.connection{padding:12px;border:1px solid var(--border-tok);border-radius:var(--r-2);background:var(--bg)}.handoff p{min-height:38px}.card-head{justify-content:space-between}.card h3{margin:0;font-size:16px}.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}label{display:grid;gap:5px;margin:7px 0;font-size:13px;font-weight:600}input,textarea{width:100%;background:var(--bg)}textarea{min-height:76px;resize:vertical}.wide{grid-column:1/-1}.secondary{background:transparent;color:var(--fg)}.danger{background:transparent;color:var(--signal)}.raw{min-height:420px;font-family:var(--font-mono);font-size:12px}output{display:block;min-height:24px;margin:8px 2px}.badge{font-size:12px;color:var(--fg-muted)}\\n@media(max-width:760px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:520px){.canvas-workspace{padding:0}.grid,.handoffs{grid-template-columns:1fr}.actions{width:100%}.actions button{flex:1}section,details{padding:12px}}\\n\\n#library .handoff{min-width:0}#library .handoff button{width:100%}\\n\",\n  \"javascript\": \"const status=document.getElementById('status'),summary=document.getElementById('summary'),raw=document.getElementById('raw'),save=document.getElementById('save');\\nlet source={schemaVersion:1,nodes:[],edges:[]},draft={schemaVersion:1,nodes:[],edges:[]},draftSource=source,overlay={edits:[]},shared={state:{},outputs:{},canEdit:false};\\nlet canEdit=false,busy=false,failed=false,pending=0,queuedBase=null,queuedEdits=null,revision=0,inputReady=false,notice='',rawEditing=false,rawPending=0,rawQueued=null,rawBefore=null,rawFlight=null;\\nlet contextProposal=null;\\nlet connectedImage=null,connectedVideo=null,connectedAudio=null,titles={image:'Generated image',video:'Generated video',audio:'Voiceover'},library=[];\\nconst pages=[],identities=[];let resetBase=null;\\nlet rawRenderedDraft=null,rawRenderedRevision=-1;\\nconst clone=value=>JSON.parse(JSON.stringify(value));\\nconst text=value=>value===undefined||value===null?'':String(value);\\nconst managed=node=>node.type==='programmableBlockNode'||!!node.data?.hostBlock;\\nconst editable=()=>inputReady&&canEdit&&!busy&&!failed;\\nconst keyFor=id=>{let key=identities.indexOf(id);if(key<0){identities.push(id);key=identities.length-1;}return key;};\\nconst controlId=(id,purpose)=>{const value=JSON.stringify([id,purpose]),a=value.split('').reduce((hash,char)=>Math.imul(hash^char.charCodeAt(0),16777619),2166136261)>>>0,b=value.split('').reduce((hash,char)=>Math.imul(hash^char.charCodeAt(0),2246822519),3266489917)>>>0;return 'canvas-'+a.toString(36)+'-'+b.toString(36);};\\nconst paged=(group,items,render,size)=>{\\n let slot=pages.find(item=>item.group===group);if(!slot){slot={group,page:0};pages.push(slot);}\\n slot.page=Math.min(slot.page,Math.max(0,Math.ceil(items.length/size)-1));const key=pages.indexOf(slot);\\n const tree=items.slice(slot.page*size,slot.page*size+size).map((item,index)=>render(item,slot.page*size+index));\\n if(items.length>size)tree.push({tag:'div',className:'actions',children:[{tag:'button',action:'page-back:'+String(key),text:'Previous',disabled:slot.page===0},{tag:'span',text:'Page '+String(slot.page+1)+' of '+String(Math.ceil(items.length/size))},{tag:'button',action:'page-next:'+String(key),text:'Next',disabled:(slot.page+1)*size>=items.length}]});\\n return tree;\\n};\\nconst field=(node,label,kind,value,type='text',wide=false)=>({tag:'label',className:wide?'wide':'',children:[{tag:'span',text:label},{tag:'input',id:controlId(node.id,kind),type,action:kind+':'+String(keyFor(node.id)),value:Number.isFinite(value)?value:text(value),disabled:!editable(),label}]});\\nconst nodeCard=node=>({tag:'article',id:controlId(node.id,'card'),className:'card',children:[{tag:'div',className:'card-head',children:[{tag:'h3',text:(text(node.data?.title)||text(node.data?.label)||text(node.data?.name)||text(node.id)).slice(0,240)},{tag:'span',className:'badge',text:managed(node)?'Project Type Step':text(node.type)},{tag:'button',action:'remove:'+String(keyFor(node.id)),text:'Remove',className:'danger',disabled:!editable()||managed(node)}]},{tag:'div',className:'grid',children:[field(node,'Title','title',node.data?.title),field(node,'Label','label',node.data?.label),field(node,'X','x',node.position?.x,'number'),field(node,'Y','y',node.position?.y,'number'),field(node,'Width','width',node.width,'number'),field(node,'Height','height',node.height,'number'),managed(node)?{tag:'p',className:'wide hint',text:'Its executable source and connections are preserved.'}:field(node,'Content','content',node.data?.text===undefined?node.data?.content:node.data.text,'text',true)]}]});\\nconst edgeCard=edge=>({tag:'article',id:controlId(edge.id,'edge'),className:'connection',children:[{tag:'strong',text:text(edge.source)+' → '+text(edge.target)},{tag:'p',className:'hint',text:text(edge.sourceHandle)+' '+text(edge.targetHandle)}]});\\nconst libraryCard=(value,index)=>({tag:'article',className:'handoff',children:[{tag:'strong',text:(text(value.label)||text(value.assetId)).slice(0,240)},{tag:'p',className:'hint',text:text(value.kind)+' · version '+text(value.versionId)},{tag:'button',action:'library-'+text(value.kind)+':'+String(index),text:'Add to Canvas',className:'secondary',disabled:!editable()}]});\\nconst handoff=(kind,value,label)=>value?[{tag:'article',className:'handoff',children:[{tag:'strong',text:label},{tag:'p',className:'hint',text:text(value.assetId)+' · version '+text(value.versionId)},{tag:'button',action:'attach-'+kind,text:'Add '+kind+' to Canvas',className:'secondary',disabled:!editable()}]}]:[{tag:'article',className:'handoff',children:[{tag:'strong',text:label},{tag:'p',className:'hint',text:'Connect a stored '+kind+' output to add it with exact asset and version identity.'}]}];\\nconst refreshRaw=()=>{\\n if(!rawEditing&&document.getElementById('rawDetails').hasAttribute('open')&&(rawRenderedDraft!==draft||rawRenderedRevision!==revision)){\\n  raw.value=JSON.stringify(draft);rawRenderedDraft=draft;rawRenderedRevision=revision;\\n }\\n};\\nconst draw=()=>{\\n StillMade.render('handoff-image',handoff('image',connectedImage,'Image'));StillMade.render('handoff-video',handoff('video',connectedVideo,'Video'));StillMade.render('handoff-audio',handoff('audio',connectedAudio,'Narration'));\\n StillMade.render('library',library.length?paged('library',library,libraryCard,24):[{tag:'p',text:'No saved media versions yet.'}]);\\n StillMade.render('nodes',draft.nodes.length?paged('nodes',draft.nodes,nodeCard,8):[{tag:'p',text:'No Canvas items yet.'}]);StillMade.render('edges',draft.edges.length?paged('edges',draft.edges,edgeCard,24):[{tag:'p',text:'No connections.'}]);\\n StillMade.render('contextConnections',contextProposal?.edges?.length?paged('contextConnections',contextProposal.edges,edgeCard,24):[{tag:'p',text:'No context connection proposal supplied.'}]);document.getElementById('connectContext').disabled=!editable()||pending>0||rawPending>0||!contextProposal?.edges?.length;\\n refreshRaw();\\n summary.textContent=String(draft.nodes.length)+' items · '+String(draft.edges.length)+' connections';\\n save.disabled=!editable()||pending>0||!!queuedBase||rawEditing;document.getElementById('reset').disabled=busy||!canEdit||pending>0||rawPending>0||(!failed&&(!!queuedBase||!!rawQueued));document.getElementById('reset').textContent=resetBase?'Confirm reset for everyone':'Reset shared draft to project';document.getElementById('add').disabled=!editable();\\n document.getElementById('applyRaw').disabled=!editable()||pending>0||!!queuedBase||rawPending>0||!!rawQueued;raw.disabled=busy||!canEdit;\\n document.getElementById('discard').disabled=busy||!canEdit||pending>0||rawPending>0;status.textContent=notice;\\n};\\nconst validCanvas=(value,edits)=>{\\n if(!value||value.schemaVersion!==1||!Array.isArray(value.nodes)||!Array.isArray(value.edges)||value.nodes.length>5000||value.edges.length>10000)throw Error('Use a version 1 Canvas with at most 5,000 nodes and 10,000 connections.');\\n const lookup=(items,id)=>items.find(item=>item.id===id);\\n edits.forEach(edit=>{\\n  const kind=edit.path[0],id=edit.path[1]?.id;\\n  if(kind==='nodes'&&id){\\n   const old=lookup(draftSource.nodes,id),node=lookup(value.nodes,id);\\n   if(old&&managed(old)&&(!node||node.type!==old.type))throw Error('Change executable Steps through the Project Type builder.');\\n   if(node){\\n    if(typeof node.type!=='string'||!node.type||!node.position||!Number.isFinite(node.position.x)||!Number.isFinite(node.position.y))throw Error('Every Canvas node needs a type and finite position.');\\n    if(node.type==='programmableBlockNode'&&(!old||old.type!==node.type||Object.keys(node.data||{}).some(key=>!['title','label'].includes(key))||Object.keys(node).some(key=>!['id','type','position','width','height','data'].includes(key))))throw Error('Executable source cannot be changed through this Canvas draft.');\\n    if(JSON.stringify(node.data?.hostBlock||null)!==JSON.stringify(old?.data?.hostBlock||null))throw Error('Host Block bindings are protected.');\\n   }\\n  }\\n  if(kind==='edges'&&id){\\n   const old=lookup(draftSource.edges,id),edge=lookup(value.edges,id);\\n   const touches=edge=>edge&&[edge.source,edge.target].some(key=>{const item=lookup(draftSource.nodes,key);return item&&managed(item);});\\n   if(touches(old)||touches(edge))throw Error('Change Step connections through the Project Type builder.');\\n   if(edge&&(!lookup(value.nodes,edge.source)||!lookup(value.nodes,edge.target)))throw Error('Every connection needs existing nodes.');\\n  }\\n  if((kind==='nodes'||kind==='edges')&&edit.path.length===1&&!edit.order)throw Error('Keep distinct item and connection IDs.');\\n });\\n if(Object.keys(value).some(key=>!['schemaVersion','nodes','edges','viewport'].includes(key))||JSON.stringify(value.viewport||null)!==JSON.stringify(draftSource.viewport||null))throw Error('Canvas navigation stays local; preserve the supplied viewport and document fields.');\\n};\\nconst condition=(state,key)=>Object.hasOwn(state,key)?{exists:true,value:clone(key==='canvasDraft'?state.canvasDraft:state.rawDraft)}:{exists:false};\\nconst sourceField=(id,path)=>{\\n const node=draftSource.nodes.find(item=>item.id===id),key=path.join('.');if(!node)return {exists:false};\\n if(key==='data.title')return Object.hasOwn(node.data||{},'title')?{exists:true,value:clone(node.data.title)}:{exists:false};\\n if(key==='data.label')return Object.hasOwn(node.data||{},'label')?{exists:true,value:clone(node.data.label)}:{exists:false};\\n if(key==='data.text')return Object.hasOwn(node.data||{},'text')?{exists:true,value:clone(node.data.text)}:{exists:false};\\n if(key==='data.content')return Object.hasOwn(node.data||{},'content')?{exists:true,value:clone(node.data.content)}:{exists:false};\\n if(key==='position.x')return Object.hasOwn(node.position||{},'x')?{exists:true,value:clone(node.position.x)}:{exists:false};\\n if(key==='position.y')return Object.hasOwn(node.position||{},'y')?{exists:true,value:clone(node.position.y)}:{exists:false};\\n if(key==='width')return Object.hasOwn(node,'width')?{exists:true,value:clone(node.width)}:{exists:false};\\n if(key==='height')return Object.hasOwn(node,'height')?{exists:true,value:clone(node.height)}:{exists:false};return {exists:false};\\n};\\nconst queueFieldEdit=(id,path,value)=>{\\n if(!queuedEdits)return;const full=['nodes',{id},...path],key=JSON.stringify(full),before=sourceField(id,path),after={exists:true,value:clone(value)};\\n queuedEdits=queuedEdits.filter(edit=>JSON.stringify(edit.path)!==key);\\n if(JSON.stringify(before)!==JSON.stringify(after))queuedEdits.push({path:full,before,after});\\n};\\ndocument.getElementById('rawDetails').addEventListener('toggle',refreshRaw);\\nconst fail=error=>{failed=true;notice=error.message+' Your local draft is retained. Copy it before discarding and loading the shared draft.';draw();};\\nconst receive=value=>{\\n shared=value;canEdit=!!value.canEdit;\\n if(inputReady&&!pending&&!queuedBase&&!failed){\\n  try{const next=value.state.canvasDraft||{edits:[]};draft=StillMade.applyDocument(source,next.edits);draftSource=source;overlay=clone(next);}\\n  catch(error){fail(error);return;}\\n }\\n if(!rawPending&&!rawQueued&&!failed){rawFlight=null;rawEditing=!!value.state.rawDraft;if(rawEditing)raw.value=value.state.rawDraft.text;}\\n if(!busy&&!pending&&!queuedBase&&!failed)notice=value.outputs.edit?'A shared Canvas proposal is ready below. Review before applying.':'Working edits are shared. Review and Apply still govern project changes.';\\n draw();\\n};\\nconst publish=async()=>{\\n if(failed)throw Error(notice);if(!queuedBase)return;const before=queuedBase;queuedBase=null;pending+=1;\\n try{\\n  const edits=(queuedEdits||StillMade.diffDocument(draftSource,draft)).map(edit=>({...edit,id:JSON.stringify(edit.path)}));queuedEdits=null;validCanvas(draft,edits);const next={edits};overlay=clone(next);\\n  if(!Object.hasOwn(StillMade.getShared().state,'canvasDraft')){\\n   try{await StillMade.updateShared({canvasDraft:{edits:[]}},{canvasDraft:{exists:false}});}\\n   catch(error){if(!StillMade.getShared().canEdit||!Object.hasOwn(StillMade.getShared().state,'canvasDraft'))throw error;}\\n  }\\n  await StillMade.updateShared({canvasDraft:next},{canvasDraft:{exists:true,value:before}},{merge:true});\\n }catch(error){fail(error);throw error;}\\n finally{pending-=1;if(!pending&&!queuedBase&&!failed)receive(StillMade.getShared());else draw();}\\n};\\nconst changed=edit=>{if(!queuedBase){queuedBase=clone(overlay);queuedEdits=clone(overlay.edits||[]);}if(edit)queueFieldEdit(edit.id,edit.path,edit.value);else queuedEdits=null;revision+=1;notice='Sharing Canvas edits. Review before applying to the project.';save.disabled=true;StillMade.scheduleSharedEdit('canvasDraft',publish);};\\nconst publishRaw=async()=>{\\n if(failed)throw Error(notice);if(!rawQueued)return;const value=rawQueued.value,before=rawBefore;rawQueued=null;rawPending+=1;rawFlight={exists:true,value:clone(value)};draw();\\n try{await StillMade.updateShared({rawDraft:value},{rawDraft:before});}catch(error){fail(error);throw error;}\\n finally{rawPending-=1;if(!rawQueued&&!failed)receive(StillMade.getShared());else draw();}\\n};\\nconst queueRaw=value=>{if(!rawQueued)rawBefore=rawPending?rawFlight:condition(StillMade.getShared().state,'rawDraft');rawQueued={value};StillMade.scheduleSharedEdit('canvasRaw',publishRaw);};\\nraw.addEventListener('input',()=>{if(!editable())return;const base=rawEditing?(rawQueued?.value?.base||rawFlight?.value?.base||shared.state.rawDraft?.base||clone(draft)):clone(draft);rawEditing=true;queueRaw({text:raw.value,base});save.disabled=true;document.getElementById('applyRaw').disabled=true;});\\nconst newId=prefix=>prefix+'-'+String(Date.now())+'-'+Math.random().toString(36).slice(2);\\nconst attach=(kind,ref,title)=>{\\n const id=newId('connected-'+kind),position={x:80+draft.nodes.length*24,y:80+draft.nodes.length*24};let node;\\n if(kind==='image')node={id,type:'imageGenNode',position,width:320,height:320,data:{title,assetId:ref.assetId,versionId:ref.versionId,url:ref.url,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId}],activeVersion:0,selectedVersion:0,status:'complete',source:'connected-block'}};\\n if(kind==='video')node={id,type:'videoGenNode',position,width:360,height:260,data:{title,assetId:ref.assetId,versionId:ref.versionId,url:ref.url,duration:ref.duration,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId,duration:ref.duration}],currentVersion:0,approvedVersion:0,status:'complete',source:'connected-block'}};\\n if(kind==='audio')node={id,type:'voiceoverGenNode',position,width:360,height:260,data:{title,assetId:ref.assetId,versionId:ref.versionId,url:ref.url,duration:ref.duration,versions:[{id:ref.versionId,url:ref.url,assetId:ref.assetId,duration:ref.duration}],currentVersion:0,status:'complete',source:'connected-block'}};\\n if(!node)return;draft.nodes=[...draft.nodes,clone(node)];changed();draw();\\n};\\nStillMade.onAction(event=>{\\n const parts=event.action.split(':'),kind=parts[0],index=Number(parts[1]);if(kind==='page-back'||kind==='page-next'){const slot=pages.at(index);if(slot){slot.page=Math.max(0,slot.page+(kind==='page-next'?1:-1));draw();}return;}\\n if(!editable())return;\\n if(kind==='attach-image'&&connectedImage){attach('image',connectedImage,titles.image);return;}if(kind==='attach-video'&&connectedVideo){attach('video',connectedVideo,titles.video);return;}if(kind==='attach-audio'&&connectedAudio){attach('audio',connectedAudio,titles.audio);return;}\\n if(kind.startsWith('library-')){const value=library.at(index),mediaKind=kind.slice(8);if(value&&value.kind===mediaKind)attach(mediaKind,value,text(value.label)||text(value.assetId));return;}\\n const id=identities.at(index),node=draft.nodes.find(item=>item.id===id);if(!node)return;\\n if(kind==='remove'&&!managed(node)){draft.nodes=draft.nodes.filter(item=>item.id!==id);draft.edges=draft.edges.filter(edge=>edge.source!==id&&edge.target!==id);changed();draw();return;}\\n if(!node.data||Array.isArray(node.data))node.data={};\\n let path=null,value=null;if(kind==='title'){node.data.title=event.value;path=['data','title'];value=node.data.title;}if(kind==='label'){node.data.label=event.value;path=['data','label'];value=node.data.label;}if(kind==='x'){node.position.x=Number(event.value)||0;path=['position','x'];value=node.position.x;}if(kind==='y'){node.position.y=Number(event.value)||0;path=['position','y'];value=node.position.y;}if(kind==='width'){node.width=Math.max(1,Number(event.value)||1);path=['width'];value=node.width;}if(kind==='height'){node.height=Math.max(1,Number(event.value)||1);path=['height'];value=node.height;}\\n if(kind==='content'&&!managed(node)){if(node.data.text!==undefined){node.data.text=event.value;path=['data','text'];value=node.data.text;}else{node.data.content=event.value;path=['data','content'];value=node.data.content;}}if(path)changed({id,path,value});\\n});\\ndocument.getElementById('add').addEventListener('click',()=>{if(!editable())return;draft.nodes=[...draft.nodes,{id:newId('portable-note'),type:'stickyNote',position:{x:80+draft.nodes.length*24,y:80+draft.nodes.length*24},width:260,height:180,data:{title:'Note',text:'New note'}}];changed();draw();});\\ndocument.getElementById('applyRaw').addEventListener('click',()=>{\\n if(!editable()||pending||queuedBase||rawPending||rawQueued)return;\\n try{const parsed=JSON.parse(raw.value);validCanvas(parsed,StillMade.diffDocument(draftSource,parsed));const edits=StillMade.diffDocument(shared.state.rawDraft?.base||draft,parsed);draft=StillMade.applyDocument(draft,edits);changed();rawEditing=false;queueRaw(null);draw();}\\n catch(error){notice=error.message+' The JSON draft is retained.';draw();}\\n});\\ndocument.getElementById('reset').addEventListener('click',()=>{\\n if(busy||!canEdit||pending||rawPending||(!failed&&(queuedBase||rawQueued)))return;\\n if(!resetBase){resetBase={canvasDraft:condition(shared.state,'canvasDraft'),rawDraft:condition(shared.state,'rawDraft')};notice='This resets the shared draft for everyone to the current project. Press Confirm reset for everyone to continue.';draw();return;}\\n const before=resetBase;resetBase=null;busy=true;draw();\\n StillMade.scheduleSharedEdit('canvasDraft',async()=>{\\n  try{await StillMade.updateShared({canvasDraft:{edits:[]},rawDraft:null},before);failed=false;queuedBase=null;queuedEdits=null;rawQueued=null;rawEditing=false;}\\n  catch(error){fail(error);throw error;}\\n  finally{busy=false;if(!failed)receive(StillMade.getShared());else draw();}\\n });\\n StillMade.scheduleSharedEdit('canvasRaw',()=>{});\\n});\\ndocument.getElementById('discard').addEventListener('click',()=>{\\n if(busy||!canEdit||pending||rawPending)return;\\n StillMade.scheduleSharedEdit('canvasDraft',async()=>{\\n  const latest=StillMade.getShared(),value=latest.state.canvasDraft||{edits:[]};\\n  try{const restored=StillMade.applyDocument(source,value.edits);await StillMade.updateShared({canvasDraft:value,rawDraft:latest.state.rawDraft||null},{canvasDraft:condition(latest.state,'canvasDraft'),rawDraft:condition(latest.state,'rawDraft')});draft=restored;failed=false;queuedBase=null;queuedEdits=null;rawQueued=null;receive(StillMade.getShared());}\\n  catch(error){fail(error);throw error;}\\n });\\n StillMade.scheduleSharedEdit('canvasRaw',()=>{});\\n});\\nsave.addEventListener('click',async()=>{\\n if(!editable()||pending||queuedBase||rawEditing)return;const started=revision;busy=true;notice='Checking the complete Canvas…';draw();\\n try{await StillMade.run({canvas:clone(source),updatedCanvas:clone(draft),commit:true});if(started===revision)notice='Canvas changes are ready below. Review the before and after, then choose Apply.';}\\n catch(error){notice=error.message||'Canvas changes could not be prepared.';}finally{busy=false;draw();}\\n});\\ndocument.getElementById('connectContext').addEventListener('click',async()=>{\\n if(!editable()||pending||rawPending||!contextProposal?.edges?.length)return;\\n const started=revision;busy=true;notice='Checking the current connection sources…';draw();\\n try{await StillMade.run({canvas:clone(source),connectionProposal:clone(contextProposal),applyContextConnections:true,commit:false,addGeneratedImage:false,addGeneratedVideo:false,addGeneratedAudio:false});if(started===revision)notice='Only the proposed connections are ready below. Review the before and after, then choose Apply. Other draft edits are retained.';}\\n catch(error){notice=error.message||'Connections could not be prepared.';}finally{busy=false;draw();}\\n});\\nStillMade.onShared(receive);\\nStillMade.onInput(values=>{\\n source=values.canvas||source;contextProposal=values.connectionProposal&&Array.isArray(values.connectionProposal.edges)?clone(values.connectionProposal):null;inputReady=true;connectedImage=values.generatedImage?clone(values.generatedImage):null;connectedVideo=values.generatedVideo?clone(values.generatedVideo):null;connectedAudio=values.generatedAudio?clone(values.generatedAudio):null;\\n const current=Array.isArray(values.assets)?values.assets:[],historic=(Array.isArray(values.versions)?values.versions:[]).map(item=>item&&item.asset).filter(Boolean);\\n const seen=new Set();library=[...current,...historic].filter(item=>{if(!item||!['image','video','audio'].includes(item.kind))return false;const key=JSON.stringify([item.kind,item.assetId,item.versionId]);if(seen.has(key))return false;seen.add(key);return true;}).slice(0,120);\\n titles={image:values.generatedImageTitle||'Generated image',video:values.generatedVideoTitle||'Generated video',audio:values.generatedAudioTitle||'Voiceover'};revision+=1;receive(StillMade.getShared());\\n});\\n\"\n}"},"lineage":{"parents":[],"changes":"Packaged original Block source for StillMade."}}}],"license":"MIT"}