Skip to content

Rename Run Inspector to Pulse Inspector with stage-multiplexed sidebar tab and live E2E data #214

Description

@jeonghun-jj-lee

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

  1. The sidebar tab is labeled "Pulse Inspector" with a pulse icon.
  2. A segmented control at the top of the tab content shows three stages: Optimization, Calibration, Compilation.
  3. Calibration and Compilation buttons are greyed out (opacity-50, pointer-events-none) with an inline "Soon" badge.
  4. The Optimization button shows a green dot (4px) when the bridge has an active run (iterations arriving, no completion yet).
  5. The existing RunInspector component renders inside the Optimization sub-view, receiving the bridge via context.
  6. 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."
  7. When a local Piccolo solve is launched, live data flows end-to-end:
    • Julia emits AMICODE_ITER / AMICODE_PULSE → extension parses → broadcasts with kind: "run:*" → relay forwards to iframe → app bridge dispatches → RunInspector renders.
  8. The pulse sparkline updates per iteration; iteration metrics (iter, objective, inf_pr/inf_du) update live.
  9. On completion, fidelity card appears (status, F value, iterations, elapsed time) and the green dot disappears.
  10. The tab ID throughout the codebase is "pulseInspector" (renamed from "runInspector").
  11. 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 kind rename 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 on kind.
  • The command ID amicode.openInspector is 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 (direct postMessage to its own webview, not through the iframe relay).
  • The RunInspector SolidJS component and inspector-bridge.ts were 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 on type internally — when we rename to kind in 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)

  1. Rename typekind in RunBridgeMessage and DeviceBridgeMessage unions in inspector_bridge.ts
  2. Check the dedicated inspector webview for type dispatch — update if needed
  3. Rebuild extension

Phase 2: App changes (~/harmoniqs/opencode)

  1. Update inspector-bridge.ts: typekind in types and switch statement
  2. Create inspector-context.tsx with InspectorProvider and useInspectorBridge
  3. Mount provider in session page layout
  4. Rename "runInspector""pulseInspector" across session-side-panel.tsx, helpers.ts
  5. Replace placeholder content with stage segmented control + <RunInspector bridge={bridge} />
  6. Update labels: "Pulse Inspector" in tab triggers, panel menu, command title
  7. Update entity-rail comments, CSS comment
  8. Update test expectations

Phase 3: E2E verification

  1. Install rebuilt extension
  2. Launch a local Piccolo solve (e.g. X gate, 10 ns)
  3. Confirm live data flows through to the Pulse Inspector sidebar tab
  4. Confirm completion card renders with correct fidelity

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions