const data=JSON.parse(document.getElementById('agentr-review-data').textContent);
const config=JSON.parse(document.getElementById('agentr-review-config').textContent);
const feedback=[];
const main=document.getElementById('main'); const panel=document.getElementById('feedbackPanel'); const wrap=document.querySelector('.wrap'); const splitter=document.getElementById('splitter');
const subsystemPalette={rwm:{fill:'#E0F2FE',stroke:'#0369A1',label:'RWM'},pg:{fill:'#DCFCE7',stroke:'#15803D',label:'PG'},ae:{fill:'#FDE68A',stroke:'#B45309',label:'AE'},la:{fill:'#FCE7F3',stroke:'#BE185D',label:'LA'},iac:{fill:'#EDE9FE',stroke:'#6D28D9',label:'IAC'}};
let currentWorkflowGraphEl=null; let currentTaskPreviewId=null; let currentSelectedWorkflowNodeId=null;
function esc(x){return String(x??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
function val(x){return x===null||x===undefined||x===''?'':x;}
function section(title,id){const s=document.createElement('section');s.className='panel';s.id=id;s.innerHTML='<h2>'+esc(title)+'</h2>';main.appendChild(s);return s;}
function selectTarget(target,targetId,field){if(!config.include_feedback_panel)return;document.getElementById('fb_target').value=target;document.getElementById('fb_target_id').value=targetId||'';document.getElementById('fb_field').value=field||target;}
function jsonBlock(x){if(x===null||x===undefined)return'<pre>null</pre>';return'<pre>'+esc(JSON.stringify(x,null,2))+'</pre>';}
function arrayValue(x){return Array.isArray(x)?x:(x===null||x===undefined||x===''?[]:[x]);}
function knowledgeRefs(x){return arrayValue(x).map(String);}
function normalizeSubsystemKey(x){x=String(x||'').toLowerCase();return['rwm','pg','ae','la','iac'].includes(x)?x:'';}
function nodeSubsystem(id){const subsystemMap=(data.workflow_graph.metadata||{}).node_subsystems||(data.metadata||{}).node_subsystems||{};const labels=subsystemMap[id];const normalized=arrayValue(labels).map(normalizeSubsystemKey).filter(Boolean);return normalized.length?normalized[0]:'';}
function nodeColorTheme(){const sel=document.getElementById('workflow-theme-select');return sel?sel.value:(config.node_color_theme||'default');}
function baseWorkflowCategory(n){if(n.human_required)return{key:'human',fill:'#FFF1D6',stroke:'#B45309',label:'Human gate'};if(String(val(n.automation_status))==='llm_assisted'||String(val(n.automation_status))==='external_llm')return{key:'external',fill:'#E9E4FB',stroke:'#6D28D9',label:'External stochastic LLM'};return{key:'deterministic',fill:'#E8F3EE',stroke:'#607466',label:'Deterministic automation'};}
function nodeKind(n){const k=String(val(n&&n.node_kind)||'action').toLowerCase();return['action','status','knowledge','memory','file','api','schema','data'].includes(k)?k:'action';}
function isDataNode(n){return['knowledge','memory','file','api','schema','data'].includes(nodeKind(n));}
function isStatusNode(n){return nodeKind(n)==='status';}
function dataNodeVisual(n){const k=nodeKind(n);const map={knowledge:{key:'knowledge',fill:'#DBEAFE',stroke:'#1D4ED8',label:'Knowledge data'},memory:{key:'memory',fill:'#DCFCE7',stroke:'#15803D',label:'Memory data'},file:{key:'file',fill:'#FEF3C7',stroke:'#B45309',label:'File data'},api:{key:'api',fill:'#E0F2FE',stroke:'#0369A1',label:'API data'},schema:{key:'schema',fill:'#F3E8FF',stroke:'#7E22CE',label:'Schema data'},data:{key:'data',fill:'#F3F4F6',stroke:'#4B5563',label:'Data resource'}};return map[k]||map.data;}
function categoryPriority(key){return key==='human'?3:key==='external'?2:1;}
function effectiveWorkflowCategory(n){const base=baseWorkflowCategory(n);let best=base;const nested=(n&&n.nested_workflow&&n.nested_workflow.nodes)||[];nested.forEach(child=>{const childCategory=effectiveWorkflowCategory(child);if(categoryPriority(childCategory.key)>categoryPriority(best.key))best=childCategory;});return best;}
function nodeVisual(n){if(isDataNode(n))return dataNodeVisual(n);if(isStatusNode(n))return{key:'status',fill:'#F8FAFC',stroke:'#64748B',label:'Status marker'};if(nodeColorTheme()==='subsystems'){const key=nodeSubsystem(n.id);if(key&&subsystemPalette[key])return subsystemPalette[key];return{fill:'#EEF2F7',stroke:'#7B8794',label:'Unlabeled'};}return effectiveWorkflowCategory(n);}
function hasNestedWorkflow(n){return!!(n&&((n.nested_workflow&&Array.isArray(n.nested_workflow.nodes)&&n.nested_workflow.nodes.length)||val(n.subworkflow_ref)));}
function hasSubworkflow(n){return hasNestedWorkflow(n)||!!val(n&&n.subworkflow_ref);}
function workflowNodeById(id){return(data.workflow_graph.nodes||[]).find(n=>n.id===id);}
function workflowEdgeByIndex(index,edges){return((edges||[])[index])||null;}
function actionNodes(nodes){return(nodes||[]).filter(n=>!isDataNode(n));}
function workflowActionNodes(){return actionNodes(data.workflow_graph.nodes||[]);}
function workflowActionEdges(nodes,edges){const ids=new Set((nodes||[]).map(n=>n.id));return(edges||[]).filter(e=>ids.has(e.from)&&ids.has(e.to));}
function renderDetailObject(n,note,boxId='node_detail'){const box=document.getElementById(boxId);if(!box)return;const refs=knowledgeRefs(n.knowledge_refs).join(', ');const linked=arrayValue(n.linked_spec_ids).map(String).join(', ');const hasNested=n.nested_workflow&&n.nested_workflow.nodes;const hasRef=!!val(n.subworkflow_ref);const subsystem=nodeSubsystem(n.id);const category=isDataNode(n)?dataNodeVisual(n):(nodeColorTheme()==='subsystems'?(subsystemPalette[subsystem]||{label:'Unlabeled'}):effectiveWorkflowCategory(n));box.innerHTML=`<div class='detail-box'><div class='id'>${esc(n.id)}</div><div class='label'>${esc(n.label)}</div>${note?`<div class='small'>${esc(note)}</div>`:''}<div class='detail-kv'><b>Node kind</b><span>${esc(nodeKind(n))}</span><b>Rule</b><span>${esc(val(n.rule_spec))}</span><b>Owner</b><span>${esc(val(n.owner))}</span><b>Automation</b><span>${esc(val(n.automation_status))}</span><b>Human gate</b><span>${esc(n.human_required)}</span><b>Category</b><span>${esc(category.label||'')}</span><b>Subsystem</b><span>${esc(subsystem||'')}</span><b>Source path</b><span>${esc(val(n.source_path))}</span><b>Retrieval</b><span>${esc(val(n.retrieval_mode))}</span><b>Persistence</b><span>${esc(val(n.persistence))}</span><b>Linked specs</b><span>${esc(linked)}</span><b>Hint</b><span>${esc(val(n.implementation_hint))}</span><b>Knowledge</b><span>${esc(refs)}</span><b>Subworkflow</b><span>${esc(val(n.subworkflow_ref))}</span></div><h3>Input schema</h3>${jsonBlock(n.input_schema||{})}<h3>Output schema</h3>${jsonBlock(n.output_schema||{})}${hasNested?'<div class="small">Click the subworkflow badge on the graph node to open the embedded subworkflow.</div>':(hasRef?'<div class="small">This node references a subworkflow, but it was not embedded in this review bundle.</div>':'')}</div>`;}
function renderNodeDetail(id){const n=workflowNodeById(id);if(!n){renderDetailObject({id:'',label:'Click a workflow node or edge to inspect its details.'},'');return;}renderDetailObject(n,isDataNode(n)?'Workflow resource node':'Top-level workflow node');if(!isDataNode(n)){currentSelectedWorkflowNodeId=id;renderResourcePanel(id);}}
function renderNestedNodeDetail(parentId,nodeId,boxId='node_detail'){const parent=workflowNodeById(parentId);const n=parent&&parent.nested_workflow&&parent.nested_workflow.nodes?(parent.nested_workflow.nodes||[]).find(x=>x.id===nodeId):null;if(!n)return;renderDetailObject(n,'Nested node inside '+parentId,boxId);if(boxId==='subworkflow_node_detail')renderSubworkflowResourcePanel(parent,nodeId);selectTarget('workflow_node',parentId+'::'+nodeId,'workflow.nodes.'+parentId+'.nested_workflow.nodes.'+nodeId);}
function renderEdgeDetailObject(e,note,nodes,boxId='node_detail'){const box=document.getElementById(boxId);if(!box)return;const a=(nodes||[]).find(n=>n.id===e.from)||workflowNodeById(e.from)||{};const b=(nodes||[]).find(n=>n.id===e.to)||workflowNodeById(e.to)||{};box.innerHTML=`<div class='detail-box'><div class='id'>${esc(e.from)} -> ${esc(e.to)}</div><div class='label'>${esc(val(e.relation)||'depends_on')}</div>${note?`<div class='small'>${esc(note)}</div>`:''}<div class='detail-kv'><b>From id</b><span>${esc(e.from)}</span><b>From label</b><span>${esc(val(a.label))}</span><b>To id</b><span>${esc(e.to)}</span><b>To label</b><span>${esc(val(b.label))}</span><b>Relation</b><span>${esc(val(e.relation)||'depends_on')}</span><b>Condition</b><span>${esc(val(e.condition))}</span><b>Branch group</b><span>${esc(val(e.branch_group))}</span><b>Mutually exclusive</b><span>${esc(val(e.mutually_exclusive))}</span><b>Confidence</b><span>${esc(val(e.confidence))}</span><b>Notes</b><span>${esc(val(e.notes))}</span></div></div>`;}
function renderWorkflowEdge(index,edges){const e=workflowEdgeByIndex(index,edges);if(!e)return;renderEdgeDetailObject(e,'Workflow edge',data.workflow_graph.nodes||[]);selectTarget('workflow_edge',e.from+'->'+e.to,'workflow.edges');}
function selectWorkflowNode(id){renderNodeDetail(id);selectTarget('workflow_node',id,'workflow.nodes.'+id);}
function wrapSvgText(text,maxChars=22){const words=String(text??'').split(/\s+/).filter(Boolean);const lines=[];let line='';const pushLong=w=>{while(w.length>maxChars){lines.push(w.slice(0,maxChars));w=w.slice(maxChars);}return w;};words.forEach(word=>{word=pushLong(word);if(!word)return;if(!line){line=word;}else if((line+' '+word).length<=maxChars){line+=' '+word;}else{lines.push(line);line=word;}});if(line)lines.push(line);return lines.length?lines:[''];}
function svgTspans(lines,x,startDy,lineHeight){return lines.map((line,i)=>`<tspan x='${x}' dy='${i===0?startDy:lineHeight}'>${esc(line)}</tspan>`).join('');}
function prepareWorkflowNodes(nodes){nodes.forEach(n=>{n._lines=wrapSvgText(n.label,24);n._h=Math.max(54,26+n._lines.length*15);});}
function hasBackwardEdges(nodes,edges){const order=Object.fromEntries((nodes||[]).map((n,i)=>[n.id,i]));return(edges||[]).some(e=>Number.isFinite(order[e.from])&&Number.isFinite(order[e.to])&&order[e.to]<=order[e.from]);}
function layoutWorkflowGrid(nodes,opt){const rows=Math.max(1,Math.ceil(nodes.length/3));nodes.forEach((n,i)=>{n._row=Math.floor(i/3);n._x=opt.left+(i%3)*opt.colGap;});const rowHeights=[];for(let r=0;r<rows;r++){const rowNodes=nodes.filter(n=>n._row===r);rowHeights[r]=rowNodes.length?Math.max(...rowNodes.map(n=>n._h)):62;}let cursor=opt.top;for(let r=0;r<rows;r++){nodes.filter(n=>n._row===r).forEach(n=>{n._y=cursor;});cursor+=rowHeights[r]+opt.rowGap;}return Math.max(190,cursor-opt.rowGap+opt.top);}
function dagDepth(nodes,edges){const byId=Object.fromEntries(nodes.map(n=>[n.id,n]));const order=Object.fromEntries(nodes.map((n,i)=>[n.id,i]));const indeg=Object.fromEntries(nodes.map(n=>[n.id,0]));edges.forEach(e=>{if(byId[e.from]&&byId[e.to])indeg[e.to]=(indeg[e.to]||0)+1;});const q=nodes.filter(n=>(indeg[n.id]||0)===0).map(n=>n.id);const depth=Object.fromEntries(nodes.map(n=>[n.id,0]));const processed=new Set();let guard=0;while(q.length&&guard++<nodes.length*nodes.length){const id=q.shift();processed.add(id);edges.filter(e=>e.from===id&&byId[e.to]).forEach(e=>{depth[e.to]=Math.max(depth[e.to]||0,(depth[id]||0)+1);indeg[e.to]-=1;if(indeg[e.to]===0)q.push(e.to);});}if(processed.size<nodes.length){nodes.forEach(n=>{depth[n.id]=0;});for(let pass=0;pass<nodes.length;pass++){let changed=false;edges.forEach(e=>{if(byId[e.from]&&byId[e.to]&&order[e.to]>order[e.from]){const d=(depth[e.from]||0)+1;if(d>(depth[e.to]||0)){depth[e.to]=d;changed=true;}}});if(!changed)break;}}return depth;}
function layoutWorkflowLayered(nodes,edges,opt){const depth=dagDepth(nodes,edges);nodes.forEach((n,i)=>{n._depth=depth[n.id]??i;n._col=n._depth;n._row=0;});const maxDepth=nodes.length?Math.max(...nodes.map(n=>n._depth)):0;const maxCols=4;if(maxDepth>=maxCols){nodes.forEach(n=>{n._band=Math.floor(n._depth/maxCols);n._col=n._depth%maxCols;});const bands=[...new Set(nodes.map(n=>n._band))].sort((a,b)=>a-b);let y=opt.top;bands.forEach(band=>{const bandNodes=nodes.filter(n=>n._band===band);const groups={};bandNodes.forEach(n=>{const k=String(n._col);if(!groups[k])groups[k]=[];groups[k].push(n);});let bandHeight=70;Object.keys(groups).forEach(k=>{groups[k].sort((a,b)=>String(a.id).localeCompare(String(b.id)));const stackHeight=groups[k].reduce((sum,n)=>sum+n._h+opt.rowGap/2,0);bandHeight=Math.max(bandHeight,stackHeight);});Object.keys(groups).forEach(k=>{let stackY=y;groups[k].forEach((n,ri)=>{n._x=opt.left+n._col*opt.colGap;n._y=stackY;n._row=ri;stackY+=n._h+opt.rowGap/2;});});y+=bandHeight+opt.rowGap*2;});return Math.max(190,y-opt.rowGap);}const cols=[...new Set(nodes.map(n=>n._col))].sort((a,b)=>a-b);let maxBottom=0;cols.forEach((col,ci)=>{const colNodes=nodes.filter(n=>n._col===col);let y=opt.top;colNodes.forEach((n,ri)=>{n._x=opt.left+ci*opt.colGap;n._y=y;n._row=ri;y+=n._h+opt.rowGap;maxBottom=Math.max(maxBottom,n._y+n._h+opt.top);});});return Math.max(190,maxBottom);}
function positionResourceNodes(nodes,edges,opt){const resources=nodes.filter(isDataNode);if(!resources.length)return;const actions=nodes.filter(n=>!isDataNode(n)&&Number.isFinite(n._x));const minActionX=actions.length?Math.min(...actions.map(n=>n._x)):opt.left+opt.nodeW+120;const resourceX=minActionX-opt.nodeW-120;const actionOrder=Object.fromEntries(actions.map((n,i)=>[n.id,i]));resources.sort((a,b)=>{const firstA=Math.min(...edges.filter(e=>e.from===a.id||e.to===a.id).map(e=>actionOrder[e.from]??actionOrder[e.to]??9999),9999);const firstB=Math.min(...edges.filter(e=>e.from===b.id||e.to===b.id).map(e=>actionOrder[e.from]??actionOrder[e.to]??9999),9999);return firstA-firstB||nodeKind(a).localeCompare(nodeKind(b))||String(a.id).localeCompare(String(b.id));});let y=opt.top;resources.forEach(n=>{n._x=resourceX;n._y=y;n._resourceLane='Resource data';y+=n._h+opt.rowGap;});nodes.filter(isDataNode).forEach(n=>{n._depth=-1;n._processIndex=-1;});}
function layoutWorkflowProcess(nodes,edges,opt){const order=Object.fromEntries(nodes.map((n,i)=>[n.id,i]));const byId=Object.fromEntries(nodes.map(n=>[n.id,n]));const out=Object.fromEntries(nodes.map(n=>[n.id,0]));edges.forEach(e=>{if(Number.isFinite(order[e.from])&&Number.isFinite(order[e.to]))out[e.from]=(out[e.from]||0)+1;});const branchBySource={};edges.forEach(e=>{if(Number.isFinite(order[e.from])&&Number.isFinite(order[e.to])&&isBranchEdge(e)){if(!branchBySource[e.from])branchBySource[e.from]=[];branchBySource[e.from].push(e);}});Object.keys(branchBySource).forEach(src=>{if(branchBySource[src].length<2)delete branchBySource[src];});const branchIds=new Set();Object.values(branchBySource).forEach(group=>group.forEach(e=>branchIds.add(e.to)));const decisionJoinIds=new Set();const decisionJoinBySource={};Object.entries(branchBySource).forEach(([src,group])=>{const downstream=group.map(e=>new Set(edges.filter(edge=>edge.from===e.to).map(edge=>edge.to)));if(!downstream.length)return;const common=[...downstream[0]].filter(id=>downstream.every(set=>set.has(id)));if(common.length){const joinId=common.sort((a,b)=>(order[a]??9999)-(order[b]??9999))[0];decisionJoinBySource[src]=joinId;decisionJoinIds.add(joinId);}});edges.forEach(e=>{if(Number.isFinite(order[e.from])&&Number.isFinite(order[e.to])&&order[e.to]>order[e.from]+1&&(out[e.to]||0)===0)branchIds.add(e.to);});nodes.forEach((n,i)=>{n._processIndex=i;n._depth=i;n._processBranch=branchIds.has(n.id);n._processBranchSource='';n._decisionJoin=decisionJoinIds.has(n.id);n._processBranchLane=null;delete n._x;delete n._y;});let y=opt.top;const spineX=opt.left+120;const branchGap=Math.max(opt.colGap,320);nodes.forEach(n=>{if(n._processBranch)return;if(n._decisionJoin&&Number.isFinite(n._y)){y=Math.max(y,n._y+n._h+opt.rowGap);return;}n._x=spineX;n._y=y;y+=n._h+opt.rowGap;const group=branchBySource[n.id];if(group){const targets=group.map(e=>byId[e.to]).filter(Boolean);const count=targets.length;const startX=n._x-((count-1)*branchGap)/2;const branchY=n._y+n._h+opt.rowGap;let maxBranchBottom=n._y+n._h;targets.forEach((b,idx)=>{b._processBranch=true;b._processBranchSource=n.id;b._x=startX+idx*branchGap;b._y=branchY;b._processBranchLane=idx;maxBranchBottom=Math.max(maxBranchBottom,b._y+b._h);});const join=byId[decisionJoinBySource[n.id]];if(join&&!branchIds.has(join.id)){join._decisionJoin=true;join._x=spineX;join._y=maxBranchBottom+opt.rowGap;y=Math.max(y,join._y+join._h+opt.rowGap);}}});const branchCount={};edges.forEach(e=>{const a=byId[e.from],b=byId[e.to];if(a&&b&&b._processBranch&&!Number.isFinite(b._y)){branchCount[a.id]=(branchCount[a.id]||0)+1;b._processBranchSource=a.id;b._x=spineX+branchGap;b._y=a._y+(branchCount[a.id]-1)*(b._h+opt.rowGap/2);}});nodes.filter(n=>n._processBranch&&!Number.isFinite(n._y)).forEach(n=>{n._x=spineX+branchGap;n._y=y;y+=n._h+opt.rowGap;});positionResourceNodes(nodes,edges,opt);const minNodeX=nodes.length?Math.min(...nodes.map(n=>n._x)):spineX;edges.forEach(e=>{const a=byId[e.from],b=byId[e.to];if(a&&b&&a._processBranch&&!b._processBranch){e._branchRejoin=true;e._branchRejoinSide=a._x<b._x?'left':'right';e._branchRailX=e._branchRejoinSide==='left'?Math.min(a._x,b._x)-58:Math.max(a._x+opt.nodeW,b._x+opt.nodeW)+58;}});const feedbackEdges=edges.filter(e=>{const a=byId[e.from],b=byId[e.to];return a&&b&&Number.isFinite(a._processIndex)&&Number.isFinite(b._processIndex)&&b._processIndex<a._processIndex;}).sort((e1,e2)=>((order[e2.from]-order[e2.to])-(order[e1.from]-order[e1.to])));feedbackEdges.forEach((e,idx)=>{e._feedbackRailX=minNodeX-140-idx*56;});const maxBottom=nodes.length?Math.max(...nodes.map(n=>n._y+n._h+opt.top)):190;return Math.max(190,maxBottom);}
function laneLabel(n){const owner=val(n.owner);if(owner)return'owner: '+owner;const automation=val(n.automation_status);if(automation)return'automation: '+automation;return n.human_required?'human gate':'no human gate';}
function layoutWorkflowSwimlane(nodes,edges,opt){const depth=dagDepth(nodes,edges);nodes.forEach((n,i)=>{n._lane=laneLabel(n);n._col=depth[n.id]??i;});const lanes=[];nodes.forEach(n=>{if(!lanes.includes(n._lane))lanes.push(n._lane);});let y=opt.top;lanes.forEach(lane=>{const laneNodes=nodes.filter(n=>n._lane===lane).sort((a,b)=>(a._col-b._col)||String(a.id).localeCompare(String(b.id)));const colGroups={};laneNodes.forEach(n=>{const k=String(n._col);if(!colGroups[k])colGroups[k]=[];colGroups[k].push(n);});let laneHeight=92;Object.keys(colGroups).forEach(k=>{const stack=colGroups[k];const stackHeight=24+stack.reduce((sum,n)=>sum+n._h+opt.rowGap/2,0);laneHeight=Math.max(laneHeight,stackHeight);});Object.keys(colGroups).forEach(k=>{let stackY=y+24;colGroups[k].forEach(n=>{n._x=opt.left+150+n._col*opt.colGap;n._y=stackY;stackY+=n._h+opt.rowGap/2;});});laneNodes.forEach(n=>{n._laneY=y;n._laneH=laneHeight;});y+=laneHeight+opt.rowGap;});return Math.max(190,y-opt.rowGap+opt.top);}
function isBranchEdge(e){return String(val(e.relation)).includes('branch')||!!val(e.condition)||!!val(e.branch_group)||e.mutually_exclusive===true;}
function isResourceEdge(e){return['reads','writes','updates','prompts_with','validates_against','produces'].includes(String(val(e.relation)));}
function edgeVisual(e){if(isBranchEdge(e))return{stroke:'#B45309',dash:'6 4',marker:'url(#arrow-branch)',labelFill:'#FFF7E6',labelStroke:'#D69E2E'};if(isResourceEdge(e))return{stroke:'#2563EB',dash:String(val(e.relation))==='reads'||String(val(e.relation))==='prompts_with'?'4 3':'',marker:'url(#arrow)',labelFill:'#EFF6FF',labelStroke:'#93C5FD'};return{stroke:'#6b7280',dash:'',marker:'url(#arrow)',labelFill:'#fffdf7',labelStroke:'#d7cebd'};}
function edgeLabel(e){if(val(e.condition))return String(e.condition);if(isBranchEdge(e)&&val(e.relation))return String(e.relation);if(isResourceEdge(e))return String(e.relation);return'';}
function edgeLabelPosition(a,b,e={}){if(isBranchEdge(e)&&b._processBranch){return{x:b._x+90,y:Math.min(a._y+a._h+22,b._y-10)};}return{x:(a._x+b._x)/2+90,y:(a._cy+b._cy)/2-6};}
function edgeAnchorSides(a,b,nodeW){const acx=a._x+nodeW/2,bcx=b._x+nodeW/2,dy=b._cy-a._cy,dx=bcx-acx;if(Math.abs(dx)<nodeW*0.35&&Math.abs(dy)>8){return dy>=0?{source:'bottom',target:'top',axis:'vertical'}:{source:'top',target:'bottom',axis:'vertical'};}return dx>=0?{source:'right',target:'left',axis:'horizontal'}:{source:'left',target:'right',axis:'horizontal'};}
function sidePoint(n,nodeW,side,ratio){ratio=Number.isFinite(ratio)?ratio:0.5;if(side==='top')return{x:n._x+nodeW*ratio,y:n._y};if(side==='bottom')return{x:n._x+nodeW*ratio,y:n._y+n._h};if(side==='right')return{x:n._x+nodeW,y:n._y+n._h*ratio};return{x:n._x,y:n._y+n._h*ratio};}
function assignEdgePorts(nodes,edges,nodeW){const byId=Object.fromEntries((nodes||[]).map(n=>[n.id,n]));const groups={};(edges||[]).forEach((e,i)=>{const a=byId[e.from],b=byId[e.to];if(!a||!b)return;let sides=edgeAnchorSides(a,b,nodeW);const feedback=(Number.isFinite(a._processIndex)&&Number.isFinite(b._processIndex)&&b._processIndex<a._processIndex)||(Number.isFinite(a._depth)&&Number.isFinite(b._depth)&&b._depth<a._depth);if(isBranchEdge(e)&&b._processBranch&&!e._branchRejoin){sides={source:b._x<a._x?'left':'right',target:'top',axis:'horizontal'};}else if(e._branchRejoin){const left=e._branchRejoinSide==='left';sides={source:left?'left':'right',target:left?'left':'right',axis:'horizontal'};}else if(feedback&&Math.abs(a._x-b._x)<2){sides={source:'left',target:'left',axis:'horizontal'};}e._sourceSide=sides.source;e._targetSide=sides.target;e._anchorAxis=sides.axis;[['source',e.from,sides.source],['target',e.to,sides.target]].forEach(([role,nodeId,side])=>{const key=nodeId+':'+side;if(!groups[key])groups[key]=[];groups[key].push({edge:e,role,index:i});});});Object.values(groups).forEach(items=>{items.sort((a,b)=>a.index-b.index||(a.role==='target'?-1:1));const n=items.length;items.forEach((item,pos)=>{const ratio=n===1?0.5:(pos+1)/(n+1);if(item.role==='source')item.edge._sourcePortRatio=ratio;else item.edge._targetPortRatio=ratio;});});}
function edgeAnchors(a,b,nodeW,e={}){const sides=e._sourceSide&&e._targetSide?{source:e._sourceSide,target:e._targetSide,axis:e._anchorAxis||'horizontal'}:edgeAnchorSides(a,b,nodeW);const s=sidePoint(a,nodeW,sides.source,e._sourcePortRatio);const t=sidePoint(b,nodeW,sides.target,e._targetPortRatio);return{sx:s.x,sy:s.y,tx:t.x,ty:t.y,axis:sides.axis,sourceSide:sides.source,targetSide:sides.target};}
function edgePath(a,b,nodeW,style,index,e={}){const p=edgeAnchors(a,b,nodeW,e);const sx=p.sx,sy=p.sy,tx=p.tx,ty=p.ty;const offset=((index%5)-2)*10;const feedback=(Number.isFinite(a._processIndex)&&Number.isFinite(b._processIndex)&&b._processIndex<a._processIndex)||(Number.isFinite(a._depth)&&Number.isFinite(b._depth)&&b._depth<a._depth);if(style==='straight')return`M ${sx} ${sy} L ${tx} ${ty}`;if(style==='orthogonal'){if(isBranchEdge(e)&&b._processBranch&&!e._branchRejoin){const startX=sx;const startY=sy;const endX=tx;const endY=ty;return`M ${startX} ${startY} L ${endX} ${startY} L ${endX} ${endY}`;}if(e._branchRejoin){const left=e._branchRejoinSide==='left';const railX=Number.isFinite(e._branchRailX)?e._branchRailX:(left?Math.min(a._x,b._x)-58:Math.max(a._x+nodeW,b._x+nodeW)+58);return`M ${sx} ${sy} L ${railX} ${sy} L ${railX} ${ty} L ${tx} ${ty}`;}if(feedback){const railX=Number.isFinite(e._feedbackRailX)?e._feedbackRailX:Math.min(a._x,b._x)-58-Math.abs(offset);return`M ${sx} ${sy} L ${railX} ${sy} L ${railX} ${ty} L ${tx} ${ty}`;}if(Math.abs(sy-ty)<2||Math.abs(sx-tx)<2)return`M ${sx} ${sy} L ${tx} ${ty}`;if(p.axis==='vertical'){const midY=(sy+ty)/2+offset;return`M ${sx} ${sy} L ${sx} ${midY} L ${tx} ${midY} L ${tx} ${ty}`;}const midX=(sx+tx)/2+offset;return`M ${sx} ${sy} L ${midX} ${sy} L ${midX} ${ty} L ${tx} ${ty}`;}const dx=Math.max(40,Math.abs(tx-sx)*0.45);return`M ${sx} ${sy} C ${sx+dx+offset} ${sy}, ${tx-dx+offset} ${ty}, ${tx} ${ty}`;}
function drawWorkflowGraph(el,nodes,edges,onNodeClick,onEdgeClick){
nodes=(nodes||[]).map(n=>({...n}));edges=(edges||[]).map(e=>({...e}));
onNodeClick=onNodeClick||selectWorkflowNode;onEdgeClick=onEdgeClick||((index)=>renderWorkflowEdge(index,edges));
const opt={nodeW:180,colGap:250,rowGap:34,top:34,left:48};
prepareWorkflowNodes(nodes);
let h;
if(config.graph_layout==='layered'){h=layoutWorkflowLayered(nodes,edges,opt);}
else if(config.graph_layout==='swimlane'){h=layoutWorkflowSwimlane(nodes,edges,opt);}
else if(config.graph_layout==='process'){h=layoutWorkflowProcess(nodes,edges,opt);}
else{h=layoutWorkflowGrid(nodes,opt);}
nodes.forEach(n=>{n._cy=n._y+n._h/2;});
assignEdgePorts(nodes,edges,opt.nodeW);
const maxRight=nodes.length?Math.max(...nodes.map(n=>n._x+opt.nodeW+opt.left)):820;
const minLeft=nodes.length?Math.min(...nodes.map(n=>n._x)):0;
const minEdgeX=edges.length?Math.min(...edges.map(e=>Math.min(Number.isFinite(e._feedbackRailX)?e._feedbackRailX:Infinity,Number.isFinite(e._branchRailX)?e._branchRailX:Infinity))):Infinity;
const maxEdgeX=edges.length?Math.max(...edges.map(e=>Math.max(Number.isFinite(e._branchRailX)?e._branchRailX:-Infinity,Number.isFinite(e._feedbackRailX)?e._feedbackRailX:-Infinity))):-Infinity;
const viewLeft=Math.min(0,minLeft-80,Number.isFinite(minEdgeX)?minEdgeX-40:0);
const viewRight=Math.max(maxRight,Number.isFinite(maxEdgeX)?maxEdgeX+opt.left+40:0);
const w=Math.max(820,viewRight-viewLeft);
let svg=`<svg viewBox='${viewLeft} 0 ${w} ${h}' width='${w}' height='${h}' role='img' aria-label='Workflow graph'>`;
svg+=`<defs><marker id='arrow' viewBox='0 0 10 10' refX='8' refY='5' markerWidth='6' markerHeight='6' orient='auto-start-reverse'><path d='M 0 0 L 10 5 L 0 10 z' fill='#6b7280'/></marker><marker id='arrow-branch' viewBox='0 0 10 10' refX='8' refY='5' markerWidth='6' markerHeight='6' orient='auto-start-reverse'><path d='M 0 0 L 10 5 L 0 10 z' fill='#B45309'/></marker></defs>`;
if(config.graph_layout==='swimlane'){
const lanes=[...new Set(nodes.map(n=>n._lane))];
lanes.forEach(lane=>{const n=nodes.find(x=>x._lane===lane);if(n){svg+=`<rect x='${opt.left}' y='${n._laneY}' width='${w-opt.left*2}' height='${n._laneH}' rx='10' fill='${lanes.indexOf(lane)%2?'#f3efe5':'#faf7ef'}' stroke='#d7cebd'/><text x='${opt.left+12}' y='${n._laneY+20}' font-size='12' fill='#52606d'>${esc(lane)}</text>`;}});
}
edges.forEach((e,i)=>{const a=nodes.find(n=>n.id===e.from),b=nodes.find(n=>n.id===e.to);if(a&&b){const visual=edgeVisual(e);const label=edgeLabel(e);svg+=`<path data-edge-index='${i}' d='${edgePath(a,b,opt.nodeW,config.edge_style,i,e)}' fill='none' stroke='${visual.stroke}' stroke-width='${isBranchEdge(e)?2:1.5}' stroke-dasharray='${visual.dash}' marker-end='${visual.marker}' style='pointer-events:stroke;cursor:pointer'/>`;if(label){const pos=edgeLabelPosition(a,b,e);const text=esc(label);const labelW=Math.min(210,Math.max(44,text.length*6.8+16));svg+=`<g data-edge-index='${i}' style='cursor:pointer'><rect x='${pos.x-labelW/2}' y='${pos.y-15}' width='${labelW}' height='20' rx='4' fill='${visual.labelFill}' stroke='${visual.labelStroke}'/><text x='${pos.x}' y='${pos.y}' text-anchor='middle' font-size='11' fill='#374151'>${text}</text></g>`;}}});
nodes.forEach(n=>{const tx=n._x+opt.nodeW/2;const visual=nodeVisual(n);const titleText=`${n.label} (${visual.label||''})`;const nested=hasNestedWorkflow(n);const dash=isStatusNode(n)?" stroke-dasharray='5 4'":'';svg+=`<g tabindex='0' role='button' data-node-id='${esc(n.id)}'><title>${esc(titleText)}${nested?' - subworkflow':''}</title><rect x='${n._x}' y='${n._y}' width='${opt.nodeW}' height='${n._h}' rx='8' fill='${visual.fill}' stroke='${visual.stroke}'${dash}/>${nested?`<g data-subworkflow-id='${esc(n.id)}' class='subworkflow-badge' role='button' aria-label='Open subworkflow'><rect x='${n._x+opt.nodeW-24}' y='${n._y+8}' width='16' height='16' rx='4' fill='${visual.stroke}' stroke='${visual.stroke}'/><path d='M ${n._x+opt.nodeW-20} ${n._y+14} L ${n._x+opt.nodeW-16} ${n._y+10} L ${n._x+opt.nodeW-12} ${n._y+14} L ${n._x+opt.nodeW-16} ${n._y+18} Z' fill='#fff'/></g>`:''}<text text-anchor='middle' font-size='12'>${svgTspans(n._lines,tx,n._y+28,15)}</text></g>`;});
svg+='</svg>';el.innerHTML=`<div class='graph-scroll'>${svg}</div>`;
el.querySelectorAll('[data-node-id]').forEach(x=>{x.onclick=()=>onNodeClick(x.getAttribute('data-node-id'));});
el.querySelectorAll('[data-edge-index]').forEach(x=>{x.onclick=()=>onEdgeClick(Number(x.getAttribute('data-edge-index')));});
el.querySelectorAll('[data-subworkflow-id]').forEach(x=>{x.onclick=e=>{e.stopPropagation();openSubworkflowModal(x.getAttribute('data-subworkflow-id'));};});
}
function reviewGraphVisual(n){const kind=String(n.kind||n.node_type||n.memory_type||'item');const palette={root:{fill:'#F3F4F6',stroke:'#374151'},context:{fill:'#DBEAFE',stroke:'#1D4ED8'},semantic:{fill:'#DCFCE7',stroke:'#15803D'},episodic:{fill:'#FEF3C7',stroke:'#B45309'},procedural:{fill:'#FCE7F3',stroke:'#BE185D'},knowledge_item:{fill:'#DBEAFE',stroke:'#1D4ED8'},graph_node:{fill:'#EDE9FE',stroke:'#6D28D9'},domain:{fill:'#F3E8FF',stroke:'#7E22CE'},type:{fill:'#ECFDF5',stroke:'#047857'}};return palette[kind]||{fill:'#F3F4F6',stroke:'#6B7280'};}
function layoutReviewGraph(nodes,edges,opt){const byId=Object.fromEntries(nodes.map(n=>[n.id,n]));const indeg=Object.fromEntries(nodes.map(n=>[n.id,0]));edges.forEach(e=>{if(byId[e.from]&&byId[e.to])indeg[e.to]=(indeg[e.to]||0)+1;});const depth=Object.fromEntries(nodes.map(n=>[n.id,0]));const q=nodes.filter(n=>(indeg[n.id]||0)===0).map(n=>n.id);let guard=0;while(q.length&&guard++<nodes.length*nodes.length){const id=q.shift();edges.filter(e=>e.from===id&&byId[e.to]).forEach(e=>{depth[e.to]=Math.max(depth[e.to]||0,(depth[id]||0)+1);indeg[e.to]-=1;if(indeg[e.to]===0)q.push(e.to);});}const groups={};nodes.forEach((n,i)=>{n._lines=wrapSvgText(n.label||n.id,28);n._h=Math.max(58,28+n._lines.length*15);n._depth=depth[n.id]||0;if(!groups[n._depth])groups[n._depth]=[];groups[n._depth].push(n);});let maxBottom=0;Object.keys(groups).sort((a,b)=>Number(a)-Number(b)).forEach(k=>{let y=opt.top;groups[k].forEach((n,i)=>{n._x=opt.left+Number(k)*opt.colGap;n._y=y;n._cy=n._y+n._h/2;maxBottom=Math.max(maxBottom,n._y+n._h+opt.top);y+=n._h+opt.rowGap;});});return Math.max(180,maxBottom);}
function prepareReviewEdgeLanes(nodes,edges){const byId=Object.fromEntries(nodes.map(n=>[n.id,n]));const out={},inc={};edges.forEach((e,i)=>{if(!byId[e.from]||!byId[e.to])return;const ok=e.from,ik=e.to;if(!out[ok])out[ok]=[];if(!inc[ik])inc[ik]=[];out[ok].push(i);inc[ik].push(i);});edges.forEach((e,i)=>{const os=out[e.from]||[i],is=inc[e.to]||[i];e._outIndex=Math.max(0,os.indexOf(i));e._outTotal=os.length;e._inIndex=Math.max(0,is.indexOf(i));e._inTotal=is.length;});}
function reviewEdgeAnchor(node,total,index,side,opt){if(!node)return{x:0,y:0};const slots=Math.max(1,total||1);const pad=Math.min(22,Math.max(10,node._h*.18));const usable=Math.max(1,node._h-pad*2);const y=node._y+pad+usable*((index+.5)/slots);return{x:side==='right'?node._x+opt.nodeW:node._x,y};}
function drawReviewGraph(el,nodes,edges,onNodeClick,onEdgeClick,title='Design graph'){nodes=(nodes||[]).map(n=>({...n}));edges=(edges||[]).map(e=>({...e}));if(!nodes.length){el.innerHTML='';return;}const opt={nodeW:220,colGap:320,rowGap:42,top:36,left:52};const h=layoutReviewGraph(nodes,edges,opt);prepareReviewEdgeLanes(nodes,edges);const maxRight=Math.max(...nodes.map(n=>n._x+opt.nodeW+opt.left));const w=Math.max(760,maxRight);let svg=`<svg viewBox='0 0 ${w} ${h}' width='${w}' height='${h}' role='img' aria-label='${esc(title)}'>`;svg+=`<defs><marker id='arrow-review' viewBox='0 0 10 10' refX='8' refY='5' markerWidth='6' markerHeight='6' orient='auto-start-reverse'><path d='M 0 0 L 10 5 L 0 10 z' fill='#6b7280'/></marker></defs>`;edges.forEach((e,i)=>{const a=nodes.find(n=>n.id===e.from),b=nodes.find(n=>n.id===e.to);if(!a||!b)return;const start=reviewEdgeAnchor(a,e._outTotal,e._outIndex,'right',opt),end=reviewEdgeAnchor(b,e._inTotal,e._inIndex,'left',opt);const sx=start.x,sy=start.y,tx=end.x,ty=end.y,lane=(e._outIndex-(e._outTotal-1)/2)*18,mid=(sx+tx)/2+lane;svg+=`<path data-review-edge-index='${i}' d='M ${sx} ${sy} C ${mid} ${sy}, ${mid} ${ty}, ${tx} ${ty}' fill='none' stroke='#6b7280' stroke-width='1.25' marker-end='url(#arrow-review)' style='pointer-events:stroke;cursor:pointer'/>`;if(e.relation){const label=String(e.relation),lx=mid,ly=(sy+ty)/2-8+(e._inIndex-(e._inTotal-1)/2)*12,lw=Math.min(120,Math.max(34,label.length*6+14));svg+=`<rect x='${lx-lw/2}' y='${ly-12}' width='${lw}' height='16' rx='4' fill='#fbfaf6' stroke='#d7cebd'/>`;svg+=`<text x='${lx}' y='${ly}' text-anchor='middle' font-size='10' fill='#52606d'>${esc(label)}</text>`;}});nodes.forEach(n=>{const visual=reviewGraphVisual(n),tx=n._x+opt.nodeW/2;svg+=`<g tabindex='0' role='button' data-review-node-id='${esc(n.id)}'><title>${esc(n.id)}</title><rect x='${n._x}' y='${n._y}' width='${opt.nodeW}' height='${n._h}' rx='8' fill='${visual.fill}' stroke='${visual.stroke}'/><text text-anchor='middle' font-size='12'>${svgTspans(n._lines,tx,n._y+26,15)}</text></g>`;});svg+='</svg>';el.innerHTML=`<div class='graph-scroll'>${svg}</div>`;el.querySelectorAll('[data-review-node-id]').forEach(x=>{x.onclick=()=>onNodeClick&&onNodeClick(x.getAttribute('data-review-node-id'));});el.querySelectorAll('[data-review-edge-index]').forEach(x=>{x.onclick=()=>onEdgeClick&&onEdgeClick(Number(x.getAttribute('data-review-edge-index')));});}
function memoryGraphData(fields){const nodes=[{id:'memory_schema',label:'Memory schema',kind:'root'}],edges=[];(fields||[]).forEach(f=>{nodes.push({id:f.id,label:f.label||f.id,kind:f.memory_type||'memory_field',field:f});edges.push({from:'memory_schema',to:f.id,relation:f.memory_type||'field'});});return{nodes,edges};}
function knowledgeGraphData(items,graphNodes,graphEdges){if((graphNodes||[]).length){return{nodes:(graphNodes||[]).map(n=>({id:n.id,label:n.label||n.id,kind:'graph_node',node:n})),edges:(graphEdges||[]).map(e=>({from:e.from,to:e.to,relation:e.relation||e.relation_type||'relates_to',edge:e}))};}const nodes=[],edges=[];(items||[]).forEach(k=>{nodes.push({id:k.id,label:k.normalized_statement||k.raw_statement||k.id,kind:'knowledge_item',item:k});if(k.domain){const domainId='domain::'+String(k.domain);if(!nodes.find(n=>n.id===domainId))nodes.push({id:domainId,label:k.domain,kind:'domain'});edges.push({from:k.id,to:domainId,relation:'domain'});}});return{nodes,edges};}
function renderMemoryFieldDetail(f,boxId='node_detail'){const box=document.getElementById(boxId);if(!box)return;box.innerHTML=`<div class='detail-box'><div class='id'>${esc(f.id)}</div><div class='label'>${esc(f.label)}</div><div class='detail-kv'><b>Memory type</b><span>${esc(val(f.memory_type))}</span><b>Persistence</b><span>${esc(val(f.persistence))}</span><b>Source</b><span>${esc(val(f.source))}</span><b>Description</b><span>${esc(val(f.description))}</span></div><h3>Schema</h3>${jsonBlock(f.schema||{})}<h3>Update policy</h3>${jsonBlock(f.update_policy||{})}</div>`;}
function renderKnowledgeItemDetail(k,boxId='node_detail'){const box=document.getElementById(boxId);if(!box)return;box.innerHTML=`<div class='detail-box'><div class='id'>${esc(k.id)}</div><div class='label'>${esc(k.type)}</div><div class='detail-kv'><b>Domain</b><span>${esc(val(k.domain))}</span><b>Confidence</b><span>${esc(val(k.confidence))}</span><b>Review</b><span>${esc(k.review&&k.review.status?k.review.status:'')}</span></div><h3>Normalized statement</h3><div class='small'>${esc(val(k.normalized_statement))}</div><h3>Raw statement</h3><div class='small'>${esc(val(k.raw_statement))}</div></div>`;}
function renderGraphNodeDetail(n){const box=document.getElementById('node_detail');if(!box)return;box.innerHTML=`<div class='detail-box'><div class='id'>${esc(n.id)}</div><div class='label'>${esc(n.label)}</div><div class='detail-kv'><b>Node type</b><span>${esc(val(n.node_type))}</span><b>Memory type</b><span>${esc(val(n.memory_type))}</span><b>Review</b><span>${esc(n.review&&n.review.status?n.review.status:'')}</span></div><h3>Raw graph node</h3>${jsonBlock(n)}</div>`;}
function renderGraphEdgeDetail(e){const box=document.getElementById('node_detail');if(!box)return;box.innerHTML=`<div class='detail-box'><div class='id'>${esc(e.from)} -> ${esc(e.to)}</div><div class='label'>${esc(val(e.relation)||val(e.relation_type)||'relates_to')}</div><div class='detail-kv'><b>From</b><span>${esc(e.from)}</span><b>To</b><span>${esc(e.to)}</span><b>Relation</b><span>${esc(val(e.relation))}</span><b>Relation type</b><span>${esc(val(e.relation_type))}</span></div><h3>Raw graph edge</h3>${jsonBlock(e)}</div>`;}
function ensureSubworkflowModal(){let modal=document.getElementById('subworkflow_modal');if(modal)return modal;modal=document.createElement('div');modal.id='subworkflow_modal';modal.className='modal hidden';modal.innerHTML="<div class='modal-backdrop' data-close-subworkflow='1'></div><div class='modal-card'><div class='modal-head'><div><h2 id='subworkflow_modal_title'>Subworkflow</h2><div id='subworkflow_modal_ref' class='small'></div></div><button class='btn secondary' data-close-subworkflow='1'>Close</button></div><div class='modal-layout' id='subworkflow_modal_layout'><div id='subworkflow_modal_body' class='graph modal-graph'></div><div class='modal-splitter' id='subworkflow_modal_splitter' aria-hidden='true' title='Drag to resize panels'></div><aside class='modal-side'><div id='subworkflow_resources' class='detail-box resource-panel'><h2>Knowledge, memory, and resource schema</h2><div class='small'>Click a subworkflow action node to inspect related resources.</div></div><h2>Detail inspector</h2><div id='subworkflow_node_detail' class='detail-box'><div class='small'>Click a subworkflow node or edge to inspect its details.</div></div><div class='feedback-section-gap'></div><h2>Structured feedback</h2><div class='row'><label>Target</label><select id='sw_fb_target'><option>workflow_node</option><option>workflow_edge</option><option>memory_schema</option><option>knowledge_item</option></select></div><div class='row'><label>Target id</label><input id='sw_fb_target_id'></div><div class='row'><label>Field</label><input id='sw_fb_field' value='workflow_node'></div><div class='row'><label>Issue</label><textarea id='sw_fb_issue'></textarea></div><div class='row'><label>Suggestion</label><textarea id='sw_fb_suggestion'></textarea></div><div class='row'><label>Severity</label><select id='sw_fb_severity'><option>medium</option><option>low</option><option>high</option></select></div><div class='btns'><button class='btn' id='sw_addFb'>Add feedback</button></div></aside></div></div>";document.body.appendChild(modal);modal.querySelectorAll('[data-close-subworkflow]').forEach(x=>{x.onclick=closeSubworkflowModal;});document.getElementById('sw_addFb').onclick=addSubworkflowFeedback;initModalSplit();return modal;}
function closeSubworkflowModal(){const modal=document.getElementById('subworkflow_modal');if(modal)modal.classList.add('hidden');}
function openSubworkflowModal(parentId){const parent=workflowNodeById(parentId);const modal=ensureSubworkflowModal();const title=document.getElementById('subworkflow_modal_title'),ref=document.getElementById('subworkflow_modal_ref'),body=document.getElementById('subworkflow_modal_body');title.textContent=parent?(parent.label||parent.id):'Subworkflow';ref.textContent=parent&&parent.subworkflow_ref?parent.subworkflow_ref:'';modal.setAttribute('data-parent-id',parentId||'');modal.classList.remove('hidden');document.getElementById('subworkflow_resources').innerHTML='<h2>Knowledge, memory, and resource schema</h2><div class="small">Click a subworkflow action node to inspect related resources.</div>';document.getElementById('subworkflow_node_detail').innerHTML='<div class="small">Click a subworkflow node or edge to inspect its details.</div>';if(!parent||!parent.nested_workflow||!parent.nested_workflow.nodes){body.innerHTML="<div class='notice'>This node references a subworkflow, but the child workflow was not embedded in this standalone review bundle.</div>";return;}currentTaskPreviewId=parentId;const nodes=actionNodes(parent.nested_workflow.nodes||[]),edges=workflowActionEdges(nodes,parent.nested_workflow.edges||[]);drawWorkflowGraph(body,nodes,edges,nodeId=>{renderNestedNodeDetail(parentId,nodeId,'subworkflow_node_detail');setSubworkflowFeedbackTarget(parentId,'workflow_node',nodeId,'workflow.nodes.'+parentId+'.nested_workflow.nodes.'+nodeId);},edgeIndex=>{const edge=workflowEdgeByIndex(edgeIndex,edges);if(!edge)return;renderEdgeDetailObject(edge,'Nested edge inside '+parentId,parent.nested_workflow.nodes||[],'subworkflow_node_detail');setSubworkflowFeedbackTarget(parentId,'workflow_edge',edge.from+'->'+edge.to,'workflow.nodes.'+parentId+'.nested_workflow.edges');});}
function renderTaskTabs(nodes){}
function resourcePanel(){let s=document.getElementById('resources');if(!s){s=document.createElement('div');s.id='resources';s.className='detail-box resource-panel';const detail=document.getElementById('node_detail');if(detail&&detail.parentNode)detail.parentNode.insertBefore(s,detail);else panel.appendChild(s);}return s;}
function relatedWorkflowResourcesFrom(actionId,nodes,edges){const byId={};(nodes||[]).forEach(n=>{byId[n.id]=n;});return(edges||[]).filter(e=>isResourceEdge(e)&&(e.from===actionId||e.to===actionId)).map(e=>{const other=e.from===actionId?byId[e.to]:byId[e.from];return other&&isDataNode(other)?{node:other,edge:e}:null;}).filter(Boolean);}
function relatedWorkflowResources(actionId){return relatedWorkflowResourcesFrom(actionId,data.workflow_graph.nodes||[],data.workflow_graph.edges||[]);}
function relatedKnowledgeItems(actionNode){const refs=new Set(knowledgeRefs(actionNode&&actionNode.knowledge_refs).concat(arrayValue(actionNode&&actionNode.linked_spec_ids).map(String)));return(data.narrative_knowledge.items||[]).filter(k=>refs.has(String(k.id)));}
function relatedMemoryFields(actionNode){const refs=new Set(arrayValue(actionNode&&actionNode.linked_spec_ids).map(String));return(data.memory_schema.fields||[]).filter(f=>refs.has(String(f.id)));}
function renderResourcePanel(actionId){const s=resourcePanel();const action=workflowNodeById(actionId);s.innerHTML='<h2>Knowledge, memory, and resource schema</h2>';if(!action||isDataNode(action)){s.innerHTML+='<div class="small">Click an action node to inspect related knowledge, memory, files, APIs, schemas, and other resource nodes.</div>';return;}s.innerHTML+=`<div class='small'>Showing resources linked to: ${esc(action.label||action.id)}</div>`;const pairs=relatedWorkflowResources(action.id),knowledge=relatedKnowledgeItems(action),memory=relatedMemoryFields(action);if(!pairs.length&&!knowledge.length&&!memory.length){s.innerHTML+='<div class="notice">No linked resource nodes, knowledge items, or memory fields were found for this action node.</div>';return;}const grid=document.createElement('div');grid.className='grid';pairs.forEach(({node,edge})=>{const visual=dataNodeVisual(node);const direction=edge.from===action.id?'action -> resource':'resource -> action';const c=document.createElement('div');c.className='card';c.innerHTML=`<button><div class='id'>${esc(node.id)}</div><div class='label'>${esc(node.label||visual.label)}</div><div class='small'>${esc(visual.label)} | ${esc(val(edge.relation))} | ${esc(direction)}</div><div class='small'>${esc(val(node.source_path||node.retrieval_mode||node.persistence))}</div></button>`;c.onclick=()=>{renderDetailObject(node,'Workflow resource linked by '+val(edge.relation));selectTarget('workflow_node',node.id,'workflow.nodes.'+node.id);};grid.appendChild(c);});knowledge.forEach(k=>{const c=document.createElement('div');c.className='card';c.innerHTML=`<button><div class='id'>${esc(k.id)}</div><div class='label'>${esc(k.type)}</div><div class='small'>${esc(k.normalized_statement||k.raw_statement)}</div></button>`;c.onclick=()=>{renderKnowledgeItemDetail(k);selectTarget('knowledge_item',k.id,'knowledge.items.'+k.id);};grid.appendChild(c);});memory.forEach(f=>{const c=document.createElement('div');c.className='card';c.innerHTML=`<button><div class='id'>${esc(f.id)}</div><div class='label'>${esc(f.label)}</div><div class='small'>${esc(f.memory_type)} | ${esc(f.persistence)}</div><div class='small'>${esc(f.description)}</div></button>`;c.onclick=()=>{renderMemoryFieldDetail(f);selectTarget('memory_schema',f.id,'memory.fields.'+f.id);};grid.appendChild(c);});s.appendChild(grid);}
function renderSubworkflowResourcePanel(parent,actionId){
const s=document.getElementById('subworkflow_resources');if(!s)return;
const nodes=parent&&parent.nested_workflow?parent.nested_workflow.nodes||[]:[];
const edges=parent&&parent.nested_workflow?parent.nested_workflow.edges||[]:[];
const action=(nodes||[]).find(n=>n.id===actionId);
s.innerHTML='<h2>Knowledge, memory, and resource schema</h2>';
if(!action||isDataNode(action)){s.innerHTML+='<div class="small">Click a subworkflow action node to inspect related resources.</div>';return;}
s.innerHTML+=`<div class='small'>Subworkflow resources linked to: ${esc(action.label||action.id)}</div>`;
const pairs=relatedWorkflowResourcesFrom(action.id,nodes,edges),knowledge=relatedKnowledgeItems(action),memory=relatedMemoryFields(action);
if(!pairs.length&&!knowledge.length&&!memory.length){s.innerHTML+='<div class="notice">No linked resource nodes, knowledge items, or memory fields were found for this subworkflow node.</div>';return;}
const grid=document.createElement('div');grid.className='grid';
pairs.forEach(({node,edge})=>{const visual=dataNodeVisual(node);const direction=edge.from===action.id?'action -> resource':'resource -> action';const c=document.createElement('div');c.className='card';c.innerHTML=`<button><div class='id'>${esc(node.id)}</div><div class='label'>${esc(node.label||visual.label)}</div><div class='small'>${esc(visual.label)} | ${esc(val(edge.relation))} | ${esc(direction)}</div><div class='small'>${esc(val(node.source_path||node.retrieval_mode||node.persistence))}</div></button>`;c.onclick=()=>{renderDetailObject(node,'Subworkflow resource linked by '+val(edge.relation),'subworkflow_node_detail');};grid.appendChild(c);});
knowledge.forEach(k=>{const c=document.createElement('div');c.className='card';c.innerHTML=`<button><div class='id'>${esc(k.id)}</div><div class='label'>${esc(k.type)}</div><div class='small'>${esc(k.normalized_statement||k.raw_statement)}</div></button>`;c.onclick=()=>renderKnowledgeItemDetail(k,'subworkflow_node_detail');grid.appendChild(c);});
memory.forEach(f=>{const c=document.createElement('div');c.className='card';c.innerHTML=`<button><div class='id'>${esc(f.id)}</div><div class='label'>${esc(f.label)}</div><div class='small'>${esc(f.memory_type)} | ${esc(f.persistence)}</div><div class='small'>${esc(f.description)}</div></button>`;c.onclick=()=>renderMemoryFieldDetail(f,'subworkflow_node_detail');grid.appendChild(c);});
s.appendChild(grid);
}
function renderWorkflowLegend(){const host=document.getElementById('workflow-legend');if(!host)return;let items=[];if(nodeColorTheme()==='subsystems'){items=['rwm','pg','ae','la','iac'].map(key=>subsystemPalette[key]).concat([{fill:'#EEF2F7',stroke:'#7B8794',label:'Unlabeled'},{fill:'#F8FAFC',stroke:'#64748B',label:'Status marker'}]);}else{items=[{fill:'#FFF1D6',stroke:'#B45309',label:'Human gate'},{fill:'#E8F3EE',stroke:'#607466',label:'Deterministic automation'},{fill:'#E9E4FB',stroke:'#6D28D9',label:'External stochastic LLM'},{fill:'#F8FAFC',stroke:'#64748B',label:'Status marker'}];}host.innerHTML=items.map(item=>`<span class='workflow-legend-item'><span class='workflow-swatch' style='background:${item.fill};border-color:${item.stroke}'></span>${esc(item.label)}</span>`).join('');}
function applyWorkflowTheme(){const nodes=workflowActionNodes(),edges=workflowActionEdges(nodes,data.workflow_graph.edges||[]);if(currentWorkflowGraphEl)drawWorkflowGraph(currentWorkflowGraphEl,nodes,edges);renderWorkflowLegend();if(currentTaskPreviewId&&document.getElementById('subworkflow_modal')&&!document.getElementById('subworkflow_modal').classList.contains('hidden'))openSubworkflowModal(currentTaskPreviewId);}
function renderWorkflow(){if(!config.include_workflow)return;const s=section('Workflow graph','workflow');const nodes=workflowActionNodes(),edges=workflowActionEdges(nodes,data.workflow_graph.edges||[]);if(hasBackwardEdges(nodes,edges)&&config.graph_layout!=='process'){const note=document.createElement('div');note.className='notice';note.textContent='This workflow contains backward/loop edges. Use graph_layout = "process" for a clearer loop-aware review diagram.';s.appendChild(note);}const toolbar=document.createElement('div');toolbar.className='workflow-toolbar';toolbar.innerHTML=`<div class='small'>Graph nodes show the action flow only. Knowledge, memory, file, API, schema, and other resource nodes are shown contextually after selecting an action node.</div><div class='row-inline'><label for='workflow-theme-select'>Node colors</label><select id='workflow-theme-select'><option value='default'>Default</option><option value='subsystems'>Subsystems</option></select></div>`;s.appendChild(toolbar);const legend=document.createElement('div');legend.id='workflow-legend';legend.className='workflow-legend';s.appendChild(legend);const g=document.createElement('div');g.className='graph';g.id='workflow_graph_canvas';s.appendChild(g);currentWorkflowGraphEl=g;drawWorkflowGraph(g,nodes,edges);document.getElementById('workflow-theme-select').value=config.node_color_theme||'default';document.getElementById('workflow-theme-select').onchange=e=>{config.node_color_theme=e.target.value;applyWorkflowTheme();};renderWorkflowLegend();renderTaskTabs(nodes);}
function renderFeedback(){if(!config.include_feedback_panel){panel.style.display='none';return;}const schema=data.feedback_schema;panel.innerHTML=`<div id='resources' class='detail-box resource-panel'><h2>Knowledge, memory, and resource schema</h2><div class='small'>Click an action node to inspect related knowledge, memory, files, APIs, schemas, and other resource nodes.</div></div><h2>Detail inspector</h2><div id='node_detail' class='detail-box'><div class='small'>Click a workflow node or edge to inspect its details.</div></div><div class='feedback-section-gap'></div><h2>Structured feedback</h2><div class='row'><label>Target</label><select id='fb_target'>${schema.targets.map(t=>`<option>${esc(t)}</option>`).join('')}</select></div><div class='row'><label>Target id</label><input id='fb_target_id'></div><div class='row'><label>Field</label><input id='fb_field' value='workflow_node'></div><div class='row'><label>Issue type</label><select id='fb_issue_type'>${schema.issue_types.map(t=>`<option>${esc(t)}</option>`).join('')}</select></div><div class='row'><label>Issue</label><textarea id='fb_issue'></textarea></div><div class='row'><label>Suggestion</label><textarea id='fb_suggestion'></textarea></div><div class='row'><label>Severity</label><select id='fb_severity'>${schema.severities.map(t=>`<option>${esc(t)}</option>`).join('')}</select></div><div class='btns'><button class='btn' id='addFb'>Add feedback</button><button class='btn secondary' id='copyFb'>Copy JSON</button><button class='btn secondary' id='downloadFb'>Download JSON</button></div><h3>Feedback JSON</h3><pre id='fb_json'>{"feedback":[]}</pre><div id='fb_list'></div>`;document.getElementById('addFb').onclick=addFeedback;document.getElementById('copyFb').onclick=()=>navigator.clipboard&&navigator.clipboard.writeText(document.getElementById('fb_json').textContent);document.getElementById('downloadFb').onclick=downloadFeedback;}
function setSideWidth(px){const min=300,max=Math.max(320,wrap.getBoundingClientRect().width-320-10);const clamped=Math.min(max,Math.max(min,Math.round(px)));wrap.style.setProperty('--side-width',clamped+'px');try{localStorage.setItem('agentr-review-side-width',String(clamped));}catch(e){}return clamped;}
function initSplit(){if(!wrap||!splitter)return;try{const saved=Number(localStorage.getItem('agentr-review-side-width'));if(Number.isFinite(saved)&&saved>0)setSideWidth(saved);}catch(e){}splitter.addEventListener('pointerdown',e=>{if(e.button!==0)return;e.preventDefault();splitter.classList.add('dragging');const move=ev=>{setSideWidth(wrap.getBoundingClientRect().right-ev.clientX);};const up=()=>{splitter.classList.remove('dragging');window.removeEventListener('pointermove',move);window.removeEventListener('pointerup',up);};window.addEventListener('pointermove',move);window.addEventListener('pointerup',up);});window.addEventListener('resize',()=>{const current=parseFloat(getComputedStyle(wrap).getPropertyValue('--side-width'));if(Number.isFinite(current)&&current>0)setSideWidth(current);});}
function setModalSideWidth(px){const layout=document.getElementById('subworkflow_modal_layout');if(!layout)return 0;const min=300,max=Math.max(320,layout.getBoundingClientRect().width-420-12);const clamped=Math.min(max,Math.max(min,Math.round(px)));layout.style.setProperty('--modal-side-width',clamped+'px');try{localStorage.setItem('agentr-review-modal-side-width',String(clamped));}catch(e){}return clamped;}
function initModalSplit(){const layout=document.getElementById('subworkflow_modal_layout'),split=document.getElementById('subworkflow_modal_splitter');if(!layout||!split||split.dataset.bound==='1')return;split.dataset.bound='1';try{const saved=Number(localStorage.getItem('agentr-review-modal-side-width'));if(Number.isFinite(saved)&&saved>0)setModalSideWidth(saved);}catch(e){}split.addEventListener('pointerdown',e=>{if(e.button!==0)return;e.preventDefault();split.classList.add('dragging');const move=ev=>{setModalSideWidth(layout.getBoundingClientRect().right-ev.clientX);};const up=()=>{split.classList.remove('dragging');window.removeEventListener('pointermove',move);window.removeEventListener('pointerup',up);};window.addEventListener('pointermove',move);window.addEventListener('pointerup',up);});}
function addFeedback(){const item={id:'fb_'+String(feedback.length+1).padStart(3,'0'),target:fb_target.value,target_id:fb_target_id.value,item_id:fb_target_id.value,field:fb_field.value,issue_type:fb_issue_type.value,issue:fb_issue.value,suggestion:fb_suggestion.value,severity:fb_severity.value,status:'open',source:'human',created_at:new Date().toISOString(),metadata:{review_id:data.review_id}};feedback.push(item);fb_issue.value='';fb_suggestion.value='';renderFeedbackList();}
function setSubworkflowFeedbackTarget(parentId,target,targetId,field){const prefix=parentId?parentId+'::':'';const targetBox=document.getElementById('sw_fb_target'),idBox=document.getElementById('sw_fb_target_id'),fieldBox=document.getElementById('sw_fb_field');if(targetBox)targetBox.value=target;if(idBox)idBox.value=prefix+(targetId||'');if(fieldBox)fieldBox.value=field||target;}
function addSubworkflowFeedback(){const modal=document.getElementById('subworkflow_modal');const parentId=modal?modal.getAttribute('data-parent-id')||'':'';const item={id:'fb_'+String(feedback.length+1).padStart(3,'0'),target:sw_fb_target.value,target_id:sw_fb_target_id.value,item_id:sw_fb_target_id.value,field:sw_fb_field.value,issue_type:'subworkflow_review',issue:sw_fb_issue.value,suggestion:sw_fb_suggestion.value,severity:sw_fb_severity.value,status:'open',source:'human',created_at:new Date().toISOString(),metadata:{review_id:data.review_id,subworkflow_parent_id:parentId}};feedback.push(item);sw_fb_issue.value='';sw_fb_suggestion.value='';renderFeedbackList();}
function renderFeedbackList(){const txt=JSON.stringify({feedback},null,2);document.getElementById('fb_json').textContent=txt;const list=document.getElementById('fb_list');list.innerHTML='';feedback.forEach((f,i)=>{const d=document.createElement('div');d.className='feedback-item';d.innerHTML=`<b>${esc(f.target)}</b> ${esc(f.target_id)}<br>${esc(f.issue)}<br><button class='btn secondary' data-i='${i}'>Delete</button>`;d.querySelector('button').onclick=()=>{feedback.splice(i,1);renderFeedbackList();};list.appendChild(d);});}
function downloadFeedback(){const blob=new Blob([JSON.stringify({feedback},null,2)],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=(data.review_id||'agentr_design_feedback')+'.json';a.click();URL.revokeObjectURL(a.href);}
renderWorkflow();renderFeedback();renderResourcePanel();initSplit();const firstNode=workflowActionNodes()[0];if(firstNode)selectWorkflowNode(firstNode.id);
