From 1df4e3d90df08abd1f273b7b7fd31ef88a631b31 Mon Sep 17 00:00:00 2001 From: Raghav Chari Date: Wed, 17 Jun 2026 18:20:50 -0400 Subject: [PATCH 1/4] =?UTF-8?q?feat(extension):=20Run=20Inspector=20aesthe?= =?UTF-8?q?tic=20pass=20=E2=80=94=20status=20badge,=20metric=20cards,=20br?= =?UTF-8?q?anded=20chrome?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the ported dev-spike layout (raw status/frame/last-load header + Hz/ latency/post-recv telemetry footer) with a researcher-facing look: - top bar: <0||0> amico mark + pulsing status badge (idle/running/converged/failed) - plot hero in a rounded framed panel with a smoother crossfade - metric cards: objective (violet hero), iteration, feasibility, optimality - friendlier branded empty state Theme-aware (VS Code color vars) with the amico violet accent. Webview-only; message protocol + watcher feed unchanged. Tests green (37). Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/extension/src/inspector_webview.ts | 43 ++++------- packages/extension/src/run_inspector.ts | 79 ++++++++++++++++----- 2 files changed, 75 insertions(+), 47 deletions(-) diff --git a/packages/extension/src/inspector_webview.ts b/packages/extension/src/inspector_webview.ts index e7f6ae19..3cd497f5 100644 --- a/packages/extension/src/inspector_webview.ts +++ b/packages/extension/src/inspector_webview.ts @@ -1,8 +1,7 @@ // Run Inspector webview script — runs inside the sandboxed Chromium webview. -// Ported from amicode/src/spikes/inspector_webview.ts with no semantic changes: // - double-buffer image swap (zero flicker between iter frames at 5 Hz) -// - canonical Ipopt-format stats row (iter, f, inf_pr, inf_du, lat) -// - Date.now() for cross-process timestamp (performance.now origins differ). +// - status badge (idle / running / converged) + researcher metric cards +// (objective, iteration, feasibility, optimality) driven by AMICODE_ITER. declare function acquireVsCodeApi(): { postMessage(msg: unknown): void; @@ -11,38 +10,29 @@ declare function acquireVsCodeApi(): { const vscodeApi = acquireVsCodeApi(); const $ = (id: string) => document.getElementById(id) as HTMLElement; -let iterCount = 0; -let lastIterAt = performance.now(); -let smoothedHz = 0; let visibleBuffer: "a" | "b" = "a"; +function setBadge(state: "idle" | "running" | "done" | "failed", text: string): void { + const badge = $("badge"); + badge.className = "badge " + state; + badge.textContent = text; +} + window.addEventListener("message", (e) => { const msg = e.data; if (!msg || typeof msg !== "object") return; - const recv = performance.now(); switch (msg.type) { case "ping": { vscodeApi.postMessage({ type: "pong", seq: msg.seq, t0: msg.t0 }); - $("status").textContent = "pinging"; break; } case "iteration": { - iterCount++; - const dt = recv - lastIterAt; - lastIterAt = recv; - const instHz = dt > 0 ? 1000 / dt : 0; - smoothedHz = smoothedHz === 0 ? instHz : 0.9 * smoothedHz + 0.1 * instHz; - const lat = Date.now() - msg.t_post; - $("iter").textContent = String(iterCount); - $("hz").textContent = smoothedHz.toFixed(1); - $("rec").textContent = - `iter=${String(msg.iter).padStart(4, "0")}` + - ` f=${(msg.f_val as number).toExponential(6)}` + - ` inf_pr=${(msg.eq_viol as number).toExponential(3)}` + - ` inf_du=${(msg.kkt_error as number).toExponential(3)}`; - $("lat").textContent = `${lat.toFixed(0)}ms`; - $("status").textContent = msg.isFinal ? "final frame" : "streaming"; + $("m-iter").textContent = String(msg.iter); + $("m-obj").textContent = (msg.f_val as number).toExponential(4); + $("m-pr").textContent = (msg.eq_viol as number).toExponential(2); + $("m-du").textContent = (msg.kkt_error as number).toExponential(2); + setBadge(msg.isFinal ? "done" : "running", msg.isFinal ? "converged" : "running"); break; } case "refresh": { @@ -53,15 +43,12 @@ window.addEventListener("message", (e) => { const incomingBuffer = visibleBuffer === "a" ? "b" : "a"; const incomingImg = $("preview-" + incomingBuffer) as HTMLImageElement; const outgoingImg = $("preview-" + visibleBuffer) as HTMLImageElement; - const tPost = msg.t_post as number; const handleLoaded = () => { - const loadedAt = Date.now(); incomingImg.style.opacity = "1"; outgoingImg.style.opacity = "0"; visibleBuffer = incomingBuffer; - $("img-iter").textContent = String(msg.iter); - $("img-load").textContent = `${(loadedAt - tPost).toFixed(0)}ms`; + $("m-iter").textContent = String(msg.iter); }; incomingImg.src = msg.url; @@ -73,7 +60,7 @@ window.addEventListener("message", (e) => { handleLoaded(); }); } - $("status").textContent = msg.isFinal ? "final frame" : "streaming"; + setBadge(msg.isFinal ? "done" : "running", msg.isFinal ? "converged" : "running"); break; } } diff --git a/packages/extension/src/run_inspector.ts b/packages/extension/src/run_inspector.ts index 7d4634ac..c2eb99ad 100644 --- a/packages/extension/src/run_inspector.ts +++ b/packages/extension/src/run_inspector.ts @@ -134,36 +134,77 @@ class InspectorView implements vscode.WebviewViewProvider { script-src 'nonce-${nonce}'; style-src ${webview.cspSource} 'unsafe-inline';"> -
-

Run Inspector

-
status: idle
-
frame:
-
last load:
+
+
<0||0> Run Inspector
+ + idle
frame preview A frame preview B -
No solve in progress — fire one from the Amicode chat.
+
+ <0||0> + No solve in progress — fire one from the Amicode chat, or run “Replay demo run”. +
-
- opencode-backed - iter stream: 0 recv · Hz · · post→recv +
+
objective
+
iteration
+
feasibility
+
optimality
From 5ec9d02a026541200316cec3b39585f568d1ea28 Mon Sep 17 00:00:00 2001 From: Raghav Chari Date: Wed, 17 Jun 2026 18:51:52 -0400 Subject: [PATCH 2/4] fix(inspector): brand-yellow accent (#FFF676) + badge no longer stuck on 'running' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - accent + running state → #FFF676 (was violet/amber) - the watcher streamed frames without a terminal signal, so the badge only ever showed 'running'. Add InspectorView.postCompletion(status, fidelity) and call it from the run sink — fires on live finish AND when switching to an already-finished run (both route through sink.run), flushing the last frame first so completion is authoritative. Badge now: idle → running → converged (green) / failed (red). Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/extension/src/file_watcher.ts | 4 ++++ packages/extension/src/inspector_webview.ts | 10 ++++++++-- packages/extension/src/run_inspector.ts | 16 ++++++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/extension/src/file_watcher.ts b/packages/extension/src/file_watcher.ts index 6d9a7076..beea80d2 100644 --- a/packages/extension/src/file_watcher.ts +++ b/packages/extension/src/file_watcher.ts @@ -59,6 +59,10 @@ class LiveRunSink implements RunSink { }); } run(c: RunCompletion): void { + // Tell the inspector the run is terminal so the badge leaves "running". + // Fires on live finish (onFinished) AND replay of an already-finished run + // (ingestRunDir) — both route through this sink. + getInspector()?.postCompletion(c.status, c.fidelity); this.opts.statusBar?.setRun({ runId: c.runId, outputDir: c.runDir, startedAt: 0, status: c.status, latestIter: this.latestIter >= 0 ? this.latestIter : undefined, diff --git a/packages/extension/src/inspector_webview.ts b/packages/extension/src/inspector_webview.ts index 3cd497f5..2219f4ec 100644 --- a/packages/extension/src/inspector_webview.ts +++ b/packages/extension/src/inspector_webview.ts @@ -32,7 +32,13 @@ window.addEventListener("message", (e) => { $("m-obj").textContent = (msg.f_val as number).toExponential(4); $("m-pr").textContent = (msg.eq_viol as number).toExponential(2); $("m-du").textContent = (msg.kkt_error as number).toExponential(2); - setBadge(msg.isFinal ? "done" : "running", msg.isFinal ? "converged" : "running"); + setBadge("running", "running"); + break; + } + case "completed": { + // Authoritative terminal state from the watcher (FINISHED on disk). + const ok = msg.status === "completed"; + setBadge(ok ? "done" : "failed", ok ? "converged" : String(msg.status)); break; } case "refresh": { @@ -60,7 +66,7 @@ window.addEventListener("message", (e) => { handleLoaded(); }); } - setBadge(msg.isFinal ? "done" : "running", msg.isFinal ? "converged" : "running"); + setBadge("running", "running"); // a new frame means a live solve; completion arrives via "completed" break; } } diff --git a/packages/extension/src/run_inspector.ts b/packages/extension/src/run_inspector.ts index c2eb99ad..ce524b25 100644 --- a/packages/extension/src/run_inspector.ts +++ b/packages/extension/src/run_inspector.ts @@ -87,6 +87,18 @@ class InspectorView implements vscode.WebviewViewProvider { }); } + /** Terminal-state signal so the badge stops saying "running". The watcher + * streams frames without an isFinal marker (it can't know which frame is + * last mid-solve), so completion is delivered separately — on live finish + * AND when switching to an already-finished run. Flush any pending frame + * first so this is the last word the webview hears for the run. */ + postCompletion(status: string, fidelity?: number): void { + if (!this.view) return; + this.clearTimer(); + this.flushRefresh(); + this.view.webview.postMessage({ type: "completed", status, fidelity }); + } + reveal(): void { // Force materialize the view via its auto-registered .focus command. // Unconditional — without an existing view, this is what creates one. @@ -135,8 +147,8 @@ class InspectorView implements vscode.WebviewViewProvider { style-src ${webview.cspSource} 'unsafe-inline';">