# API ↔ Frontend Gap Analysis — Pi Multi-FX Pedal Web UI

**Date:** June 14, 2026
**Scope:** All backend API routes vs. calls from Legacy Jinja2 UI + React SPA

---

## Summary

| Metric | Count |
|--------|-------|
| Total backend API routes | **76** |
| Routes called by at least one frontend | **57** |
| Routes with NO frontend consumer | **10** (+ 2 alias endpoints) |
| Critical bugs found | **2** |
| Missing features (backend exists, no UI) | **5** |

---

## Changes Applied — Fixed Items

| # | Item | Status | Files Changed |
|---|------|--------|--------------|
| B1 | IR Install on IRs page broken — `installToneIR` added to irs.js | ✅ Fixed | `irs.js`, `models.js` |
| G6 | Tuner pitch display — note + cents bar + string indicator on dashboard | ✅ Added | `app.js`, `dashboard.html`, `style.css` |
| G1–G3 | Audio profile selector — dropdown with JACK status in Settings > Audio | ✅ Added | `app.js`, `settings.html`, `style.css` |
| G4 | FX chain clickable toggles — each block toggles ON/OFF via `PATCH /api/blocks` | ✅ Added | `app.js`, `style.css` |
| O1 | FX type labels — proper label map (FX_TYPE_LABELS) instead of `replace(/_/g,' ')` | ✅ Added | `app.js` |
| B2 | `installToneIR` removed from models.js (was duplicated, used wrong localStorage key) | ✅ Fixed | `models.js` |

---

## 1. Backend APIs NOT Called by Any Frontend

These are routes defined in `server.py` that neither the legacy UI (`static/`) nor the React SPA (`ui-dist/`) ever calls.

### Tier 1 — Full Features Without UI

| # | Route | Method | Endpoint exists but | Why it matters |
|---|-------|--------|--------------------|---------------|
| G1 | `/api/audio/profile` | GET | No UI to read current audio profile | Users can't see what latency profile is active |
| G2 | `/api/audio/profiles` | GET | No UI to list available profiles | Users can't see what options exist |
| G3 | `/api/audio/profile` | POST | No UI to switch profiles | Users can't switch between standard/low latency! |
| G4 | `PATCH /api/blocks` | PATCH | No UI to enable/disable individual FX blocks | FX chain shown on dashboard is display-only — can't toggle blocks on/off |
| G5 | `PATCH /api/block` | PATCH | No UI to update individual block params | Can't tweak per-block settings (drive, level, mix, etc.) |
| G6 | `GET /api/tuner/pitch` | GET | No tuner pitch display UI | Tuner toggle works but you can't *see* what note you're playing |

### Tier 2 — Secondary / Alternative Endpoints

| # | Route | Method | Notes |
|---|-------|--------|-------|
| G7 | `GET /api/routing` | GET | Both UIs only POST routing changes and rely on WS for state — GET is defined but never read separately |
| G8 | `GET /api/channel-mode` | GET | SPA POSTs to set channel mode but never GETs current mode — relies on `/api/state` snapshot |
| G9 | `POST /api/bypass/toggle` | POST | Alias — both UIs call `/api/bypass` directly |

### Tier 3 — External Content Sources (No UI)

| # | Route | Method | Notes |
|---|-------|--------|-------|
| G10 | `GET /api/tonedownload/status` | GET | Tone3000 connection status — never queried by UI |
| G11 | `GET /api/tonehub/search` | GET | ToneHub search — never called (Tone3000 preferred) |
| G12 | `GET /api/tonehub/search/irs` | GET | ToneHub IR search — never called |

### Tier 4 — Unused Alias

| # | Route | Method | Notes |
|---|-------|--------|-------|
| G13 | `POST /api/volume` | POST | POST alias for volume — legacy UI calls `apiPut('/volume')`, SPA... see below |

---

## 2. Critical Bugs Found

### Bug B1 — IR Install on IRs Page is Broken

**File:** `src/web/static/irs.js:197` calls `installToneIR()` but function only exists in `models.js:235`

**Root cause:** The Tone3000 IR search results on the `/irs` page render "Install" buttons that call `installToneIR()`. This function is defined in `models.js`, which is loaded on the **Models page** (`/models`). The **IRs page** (`/irs`) loads `irs.js`, which does NOT define `installToneIR()` — the function appears in `irs.js` only as a call reference (line 197), not a definition.

**Impact:** Clicking "Install" on any Tone3000 IR result from the IRs page throws `ReferenceError: installToneIR is not defined`. The button action silently fails.

**Fix:** Either (a) copy the entire `installToneIR()` function (lines 235-256) from `models.js` into `irs.js`, or (b) extract the shared Tone3000 install logic into a shared module loaded by both pages.

```bash
# Confirm the gap
grep -n 'function installToneIR' src/web/static/*.js
# → only in models.js:235, NOT in irs.js
grep -n 'installToneIR' src/web/static/irs.js
# → line 197 is a CALL to it (onclick=...), not a definition
```

### Bug B2 — `installToneIR` Duplicated Across Files

**File:** Models page (`/models`) also has IR install logic.

`installToneIR` is defined in `models.js:235` (not just `installToneModel`). This means the Models page can install IR files via the Tone3000 button embedded in model search results — which makes no sense. The function is cargo-culted but only the `installToneModel` variant is relevant to the Models page.

