Repository navigation
Description
schema_version: "1"
spec_id: pulse-inspector-sidebar-tab
type: spec
task_type: implement-slice
date: 2026-08-19
session_id: pulse-inspector-sidebar
status: draft
priority: p2
platform: transmon
tags: [spec, ui, pulse-inspector, sidebar]
linked_plan: null
acceptance:
- "tab_label_matches >= 1"
- "stage_count >= 3"
- "disabled_stages >= 2"
- "live_data_e2e >= 1"
- "green_dot_active >= 1"
- "completion_renders >= 1"
- "relay_kind_passes >= 1"
Pulse Inspector: stage-multiplexed sidebar tab with live optimization data
Important
Problem: The sidebar "Run Inspector" tab is a static placeholder with hardcoded mock data. The data path from the extension host to the app is broken (a kind vs type field mismatch silently drops all run messages at the relay). No live optimization data reaches the SolidJS app.
Approach: Rename to "Pulse Inspector", add a stage-multiplexed segmented control (Optimization | Calibration | Compilation), fix the extension relay mismatch, wire the inspector bridge via a shared context provider, and mount the existing RunInspector component in the Optimization sub-view. Verify E2E with a real local Piccolo solve.
Approaches Considered:
- (A) Fix relay + wire bridge + mount component — correct, minimal, uses existing architecture. Selected.
- (B) HTTP polling fallback — avoids extension changes but loses per-iteration live updates and diverges from the bridge architecture.
- (C) WebSocket bridge — over-engineered; the postMessage path already works for the dedicated inspector panel.
Scope: Two repos (harmoniqs/amicode extension, harmoniqs/opencode app). Local Piccolo runs only — cloud/remote execution out of scope.
Assumptions: The extension's run_dir_reader correctly parses AMICODE_ITER / AMICODE_PULSE protocol lines from Julia stdout and calls postRunIteration() / postRunPulse() etc. on the RunsManager. The dedicated inspector WebviewPanel (Work Column) already works — validating that the extension-side parsing is correct.
Acceptance Criteria
- The sidebar tab is labeled "Pulse Inspector" with a pulse icon.
- A segmented control at the top of the tab content shows three stages: Optimization, Calibration, Compilation.
- Calibration and Compilation buttons are greyed out (
opacity-50,pointer-events-none) with an inline "Soon" badge. - The Optimization button shows a green dot (4px) when the bridge has an active run (iterations arriving, no completion yet).
- The existing
RunInspectorcomponent renders inside the Optimization sub-view, receiving the bridge via context. - When no solve is running, the stage selector is visible and the RunInspector shows its empty state: "No solve yet — launch one from the chat."
- When a local Piccolo solve is launched, live data flows end-to-end:
- Julia emits
AMICODE_ITER/AMICODE_PULSE→ extension parses → broadcasts withkind: "run:*"→ relay forwards to iframe → app bridge dispatches → RunInspector renders.
- Julia emits
- The pulse sparkline updates per iteration; iteration metrics (iter, objective, inf_pr/inf_du) update live.
- On completion, fidelity card appears (status, F value, iterations, elapsed time) and the green dot disappears.
- The tab ID throughout the codebase is
"pulseInspector"(renamed from"runInspector"). - Command palette title: "Open pulse inspector".
Key Decisions
D1: Extension message field — kind (not type)
All extension-to-webview messages use kind as the discriminator (kind: "theme", kind: "clipboard", etc.). The inspector bridge currently uses type, which causes the relay filter (d.kind.indexOf("run:") === 0) to fail silently. Align to kind.
Data contract (wire format):
{ "source": "amicode", "kind": "run:iteration", "runId": "...", "iter": 42, "objective": 0.023, "inf_pr": 1.2e-4, "inf_du": 3.1e-5 }
{ "source": "amicode", "kind": "run:pulse-meta", "runId": "...", "drives": 2, "knots": 50, "labels": ["a_1","a_2"], "bounds": [[-0.2,0.2],[-0.2,0.2]] }
{ "source": "amicode", "kind": "run:pulse", "runId": "...", "iter": 42, "dt": 0.2, "values": [[...],[...]] }
{ "source": "amicode", "kind": "run:completion", "runId": "...", "fidelity": 0.9995, "iterations": 60, "status": "FINISHED" }D2: Bridge shared via SolidJS context
createInspectorBridge() is called once in an InspectorProvider component, mounted at the session page level. Consumers use useInspectorBridge(). This ensures one listener, one state, shared across sidebar and any future Work Column integration.
D3: Stage selector — segmented control
Three inline buttons, local createSignal drives which sub-view renders. Only Optimization is clickable. The green dot reactivity: bridge.runs().size > 0 && !latestRun.completion.
D4: RunInspector component stays unchanged
The existing component (108 lines) renders correctly. Its internal "Run Inspector" title and run-selector dropdown remain — they're small and removing them would require forking the component, which is deferred.
D5: Sidebar is primary, Work Column untouched
The dedicated inspector WebviewPanel in the Work Column is a separate system (vanilla JS, inspector_webview.js). It continues to work independently. This effort does not modify or deprecate it.
Constraints & Invariants
- The
kindrename must be atomic across extension + app: if one side ships without the other, messages are dropped. - The extension's relay scripts (
chat_panel.ts,deck/shell.ts) must NOT be modified — they already filter correctly onkind. - The command ID
amicode.openInspectoris stable (internal, not user-facing) — no rename needed. - Cloud/remote execution is out of scope (Raghav and Jack's domain).
Prior Art
- The dedicated inspector WebviewPanel (
inspector_webview.js) validates that the extension-side parsing and broadcasting works correctly — just through a different delivery path (directpostMessageto its own webview, not through the iframe relay). - The
RunInspectorSolidJS component andinspector-bridge.tswere merged in an earlier PR but never wired up.
Source
Brainstorming session, August 2026. Design resolved through grilling: stage-multiplexed Pulse Inspector with Optimization active, Calibration/Compilation greyed with "Soon" badges. The relay bug (type vs kind) was discovered during codebase exploration.
Notes
- The dedicated inspector webview (
inspector_webview.js) likely dispatches ontypeinternally — when we rename tokindin the broadcast, we need to check whether it breaks. If it does, update its dispatch too. - Future work: wire Calibration stage (Intonato), wire Compilation stage (Legato), potentially consolidate the Work Column inspector into the same SolidJS component.
Implementation Plan
Phase 1: Extension fix (~/harmoniqs/amicode)
- Rename
type→kindinRunBridgeMessageandDeviceBridgeMessageunions ininspector_bridge.ts - Check the dedicated inspector webview for
typedispatch — update if needed - Rebuild extension
Phase 2: App changes (~/harmoniqs/opencode)
- Update
inspector-bridge.ts:type→kindin types and switch statement - Create
inspector-context.tsxwithInspectorProvideranduseInspectorBridge - Mount provider in session page layout
- Rename
"runInspector"→"pulseInspector"acrosssession-side-panel.tsx,helpers.ts - Replace placeholder content with stage segmented control +
<RunInspector bridge={bridge} /> - Update labels: "Pulse Inspector" in tab triggers, panel menu, command title
- Update entity-rail comments, CSS comment
- Update test expectations
Phase 3: E2E verification
- Install rebuilt extension
- Launch a local Piccolo solve (e.g. X gate, 10 ns)
- Confirm live data flows through to the Pulse Inspector sidebar tab
- Confirm completion card renders with correct fidelity
Activity
feat(inspector): align bridge to `kind` field + add InspectorProvider…
feat(app): rename to Pulse Inspector with stage-multiplexed content
feat(ui): add 'pulse' icon — single-period sine wave
fix(inspector): polish Pulse Inspector UI per feedback
fix(bridge): align inspector messages to `kind` field convention (#432)
feat(app): Pulse Inspector — stage-multiplexed sidebar tab with live …