---
phase: 14-preview-bridge-infrastructure
plan: 02
type: execute
wave: 2
depends_on:
  - 14-01
files_modified:
  - crates/mcp-preview/assets/index.html
autonomous: true
requirements:
  - PREV-01
  - PREV-05
  - PREV-06
  - PREV-07

must_haves:
  truths:
    - "Developer runs cargo pmcp preview and immediately sees widget HTML rendered in the iframe from the first UI resource"
    - "When server has multiple UI resources, a picker above the tool list lets the developer switch between them"
    - "When server has only one UI resource, just a label shows (no picker dropdown)"
    - "Each bridge callTool invocation logs an expandable entry in the Network tab with tool name, args, response, and duration"
    - "Network tab shows a badge count when new bridge calls happen while another tab is active"
    - "Connected/disconnected status dot in the header reflects actual MCP session state"
    - "Reconnect button in the header resets session and refreshes tools/resources"
    - "When MCP server is unreachable, inline error message with Retry button shows in widget area"
    - "Per-tab clear buttons exist for Console, Network, and Events tabs"
  artifacts:
    - path: "crates/mcp-preview/assets/index.html"
      provides: "Resource picker, auto-load, enhanced DevTools, connection status, reconnect"
      contains: "loadResourceWidget"
  key_links:
    - from: "crates/mcp-preview/assets/index.html"
      to: "/api/resources"
      via: "fetch in initSession()"
      pattern: "fetch.*api/resources"
    - from: "crates/mcp-preview/assets/index.html"
      to: "/api/resources/read"
      via: "fetch in loadResourceWidget()"
      pattern: "fetch.*api/resources/read"
    - from: "crates/mcp-preview/assets/index.html"
      to: "/api/reconnect"
      via: "fetch in handleReconnect()"
      pattern: "fetch.*api/reconnect"
---

<objective>
Enhance the preview UI to auto-load widgets from MCP resources, add a resource picker, upgrade DevTools with bridge call logging and badge counts, and add connection status with reconnect capability.

Purpose: The frontend currently only renders widgets from tool call results. This plan makes it a full widget preview environment that auto-loads resource-based widgets on startup, provides a resource picker for multi-widget servers, and gives developers real-time visibility into bridge calls via enhanced DevTools.

Output: Complete preview UI that auto-loads the first widget, provides resource switching, logs all bridge calls with expandable details and badge counts, shows connection status, and supports session reconnect.
</objective>

<execution_context>
@./.claude/get-shit-done/workflows/execute-plan.md
@./.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/14-preview-bridge-infrastructure/14-CONTEXT.md
@.planning/phases/14-preview-bridge-infrastructure/14-RESEARCH.md
@.planning/phases/14-preview-bridge-infrastructure/14-01-SUMMARY.md
@crates/mcp-preview/assets/index.html
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add resource picker, auto-load, connection status, and reconnect to preview UI</name>
  <files>crates/mcp-preview/assets/index.html</files>
  <action>
Modify the preview UI HTML/CSS/JS to add resource management, connection status, and reconnect. All changes are in `crates/mcp-preview/assets/index.html`.

**HTML structure changes:**

1. **Resource picker in left sidebar** (above the existing tool list):
   - Add a `<div id="resource-picker">` section at the top of the left sidebar, above the tool list.
   - Contains a `<div id="resource-label">` for single-resource mode (just shows resource name).
   - Contains a `<div id="resource-list">` for multi-resource mode (clickable list of resources).
   - Each resource entry shows: resource name (bold) and description (muted) below it.
   - Clicking a resource entry calls `loadResourceWidget(uri)` and highlights the active entry.
   - Per CONTEXT.md: when server has only one UI resource, hide the picker list and show just the resource name label. When multiple, show the full clickable list.

2. **Connection status in header:**
   - The existing `#status-dot` element should be wired to actual connection state.
   - Add a `<button id="reconnect-btn">` next to the status area in the header. Label: "Reconnect" (small button).
   - Per CONTEXT.md: minimal status display -- just connected/disconnected dot, no session ID or duration.

3. **Error state in widget area:**
   - Add a `showWidgetError(message)` method that replaces the iframe content with an error message and a "Retry" button.
   - The Retry button calls `loadResourceWidget()` again for the current resource URI.
   - Per CONTEXT.md: inline error message in the widget area where the widget would be.

