9.2 KiB
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.
# 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
# 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 |