fix: pass config+config_path to WebServerDeps so audio profile persists to disk; add boot-time period/rate override loading from config.yaml

This commit is contained in:
2026-06-14 12:39:10 -04:00
parent f41ea6a9a0
commit 1991dc6032
71 changed files with 1604 additions and 13170 deletions
+175
View File
@@ -0,0 +1,175 @@
# 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` |
| G1G3 | 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 |
+441
View File
@@ -0,0 +1,441 @@
# UX Review Test Plan — Pi Multi-FX Pedal Web UI
**Date:** June 14, 2026
**Scope:** Full web UI — React SPA + legacy Jinja2 pages
**Target URL:** http://pedal.local:8080
**Methodology:** Nielsen's 10 Usability Heuristics + Flow Analysis + Visual Cohesion Audit
---
## 1. Scope & Personas
### Personas
| Persona | Goal | Context |
|---------|------|---------|
| **Session Guitarist** | Dial in a tone fast during a live session or recording | On a phone/tablet near the amp, tweaking EQs and effects between takes. Wants minimal taps to hear results. |
| **Preset Tweaker** | Browse, load, save, and edit presets from a laptop | Sitting at a desk building a pedalboard. Wants deep control over FX chain, NAM models, IR cab sims. |
| **Headless Stage User** | Switch presets, toggle bypass, check tuner — with footswitches only | Never touches the web UI during performance. Uses it *only* for setup/config behind the scenes. |
| **Admin / Integrator** | Configure WiFi, Bluetooth MIDI, audio profiles, hotspot | Technical user connecting the pedal to different environments (studio, stage, rehearsal space). |
### Flows to Test
| # | Flow | Steps | Priority |
|---|------|-------|----------|
| F1 | **Load pedal dashboard → check status** | 2 | Critical |
| F2 | **Browse preset banks → load a preset** | 4 | Critical |
| F3 | **Edit preset parameters (name, volume, FX chain)** | 5 | High |
| F4 | **Toggle bypass and adjust master volume** | 3 | Critical |
| F5 | **Browse and install a NAM model from Tone3000** | 6 | High |
| F6 | **Upload and load an IR cab sim file** | 4 | High |
| F7 | **Switch audio latency profile** | 3 | Medium |
| F8 | **Configure and enable WiFi hotspot** | 5 | Low |
| F9 | **Scan for, connect to WiFi network** | 4 | Low |
| F10 | **Bluetooth scan, pair, and view MIDI status** | 5 | Low |
| F11 | **Toggle 4CM routing mode and set breakpoint** | 3 | Medium |
| F12 | **Check tuner display with guitar input** | 2 | Medium |
| F13 | **Save/recall snapshot slots** | 4 | Medium |
| F14 | **Empty state: unload model, view dashboard with no presets** | 2 | High |
### UI Surfaces Reviewed
- [ ] React SPA (`/` — root URL)
- [ ] Legacy Dashboard (`/` — fallback without React build)
- [ ] Presets page (`/presets`)
- [ ] Models page (`/models`)
- [ ] IRs page (`/irs`)
- [ ] Settings > Connection tab
- [ ] Settings > Audio tab
- [ ] Settings > 4CM Routing tab
- [ ] Settings > Network tab (WiFi scan, hotspot)
- [ ] Settings > Bluetooth tab
- [ ] Settings > About tab
- [ ] Preset Edit modal
- [ ] WiFi Connect modal
- [ ] Connection status indicator (global header)
---
## 2. Heuristic Walkthrough
### H1 — Visibility of System Status
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H1.1 | Page load indicator | Navigate to `/` | Loading placeholder or skeleton while API/WS connects | Browser |
| H1.2 | Connection status | Open page when pedal is off | "Disconnected" badge (red) in header | Browser |
| H1.3 | Connection status | Open page when pedal is on | "Connected" badge (green) in header | Browser |
| H1.4 | Preset activation feedback | Click a preset card | Card highlights, dashboard preset name updates, WS broadcast visible | Browser |
| H1.5 | Volume slider feedback | Drag volume slider | Value % text updates in real time | Browser |
| H1.6 | Bypass toggle feedback | Click bypass button | Button text toggles BYPASSED/ACTIVE, color changes | Browser |
| H1.7 | Tuner toggle feedback | Click tuner button | Button toggles ON/OFF, color changes | Browser |
| H1.8 | WebSocket reconnection | Kill server, wait, restart | "Disconnected" → "Connected" after auto-reconnect | Browser |
| H1.9 | Model load progress | Click Load on a NAM model | Loading state on the button, list refreshes after | Browser |
| H1.10 | Tone3000 search feedback | Search Tone3000 for models | "Searching..." spinner shown, results appear, offline warning shown if 503 | Browser |
| H1.11 | Tone3000 install feedback | Install a model from Tone3000 | Button shows "Downloading...", then "✓ Done", list refreshes | Browser |
| H1.12 | File upload feedback | Upload a .nam or .wav file | Input clears, list refreshes with new entry | Browser |
| H1.13 | Audio profile switch | Switch to low latency profile | "restarted: true" in response, brief JACK restart pause | API + Browser |
| H1.14 | RSSI/connection data in settings | Open Network tab | WiFi SSID, IP, signal % shown, visible from refresh | Browser |
| H1.15 | BT status indicators | Open Bluetooth tab | Power, discoverable, adapter name visible | Browser |
| H1.16 | WS client count | Open Settings → Connection | Connected clients count shows active WS sessions | Browser |
**Grep for verification:**
```bash
# Loading states in source
grep -n "loading\|spinner\|skeleton\|Loading" src/web/static/*.js src/web/static/style.css
# Connection status toggle
grep -n "connection-status\|Connected\|Disconnected" src/web/static/*.js src/web/templates/base.html
# Button disabled states on async actions
grep -n "\.disabled\|disabled =" src/web/static/*.js
```
---
### H2 — Match Between System and Real World
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H2.1 | FX type labels in UI | View FX chain, preset edit modal | Labels use real-world names (e.g., "Noise Gate" not "noise_gate") | Source + Browser |
| H2.2 | Model/IR list readability | View Models or IRs page | File names appear as human-readable labels | Browser |
| H2.3 | 4CM routing description | Enable 4CM in Settings | Plain language: "Input 1 (Guitar) → Pre blocks → Send \| Input 2 (Return) → Post blocks → Output" | Browser |
| H2.4 | Tuner note display | Enable tuner with guitar plugged in | Shows musical note names (A, A#, Bb...) not frequencies only | Browser |
| H2.5 | Preset meta labels | View preset card | Shows "Bank 0 · Program 2" — accessible musician language | Browser |
| H2.6 | Audio profile names | View profiles list | "Standard", "Low" — musician-friendly not technical | Browser |
| H2.7 | NAM architecture info | View model details | Shows "featherwave-v2" or architecture name, not internal paths | Browser |
**Grep for verification:**
```bash
# Check for snake_case / internal names leaking into UI
grep -n "fx_type\|replace.*_/g\|FXType" src/web/static/presets.js | head -20
# presets.js line 92: const label = block.fx_type.replace(/_/g, ' '); — detects raw fx_type
```
---
### H3 — User Control and Freedom
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H3.1 | Modal close options | Open preset edit modal | Close on ✕ button, close on overlay click, close on Cancel button | Browser |
| H3.2 | Modal close on Escape | Open modal, press Escape | Modal closes | Browser |
| H3.3 | Delete preset confirmation | Click Delete in preset modal | `confirm()` dialog appears before deletion | Browser |
| H3.4 | Cancel WiFi connect | Open WiFi modal, click Cancel | Modal closes, no connection attempted | Browser |
| H3.5 | Back navigation | Navigate to any page, press Back | Browser back works, returns to previous page | Browser |
| H3.6 | Tab navigation persists | Switch to Network tab, navigate away and back | Active tab remembered via URL hash | Browser |
| H3.7 | Tuner toggle graceful | Toggle tuner ON while playing | Audio mutes or tuner activates without loud pop/clip | Browser + Audio |
| H3.8 | Bypass toggle graceful | Toggle bypass ON while playing | Clean DI signal, no pop/clip | Browser + Audio |
| H3.9 | Unload model without issues | Click Unload on loaded model | Model unloads, dashboard shows "None loaded", audio still flows | Browser |
---
### H4 — Consistency and Standards
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H4.1 | Navigation active state | Click each nav link | Active link highlighted, consistent style across all pages | Browser |
| H4.2 | Button style consistency | Compare primary/secondary/danger across pages | Same styles everywhere (colors, padding, font size) | Browser |
| H4.3 | Toggle button pattern | Compare bypass, tuner, 4CM toggles | Same ON/OFF pattern, same active/inactive colors | Browser |
| H4.4 | Slider pattern | Compare volume sliders on Dashboard vs Settings | Same visual, same behavior, synced values | Browser |
| H4.5 | Connection status in header | Navigate through all pages | Connection indicator always present, same position | Browser |
| H4.6 | Page header pattern | Visit Presets, Models, IRs, Settings pages | Consistent page-header with h1, same spacing | Browser |
| H4.7 | Card structure | Compare all card components | Uniform header/body/footer pattern — identical padding, border, radius | Browser |
| H4.8 | Danger button meaning | Compare Delete preset, Unload model/IR | All red danger buttons are destructive actions | Browser |
| H4.9 | Tab pattern (Settings) | Switch between all 6 settings tabs | Underline active indicator, same transition | Browser |
---
### H5 — Error Prevention
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H5.1 | Disabled submit during load | Click Load on model rapidly | Button disabled after first click, no duplicate load | Browser |
| H5.2 | Disabled submit during upload | Upload a file, click multiple times | Button disabled during upload | Source |
| H5.3 | Delete preset confirmation | Click Delete | Confirm dialog: "Delete this preset?" | Browser |
| H5.4 | File type validation on upload | Try uploading a .txt file | Rejected at client or server level | Browser |
| H5.5 | Audio profile double-switch | Switch to same profile twice | `restarted: false`, no unnecessary JACK restart | API |
| H5.6 | 4CM breakpoint bounds | Drag breakpoint slider min/max | Clamped 016 | Browser |
| H5.7 | Volume clamping | Set volume > 100% or < 0% via API | Clamped to 0.01.0 | API |
| H5.8 | Empty preset name | Save preset with empty name | Should default to "Preset" or reject with validation | Browser |
| H5.9 | Hotspot password length | Try password > 63 chars | Maxlength on input field prevents it | Browser |
**Code verification:**
```bash
grep -n "confirm\|showModal\|are you sure" src/web/static/*.js
grep -n "\.disabled" src/web/static/models.js src/web/static/irs.js
```
---
### H6 — Recognition Rather Than Recall
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H6.1 | Current preset name visible | Load any page | Preset name clearly shown in Dashboard or in header | Browser |
| H6.2 | Currently loaded model/IR | View Models/IRs page | "CURRENT" badge visible on active model/IR | Browser |
| H6.3 | Active preset card highlighted | Browse presets | The active preset card has a distinct border/background | Browser |
| H6.4 | Bank/program location | View preset card | "Bank N · Slot N" visible without clicking | Browser |
| H6.5 | Navigation labels visible | All pages | All 5 nav links visible, no hamburger hiding | Browser |
| H6.6 | FX chain status on dashboard | Connected, preset loaded | Blocks visible with ON/OFF badges | Browser |
| H6.7 | Modal title shows preset name | Click to edit preset | "Edit: [Preset Name]" — user knows what they're editing | Browser |
| H6.8 | Routing mode visible from dashboard | Any page | Badge shows "Mono" or "4CM" | Browser |
| H6.9 | Tone3000 installed state | Revisit after install | Previously installed models show ✓ Installed badge (localStorage) | Browser |
---
### H7 — Flexibility and Efficiency
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H7.1 | Keyboard submit on search | Type in Tone3000 search, press Enter | Triggers search | Browser |
| H7.2 | FAQ: Keyboard shortcuts | Test Tab order through form fields | Logical focus order, all interactive elements reachable | Browser |
| H7.3 | Bulk actions | Models/IRs pages | No bulk select/load/delete — note as missing efficiency | Browser |
| H7.4 | Auto-refresh on preset save | Save preset edit | Modal closes, preset list auto-refreshes | Browser |
| H7.5 | Auto-refresh after model/IR upload | Upload model/IR | List auto-refreshes after upload completes | Browser |
| H7.6 | Trending option on Tone3000 | Click "Trending" | Fetches trending models/IRs without typing a query | Browser |
| H7.7 | Tab navigation in Settings | URL hash (`#network`, `#bluetooth`) | Deep-linkable settings tabs | Browser |
| H7.8 | WS-driven live updates | Change volume on another tab | Dashboard and Settings sliders sync in real time | Browser |
---
### H8 — Aesthetic and Minimalist Design
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H8.1 | Content-to-chrome ratio | View Dashboard | Essential info above the fold: preset, bypass, volume, FX chain | Browser |
| H8.2 | Visual hierarchy | View any page | Most important element draws eye first (preset name, status) | Browser |
| H8.3 | Information density | Dashboard page | Not overcrowded — cards with clear sections | Browser |
| H8.4 | Typography consistency | Compare h1/h2 across pages | Same font sizes, weights, colors for each heading level | Browser |
| H8.5 | Line length readability | Any card body | Text doesn't span full viewport — contained by max-width | Browser |
| H8.6 | Dark theme cohesion | All pages | Consistent dark palette, no jarring color changes | Browser |
| H8.7 | Redundant elements | Check for duplicate info | No data shown in multiple places unnecessarily | Browser |
| H8.8 | Empty states visual | Unload model, clear presets | Helpful empty states, not just blank areas | Browser |
| H8.9 | Modal sizing | Open preset edit modal | Appropriate width, doesn't overwhelm on mobile | Browser |
---
### H9 — Help Users Recognize, Diagnose, and Recover from Errors
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H9.1 | API error → user message | Trigger a 500 on preset load | User-facing error, not raw JSON or console-only | Browser |
| H9.2 | Tone3000 offline state | Disconnect internet, search | "⚠ Offline — search unavailable" warning shown | Browser |
| H9.3 | Upload error handling | Upload corrupt .nam file | Alert with error message: "Upload error: ..." | Browser |
| H9.4 | Model load failure | Try to load corrupt model | Alert: "Failed to load model: ..." | Browser |
| H9.5 | Preset load failure | Access empty slot via direct URL | 404 — "Preset not found" | Browser |
| H9.6 | WiFi connect error | Wrong password on WiFi modal | Error message shown in modal, modal stays open | Browser |
| H9.7 | JACK restart failure | Kill JACK, switch profile | API returns 500 with "Failed to start JACK — rolled back" | API |
| H9.8 | API error on Dashboard | Kill backend, load dashboard | Silent catch with console.warn — check for user-facing message | Browser |
| H9.9 | Empty preset slot styling | Presets page, empty slot | Shows "— Empty —" with reduced opacity | Browser |
**Code verification:**
```bash
# Error handling patterns — check for user-facing messages vs silent console
grep -n "\.catch\|catch(" src/web/static/*.js | grep -v "console.warn\|console.error"
grep -n "alert(" src/web/static/*.js
grep -n "text-muted\|network-error\|Error:" src/web/static/*.js
```
---
### H10 — Help and Documentation
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| H10.1 | Tooltips on controls | Hover over any button | No tooltips currently — add recommendation | Browser |
| H10.2 | Upload hints | Check upload sections | "Upload .nam model files to the pedal's model directory." — clear | Browser |
| H10.3 | Tone3000 hints | Check Tone3000 section | "Search and download NAM models directly from the Tone3000 community." | Browser |
| H10.4 | Empty state guidance | No models/presets/IRs | Actionable: "No .nam models found" + upload option visible | Browser |
| H10.5 | About page | Navigate to About tab | Version, platform, repo link available | Browser |
| H10.6 | Help within the app | Check for question marks, help links | None found — add recommendation for critical flows | Browser |
| H10.7 | IR details context | View IR list | Shows "1024 taps · 21.3ms @ 48000Hz" — helpful technical detail | Browser |
---
## 3. Flow Friction Map
Each flow from Section 1 tested step-by-step. Score 1-5:
| Flow | Step | Friction | Issue Ref | Notes |
|------|------|----------|-----------|-------|
| **F1 — Load dashboard** | Navigate to `pedal.local:8080` | 1 | — | React SPA loads (or legacy fallback) |
| **F1** | Check connection status | 1 | — | Color-coded badge in header |
| **F1** | View current state | 1 | — | WS pushes state on connect |
| **F2 — Browse presets** | Click Presets nav | 1 | — | |
| **F2** | Wait for bank list | 2 | H1-like | Loading indicator present |
| **F2** | Click preset card | 1 | — | |
| **F2** | Preset loads, dashboard updates | 1 | — | WS pushes update |
| **F3 — Edit preset** | Click preset card to open modal | 1 | — | |
| **F3** | Change name | 1 | — | Text input clear |
| **F3** | Adjust volume | 1 | — | Slider works |
| **F3** | View FX chain in modal | 2 | H2, H6 | Labels use `replace(/_/g,' ')` — see notes |
| **F3** | Click Save | 1 | — | Modal closes, list refreshes |
| **F3** | Click Delete | 1 | — | Confirm dialog appears |
| **F4 — Bypass & volume** | Click bypass button | 1 | — | Immediate toggle |
| **F4** | Verify bypassed audio | 1 | — | Clean DI signal |
| **F4** | Drag volume slider | 1 | — | Real-time update |
| **F5 — Tone3000 browse** | Go to Models page | 1 | — | |
| **F5** | Type search query | 1 | — | Enter triggers search |
| **F5** | Wait for results | 3 | H1 | "Searching..." spinner, but no timeout info |
| **F5** | Click Install on result | 2 | H1 | Button shows "Downloading..." |
| **F5** | Verify model appears in list | 1 | — | Auto-refreshes |
| **F6 — Upload IR** | Go to IRs page | 1 | — | |
| **F6** | Click file input | 1 | — | |
| **F6** | Select .wav file | 1 | — | File type restricted |
| **F6** | Click Upload | 2 | H1 | No upload progress bar — just button state |
| **F6** | Verify in list | 1 | — | Refreshes |
| **F7 — Audio profile switch** | Go to Settings > Audio | 1 | — | |
| **F7** | Profiles are visible | 2 | — | No explicit "switch profile" button in UI — needs API |
| **F7** | Switch profile | 2 | H10 | No in-UI profile switcher visible in Audio tab |
| **F8 — Hotspot config** | Go to Network tab | 1 | — | |
| **F8** | Edit SSID/password | 1 | — | Input fields ready |
| **F8** | Click Enable Hotspot | 2 | H1 | No "enabling..." state |
| **F8** | Verify clients listed | 2 | H1 | Auto-polls or manual refresh? |
| **F9 — WiFi scan** | Go to Network tab | 1 | — | |
| **F9** | Click Scan | 2 | H1 | "Scanning..." button state, results appear |
| **F9** | Click Connect on network | 1 | — | Modal opens |
| **F9** | Enter password | 1 | — | |
| **F9** | Click Connect | 2 | H1/H9 | Modal error if fails |
| **F10 — BT scan** | Go to Bluetooth tab | 1 | — | |
| **F10** | Scan for devices | 2 | H1 | "Scanning (12s)..." — shows expected duration |
| **F10** | Pair with device | 2 | H1 | No pairing feedback beyond spinner |
| **F11 — 4CM routing** | Go to Settings > 4CM Routing | 1 | — | |
| **F11** | Toggle 4CM ON | 1 | — | Breakpoint row enables |
| **F11** | Drag breakpoint | 1 | — | Description updates live |
| **F12 — Tuner** | Toggle tuner ON | 1 | — | |
| **F12** | Check note detection | 1 | — | Fast poll endpoint |
| **F13 — Snapshots** | Go to snapshot section | 3 | — | No snapshot UI on dashboard — check React SPA |
| **F13** | Save snapshot to slot | 2 | — | Via API or in-app? |
| **F14 — Empty states** | Unload all models | 1 | H6 | "None loaded" shown clearly |
| **F14** | Delete all presets | 1 | H9 | "No presets found. Create one from the Dashboard." |
---
## 4. Visual & Interaction Checklist
| Check | Pass? | Issues | Notes |
|-------|-------|--------|-------|
| Consistent spacing / grid | ✅ | — | Cards share `--radius`, `--border`, `padding: 10-14px` |
| Typography hierarchy | ✅ | — | h1=1.1rem, h2=0.9-0.95rem, body=0.85rem — clear hierarchy |
| Color consistency | ✅ | — | Single accent `#4fc3f7`, consistent danger/success |
| Interactive states | ⚠️ | See below | Hover, active states defined; missing focus-visible styles |
| Responsive layout | ✅ | — | 82% max-width mobile, 600px/720px breakpoints |
| Loading states | ⚠️ | H1-series | Most pages have loading indicators; Tone3000 spinner lacks progress |
| Empty states | ✅ | — | Clear messages: "No models found", "None loaded" |
| Error states | ⚠️ | H9-series | API errors shown via alert(); silent catches on Dashboard WS |
| Edge cases (long text, rapid click) | ⚠️ | H5, H8 | Long preset names handled with ellipsis; Button disabled states present |
| Touch targets ≥ 48px | ⚠️ | — | `.btn` padding 8px 16px — may be borderline on mobile |
| Dark theme readability | ✅ | — | High contrast: `#e0e0e0` on `#0d0d0d` background |
| Smooth transitions | ✅ | — | `transition: background 0.2s, border-color 0.2s` on interactive elements |
---
## 5. Responsive & Mobile UX Checks
| # | Test | Viewport | Expected | Method |
|---|------|----------|----------|--------|
| R1 | Dashboard on mobile | 375px wide | Status row stacks (grid → 1 column), nav links compact | Browser |
| R2 | Presets grid on mobile | 375px wide | 2-column grid intact, cards shrink appropriately | Browser |
| R3 | Settings tabs on mobile | 375px wide | Horizontal scrollable tabs with hidden scrollbar | Browser |
| R4 | Modal on mobile | 375px wide | Modal fills width with padding, scrollable body | Browser |
| R5 | Touch targets on mobile | 375px, touch | Buttons have `touch-action: manipulation`, no 300ms delay | Source |
| R6 | Nav overflow | Very long nav labels | `overflow-x: auto` with hidden scrollbar | Browser |
| R7 | Header sticky on scroll | Scroll down on any page | Header stays at top (`position: sticky; z-index: 100`) | Browser |
| R8 | Input fields on mobile | Focus any input | No viewport zoom (`user-scalable=no` set) — intentional? | Browser |
| R9 | Content max-width on large screen | 1920px wide | `--max-width: 720px` — content centered, not stretched | Browser |
---
## 6. Real-Time & WebSocket UX
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| WS1 | Multiple tabs sync | Open 2 browser tabs, change volume on tab 1 | Tab 2 slider and value update within 1s | Browser |
| WS2 | Bypass sync across tabs | Toggle bypass on tab 1 | Tab 2 button text and color update | Browser |
| WS3 | Preset change broadcast | Activate preset via /presets page | Dashboard preset card updates on all connected clients | Browser |
| WS4 | Reconnection UX | Unplug network cable, wait 10s, reconnect | "Disconnected" appears, then auto-reconnects showing "Connected" | Browser |
| WS5 | WS reconnection backoff | Keep pedal disconnected for 1 min | Reconnect delay increases from 1s to max 15s | Browser |
| WS6 | Concurrency: 3 tabs + API | Open 3 browser tabs + curl API calls | All clients receive broadcasts, no dropped messages | Multi-browser |
| WS7 | Initial state on WS connect | Open fresh browser tab | Receives `{"type": "connected", "state": {...}}` with full current state | Browser |
---
## 7. Accessibility Quick Checks
| # | Test | Expected | Method |
|---|------|----------|--------|
| A1 | Tab navigation through nav links | All 5 nav links focused in order | Browser |
| A2 | Tab navigation through preset grid | Each preset card focusable | Browser |
| A3 | Tab navigation through modal | Input, slider, Save, Delete, Cancel in logical order | Browser |
| A4 | Focus-visible styles on interactive | Visible focus ring on buttons, links, inputs | Browser |
| A5 | Color contrast: text on bg | `#e0e0e0` on `#1a1a1a` — passes WCAG AA | Source |
| A6 | Color contrast: accent on bg | `#4fc3f7` on `#222` — passes WCAG AA | Source |
| A7 | Alt text on Tone3000 thumbnails | `alt=""` (decorative) — acceptable | Source |
| A8 | ARIA labels on toggle buttons | No ARIA — buttons have text content (ON/OFF) so passable | Source |
| A9 | Form labels associated with inputs | File inputs have no explicit `<label>` — just adjacent text | Source |
---
## 8. React SPA-Specific Tests (if mounted at root)
The React SPA at `src/web/ui-dist/` replaces the legacy dashboard at `/`.
| # | Test | Action | Expected | Method |
|---|------|--------|----------|--------|
| SPA1 | SPA loads at root | GET `/` | Returns React SPA `index.html` (not legacy dashboard) | Browser |
| SPA2 | Fallback if no ui-dist | Delete ui-dist dir, restart | Falls back to legacy dashboard at `/` | Browser |
| SPA3 | SPA navigation | Click through the SPA | Client-side routing, no full page reloads | Browser |
| SPA4 | SPA ↔ legacy pages | Navigate from SPA root to `/presets` | Full page navigation to legacy page — smooth transition? | Browser |
| SPA5 | Favicon loads | Check browser tab icon | SVG favicon at `/ui/favicon.svg` | Browser |
| SPA6 | JS bundle errors | Open browser console on SPA | No console errors, no uncaught exceptions | Browser |
---
## 9. Key UX Observations from Source Code (Pre-Findings)
These are issues visible from code review alone. Confirm in browser during execution.
| # | Observation | Heuristic | Source |
|---|-------------|-----------|--------|
| O1 | FX type labels use `block.fx_type.replace(/_/g, ' ')` — simple replace, no proper label map. Some types may render with underscores if unmatched. | H2 | `presets.js:92` |
| O2 | WS `preset_changed` handler updates name/meta but doesn't re-render FX chain on dashboard. Block bypass states may visually lag. | H1 | `app.js:62-69` |
| O3 | `toggle4cm()` reads button state from DOM classList, not from server — race risk if another client or WS event flips state between read and POST. | H5 | `app.js:142-143` |
| O4 | No keyboard shortcut support (Ctrl+S for save, / for search). | H7 | Whole codebase |
| O5 | No undo for any action — delete is permanent after confirm. | H3 | `presets.js:132` |
| O6 | Signal strength icons for WiFi use the same emoji for all levels (`📶` — same Unicode codepoint, different on each platform). | H8 | `network.js:83-88` |
| O7 | SN: signal icon `📶` vs `📶` — same char repeated, 4 levels collapse to same visual. | H8 | `network.js:23-26` |
| O8 | BT scan timeout (12s) displayed to user — good. No cancel option on scan. | H3 + H1 | `bluetooth.js:72` |
| O9 | No feedback when JACK restarts during profile switch — audio dropout with no visual indicator. | H1 | `server.py:1402-1443` |
| O10 | Volume slider on Dashboard and Settings page both update via REST per `oninput` — may cause excessive API calls during drag. | H7 | `app.js:227-230` |
| O11 | Connection status `_handlers` check (`pedalWS._handlers ? '1' : '0'`) looks for handler registration, not actual WS count. | H1 | `app.js:261` |
---
## 10. Pre-Completion Checklist
Before marking a section complete, verify:
- [ ] Browser tested: each test executed and result recorded
- [ ] JS console checked for errors after each interaction
- [ ] WS messages confirmed where applicable (via browser console: `pedalWS._handlers`)
- [ ] Responsive view tested for mobile flows (R1-R9)
- [ ] Code findings (O1-O11) confirmed in live browser session
- [ ] Screenshots captured for visual issues (use `browser_vision`)
- [ ] Cross-device sync tested for at least one flow (WS1)
---
## 11. Test Annotation Legend
| Symbol | Meaning |
|--------|---------|
| ✅ | Passes — expected behavior confirmed |
| ❌ | Fails — bug discovered (create Gitea issue) |
| ⚠️ | Non-critical / minor friction (note in recommendations) |
| ⏭️ | Skipped — explain why (e.g., "no pedal hardware connected") |
| 🔄 | Needs retest after fix |
---
*Generated by Hermes Agent UX Review Skill — based on Nielsen's 10 Usability Heuristics and ux-review methodology.*