**CSS additions:**

4. Add styles for:
   - `.resource-picker` section (border-bottom separator from tool list)
   - `.resource-entry` items (padding, hover state, cursor pointer)
   - `.resource-entry.active` (highlighted/selected state)
   - `.resource-name` (font-weight bold) and `.resource-desc` (smaller, muted color)
   - `#reconnect-btn` (small button style matching existing theme)
   - `.widget-error` (centered error message in widget area with retry button)
   - Status dot colors: green for connected, red for disconnected, orange for reconnecting

**JavaScript changes to PreviewRuntime class:**

5. **Modify `init()` method:**
   - After existing setup calls, add `await this.initSession()` which replaces or supplements the existing tool loading.

6. **Add `initSession()` method:**
   - Fetch `/api/tools` and `/api/resources` in parallel using `Promise.all`.
   - Handle tools response: populate `this.tools` and call `this.renderToolList()` (existing behavior).
   - Handle resources response: store in `this.uiResources` array, call `this.renderResourcePicker()`.
   - If resources exist: auto-load the first one via `await this.loadResourceWidget(this.uiResources[0].uri)`.
   - If no resources: show a helpful message in the widget area: "No UI resources found. Your MCP server needs to expose HTML resources via resources/list."
   - Update status dot to connected (green) on success, disconnected (red) on error.
   - Catch errors: call `this.setStatus(false)` and `this.showWidgetError('Failed to connect...')`.

7. **Add `renderResourcePicker()` method:**
   - If `this.uiResources.length === 0`: hide the resource picker section entirely.
   - If `this.uiResources.length === 1`: show only the resource name as a label (no clickable list).
   - If `this.uiResources.length > 1`: render clickable list with name + description for each entry.
   - Track `this.activeResourceUri` to highlight the active resource.

8. **Add `loadResourceWidget(uri)` method:**
   - Fetch `/api/resources/read?uri=${encodeURIComponent(uri)}`.
   - Parse JSON response. Find HTML content in `contents` array (item where `mimeType` contains "html").
   - If found: call the existing `loadWidget(htmlContent.text)` method which does srcdoc iframe wrapping with bridge injection.
   - If not found: call `showWidgetError('Resource does not contain HTML content')`.
   - On fetch error: call `showWidgetError(errorMessage)`.
   - Store `this.activeResourceUri = uri` for retry capability.

9. **Add `handleReconnect()` method:**
   - Set status to "reconnecting" (orange dot).
   - Fetch `POST /api/reconnect`.
   - On success: call `this.initSession()` to reload everything.
   - On error: set status to disconnected and show error.
   - Wire to the Reconnect button click handler.

10. **Update `setStatus(connected, message)` method:**
    - Set `#status-dot` background color: green if connected, red if disconnected.
    - Optionally update status text if a message is provided.
  </action>
  <verify>
Verify the file is valid HTML by checking for unclosed tags. Search for key functions: `initSession`, `loadResourceWidget`, `renderResourcePicker`, `handleReconnect`, `showWidgetError`. Verify fetch calls to `/api/resources`, `/api/resources/read`, `/api/reconnect` exist.
  </verify>
  <done>
Preview UI auto-loads first resource widget on startup. Resource picker renders above tool list (label mode for 1 resource, list mode for multiple). Connection status dot wired to actual state. Reconnect button resets session and reloads. Error states show inline with Retry button.
  </done>
</task>

<task type="auto">
  <name>Task 2: Enhance DevTools with bridge call logging, badge counts, and clear buttons</name>
  <files>crates/mcp-preview/assets/index.html</files>
  <action>
Enhance the DevTools panel in the preview UI for real-time bridge call visibility. All changes in `crates/mcp-preview/assets/index.html`.

**Network tab bridge call logging:**

1. **Upgrade `logBridgeCall()` method** (or create if it does not exist):
   - Each bridge call creates an expandable `<details>/<summary>` entry in the Network tab.
   - Summary line shows: tool name (bold), duration in ms (right-aligned), success/error indicator (green/red dot or icon).
   - Expanded body shows two sections:
     - "Arguments" label + `<pre>` with JSON.stringify(args, null, 2)
     - "Response" label + `<pre>` with JSON.stringify(result, null, 2)
   - Add CSS class `.network-entry` with `.success` and `.error` variants for color coding.
   - New entries append at the bottom, auto-scroll to latest.

