← Commit history

Polish dashboard with stage-aware project overview and MIT community contribution links

John Lauer ·849e4ae3a7 · ·parent 6673c34
7 files changed +161−22
Cargo.lock+10−10
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-aiflow"-version = "0.1.72"+version = "0.1.73" dependencies = [  "aiflow-analyze",  "aiflow-board",@@ -24,7 +24,7 @@ dependencies = [  [[package]] name = "aiflow-analyze"-version = "0.1.72"+version = "0.1.73" dependencies = [  "serde",  "serde_json",@@ -33,7 +33,7 @@ dependencies = [  [[package]] name = "aiflow-board"-version = "0.1.72"+version = "0.1.73" dependencies = [  "roxmltree",  "serde",@@ -42,7 +42,7 @@ dependencies = [  [[package]] name = "aiflow-bridge"-version = "0.1.72"+version = "0.1.73" dependencies = [  "aiflow-board",  "serde",@@ -51,7 +51,7 @@ dependencies = [  [[package]] name = "aiflow-copper"-version = "0.1.72"+version = "0.1.73" dependencies = [  "aiflow-board",  "aiflow-grid",@@ -61,7 +61,7 @@ dependencies = [  [[package]] name = "aiflow-grid"-version = "0.1.72"+version = "0.1.73" dependencies = [  "aiflow-board",  "serde",@@ -70,7 +70,7 @@ dependencies = [  [[package]] name = "aiflow-place"-version = "0.1.72"+version = "0.1.73" dependencies = [  "aiflow-board",  "serde",@@ -79,7 +79,7 @@ dependencies = [  [[package]] name = "aiflow-pours"-version = "0.1.72"+version = "0.1.73" dependencies = [  "aiflow-board",  "aiflow-copper",@@ -89,7 +89,7 @@ dependencies = [  [[package]] name = "aiflow-router"-version = "0.1.72"+version = "0.1.73" dependencies = [  "aiflow-board",  "aiflow-grid",@@ -99,7 +99,7 @@ dependencies = [  [[package]] name = "aiflow-run"-version = "0.1.72"+version = "0.1.73" dependencies = [  "serde",  "serde_json",
Cargo.toml+1−1
@@ -14,7 +14,7 @@ members = [ ]  [workspace.package]-version = "0.1.72"+version = "0.1.73" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
SKILL.md+14
@@ -333,3 +333,17 @@ Refill acknowledgement is asynchronous. Request it once, then read without refil ## Local thermal-via planning  Thermal-via candidates and existing-via counts must be within 3 mm of the specified heat pads and fit completely inside a planned same-net copper region on each outer layer. A remote same-net region does not satisfy this requirement. Missing polygon coordinates use the board outline; malformed or missing geometry refuses coverage. This is a conservative geometric prerequisite, not proof of native filled connectivity or thermal capacity: preflight with native refill/DRC and verify the actual heat path before landing. A TO-220 case-mounted heatsink requires an explicit package/interface/sink/airflow analysis; adding board vias cannot replace that path. Shortfalls remain visible and do not justify changing temperature limits or acknowledging unverified cooling.++## Contribute to AI Flow++When the user asks “Help me contribute an improvement to adom-aiflow through a pull request”,+read `dev-skills/review-safe-contributions/SKILL.md` from the public source repository.+Discuss the specific improvement, clone the current immutable source, implement a focused+change, run the relevant checks and review the diff before opening the requested PR.+Source and pull requests: https://wiki.adom.inc/adom/adom-aiflow/prs.+AI Flow is MIT licensed; keep notices and third-party licenses intact.++The dashboard adapts to early design stages without requiring a board. Optional saved+schematic, simulation or board images belong in `<run>/timeline/presentation.json`+with an accurate caption and a same-origin image filename; see `tools/timeline/README.md`.+Do not present a saved capture as live state or a workflow completion as hardware validation.
bin/adom-aiflow
⋯ 1 unchanged line ⋯
package.json+3−2
@@ -1,7 +1,7 @@ {   "slug": "adom-aiflow",   "type": "app",-  "version": "0.1.72",+  "version": "0.1.73",   "title": "AI Flow",   "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad today; Altium, Fusion and Adom's own web apps next.",   "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",@@ -42,7 +42,8 @@     "place the parts",     "route the board",     "run.jsonl",-    "thermal analysis"+    "thermal analysis",+    "Help me contribute an improvement to adom-aiflow through a pull request"   ],   "dependencies": {     "adom/adom-lbr": "*"
tools/timeline/README.md+21−1
@@ -1,6 +1,6 @@ # Flow timeline -A live, single-file dashboard for an adom-aiflow run. It shows every step of the flow on one path, where the AI is right now, how many steps are left, a check mark on every finished step, and a purple arc for every time a later step sent the AI back to rework an earlier one. Built for a Hydrogen webview beside the AI chat (about 700 to 900 px wide), and it switches to a horizontal layout above 1300 px.+A live, single-file dashboard for an adom-aiflow run. It shows every step of the flow on one path, where the AI is right now, how many steps are left, a check mark on every finished step, and a purple arc for every time a later step sent the AI back to rework an earlier one. Built for a Hydrogen webview beside the AI chat (about 700 to 900 px wide), and it switches to a horizontal layout above 1700 px.  ## Run it @@ -46,3 +46,23 @@ If the file is missing or invalid the page shows "Waiting for the flow to start" Adom brand palette only: teal `#00b8b1` for done marks, progress and the current step, purple `#8c6bf7` for loop backs and rework, the signature gradient on the finish line once reached. Familjen Grotesk for display, DM Sans for body (Satoshi if installed), JetBrains Mono for numbers. Icons are white line drawings. Tooltips (600 ms hover, tap on touch) clamp to the viewport. Motion is turned off under `prefers-reduced-motion`.  `window.flowTimeline.state()` returns what is on screen, for scripted checks.++## Project overview and contributions++The overview leads with the active task and completed workflow steps. Without an image,+it shows stage-specific guidance for intake, requirements, sourcing, schematic and+simulation; it never waits for a board or invents design evidence. A saved schematic,+simulation plot or board image can be supplied in `presentation.json` beside the page:++```json+{"image":"project-view.png","alt":"Saved schematic","caption":"Schematic review · saved 2026-10-09"}+```++For an early-stage run, omit `image` and supply `title` and `summary` for the real project brief or requirements. Optional `projectTitle` supplies a human-readable project name. These are presentation text, never gate evidence.++Only same-origin PNG/JPEG/WebP assets are accepted. Label the capture and date accurately;+a static capture is not a live 3D viewer. Missing/failed assets retain the stage overview.+The presentation file is preserved when CLI commands refresh the timeline.++The community card links to the public MIT license, source and pull requests. Its prompt+helps users contribute through their AI; follow the root skill's contribution guidance.
tools/timeline/index.html+112−8
@@ -384,6 +384,54 @@ body.waiting-on .appbar ~ *:not(.waiting) { visibility: hidden; }   .halo::before, .halo::after { display: none; }   .arc.new { stroke-dashoffset: 0; } }++/* Project overview: keep the work ahead of the workflow machinery. */+body { --pad: 28px; }+.hero, body.wide .hero { padding-top: 30px; padding-bottom: 22px; }+.hero h1, body.wide .hero h1 { font-size: clamp(26px, 3vw, 38px); white-space: normal; letter-spacing: -.7px; }+.finishline { margin-top: 10px; }+.overview { position: relative; display: grid; grid-template-columns: minmax(0,1.15fr) minmax(0,1fr); gap: 20px; margin: 0 var(--pad) 24px; }+.overview .card { padding: 24px; }+.board-preview { min-height: 250px; margin: 0; display: grid; place-items: center; overflow: hidden; background: radial-gradient(ellipse at center, #163534, #111820 75%); border: 1px solid var(--border-soft); border-radius: 16px; position: relative; }+.board-preview img { width: 100%; height: 290px; object-fit: contain; }+.board-preview figcaption { position: absolute; bottom: 0; left: 0; right: 0; padding: 10px 16px; background: #0d1117e8; font-size: 12px; color: var(--text-2); }+.preview-empty { text-align: center; padding: 30px; color: var(--text-2); }+.preview-empty svg { width: 56px; height: 56px; margin-bottom: 12px; color: var(--accent); }+.preview-empty strong { display: block; color: var(--text); font-size: 20px; margin-bottom: 6px; }+.overview .now h2 { font-size: 30px; margin-top: 18px; }+.overview .now p { font-size: 16px; }+.overview .now { border-top: 2px solid var(--accent); }+.recent-work { margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--border-soft); }+.recent-work ul { list-style: none; padding: 0; margin: 10px 0 0; display: flex; flex-wrap: wrap; gap: 8px; }+.recent-work li { color: #8bd5a5; font-size: 12px; padding: 5px 9px; background: #16271f; border-radius: 6px; }+.section-heading { margin: 24px var(--pad) 4px; display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }+.section-heading h2 { font-size: 18px; margin: 0; color: var(--text); }+.section-heading span { font-size: 12px; color: var(--text-2); }+.community { position: relative; margin: 24px var(--pad) 30px; padding: 22px 24px; display: flex; gap: 24px; align-items: center; justify-content: space-between; border: 1px solid var(--border-soft); border-radius: 14px; background: var(--surface); }+.community h2 { margin: 0 0 6px; font-size: 18px; color: var(--text); }+.community p { margin: 0; max-width: 570px; color: var(--text-2); font-size: 14px; }+.community nav { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; flex-shrink: 0; }+.community a { color: var(--text-body); text-decoration: none; font-size: 13px; border-radius: 6px; padding: 7px 2px; }+.community a.primary { background: var(--accent); color: var(--on-accent); padding: 10px 15px; font-weight: 700; }+.community a:hover { color: var(--accent-bright); }+.community a.primary:hover { color: var(--on-accent); background: var(--accent-bright); }+.community a:active { transform: translateY(1px); }+a:focus-visible, button:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 4px; }+.prompt-hint { font: inherit; border: 0; background: transparent; color: var(--text-2); padding: 0; width: 18px; height: 18px; cursor: pointer; }+.prompt-hint:hover { color: var(--accent-bright); }+.prompt-hint:active { transform: scale(.92); }+#copy-feedback { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: var(--elevated); padding: 10px 18px; border-radius: 8px; z-index: 100; }+body.wide .lower { grid-template-columns: 1fr; }+body.wide .stage { margin-top: 14px; }+@media (max-width: 800px) {+ body { --pad: 18px; }+ .overview { grid-template-columns: 1fr; gap: 14px; }+ .board-preview { min-height: 210px; }+ .board-preview img { height: 230px; }+ .community { flex-direction: column; align-items: flex-start; gap: 16px; }+ .section-heading { flex-wrap: wrap; }+}+ </style> </head> <body class="waiting-on">@@ -425,6 +473,22 @@ body.waiting-on .appbar ~ *:not(.waiting) { visibility: hidden; }   </div> </section> +<section class="overview" aria-label="Project overview">+  <figure class="board-preview">+    <div class="preview-empty" id="preview-empty"><svg class="i"><use href="#i-cpu"/></svg><strong id="project-phase">Define the project</strong><p id="project-context">Requirements, decisions, and evidence guide each stage.</p></div>+    <img id="board-image" hidden alt="Saved board view">+    <figcaption id="board-caption" hidden></figcaption>+  </figure>+  <div class="card now">+    <div class="eyebrow"><svg class="i"><use href="#i-now"/></svg>Now working on</div>+    <h2><span class="n" id="now-n"></span><span id="now-title"></span></h2>+    <p id="now-does"></p>+    <div class="facts" id="now-facts"></div>+    <div class="recent-work"><div class="eyebrow">Completed in this workflow</div><ul id="recent-work"></ul></div>+  </div>+</section>+<div class="section-heading"><h2>From idea to tested board</h2><span>Workflow progress · engineering checks still apply</span></div>+ <div class="callout-wrap" id="callout-wrap"><div><div class="callout" id="callout" data-tipkey="callout">   <div class="ic"><svg class="i"><use href="#i-loop"/></svg></div>   <div class="head"><span id="co-n">Loop back</span><em id="co-path"></em><span class="t" id="co-t"></span></div>@@ -442,18 +506,19 @@ body.waiting-on .appbar ~ *:not(.waiting) { visibility: hidden; } </main>  <section class="lower" id="lower">-  <div class="card now">-    <div class="eyebrow"><svg class="i"><use href="#i-now"/></svg>Now working on</div>-    <h2><span class="n" id="now-n"></span><span id="now-title"></span></h2>-    <p id="now-does"></p>-    <div class="facts" id="now-facts"></div>-  </div>   <div class="card">     <div class="eyebrow"><svg class="i"><use href="#i-loop"/></svg>Loop backs: where the AI went back to rework</div>     <ul class="looplist" id="looplist"></ul>   </div> </section> +<section class="community" aria-label="Contribute to AI Flow">+  <div><h2>Built together. Better for everyone. <button class="prompt-hint" id="contribute-prompt" title="Help me contribute an improvement to adom-aiflow through a pull request." aria-label="Copy contribution prompt">ⓘ</button></h2>+  <p>AI Flow is MIT open source. Share a fix, a skill, or a lesson from your builds so the whole community benefits.</p></div>+  <nav aria-label="Open source links"><a href="https://wiki.adom.inc/adom/adom-aiflow/files/LICENSE" target="_blank" rel="noopener noreferrer">MIT license</a><a href="https://wiki.adom.inc/adom/adom-aiflow" target="_blank" rel="noopener noreferrer">Source</a><a class="primary" href="https://wiki.adom.inc/adom/adom-aiflow/prs" target="_blank" rel="noopener noreferrer">Contribute a PR</a></nav>+</section>+<div id="copy-feedback" role="status" hidden></div>+ <div class="waiting" id="waiting">   <div>     <div class="orb"><svg class="i"><use href="#i-flow"/></svg></div>@@ -472,7 +537,7 @@ body.waiting-on .appbar ~ *:not(.waiting) { visibility: hidden; }   const params = new URLSearchParams(location.search);   // Relative on purpose: the page is served behind a proxy path. Never add a leading slash.   const SRC = (params.get('src') || 'timeline.json').replace(/^\/+/, '');-  const WIDE_AT = 1300;+  const WIDE_AT = 1700;   const reduced = matchMedia('(prefers-reduced-motion: reduce)');   const STATUSES = { todo: 1, current: 1, done: 1, redo: 1 };   const STATUS_LABEL = { done: 'Done', current: 'Working now', redo: 'Needs rework', todo: 'Not started' };@@ -496,6 +561,31 @@ body.waiting-on .appbar ~ *:not(.waiting) { visibility: hidden; }     return el;   }; +  let presentation = null;+  // Optional project presentation is separate from measured workflow state.+  // Only same-origin image assets; no arbitrary iframe, script, or remote tracking image.+  fetch('presentation.json', {cache: 'no-store'}).then(r => r.ok ? r.json() : null).then(p => {+    if (!p || typeof p !== 'object') return;+    presentation = p;+    if (model) render(model);+    if (typeof p.image !== 'string') return;+    const url = new URL(p.image, location.href);+    if (url.origin !== location.origin || !/\.(png|jpe?g|webp)$/i.test(url.pathname)) return;+    const img = $('board-image');+    img.onload = () => { img.hidden = false; $('preview-empty').hidden = true; $('board-caption').hidden = false; };+    img.alt = String(p.alt || 'Saved board view');+    $('board-caption').textContent = String(p.caption || 'Saved board view · not a live render');+    img.src = url.href;+  }).catch(() => {});+  $('contribute-prompt').addEventListener('click', async () => {+    const prompt = $('contribute-prompt').title;+    const feedback = $('copy-feedback');+    try { await navigator.clipboard.writeText(prompt); feedback.textContent = 'Prompt copied'; }+    catch { feedback.textContent = prompt; }+    feedback.hidden = false;+    setTimeout(() => { feedback.hidden = true; }, 8000);+  });+   // ---------------- state ----------------   let model = null;          // normalized data   let lastRaw = '';@@ -623,9 +713,22 @@ body.waiting-on .appbar ~ *:not(.waiting) { visibility: hidden; }     const toGo = m.steps.filter(s => s.status === 'todo' || s.status === 'redo').length;     const minutes = m.steps.reduce((a, s) => a + s.minutes, 0); +    const active = m.steps[posIdx];+    const early = ['intake','rules','design','test-plan','sourcing','components','libraries','library','schematic','simulation','models'];+    const context = {+      intake: ['Start with the goal', 'Define what to build, the constraints, and what success looks like.'],+      rules: ['Set the boundaries', 'Manufacturing limits and project requirements guide the design.'],+      design: ['Shape the circuit', 'Circuit decisions and calculations come before board layout.'],+      sourcing: ['Choose the right parts', 'Compare availability, cost, and component evidence.'],+      schematic: ['Connect the design', 'The schematic captures how the circuit works.'],+      simulation: ['Test the idea', 'Review simulation evidence against the requirements.']+    };+    const copy = context[active.name] || [early.includes(active.name) ? 'Build the design foundation' : 'Follow the engineering evidence', active.does || 'Recorded work and checks determine the next step.'];+    $('project-phase').textContent = presentation?.title || copy[0];+    $('project-context').textContent = presentation?.summary || copy[1];     // header     document.title = m.title + ' : Flow Timeline';-    $('title').textContent = m.title;+    $('title').textContent = presentation?.projectTitle || m.title;     $('engine').style.display = m.engine ? '' : 'none';     $('engine').querySelector('span').textContent = m.engine;     $('finish').textContent = m.finish;@@ -645,6 +748,7 @@ body.waiting-on .appbar ~ *:not(.waiting) { visibility: hidden; }     $('pct').firstChild.nodeValue = Math.round(fDone * 100);     $('when').textContent = fmtClockS(m.updated); +    $('recent-work').innerHTML = m.steps.filter(s => s.status === 'done').slice(-3).map(s => '<li>' + esc(s.title) + '</li>').join('') || '<li style="color:var(--text-2);background:transparent">No completed steps recorded yet</li>';     // rows     m.steps.forEach((s, i) => {       const r = rows.get(s.name);