---

## 3. Underserved Features (Present in Backend, Weak UI)

| # | Feature | Has UI? | Details |
|---|---------|---------|---------|
| F1 | **Tuner pitch display** | ❌ | `GET /api/tuner/pitch` returns frequency, note name, cents, string — but zero UI renders it. The tuner toggle exists but you get no visual feedback about what note you're playing |
| F2 | **Per-block FX control** | ❌ | `PATCH /api/blocks` (toggle) and `PATCH /api/block` (params) are never called. The dashboard shows an FX chain but it's display-only — can't toggle blocks or tweak knobs from the web UI |
| F3 | **Audio profile switching** | ❌ | `GET/POST /api/audio/profile` and `GET /api/audio/profiles` exist but no UI anywhere. Users must use curl to switch latency profiles |
| F4 | **Channel mode display** | ⚠️ | SPA shows channel mode in the header (STEREO badge or GUITAR/BASS toggle). But `GET /api/channel-mode` is never called — the value comes from the `/api/state` snapshot. Legacy UI has no channel mode awareness at all |
| F5 | **Snapshots** | ⚠️ | Snapshots exist in SPA only (save/recall/rename/delete via modal). Legacy UI has zero snapshot support |

---

## 4. Feature Parity: Legacy UI vs React SPA

| Feature | Legacy (`static/`) | React SPA (`ui-dist/`) |
|---------|-------------------|----------------------|
| Dashboard + status | ✅ Full | ✅ Full |
| Preset browse + activate | ✅ Full | ✅ Full |
| Preset edit (rename, volume) | ✅ Modal-based | ✅ Inline |
| Preset create | ❌ | ✅ "New Preset" button |
| Preset delete | ✅ With confirm | ✅ With confirm (inline) |
| Model browse + load | ✅ Full | ✅ Full |
| Model upload | ✅ | ✅ |
| Tone3000 search (models) | ✅ | ✅ |
| Tone3000 install (models) | ✅ | ✅ |
| IR browse + load | ✅ Full | ✅ Full |
| IR upload | ✅ | ✅ |
| Tone3000 search (IRs) | ✅ | ❌ Broken (Bug B1) |
| Tone3000 install (IRs) | ❌ Broken (Bug B1) | ✅ |
| Bypass toggle | ✅ | ✅ |
| Volume control | ✅ | ✅ |
| Tuner toggle | ✅ | ✅ |
| **Tuner pitch display** | **❌** | **❌** |
| 4CM routing toggle | ✅ | ✅ |
| **Per-block FX toggle** | **❌** | **❌** |
| **Per-block param edit** | **❌** | **⚠️** (block-params schema fetched) |
| **Audio profile switch** | **❌** | **❌** |
| WiFi scan + connect | ✅ | ✅ |
| Hotspot enable/disable | ✅ | ✅ |
| Bluetooth scan + pair + connect | ✅ | ✅ |
| Bluetooth MIDI enable/disable | ✅ | ✅ |
| Snapshots save/recall | ❌ | ✅ Full (CRUD) |
| Capture/record audio | ❌ | ✅ Full (start/stop/list/play) |
| Channel mode (dual-mono/stereo) | ❌ | ✅ (Settings → Routing) |

---

## 5. Source of Truth Verification

```bash
# Complete list of backend API routes:
grep -oP '@app\.\w+\(["'"'"']/api/[^"'"'"']+' src/web/server.py | sed "s/@app\.//" | sort

# Complete list of API calls from legacy JS:
grep -roP "apiGet\('/[^']+|apiPost\('/[^']+|apiPut\('/[^']+|apiDelete\('/[^']+|fetch\('/api[^']+" src/web/static/ | sed "s/.*api\(Get\|Post\|Put\|Delete\)('//;s/fetch('/\/api/" | sort -u

# Complete list of API calls from React SPA bundle:
grep -oP '`/api/[^`]+`' src/web/ui-dist/assets/index-CxlwZhqE.js | sort -u
```

---

## 6. Remaining Gaps (Not Addressed)

These are lower-priority items that still have no frontend consumer:

| # | Route | Method | Notes |
|---|-------|--------|-------|
| G7 | `GET /api/routing` | GET | Both UIs only POST routing and rely on WS for state — GET exists but is redundant |
| G8 | `GET /api/channel-mode` | GET | SPA POSTs to set mode but gets current state from `/api/state` snapshot — low value |
| G9 | `POST /api/bypass/toggle` | POST | Alias endpoint — both UIs call `/api/bypass` directly |
| G10 | `GET /api/tonedownload/status` | GET | Tone3000 connection status — no UI indicator, minor |
| G11 | `GET /api/tonehub/search` | GET | ToneHub — never wired up in favor of Tone3000 |
| G12 | `GET /api/tonehub/search/irs` | GET | ToneHub IR search — never wired up |
| G13 | `POST /api/volume` | POST | POST alias — legacy calls PUT, SPA calls POST; both work |
| G14 | Per-block param editing (`PATCH /api/block`, `GET /api/block-params/{fx_type}`) | PATCH/GET | Block param sliders still need a UI — tracked in UX review test plan |
