# 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 |