# Dashboard flow — chip-fetcher's UI is the user's home base (HARD RULE)

**Read this first.** chip-fetcher is not just a CLI that scrapes the web — it's a dashboard that the user keeps open as their home base while the agent does the scraping in a separate window. The dashboard is the single place the user looks to understand what's planned, what's in flight, and what's done. The skill below is mandatory for every batch fetch.

## The hard rule

**Two separate surfaces. The dashboard is user-preference; the scraping window is always pup.**

| Surface | Where it lives | User-preferred? | Why |
|---------|----------------|-----------------|-----|
| **Dashboard** (home base) | EITHER pup `sessionId: "chip-fetcher-dashboard"` OR Hydrogen webview tab named "chip-fetcher" | **YES — ask the user (or check user-profile memory)** | Just renders HTML + polls `/api/activity`. No automation needed. |
| **Scraping window** | pup `sessionId: "chip-fetcher"` | **NO — always pup** | Needs `browser_input_dispatch` for trusted clicks, captcha solving, vendor-login cookies on the `chip-fetcher` profile, download capture from `~/Downloads`. None of that works in a Hydrogen webview. |

### Dashboard placement — figure out the user's preference

Adom users split based on screen real estate:
- **Laptop-screen users (e.g. John)** prefer pup as the dashboard — the pup window can be moved to a second monitor, alt-tabbed, or sized to taste outside VS Code's chrome.
- **Big-monitor users (e.g. Colby)** prefer Hydrogen webview — keeps everything inside VS Code, splits cleanly alongside the editor in a workspace pane.

**Be smart about this detail:**
1. Check `~/.claude/projects/-home-adom-project/memory/user_profile.md` for a `chip_fetcher_dashboard_surface` preference (`pup` or `webview`)
2. If absent, ASK the user once: *"Where do you want the chip-fetcher dashboard — pup window or Hydrogen webview tab?"* and save the answer for future sessions
3. Default fallback if you can't ask: pup, since it works on any screen size

Either way, the **scraping work always runs in pup** — the dashboard surface is decoupled from the automation surface.

### How to open the dashboard in either surface

**Pup version:**
```bash
DASHBOARD_URL="${VSCODE_PROXY_URI/\{\{port\}\}/8786}"
adom-desktop browser_open_window "{
  \"sessionId\":\"chip-fetcher-dashboard\",
  \"profile\":\"chip-fetcher\",
  \"url\":\"$DASHBOARD_URL\"
}"
adom-desktop browser_raise_os_window '{"sessionId":"chip-fetcher-dashboard"}'
```

**Hydrogen webview version:**
```bash
# Find a non-VS-Code pane (per the "never cover VS Code" memory rule)
# Then split + add a webview tab
VSCODE_PANEL=$(adom-cli hydrogen workspace tabs | jq -r '.tabs[] | select(.name=="Visual Studio Code") | .panelId')
WEBVIEW_TYPE="adom/c1d2e3f4-aaaa-4000-a000-00000000000f"
DASHBOARD_URL="http://127.0.0.1:8786/"
adom-cli hydrogen workspace split \
  --panel-id "$VSCODE_PANEL" \
  --direction horizontal \
  --panel-type "$WEBVIEW_TYPE" \
  --display-name "chip-fetcher" \
  --position after \
  --ratio 0.4 \
  --initial-state "{\"url\":\"$DASHBOARD_URL\",\"displayName\":\"chip-fetcher\"}"
```

Both surfaces poll the same `/api/activity` endpoint, so heartbeats reach the user regardless of which surface they picked.

**Why two distinct things:**
- The user can always glance at the dashboard while the agent navigates. They don't lose context when the agent visits SnapMagic or solves a captcha.
- The dashboard surface NEVER gets navigated away from. It stays on the chip-fetcher viewer URL. The scraping window does the work.
- If the agent gets stuck, the dashboard surfaces it on the relevant chip's card — the user knows which chip needs help without reading chat.

## The canonical batch flow

```
User asks: "fetch these 6 LEDs"
  ↓
Stage 0: PLAN
  ├ Tell user the MPN list as a numbered plan
  ├ chip-fetcher plan <MPN1> <MPN2> ...   (seeds dashboard with planned-state stub cards)
  └ Open dashboard in chip-fetcher-dashboard session if not already open
  ↓
Stage 1: PROBE manufacturer site (per chip, in chip-fetcher session)
  ├ post_act <MPN> "probing <vendor>.com"
  ├ Walk product page for CAD/Documentation/Tools links
  └ post_act <MPN> "found UL deep-link" OR "no mfr-direct CAD"
  ↓
Stage 2: FETCH (per chip, in chip-fetcher session)
  ├ post_act <MPN> "downloading <format>"
  ├ Drive UL form / SnapEDA modal / Mouser CSE Library Loader
  └ post_act <MPN> "captcha — user help needed" if stuck
  ↓
Stage 3: PULL + IMPORT (per chip)
  ├ post_act <MPN> "pulling from desktop"
  ├ chip-fetcher pull <path> --mpn <MPN>
  └ post_act <MPN> "imported"
  ↓
Stage 4: CLEAR
  └ post_act <MPN> "" (or DELETE) to clear the live activity badge
```