2. **Wire bridge `callTool()` to logging:**
   - In the bridge's `callTool` function (inside `wrapWidgetHtml()` or the existing bridge code), capture the start time before the fetch and the end time after.
   - Call `window.parent.previewRuntime.logBridgeCall(toolName, args, result, duration, success)` after each call completes.
   - This must work for both success and error cases.

**Network tab badge count:**

3. **Add badge count tracking:**
   - Add `this.networkUnreadCount = 0` property to PreviewRuntime.
   - In `logBridgeCall()`: increment `this.networkUnreadCount` and call `this.updateNetworkBadge()`.
   - `updateNetworkBadge()`: find the Network tab button, add/update a `<span class="badge">N</span>` child if count > 0, remove badge if count is 0.
   - When the Network tab is selected (in tab click handler): reset `this.networkUnreadCount = 0` and call `this.updateNetworkBadge()`.
   - Per CONTEXT.md: badge pattern similar to browser devtools network tab unread count.

4. **Badge CSS:**
   - `.badge`: small circle, background red or accent color, white text, positioned top-right of tab button (use `position: relative` on tab, `position: absolute` on badge). Font size ~10px, padding 2px 5px, border-radius 50% or pill shape.

**Per-tab clear buttons:**

5. **Add clear buttons to Console, Network, and Events tabs:**
   - Each tab panel gets a small "Clear" button in the top-right corner.
   - Console clear: empties the console log container.
   - Network clear: empties the network log container, resets `networkUnreadCount` and badge.
   - Events clear: empties the events log container.
   - Per CONTEXT.md: per-tab clear buttons for Console, Network, and Events tabs. State tab does NOT get a clear button.

6. **Clear button CSS:**
   - Small button (font-size ~11px), positioned top-right of each tab panel. Subtle styling (ghost button, shows on hover or always visible but unobtrusive).

**Existing DevTools tab preservation:**

7. Per CONTEXT.md: keep all 4 existing devtools tabs (State, Console, Network, Events). Do NOT remove or rename any existing tabs. Only enhance.
  </action>
  <verify>
Search for `logBridgeCall`, `updateNetworkBadge`, `networkUnreadCount` in index.html. Verify `<details>` elements are used for expandable entries. Verify clear buttons exist for Console, Network, Events tabs. Verify badge CSS class exists.
  </verify>
  <done>
Bridge calls logged as expandable details/summary entries in Network tab with tool name, args, response, and duration. Badge count appears on Network tab for unread calls, clears when tab is selected. Clear buttons on Console, Network, and Events tabs. All 4 existing DevTools tabs preserved.
  </done>
</task>

</tasks>

<verification>
1. File is syntactically valid: search for balanced script/style tags
2. Key functions exist: `initSession`, `loadResourceWidget`, `renderResourcePicker`, `handleReconnect`, `showWidgetError`, `logBridgeCall`, `updateNetworkBadge`
3. API integration: fetch calls to `/api/resources`, `/api/resources/read`, `/api/reconnect` present
4. Resource picker: `#resource-picker` element in HTML
5. Badge: `.badge` CSS class defined
6. Clear buttons: clear button elements in Console, Network, Events tab panels
7. Status dot: colors for connected/disconnected/reconnecting states
8. Full build: `cargo check -p mcp-preview` passes (rust-embed will embed the modified HTML)
9. Clippy: `cargo clippy -p mcp-preview -- -D warnings` passes
</verification>

<success_criteria>
- Preview auto-loads first UI resource widget on startup
- Resource picker shows above tool list with correct single/multi behavior
- Bridge calls logged with expandable details in Network tab
- Badge count visible on Network tab for unread entries
- Clear buttons on Console, Network, Events tabs
- Connection status dot reflects actual state
- Reconnect button works and reloads everything
- Error states show inline with Retry button
- Compiles successfully (rust-embed re-embeds index.html)
</success_criteria>

<output>
After completion, create `.planning/phases/14-preview-bridge-infrastructure/14-02-SUMMARY.md`
</output>
