AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
Polish silkscreen dashboard and record complete face-isolated 1080p replays #19
Companion to PRs #17 and #18. This remains a draft prototype while the Rust CLI still invokes Python server/placement helpers; it must not be described as a fully Rust implementation or a released feature.
Fix the header to the 44px three-zone grammar, add the same white outline SVG for header/favicon, use Adom fonts and theme tokens, and replace the emoji gear with an outline icon. Embedded font bytes are sourced from the recorded Adom font URLs because cross-origin font fetch failed in the live Hydrogen webview.
Fix face filtering for candidate overlays and replay eligibility. Fresh runs hide unplaced labels. Camera-follow now frames board legends that have no component anchor; a face switch resets framing. Render optional curved leaders. Show active native text heights in millimetres. Record a dedicated 1920x1080 canvas instead of the arbitrary webview panel dimensions, with a final whole-board hold.
Verification: live WV header measured 44px; both brand fonts loaded; full ESC search covered 285 front and 152 back labels, including unresolved attempts and later rework. Rejected the first bottom recording because legends were placed outside the retained component close-up; retook after fixing the camera. Separate top and bottom 1080p clips, followed by native KiCad top/bottom verification, produce an 88.875s / 6.66MB review video. Native v32 text read-back finds all 437 placements; all 149 footprints, 4042 segments, 444 vias and 57 zones unchanged. DRC returns baseline 13 errors / 539 warnings with no new silk overlaps. Seven new curved leaders were checked.
Remaining audit findings: server console ring, comprehensive toast feedback/reset settings, styling of toggles, full non-KiCad adapters, and same-port restart/URL consistency require work. The fresh typography is an experimental review, not a production readability sign-off. Please preserve this honest status and integrate the reusable UI changes with the Rust implementation rather than publishing a misleading completion claim.
Diff Skip to comments
@@ -0,0 +1,37 @@+<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Adom AI Flow · Silkscreen</title><link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23e6edf3%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%223%22%20y%3D%223%22%20width%3D%2218%22%20height%3D%2218%22%20rx%3D%222%22/%3E%3Cpath%20d%3D%22M7%2016V8h4M7%2012h3M14%208h3M15.5%208v8M14%2016h3%22/%3E%3C/svg%3E"><style>+:root{color-scheme:dark;font:14px system-ui;color:#e7edef;background:#141a20}*{box-sizing:border-box}body{margin:0;height:100vh;display:grid;grid-template-rows:48px 1fr 66px}header,footer{display:flex;align-items:center;gap:12px;padding:8px 18px;background:#1d252d;border-bottom:1px solid #35424d}header b{font-size:17px}button,select,input{font:inherit;color:inherit;background:#26333e;border:1px solid #536371;border-radius:5px;padding:7px}button{cursor:pointer}button:hover{border-color:#51cdc2}button.active{background:#24504f}main{display:grid;grid-template-columns:1fr 300px;min-height:0}#stage{position:relative;overflow:hidden;background:#111a1f}canvas{display:block;width:100%;height:100%;touch-action:none}#three{position:absolute;inset:0;display:none}aside{padding:18px;overflow:auto;background:#1a222a}h2{font-size:23px;margin:8px 0}p{line-height:1.5;color:#b5c4cd}.badge{border:1px solid #648076;border-radius:12px;padding:3px 10px;color:#7be0c7;font-size:12px}#native{color:#ffd293}.muted{color:#93a5b1;font-size:12px}#scrub{flex:1}#progress{height:5px;background:#33444f;margin:14px 0}#progress span{display:block;background:#48cdbb;height:100%;width:0}#legend{display:grid;gap:8px;margin-top:24px}.swatch{display:inline-block;width:12px;height:12px;margin-right:8px;border-radius:2px}#settings{position:absolute;right:20px;top:55px;z-index:8;background:#26313b;border:1px solid #566676;padding:18px;border-radius:8px}#settings label{display:block;margin:12px 0}#hud{position:absolute;left:16px;top:14px;pointer-events:none;background:#142028de;border:1px solid #41535e;padding:10px 14px;border-radius:6px}#error{color:#ff9999}a{color:#67dacc}footer{border-top:1px solid #35424d}#fit{margin-left:auto}@media(max-width:800px){main{grid-template-columns:1fr 220px}header{gap:5px;padding:7px}header b{font-size:14px}}++@font-face{font-family:Satoshi;src:url(data:font/woff2;base64,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)}+@font-face{font-family:Familjen;src:url(data:font/woff2;base64,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)}+:root{--bg:#0d1117;--surface:#161b22;--border:#30363d;--accent:#00b8b1;font-family:Satoshi,sans-serif;color:#c9d1d9;background:var(--bg)}+body{grid-template-rows:44px 44px 1fr 66px}header{height:44px;padding:0 16px;gap:16px;background:var(--surface);border-color:var(--border)}+.brand{display:flex;align-items:center;gap:8px;font:600 16px Familjen,sans-serif;color:var(--accent);position:relative;white-space:nowrap}.brand img{width:16px;height:16px}.subject{border-left:1px solid var(--border);padding-left:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#e6edf3}.actions{margin-left:auto;display:flex;gap:8px}.actions button{width:32px;height:32px;padding:6px}.actions svg{width:18px;height:18px;fill:none;stroke:#e6edf3;stroke-width:1.8}.toolbar{display:flex;gap:12px;align-items:center;padding:4px 16px;background:var(--surface);border-bottom:1px solid var(--border)}button,select,input{background:#21262d;border-color:var(--border);border-radius:8px}button:hover{border-color:var(--accent)}button.active{background:#163e3d}aside{background:var(--surface)}h2{font-family:Familjen,sans-serif}.muted,p{color:#8b949e}#settings{top:50%;left:50%;right:auto;transform:translate(-50%,-50%);min-width:340px;z-index:20;box-shadow:0 0 0 100vmax #0004;background:#21262d;border-color:var(--border)}#appinfo{position:absolute;top:30px;left:0;width:320px;padding:16px;background:#21262d;border:1px solid var(--border);border-radius:8px;z-index:25;color:#c9d1d9;white-space:normal;font:14px Satoshi,sans-serif;pointer-events:auto}#textsize{font-variant-numeric:tabular-nums;color:#e6edf3;padding:10px 0}#fit{margin-left:0}@media(max-width:720px){.subject{display:none}.toolbar{gap:5px}main{grid-template-columns:1fr 210px}}+</style></head><body><header><div class="brand" tabindex="0"><img src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23e6edf3%22%20stroke-width%3D%221.8%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%223%22%20y%3D%223%22%20width%3D%2218%22%20height%3D%2218%22%20rx%3D%222%22/%3E%3Cpath%20d%3D%22M7%2016V8h4M7%2012h3M14%208h3M15.5%208v8M14%2016h3%22/%3E%3C/svg%3E" alt=""><span>Adom AI Flow · Silkscreen</span><div id="appinfo" hidden>Silkscreen dashboard · development build<br>Prototype: Rust CLI, Python helpers, browser UI<br>Authored by Codex Astra<br><a href="https://wiki.adom.inc/adom/adom-aiflow">AI Flow wiki and source</a></div></div><div class="subject"><span id="subject">Board</span> <span class="muted"> · full-board label placement</span></div><div class="actions"><button id="gear" aria-label="Settings" title="Settings"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="m9 3-1 3-3 1 1 3-2 2 2 2-1 3 3 1 1 3h6l1-3 3-1-1-3 2-2-2-2 1-3-3-1-1-3z"/></svg></button></div></header><div class="toolbar"><span id="mode" class="badge">Connecting</span><select id="face" aria-label="Board face"><option value="F.SilkS">Top only</option><option value="B.SilkS">Bottom only</option></select><button id="two" class="active">2D layout</button><button id="threeBtn">3D inspection</button><button id="fit">Fit board</button></div><main><div id="stage"><canvas id="board"></canvas><div id="three"></div><div id="hud"><div id="headline">Loading board geometry</div><div class="muted" id="geometry"></div></div></div><aside><div class="muted">Placement decision</div><h2 id="part">Waiting</h2><p id="reason">No solver events yet.</p><div id="progress"><span></span></div><div id="count"></div><div id="textsize">Text height in mm appears with each placement</div><p id="native">Native CAD review pending</p><label>Inspect component <select id="pick"><option value="">Whole board</option></select></label><div id="legend"><div><i class="swatch" style="background:#516a78"></i>Fitted body / reserved envelope</div><div><i class="swatch" style="background:#cbad4c"></i>Via / mask opening</div><div><i class="swatch" style="background:#ffb963"></i>Sampled candidate</div><div><i class="swatch" style="background:#57dcc4"></i>Selected proposal</div><div><i class="swatch" style="background:#f18389"></i>Existing collision</div></div><p class="muted">Selection is a proposal, not an edit to the EDA board. Native text bounds drive collision checks. Preview text uses the browser font.</p><div id="error"></div></aside></main><footer><button id="live">Follow live</button><button id="play">Replay</button><input type="range" id="scrub" min="-1" max="0" value="-1" aria-label="Event timeline"><span id="stamp" class="muted">0 / 0</span><button id="record">Record walkthrough</button><button id="short">Record 5s overview</button></footer><div id="settings" hidden><b>Playback & display</b><label>Events per second <input id="speed" type="number" min="0.5" max="60" step="0.5"></label><label><input id="obstacles" type="checkbox"> Show obstacles</label><label><input id="follow" type="checkbox" checked> Follow selected component</label><label>Idle shutdown, hours <input id="ttl" type="number" min="1" max="168"></label><p class="muted">No AI/provider calls. Solver runs independently of playback.</p></div>+<script type="module">+const $=s=>document.querySelector(s),canvas=$('#board'),ctx=canvas.getContext('2d');let data,history=[],state={},selected='',bounds,view,drag=null,viewer=null,scene=null,labelTexture=null,threeGroups=[],recording=false,handledRecording=null,recordReplay;const api=async(path,body)=>{const r=await fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({...body,aiThread:state.aiThread||'Silkscreen dashboard user'})});const j=await r.json();if(!r.ok)throw Error(j.error);if(j.state)await apply(j.state);return j};window.silkscreen={ready:false};+let cameraAnimation=0;function frame(b){cancelAnimationFrame(cameraAnimation);if(recording&&!state.summary&&view){const from=[...view],start=performance.now();const tick=now=>{const t=Math.min(1,(now-start)/220),ease=t*t*(3-2*t);view=from.map((v,i)=>v+(b[i]-v)*ease);draw();if(t<1)cameraAnimation=requestAnimationFrame(tick)};cameraAnimation=requestAnimationFrame(tick)}else{view=[...b];draw()}}+function fitPart(ref){const os=data.obstacles.filter(o=>o.reference===ref&&o.layer===state.layer);if(!os.length)return;const b=[Math.min(...os.map(o=>o.box[0]))-3,Math.min(...os.map(o=>o.box[1]))-3,Math.max(...os.map(o=>o.box[2]))+3,Math.max(...os.map(o=>o.box[3]))+3];frame(b);if(viewer){const cam=viewer.getCamera();cam.setTarget(new window.BABYLON.Vector3((b[0]+b[2])/2,-(b[1]+b[3])/2,0));cam.radius=Math.max(b[2]-b[0],b[3]-b[1])*1.8}}+function proposals(){const map=new Map();let start=0;for(let k=Math.min(state.cursor,history.length-1);k>=0;k--)if(history[k].kind==='start'){start=k;break}for(let k=start;k<=state.cursor&&k<history.length;k++){const e=history[k];if(e.kind==='placement')map.set(e.id,e.candidate)}return map}+function paint(c,w,h,extent,texture=false){c.clearRect(0,0,w,h);if(!texture){c.fillStyle='#122128';c.fillRect(0,0,w,h)}const bw=extent[2]-extent[0],bh=extent[3]-extent[1],scale=Math.min(w/bw,h/bh)*.93,sx=texture?w/bw:scale,sy=texture?h/bh:scale,ox=texture?-extent[0]*sx:(w-bw*scale)/2-extent[0]*scale,oy=texture?-extent[1]*sy:(h-bh*scale)/2-extent[1]*scale;c.save();c.translate(ox,oy);c.scale(sx,sy);c.lineWidth=1/scale;+function rect(b,color,fill=true){c.fillStyle=color;c.strokeStyle=color;if(fill)c.fillRect(b[0],b[1],b[2]-b[0],b[3]-b[1]);else c.strokeRect(b[0],b[1],b[2]-b[0],b[3]-b[1])}+if(!texture){rect(data.bounds,'#233b32');if(state.settings?.showObstacles)for(const o of data.obstacles){if(o.layer!==state.layer)continue;const col=o.kind.startsWith('fitted')?(o.reference===selected?'#607b87':'#394d58'):o.kind.startsWith('via')?'#bd9f43':'#889081';if(o.kind.startsWith('via')){const b=o.box;c.beginPath();c.arc((b[0]+b[2])/2,(b[1]+b[3])/2,(b[2]-b[0])/2,0,Math.PI*2);c.strokeStyle=col;c.stroke()}else rect(o.box,col,true)}}+const placed=proposals();const replaced=new Set([...placed.values()].flatMap(p=>(p.children||[]).map(x=>x.index)));+function label(l,color,b){if(l.leaderCurve){const p=l.leaderCurve;c.beginPath();c.moveTo(...p[0]);c.bezierCurveTo(...p[1],...p[2],...p[3]);c.strokeStyle=color;c.lineWidth=.08;c.stroke()}c.save();c.translate(l.x??l.position?.[0]??0,l.y??l.position?.[1]??0);c.rotate(-(l.angle||0)*Math.PI/180);c.font=`${l.size||l.font||.5}px sans-serif`;c.textAlign='center';c.textBaseline='middle';c.fillStyle=color;(l.text||'').split('\n').forEach((s,i,all)=>c.fillText(s,0,(i-(all.length-1)/2)*(l.size||l.font||.5)*1.5));c.restore();if(!texture&&b)rect(b,color,false)}+for(let i=0;i<data.labels.length;i++){const l=data.labels[i];if(data.freshPlacement||l.layer!==state.layer||replaced.has(i))continue;label(l,l.collision?'#f1838999':'#bfccc666')}+for(const p of placed.values()){if(p.layer&&p.layer!==state.layer)continue;for(const child of p.children||[p]){const original=child.index!=null?data.labels[child.index]:null;if(original&&original.layer!==state.layer)continue;label(child,'#79f5d9',texture?null:child.box)}}+const e=history[state.cursor];if(e?.candidate){const cs=e.candidate.children||[e.candidate];$('#textsize').textContent=cs.map(x=>(x.text||e.id)+': '+Number(x.size||x.font||0).toFixed(2)+' mm').join(' · ')}if(!texture&&e?.kind==='candidate'&&(!e.layer||e.layer===state.layer)&&(!e.candidate.layer||e.candidate.layer===state.layer)){for(const b of e.candidate.boxes||[e.candidate.box])rect(b,'#ffbd70',false);for(const child of e.candidate.children||[e.candidate])label(child,'#ffbd70')}c.restore();if(!texture&&recording){c.fillStyle='#101820df';c.fillRect(12,h-62,w-24,48);c.fillStyle='#dfece8';c.font='16px system-ui';c.fillText('REPLAY · '+(e?.id||'Silkscreen')+' · '+(e?.reason||e?.kind||''),24,h-33)} }+function draw(){if(!data||!view)return;const r=canvas.getBoundingClientRect();canvas.width=Math.max(1,r.width*devicePixelRatio);canvas.height=Math.max(1,r.height*devicePixelRatio);paint(ctx,canvas.width,canvas.height,view);if(labelTexture){const t=labelTexture.getContext();paint(t,2048,2048,data.bounds,true);labelTexture.update(false)}}+async function show3d(){if(viewer)return;$('#headline').textContent='Loading 3D geometry';try{await import('https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js?v=20260915a');const B=window.BABYLON;viewer=window.Adom3DViewerBabylon9.init($('#three'),{zUp:true,showGround:false,showViewCube:false});scene=viewer.getScene();const b=data.bounds,cx=(b[0]+b[2])/2,cy=(b[1]+b[3])/2;const board=B.MeshBuilder.CreateBox('Board envelope',{width:b[2]-b[0],height:b[3]-b[1],depth:1.6},scene);board.position.set(cx,-cy,-.8);const bm=new B.PBRMaterial('board',scene);bm.albedoColor=B.Color3.FromHexString('#24563b');bm.metallic=0;bm.roughness=.85;board.material=bm;+for(const o of data.obstacles.filter(o=>o.kind.startsWith('fitted'))){const b=o.box;const mesh=B.MeshBuilder.CreateBox(o.reference,{width:b[2]-b[0],height:b[3]-b[1],depth:o.height||1},scene);mesh.position.set((b[0]+b[2])/2,-(b[1]+b[3])/2,(o.height||1)/2+.03);const mat=new B.StandardMaterial('reserved envelope',scene);mat.diffuseColor=B.Color3.FromHexString('#5b707d');mat.alpha=.72;mesh.material=mat;threeGroups.push({mesh,o});if(o.glb){try{const imported=await B.SceneLoader.ImportMeshAsync('',o.glb,'',scene),group=new B.TransformNode('fitted_'+o.reference,scene);for(const m of imported.meshes)if(!m.parent)m.parent=group;group.scaling.setAll(1000);const theta=(o.at[2]||0)*Math.PI/180,dx=o.offset?.[0]||0,dy=o.offset?.[1]||0;group.position.set(o.at[0]+dx*Math.cos(theta)-dy*Math.sin(theta),-o.at[1]+dx*Math.sin(theta)+dy*Math.cos(theta),o.offset?.[2]||0);group.rotation.z=theta;mesh.setEnabled(false);const buckets=new Map();for(const m of imported.meshes){if(!m.getTotalVertices())continue;m.computeWorldMatrix(true);const mat=m.material,key=JSON.stringify([mat?.albedoColor?.asArray(),mat?.diffuseColor?.asArray(),mat?.emissiveColor?.asArray(),mat?.alpha,mat?.metallic,mat?.roughness]);if(!buckets.has(key))buckets.set(key,[]);buckets.get(key).push(m)}for(const ms of buckets.values()){const mat=ms[0].material;for(const m of ms)m.material=mat;if(ms.length>1)B.Mesh.MergeMeshes(ms,true,true,undefined,false,false)}}catch(error){$('#error').textContent='Model fallback for '+o.reference+': '+error.message}}}+labelTexture=new B.DynamicTexture('silkscreen proposals',{width:2048,height:2048},scene,false);labelTexture.hasAlpha=true;const plane=B.MeshBuilder.CreatePlane('Silkscreen preview',{width:b[2]-b[0],height:b[3]-b[1]},scene);plane.position.set(cx,-cy,.015);const pm=new B.StandardMaterial('silkscreen',scene);pm.diffuseTexture=labelTexture;pm.opacityTexture=labelTexture;pm.useAlphaFromDiffuseTexture=true;pm.emissiveColor=B.Color3.White();pm.disableLighting=true;pm.backFaceCulling=false;plane.material=pm;viewer.getCamera().setTarget(new B.Vector3(cx,-cy,0));viewer.getCamera().radius=100;viewer.getCamera().beta=.65;viewer.getCamera().alpha=-1.6;viewer.getCamera().minZ=.01;viewer.getCamera().upperRadiusLimit=300;draw();$('#subject').textContent=data.name||data.sourceBoard||'Board';if(data.nativeReview)$('#native').textContent=data.nativeReview;$('#geometry').textContent='Fitted component models · rectangular board preview · native CAD review still required';}catch(e){$('#error').textContent=e.message}}+async function apply(s){const oldTotal=history.length;if(s.events)history=s.events;else if(s.total!==oldTotal){const full=await(await fetch('state')).json();history=full.events}const changedFace=s.layer&&s.layer!==state.layer;state={...state,...s};if(changedFace){selected='';view=[...bounds]}window.silkscreen.state=state;const e=history[state.cursor];$('#mode').textContent=state.mode==='live'?'Live solver feed':'Recorded-event replay';$('#part').textContent=e?.id||'Whole board';$('#reason').textContent=e?.reason|| (e?.kind==='complete'?`Search complete; ${e.unresolved?.length??0} unresolved groups.`:'Waiting for the next solver event.');$('#headline').textContent=e?.kind==='complete'?'Candidate layout · review pending':e?.id?`${e.id} · ${e.kind}`:'ESC silkscreen placement';$('#face').value=state.layer;$('#stamp').textContent=`${Math.max(0,state.cursor+1)} / ${history.length}`;$('#scrub').max=Math.max(0,history.length-1);$('#scrub').value=state.cursor;$('#count').textContent=`${[...proposals().values()].filter(p=>!p.layer||p.layer===state.layer).length} labels placed on this face`;$('#progress span').style.width=(history.length?(state.cursor+1)/history.length*100:0)+'%';$('#play').textContent=state.playing?'Pause':'Replay';$('#speed').value=state.settings.speed;$('#ttl').value=state.settings.ttlHours;$('#obstacles').checked=state.settings.showObstacles;const focus=e?.candidate?.children?.[0]?.reference||e?.id;if(focus&&selected!==focus&&$('#follow').checked){selected=focus;const child=e?.candidate?.children?.[0];if(child?.box&&!child.reference){const b=child.box;frame([b[0]-3,b[1]-3,b[2]+3,b[3]+3])}else fitPart(selected)}$('#three').style.display=state.view==='3d'?'block':'none';$('#board').style.visibility=state.view==='3d'?'hidden':'visible';if(state.view==='3d'){await show3d();if(scene){const plane=scene.getMeshByName('Silkscreen preview');if(plane)plane.position.z=state.layer==='B.SilkS'?-1.615:.015;if(s.layer)viewer.getCamera().beta=state.layer==='B.SilkS'?Math.PI-.65:.65}}draw();if(state.recordRequest&&state.recordRequest.id!==handledRecording){handledRecording=state.recordRequest.id;setTimeout(()=>recordReplay(state.recordRequest.kind==='overview').catch(e=>$('#error').textContent=e.message),0)}}+try{data=await(await fetch('board.json')).json();bounds=data.bounds;view=[...bounds];const refs=[...new Set(data.obstacles.filter(o=>o.kind.startsWith('fitted')).map(o=>o.reference))].sort();for(const r of refs){const o=document.createElement('option');o.value=r;o.textContent=r;$('#pick').append(o)}$('#subject').textContent=data.name||data.sourceBoard||'Board';if(data.nativeReview)$('#native').textContent=data.nativeReview;$('#geometry').textContent=`${refs.length} fitted bodies · ${data.viaCount} vias · native measured text bounds`;+await apply(await(await fetch('state')).json());const stream=new EventSource('stream');stream.addEventListener('state',e=>apply(JSON.parse(e.data)).catch(e=>$('#error').textContent=e.message));stream.onerror=()=>$('#mode').textContent='Reconnecting';window.silkscreen.ready=true;+$('#live').onclick=()=>api('control',{mode:'live'});$('#play').onclick=()=>api('control',{mode:'replay',summary:false,playing:!state.playing,cursor:state.cursor>=history.length-1?-1:state.cursor});$('#scrub').oninput=e=>api('control',{mode:'replay',playing:false,cursor:+e.target.value});$('#face').onchange=e=>api('control',{layer:e.target.value});$('#two').onclick=()=>api('control',{view:'2d'});$('#threeBtn').onclick=()=>api('control',{view:'3d'});$('#fit').onclick=()=>{selected='';frame(bounds);if(viewer){const b=bounds;viewer.getCamera().setTarget(new window.BABYLON.Vector3((b[0]+b[2])/2,-(b[1]+b[3])/2,0));viewer.getCamera().radius=100}};$('#pick').onchange=e=>{selected=e.target.value;$('#follow').checked=false;selected?fitPart(selected):frame(bounds)};$('#gear').onclick=()=>$('#settings').hidden=!$('#settings').hidden;$('#speed').onchange=e=>api('settings',{speed:+e.target.value});$('#ttl').onchange=e=>api('settings',{ttlHours:+e.target.value});$('#obstacles').onchange=e=>api('settings',{showObstacles:e.target.checked});+canvas.onwheel=e=>{e.preventDefault();const f=e.deltaY>0?1.15:.87;const r=canvas.getBoundingClientRect(),px=(e.clientX-r.left)/r.width,py=(e.clientY-r.top)/r.height,w=view[2]-view[0],h=view[3]-view[1];view=[view[0]+w*px*(1-f),view[1]+h*py*(1-f),view[0]+w*px*(1-f)+w*f,view[1]+h*py*(1-f)+h*f];draw()};canvas.onpointerdown=e=>{drag={x:e.clientX,y:e.clientY,v:[...view]};canvas.setPointerCapture(e.pointerId)};canvas.onpointermove=e=>{if(!drag)return;const r=canvas.getBoundingClientRect(),dx=(e.clientX-drag.x)/r.width*(drag.v[2]-drag.v[0]),dy=(e.clientY-drag.y)/r.height*(drag.v[3]-drag.v[1]);view=[drag.v[0]-dx,drag.v[1]-dy,drag.v[2]-dx,drag.v[3]-dy];draw()};canvas.onpointerup=()=>drag=null;+const captureCanvas=document.createElement('canvas');captureCanvas.width=1920;captureCanvas.height=1080;const captureCtx=captureCanvas.getContext('2d');function paintCapture(){paint(captureCtx,1920,1080,view);captureCtx.fillStyle='#0d1117e8';captureCtx.fillRect(0,0,1920,50);captureCtx.fillStyle='#e6edf3';captureCtx.font='22px sans-serif';captureCtx.fillText('Adom AI Flow · '+(state.layer==='F.SilkS'?'Top':'Bottom')+' silkscreen · '+$('#textsize').textContent,20,32)}+recordReplay=async function(summary=false){if(recording)return;const latest=history.map((e,i)=>e.kind==='start'?i:-1).filter(i=>i>=0).pop()||0;await api('control',{mode:'replay',playing:false,summary,cursor:latest-1,view:'2d'});$('#follow').checked=!summary;frame(bounds);recording=true;const chunks=[],rec=new MediaRecorder(captureCanvas.captureStream(30),{mimeType:'video/webm'});rec.ondataavailable=e=>chunks.push(e.data);rec.onstop=()=>{recording=false;const blob=new Blob(chunks,{type:'video/webm'}),a=document.createElement('a');window.silkscreen.recordingBlob=blob;const reader=new FileReader();reader.onload=()=>api('recording',{kind:summary?'overview':'walkthrough',video:reader.result.split(',')[1]}).catch(e=>$('#error').textContent=e.message);reader.readAsDataURL(blob);a.href=URL.createObjectURL(blob);a.download='silkscreen-replay.webm';a.click();$('#record').textContent='Record replay';draw()};paintCapture();rec.start();$('#record').textContent='Recording replay…';await api('control',{playing:true});const timer=setInterval(()=>{draw();paintCapture();if(!state.playing&&state.cursor>=history.length-1){clearInterval(timer);frame(bounds);const hold=setInterval(paintCapture,33);setTimeout(()=>{clearInterval(hold);paintCapture();rec.stop()},1200)}},100)};$('#record').onclick=()=>recordReplay(false);$('#short').onclick=()=>recordReplay(true);+let infoTimer;const brand=$('.brand'),info=$('#appinfo');brand.onmouseenter=()=>{clearTimeout(infoTimer);infoTimer=setTimeout(()=>info.hidden=false,600)};brand.onmouseleave=()=>{clearTimeout(infoTimer);infoTimer=setTimeout(()=>info.hidden=true,220)};brand.onfocus=()=>info.hidden=false;document.addEventListener('keydown',e=>{if(e.key==='Escape'){$('#settings').hidden=true;info.hidden=true}});+new ResizeObserver(draw).observe($('#stage'));draw();}catch(e){$('#error').textContent=e.stack}+</script></body></html>+@@ -0,0 +1,166 @@+#!/usr/bin/env python3+"""AI Flow silkscreen observer: persisted solver events, no provider calls or CAD edits."""+import argparse,base64,hashlib,json,os,pathlib,subprocess,sys,time,threading,http.server,urllib.request,urllib.parse,signal+P=pathlib.Path;ROOT=P(__file__).resolve().parent;REG=P.home()/'.adom/instances/aiflow-silkscreen';REG.mkdir(parents=True,exist_ok=True)+def atomic(p,d):+ q=p.with_suffix('.tmp');q.write_text(json.dumps(d,indent=2));q.replace(p)+p=argparse.ArgumentParser();p.add_argument('action',choices=['start','serve','show','ls','state','control','stop']);p.add_argument('--run',required=True,type=P);p.add_argument('--ai-thread',required=True);p.add_argument('--port',default='auto');p.add_argument('--surface',choices=['wv','pup'],default='wv');p.add_argument('--target');p.add_argument('--url');p.add_argument('--json',default='{}');p.add_argument('--reason');a=p.parse_args();run=a.run.resolve();key=hashlib.sha256((a.ai_thread+str(run)).encode()).hexdigest()[:16];reg=REG/(key+'.json');snap=REG/(key+'-snapshot.json');folder=run/'silkscreen-dashboard';folder.mkdir(exist_ok=True);events=folder/'events.jsonl';manifest=folder/'board.json';version=hashlib.sha256((ROOT/'index.html').read_bytes()+P(__file__).read_bytes()).hexdigest()[:12]+def entry():+ try:+ e=json.loads(reg.read_text());r=json.load(urllib.request.urlopen('http://127.0.0.1:'+str(e['port'])+'/health',timeout=2));return e if r.get('key')==key else None+ except Exception:return None+def request(e,path,data=None):+ body=None if data is None else json.dumps(dict(data,aiThread=a.ai_thread)).encode();return json.load(urllib.request.urlopen(urllib.request.Request('http://127.0.0.1:'+str(e['port'])+path,data=body,headers={'Content-Type':'application/json'}),timeout=5))+if a.action=='ls':+ for f in REG.glob('*.json'):+ if f.name.endswith('-snapshot.json'):continue+ e=json.loads(f.read_text())+ try:os.kill(e['pid'],0)+ except ProcessLookupError:f.unlink();continue+ print(json.dumps(dict(e,versionDrift=e.get('version')!=version)))+ sys.exit()+e=entry()+if a.action in ['start','show']:+ if not e:+ log=open(folder/'server.log','ab');subprocess.Popen([sys.executable,str(P(__file__).resolve()),'serve','--run',str(run),'--ai-thread',a.ai_thread,'--port',a.port],stdout=log,stderr=log,start_new_session=True)+ for _ in range(80):+ time.sleep(.1);e=entry()+ if e:break+ if not e:raise SystemExit('ERROR: server did not become healthy; see '+str(folder/'server.log'))+ url=a.url or ('http://'+str(e['port'])+'.localhost:'+urllib.parse.urlparse((P.home()/'.adom/hd-proxy-url').read_text().strip()).port.__str__()+'/' if (P.home()/'.adom/hd-proxy-url').exists() else 'http://127.0.0.1:'+str(e['port'])+'/')+ if a.action=='show':+ if a.surface=='wv':opened=subprocess.run(['adom-cli','hydrogen','webview','open-or-refresh','--name','AI Flow silkscreen','--url',url],check=True,capture_output=True,text=True);print(opened.stdout);e['tabId']=json.loads(opened.stdout).get('tabId');atomic(reg,e)+ else:+ if not a.target or not a.url:raise SystemExit('ERROR: pup needs --target and a desktop-reachable --url')+ subprocess.run(['adom-bridge','pup_open_window',json.dumps({'url':url,'background':True}),'--target',a.target,'--ai-thread',a.ai_thread],check=True)+ print(json.dumps(dict(e,url=url,reused=True)));sys.exit()+if a.action in ['state','control','stop']:+ if not e:raise SystemExit('ERROR: not running; use start')+ if a.action=='stop' and not a.reason:raise SystemExit('ERROR reason_required: explain why this task-owned server should stop')+ print(json.dumps(request(e,'/shutdown' if a.action=='stop' else '/control' if a.action=='control' else '/state',dict(json.loads(a.json),reason=a.reason) if a.action!='state' else None)));sys.exit()+if e:print('OK: already running');sys.exit()+settingspath=REG/'settings.json';settings=json.loads(settingspath.read_text()) if settingspath.exists() else dict(ttlHours=24,speed=4,showObstacles=True)+state=json.loads(snap.read_text()) if snap.exists() else dict(mode='live',cursor=-1,playing=False,layer='F.SilkS',view='2d');clients=set();lock=threading.RLock();last=time.time();revision=0;history=[];offset=0++def payload():return dict(state,settings=settings,events=history,aiThread=a.ai_thread,version=version)+def save():atomic(snap,state)+def broadcast():+ global revision+ revision+=1+ # Bounded state messages contain the current event; history is fetched separately.+ data=dict(state,settings=settings,event=history[state['cursor']] if 0<=state['cursor']<len(history) else None,total=len(history),revision=revision)+ for q in list(clients):+ try:q.write(('event: state\ndata: '+json.dumps(data)+'\n\n').encode());q.flush()+ except Exception:clients.discard(q)+class Handler(http.server.BaseHTTPRequestHandler):+ def log_message(self,*args):pass+ def response(self,data,code=200,ctype='application/json'):+ b=data if isinstance(data,bytes) else json.dumps(data).encode();self.send_response(code);self.send_header('Content-Type',ctype);self.send_header('Cache-Control','no-store');self.send_header('Content-Length',str(len(b)));self.end_headers();self.wfile.write(b)+ def do_GET(self):+ global last+ last=time.time();path=urllib.parse.urlparse(self.path).path+ if path=='/health':return self.response(dict(key=key,version=version))+ if path=='/state':return self.response(payload())+ if path=='/settings':return self.response(dict(settings=settings,enums={'ttlHours':'1..168','speed':'0.5..60','showObstacles':'boolean'}))+ if path=='/stream':+ self.send_response(200);self.send_header('Content-Type','text/event-stream');self.send_header('Cache-Control','no-cache');self.send_header('X-Accel-Buffering','no');self.end_headers();self.wfile.write((':'+(' '*2048)+'\n\n').encode());clients.add(self.wfile);broadcast()+ try:+ while True:time.sleep(15);self.wfile.write(b': heartbeat\n\n');self.wfile.flush()+ except Exception:clients.discard(self.wfile)+ return+ if path in ['/','/index.html']:return self.response((ROOT/'index.html').read_bytes(),ctype='text/html')+ if path=='/board.json':return self.response(manifest.read_bytes() if manifest.exists() else b'{"obstacles":[],"labels":[],"bounds":[0,0,100,100]}')+ if path.startswith('/recordings/'):+ f=(folder/path.lstrip('/')).resolve()+ if not f.is_relative_to((folder/'recordings').resolve()) or not f.is_file():return self.response({'error':'not found'},404)+ return self.response(f.read_bytes(),ctype='video/webm')+ if path.startswith('/models/'):+ f=(folder/path.lstrip('/')).resolve()+ if not f.is_relative_to((folder/'models').resolve()) or not f.is_file():return self.response({'error':'not found'},404)+ return self.response(f.read_bytes(),ctype='model/gltf-binary')+ self.response({'error':'not found'},404)+ def do_POST(self):+ global last+ last=time.time()+ try:+ if int(self.headers.get('Content-Length','0'))>(40_000_000 if self.path=='/recording' else 65536):raise ValueError('request too large')+ body=json.loads(self.rfile.read(int(self.headers.get('Content-Length','0'))))+ if not body.get('aiThread'):return self.response(dict(errorCode='caller_identity_required',hint='Pass aiThread or CLI --ai-thread.'),400)+ with lock:+ if self.path=='/recording':+ video=base64.b64decode(body['video'],validate=True)+ if len(video)<1024 or video[:4]!=bytes.fromhex('1a45dfa3'):raise ValueError('Refused empty or invalid WebM recording')+ clips=folder/'recordings';clips.mkdir(exist_ok=True);name='replay-'+str(time.time_ns())+'.webm';(clips/name).write_bytes(video);state['lastRecording']={'file':str(clips/name),'url':'recordings/'+name,'kind':body.get('kind','walkthrough'),'bytes':len(video),'replay':True};state['recordRequest']=None;save();broadcast();self.response({'ok':True,'recording':state['lastRecording']});return+ if self.path=='/shutdown':+ if not body.get('reason'):raise ValueError('reason_required')+ self.response({'ok':True});threading.Thread(target=shutdown,daemon=True).start();return+ if self.path=='/settings':+ for k,v in body.items():+ if k=='aiThread':continue+ if k=='ttlHours' and isinstance(v,(int,float)) and 1<=v<=168:settings[k]=v+ elif k=='speed' and isinstance(v,(int,float)) and .5<=v<=60:settings[k]=v+ elif k=='showObstacles' and isinstance(v,bool):settings[k]=v+ else:raise ValueError('Allowed settings: ttlHours 1..168, speed .5..60, showObstacles boolean')+ atomic(settingspath,settings)+ elif self.path=='/control':+ for k,v in body.items():+ if k in ['aiThread','reason']:continue+ if k=='record' and v in ['overview','walkthrough']:state['recordRequest']={'id':time.time_ns(),'kind':v}+ elif k=='mode' and v in ['live','replay']:state[k]=v+ elif k=='layer' and v in ['F.SilkS','B.SilkS']:state[k]=v+ elif k=='view' and v in ['2d','3d']:state[k]=v+ elif k in ['playing','summary'] and isinstance(v,bool):state[k]=v+ elif k=='cursor' and isinstance(v,int) and -1<=v<len(history):state[k]=v+ else:raise ValueError('Allowed controls: mode live/replay, layer F.SilkS/B.SilkS, view 2d/3d, playing boolean, cursor within event history')+ if state['mode']=='live':state['cursor']=len(history)-1;state['playing']=False+ else:return self.response({'error':'not found'},404)+ save();broadcast();self.response({'ok':True,'state':state})+ except (ValueError,TypeError,KeyError) as ex:self.response({'error':str(ex)},400)+http.server.ThreadingHTTPServer.allow_reuse_address=True+import fcntl+lease=open(REG/(key+'.lock'),'w')+try:fcntl.flock(lease,fcntl.LOCK_EX|fcntl.LOCK_NB)+except BlockingIOError:raise SystemExit('OK: another instance for this run/thread is starting or running')+for attempt in range(30):+ try:+ server=http.server.ThreadingHTTPServer(('127.0.0.1',0 if a.port=='auto' else int(a.port)),Handler);break+ except OSError:+ if attempt==29:raise+ time.sleep(.1)+server.daemon_threads=True+atomic(reg,dict(pid=os.getpid(),port=server.server_port,thread=a.ai_thread,run=str(run),version=version,startedAt=time.time()))+def shutdown():+ save();current=json.loads(reg.read_text()) if reg.exists() else {};reg.unlink(missing_ok=True)+ if current.get('tabId'):+ subprocess.run(['adom-cli','hydrogen','workspace','remove-tab','--tab-id',current['tabId']],capture_output=True)+ server.shutdown()+def watch():+ global offset,history+ previous=time.time()+ while True:+ time.sleep(.05)+ with lock:+ if events.exists():+ with events.open() as f:+ f.seek(offset)+ while True:+ pos=f.tell();line=f.readline()+ if not line or not line.endswith('\n'):break+ try:history.append(json.loads(line))+ except json.JSONDecodeError:pass+ offset=f.tell()+ if state['mode']=='live' and state['cursor']!=len(history)-1:state['cursor']=len(history)-1;broadcast()+ if state['mode']=='replay' and state['playing']:+ start=max([i for i,e in enumerate(history) if e.get('kind')=='start'] or [0])+ eligible=[i for i in range(start,len(history)) if (not history[i].get('layer') or history[i].get('layer')==state.get('layer')) and (not state.get('summary') or history[i].get('kind') in ['placement','unresolved'])]+ speed=max(1,len(eligible)/5) if state.get('summary') else settings['speed']+ elapsed=time.time()-previous+ if elapsed>=1/speed:+ remaining=[i for i in eligible if i>state['cursor']];count=max(1,int(elapsed*speed));previous+=count/speed+ if remaining:state['cursor']=remaining[min(count,len(remaining))-1]+ if len(remaining)<=count:state['cursor']=len(history)-1;state['playing']=False+ broadcast()+ else:previous=time.time()+ if time.time()-last>settings['ttlHours']*3600:shutdown();return+threading.Thread(target=watch,daemon=True).start();signal.signal(signal.SIGTERM,lambda *_:threading.Thread(target=shutdown,daemon=True).start());print(json.dumps({'port':server.server_port}),flush=True);server.serve_forever();save();reg.unlink(missing_ok=True)+@@ -0,0 +1,1 @@+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#e6edf3" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 16V8h4M7 12h3M14 8h3M15.5 8v8M14 16h3"/></svg>@@ -0,0 +1,10 @@+[+ {+ "url": "https://adom.inc/fonts/satoshi/satoshi-400-normal.woff2",+ "bytes": 25516+ },+ {+ "url": "https://adom.inc/fonts/familjen-grotesk/familjen-grotesk-latin-600-normal.woff2",+ "bytes": 11232+ }+]@@ -0,0 +1,36 @@+# Silkscreen dashboard++An optional, EDA-neutral observer of placement candidates and recorded solver decisions. It makes no AI/provider calls and never edits a native board. Native geometry, text metrics, undoable edits and 2D/3D refresh belong to each EDA bridge. The ESC integration currently exports KiCad geometry; Fusion and Altium adapters still need implementation and native tests. Do not claim those integrations work from schema compatibility alone.++## Displaying this app (REQUIRED — read before showing anything)++Use `adom-aiflow silkscreen-dashboard show --ai-thread "<name>" --run <run>` to open the named Hydrogen webview. Never use VS Code Simple Browser. `--surface pup` requires an explicit desktop `--target` and desktop-reachable `--url`. Keep standard workspace navigation. Reuse the same run/thread instance; `ls` shows instances and version drift.++## Inputs and live observation++Place `board.json` and an append-only `events.jsonl` in `<run>/silkscreen-dashboard/`. These are separate artifacts; never edit the measurement ledger `run.jsonl`. The board manifest has `bounds` in mm, `labels`, `obstacles`, `viaCount`, source-board identity and validation state. Each label carries text, x/y, size, angle, face and an optional existing-collision flag. Each obstacle carries a stable reference, kind, layer and conservative world-space box. Model obstacles may additionally supply a relative GLB URL, footprint `at` in KiCad XY/degrees, model offset and height. This initial 3D adapter supports top-mounted, unit-scale models without separate model rotations; bake other transforms into the supplied GLB or refuse the preview. The viewport's board slab is an envelope, not a manufactured-board export; pads, routed copper and drilled geometry are not yet native-equivalent. Keep that distinction visible.++`adom-aiflow silkscreen-layout --input candidates.json --out layout.json --events <run>/silkscreen-dashboard/events.jsonl --run <run> --ai-thread "<name>"` emits timestamped start, sampled candidate, selected proposal, unresolved and completion events with a solve ID. Events are generated by real computation, not invented for a film. Candidate samples are bounded: this is not every internal solver iteration. Pre-filtered inputs cannot reconstruct prior rejected candidates. The event reason describes the observable constraint/result, not private internal reasoning.++SSE updates do not reload the DOM. Live follows new events; replay has shared play/pause, scrub and speed controls. Playback is independent of solver speed. A selected proposal is not a native edit. Native application/DRC/assembled-visibility evidence must be recorded separately; the dashboard never promotes proposal success into native acceptance.++## Recording++Record replay exports a WebM of the 2D canvas with a persistent REPLAY label and the current recorded decision. Detailed review and a short final-video segment are separate artifacts. Preserve original event timestamps and raw native per-step clips. Do not call replay footage a live native edit or a KiCad recording. Verify the exported video actually plays. The browser-font preview does not replace KiCad's measured stroke-font geometry. Final-video pacing should not force the solver to sleep or trigger extra AI calls for every label.++## Acceptance++Audit the whole board on both faces: fitted body projections including the area between pads, every via/hole, actual mask openings, labels and leader paths. Missing Fab geometry is not empty space. Keep each label associated with its own component. Unresolved labels remain visible and block acceptance. The independent inventory in `silkscreen-audit` must be derived from the whole board, not from the same potentially incomplete obstacle list.++## Automated recording++Use `adom-aiflow silkscreen-dashboard control --json '{"record":"overview"}' --run <run> --ai-thread "<name>"` (or `walkthrough`). An open dashboard records through the same UI path and uploads the completed WebM into `<run>/silkscreen-dashboard/recordings/`; read `state.lastRecording` for the path. A queued request is not a completed clip. Verify nonempty decoded frames and visible replay/native-review status. Normalize WebM with ffmpeg `fps=30` before H.264 composition; MediaRecorder can report a 1000 Hz nominal rate. Keep each wiki asset below its cap.++## Full-board silkscreen review++A full placement demonstration starts with no authored labels in the candidate occupancy, then attempts every required label. Reuse text content, native glyph metrics and electrical anchors, not earlier accepted positions. Preserve unresolved attempts and subsequent rework in the event history. Front and back are separate collision/placement passes in one run. Filter candidates, selected labels, leaders and playback events by the active face; never overlay the opposite face during a recording.++Show the active label's native text height in millimetres, including distinct reference/value sizes. Maximize readable size near its physical feature; 0.20 mm is a last-resort InstaPCB value, not a default. Prefer short curved leader lines whenever contact/pin association is unclear. Route leaders to the feature's visible rim, keep them off holes, pads, bodies and other text, and verify both faces in the native EDA. Include leaders as actual placement/rework events.++Record each face as a 1920x1080 detailed replay with an explicit replay label. End the review video with the same revision in the user's native EDA, including top and bottom inspection. A dashboard preview or API success is not proof of a faithful native transfer. Keep native raw clips separate from captioned/replayed dashboard clips.+
Comments
Log in to comment.
No comments yet.