## The activity-heartbeat protocol

**Always use the CLI: `chip-fetcher heartbeat <MPN> "<stage>"`** — it does five things on every call so the AI doesn't have to remember them:

1. POSTs the heartbeat to `/api/activity` so the dashboard renders the live stage.
2. **Auto-creates a stub `library/<MPN>/info.json`** the first time a new MPN heartbeats — the dashboard renders an empty placeholder card so the user has something to scroll to even before any files land.
3. **Auto-scrolls the dashboard pup window** so the active MPN's card is centered in view with a brief outline highlight (`#5cf`). No more "I'm working on this chip but the user is parked on a different row of the grid."
4. **Prints the canonical sourcing-ladder HINT on the very first heartbeat per MPN** (Mfr → SnapMagic → Mouser → DigiKey → Arrow → CSE/UL → LCSC). The AI sees this on stdout instead of having to re-read the skill.
5. **Prints a card-completeness `WARN:` on every heartbeat** listing what files the active card is still missing (PDF / STEP / `.kicad_sym` / `.kicad_mod`), plus a library-wide `WARN: N/Total cards still incomplete` count. The AI cannot quietly walk away from a half-finished batch — the count is in stdout on every heartbeat.

The legacy raw-curl flow still works as a fallback for shell scripts that don't have the binary on PATH:

```bash
ACTIVITY="http://127.0.0.1:8786/api/activity"
post_act() {
  curl -s -X POST "$ACTIVITY" -H 'Content-Type: application/json' \
    -d "{\"mpn\":\"$MPN\",\"stage\":\"$1\"}" >/dev/null
}
```

…but it does NOT trigger any of the above side-effects. Always prefer the CLI.

The dashboard polls `/api/activity` and renders the latest stage on the chip's card. The user's card shows: live stage, history of recent stages, and any "stuck" flag.

## The completion gate — `chip-fetcher gate`

Before reporting "X of Y done" or "batch complete" or shipping anything based on the library, run `chip-fetcher gate`. It walks every chip and exits 1 if ANY card lacks PDF + STEP + `.kicad_sym` + `.kicad_mod` (default mode) or also Altium + Fusion (`--strict`). For each incomplete card it prints `HINT:` lines naming the exact missing artifacts. The deterministic check replaces "did I remember to check completeness?" — the binary remembers for you.

Datasheet-only cards count as **incomplete stubs**, not done. If the ladder is genuinely exhausted (no STEP exists anywhere on Mfr / SnapMagic / Mouser / DigiKey / Arrow / CSE), record that in `info.json.fetched_via_chain` so the gate sees it as a documented dead-end — but ONLY after walking the full ladder, not as a shortcut.

**Heartbeat MUST fire on:**
- Every page navigation in the scraping session (`navigating <vendor>.com`)
- Every form-fill / button-click that's part of a multi-step flow (`pre-setting form`, `clicking reCAPTCHA`, `polling Downloads`)
- Every state change that affects "is this chip done yet?" (`downloading kicad zip`, `pulling from desktop`, `imported`)
- Every stuck state (`captcha — user help needed`, `mfr lacks CAD — needs SnapEDA approval`)

**Heartbeat must NOT lie about state.** "imported" means the file is in `library/<MPN>/`. "downloading" means the download fired and we're polling Downloads. Don't post "imported" before the file has actually landed — the user trusts the dashboard to be ground truth.

## Canonical stage labels

Use these exact strings so the dashboard can color/group consistently:

| Stage label | When | Color |
|-------------|------|-------|
| `planned` | Initial state when `chip-fetcher plan` seeds the card | grey |
| `probing <vendor>.com` | Scraping window navigated to manufacturer page | blue |
| `found UL deep-link` / `found mfr STEP` / `found CSE link` | mfr-direct CAD path identified | blue |
| `no mfr-direct CAD — falling back to <source>` | manufacturer doesn't host; await user approval | amber |
| `downloading <format>` | Format selected, click fired | blue |
| `captcha — user help needed` | Captcha escalated; pup raised to user | amber |
| `polling Downloads` | Watching ~/Downloads for the zip to land | blue |
| `pulling from desktop` | `adom-desktop pull_file` in flight | blue |
| `importing` | `chip-fetcher import` running | blue |
| `imported` | Files in `library/<MPN>/`, ready to verify | green |
| `done — verified` | step + sym + mod + pdf all present in library | green |
| `stuck — <one-line reason>` | Genuine blocker; user needs to step in | red |

