Files
pi-multifx-pedal/docs/ux-review-test-plan.md
T

442 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.*