← Commit history

Publish 1.0.66

John Lauer ·1737c2a693 ·1mo ago ·parent 555bedd
4 files changed +34−5
dashboard/kicad_dashboard.py+4−1
@@ -49,7 +49,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from urllib.parse import unquote, urlparse -VERSION = "2.2.7"+VERSION = "2.3.0" AI_THREAD = "kicad-dashboard" DEFAULT_PORT = 8931 UI_DIR = Path(__file__).resolve().parent / "ui"@@ -1003,6 +1003,9 @@ def do_action(target, name, args):             return {"success": False, "error": "phrase required"}         r = stage_phrase(phrase)         return {"success": True, "phrase": phrase, **r}+    if name == "demo_control":+        cmd = str(args.get("cmd") or "").strip().lower()+        return ab(target, "kicad_demo", {"control": cmd}, timeout=30)     if name == "restore_settings":         return ab(target, "kicad_set_settings", {"restoreDefaults": True}, timeout=60)     if name == "part_tour":
dashboard/ui/index.html+28−2
@@ -228,7 +228,15 @@ main a:hover{border-bottom-color:var(--accent)}   <div class="card">     <div class="led-label">Guided demo</div>     <div class="led-sub">Six beats on a generated sample project: schematic, 2D board, 3D board, symbol, footprint, 3D chip. Runs entirely in the background, and each finished beat streams back a screenshot.</div>-    <div class="btnrow"><button class="btn primary" id="btnDemo">Run the demo</button></div>+    <div class="btnrow">+      <button class="btn primary" id="btnDemo">Run the demo</button>+      <span id="demoCtl" style="display:none;gap:8px">+        <button class="btn" id="btnDemoPause" data-tip="Pause after the current beat finishes">Pause</button>+        <button class="btn" id="btnDemoPrev" data-tip="Replay the previous beat">Prev</button>+        <button class="btn" id="btnDemoNext" data-tip="Move on to the next beat">Next</button>+        <button class="btn" id="btnDemoExit" data-tip="Stop the tour, silence narration, clear the caption" style="color:var(--red)">Exit</button>+      </span>+    </div>     <div class="bar" id="demoBar"><div></div></div>     <div class="barlabel" id="demoLabel"></div>     <div class="shots" id="demoShots"></div>@@ -436,8 +444,11 @@ $('#btnDemo').onclick=async()=>{   const s=await act('demo');const d=s.data||s;   if(d.success===false){toast('demo failed to start: '+(d.error||'unknown'),'err');b.disabled=false;return;}   $('#demoBar').style.display='block';+  $('#demoCtl').style.display='inline-flex';+  let demoPaused=false;   const poll=setInterval(async()=>{     const p=await act('demo_progress');const q=p.data||p;+    if(typeof q.paused==='boolean'&&q.paused!==demoPaused){demoPaused=q.paused;$('#btnDemoPause').textContent=demoPaused?'Play':'Pause';}     $('#demoBar>div').style.width=(q.percent||0)+'%';     $('#demoLabel').textContent=(q.percent||0)+'% '+(q.stepLabel||'');     if(q.stepShot&&q.stepShot.image){@@ -445,9 +456,24 @@ $('#btnDemo').onclick=async()=>{       c.innerHTML='<img src="data:image/png;base64,'+q.stepShot.image+'" alt="'+(q.stepShot.title||'')+'"><div class="cap">'+(q.stepShot.title||q.stepShot.step||'')+'</div>';       $('#demoShots').appendChild(c);     }-    if(q.done){clearInterval(poll);b.disabled=false;toast(q.error?('demo failed: '+q.error):'demo complete','' + (q.error?'err':'ok'));refresh(true);}+    if(q.done){clearInterval(poll);b.disabled=false;$('#demoCtl').style.display='none';toast(q.error?('demo failed: '+q.error):(q.result&&q.result.exited?'demo stopped':'demo complete'),'' + (q.error?'err':'ok'));refresh(true);}   },3000); };+// Demo transport: mirrors the ab toast buttons; the bridge applies commands+// between beats (a beat that is mid-open finishes first).+async function demoCtl(cmd){+  const r=await act('demo_control',{cmd});const d=(r&&r.data)||r||{};+  if(d.error){toast('demo control failed: '+d.error,'err');return;}+  if(cmd==='pause'){$('#btnDemoPause').textContent='Play';toast('pausing after this beat','ok');}+  if(cmd==='play'){$('#btnDemoPause').textContent='Pause';toast('resuming','ok');}+  if(cmd==='exit')toast('stopping the demo','ok');+  if(cmd==='next')toast('skipping ahead','ok');+  if(cmd==='prev')toast('replaying the previous beat','ok');+}+$('#btnDemoPause').onclick=()=>demoCtl($('#btnDemoPause').textContent==='Pause'?'pause':'play');+$('#btnDemoPrev').onclick=()=>demoCtl('prev');+$('#btnDemoNext').onclick=()=>demoCtl('next');+$('#btnDemoExit').onclick=()=>demoCtl('exit'); $('#btnPartTour').onclick=async()=>{   const page=$('#partPage').value.trim();   const b=$('#btnPartTour');b.disabled=true;
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "kicad-bridge",-  "version": "1.0.65",+  "version": "1.0.66",   "type": "app",   "description": "Skills for your container so your AI knows how to drive the KiCad bridge. The bridge runtime itself is the release zip; Adom Bridge loads that.",   "tags": [
page.json+1−1
@@ -4,7 +4,7 @@   "slug": "kicad-bridge",   "title": "KiCad - the KiCad Bridge",   "brief": "Reference implementation of the KiCad bridge — multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.",-  "version": "1.0.65",+  "version": "1.0.66",   "tags": [     "kicad",     "pcb",