## Opening the dashboard pup window

```bash
# 1. Make sure the viewer is running
ps aux | grep "[c]hip-fetcher serve" >/dev/null || \
  nohup /usr/local/bin/chip-fetcher serve --port 8786 > /tmp/cf-serve.log 2>&1 & disown

# 2. Get the public URL for pup (proxy through the container's adom.cloud slug)
DASHBOARD_URL="${VSCODE_PROXY_URI/\{\{port\}\}/8786}"   # env var inside the container

# 3. Open in pup session "chip-fetcher-dashboard" (NEVER navigate this session away)
adom-desktop browser_open_window "{
  \"sessionId\":\"chip-fetcher-dashboard\",
  \"profile\":\"chip-fetcher\",
  \"url\":\"$DASHBOARD_URL\"
}"

# 4. Raise it so the user can see
adom-desktop browser_raise_os_window '{"sessionId":"chip-fetcher-dashboard"}'
```

## The scraping session

```bash
# Use sessionId "chip-fetcher" (NOT "chip-fetcher-dashboard") for all scraping.
# Navigate, login, click, captcha — all in this window.
adom-desktop browser_navigate '{"sessionId":"chip-fetcher","url":"https://www.vishay.com/..."}'
adom-desktop browser_eval     '{"sessionId":"chip-fetcher","expr":"..."}'
```

**Never** call `browser_navigate` against `chip-fetcher-dashboard`. That window stays on the dashboard URL for the entire batch. If the user does navigate it (clicks a link in the viewer), let them — but don't drive it programmatically.

## Plan-loading (current state of code)

⚠ **Code gap (as of chip-fetcher 0.1.2):** there is no `chip-fetcher plan <MPN>...` command yet. The viewer auto-discovers chips by scanning `library/<MPN>/` directories at server start, so a chip doesn't appear on the dashboard until it has at least one file imported.

**Until that command lands**, the canonical flow is:
1. List the planned MPNs to the user textually as a numbered batch (so they see the queue before any scraping fires)
2. Start the heartbeat the moment the chip moves into `probing <vendor>.com` (this auto-creates the chip's card via the activity API's side-effect of writing to `library/<MPN>/.activity` or similar)
3. Do not pretend "planned" cards exist on the dashboard if they don't yet

Code work to add proper plan-loading is tracked separately in chip-fetcher's TODO; keep this playbook authoritative for what the flow SHOULD look like once that lands.

## When the dashboard window dies / closes

If the user accidentally closes the dashboard window:
1. Don't reopen it silently — that's confusing if they meant to close it.
2. Surface it: *"I noticed the chip-fetcher dashboard window closed. Want me to reopen it so you can see batch progress?"* and wait for yes.
3. If they say yes, reopen with `chip-fetcher-dashboard` session and resume heartbeats.

The activity history is server-side (in `chip-fetcher serve` memory) so reopening shows current state instantly.

## Common antipatterns (don't do these)

| Antipattern | Why it's wrong |
|-------------|----------------|
| Single `sessionId: "chip-fetcher"` for both dashboard AND scraping | Navigating to vishay.com covers the dashboard; user loses context |
| Heartbeats only on stuck/done | User can't see live progress mid-flow; they think the agent froze |
| Navigating the dashboard window mid-batch | Dashboard is the user's anchor; never overwrite it |
| Posting `imported` before the file actually lands in `library/<MPN>/` | Lies on the dashboard erode user trust |
| Skipping plan-listing because "I'll just fetch and they'll see results" | User doesn't know what's coming or how long it'll take |
| Using a different profile for the dashboard session | Cookies / vendor logins won't be shared |

## Cross-references

- [`manufacturer-direct.md`](manufacturer-direct.md) — every stage in the per-vendor flow should heartbeat
- [`ultra-librarian.md`](ultra-librarian.md) — `cf-ul-fetch.sh` already implements this protocol; reference implementation
- [`snapmagic.md`](snapmagic.md) — heartbeat the format-cycling stages (kicad → altium → fusion)
- [`mouser-cse.md`](mouser-cse.md) — heartbeat the slide-CAPTCHA escalation
- [`handoff-and-watching.md`](handoff-and-watching.md) — when a stage hits "user help needed", post the heartbeat AND raise the scraping window
