R1: Dashboard 500 fix, Phase 2/7 uncommitted changes

- Fix dashboard 500 when deps disconnected — _gather_state() now returns
  full defaults instead of bare {'connected': False}
- Phase 2: FX param schemas aligned with DSP implementations
- Phase 7: Pipeline routing, preset manager improvements
- Factory presets: cleanup and normalization

Fixes dashboard crash on disconnected state (dogfood issue #1)
This commit is contained in:
2026-06-12 14:02:13 -04:00
parent 1301a8a128
commit 77a757cee6
62 changed files with 24166 additions and 20 deletions
+145
View File
@@ -0,0 +1,145 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 320" font-family="Inter, -apple-system, sans-serif">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#1a1a2e"/>
<stop offset="100%" stop-color="#16213e"/>
</linearGradient>
<linearGradient id="led-off" x1="0" y1="1" x2="0" y2="0">
<stop offset="0%" stop-color="#2a2a3a"/>
<stop offset="100%" stop-color="#3a3a4a"/>
</linearGradient>
<linearGradient id="led-on" x1="0" y1="1" x2="0" y2="0">
<stop offset="0%" stop-color="#00b894"/>
<stop offset="100%" stop-color="#55efc4"/>
</linearGradient>
<linearGradient id="knob" x1="0" y1="1" x2="0" y2="0">
<stop offset="0%" stop-color="#2d3436"/>
<stop offset="100%" stop-color="#636e72"/>
</linearGradient>
</defs>
<!-- Background -->
<rect width="480" height="320" rx="12" fill="url(#bg)" stroke="#2d3436" stroke-width="2"/>
<!-- ═══ Status Bar ═══ -->
<rect x="0" y="0" width="480" height="36" rx="12" fill="#0a0a1a" opacity="0.6"/>
<rect x="0" y="24" width="480" height="12" fill="#0a0a1a" opacity="0.6"/>
<!-- Status dots (FX chain indicator) -->
<circle cx="28" cy="18" r="4" fill="#55efc4"/>
<circle cx="44" cy="18" r="4" fill="#55efc4"/>
<circle cx="60" cy="18" r="4" fill="#55efc4"/>
<circle cx="76" cy="18" r="4" fill="#e17055"/>
<circle cx="92" cy="18" r="4" fill="#636e72"/>
<circle cx="108" cy="18" r="4" fill="#636e72"/>
<!-- Status labels -->
<text x="140" y="23" fill="#636e72" font-size="10">GN Ch Dr ◇ Dly Rvb</text>
<!-- Tuner / Tempo / CPU -->
<text x="320" y="23" fill="#55efc4" font-size="11" font-weight="bold">♩ 120</text>
<text x="380" y="23" fill="#636e72" font-size="10">CPU 23%</text>
<text x="440" y="23" fill="#636e72" font-size="10">▣ 89%</text>
<!-- ═══ Main Content Area ═══ -->
<!-- Preset name badge -->
<rect x="12" y="44" width="456" height="32" rx="6" fill="#0a0a1a" opacity="0.5"/>
<text x="24" y="65" fill="#dfe6e9" font-size="14" font-weight="bold">Bank 1 / 03 — Edge of Breakup</text>
<text x="380" y="65" fill="#636e72" font-size="11">4CM ◇ BYPASS</text>
<!-- ═══ FX Block Cards ═══ -->
<!-- Row 1 -->
<g transform="translate(12, 84)">
<rect width="108" height="56" rx="6" fill="#2d3436" opacity="0.9"/>
<text x="54" y="22" fill="#dfe6e9" font-size="11" font-weight="bold" text-anchor="middle">NOISE GATE</text>
<text x="54" y="38" fill="#636e72" font-size="9" text-anchor="middle">Thresh: -56dB</text>
<circle cx="90" cy="12" r="4" fill="#55efc4"/>
</g>
<g transform="translate(128, 84)">
<rect width="108" height="56" rx="6" fill="#2d3436" opacity="0.9"/>
<text x="54" y="22" fill="#dfe6e9" font-size="11" font-weight="bold" text-anchor="middle">COMPRESSOR</text>
<text x="54" y="38" fill="#636e72" font-size="9" text-anchor="middle">Ratio: 3:1</text>
<circle cx="90" cy="12" r="4" fill="#55efc4"/>
</g>
<g transform="translate(244, 84)">
<rect width="108" height="56" rx="6" fill="#2d3436" opacity="0.9"/>
<text x="54" y="22" fill="#dfe6e9" font-size="11" font-weight="bold" text-anchor="middle">OVERDRIVE</text>
<text x="54" y="38" fill="#636e72" font-size="9" text-anchor="middle">Drive: 68%</text>
<circle cx="90" cy="12" r="4" fill="#55efc4"/>
</g>
<g transform="translate(360, 84)">
<rect width="108" height="56" rx="6" fill="#2d3436" opacity="0.9"/>
<text x="54" y="22" fill="#dfe6e9" font-size="11" font-weight="bold" text-anchor="middle">EQ</text>
<text x="54" y="38" fill="#636e72" font-size="9" text-anchor="middle">Bass: +2 Mid: 0</text>
<circle cx="90" cy="12" r="4" fill="#55efc4"/>
</g>
<!-- Row 2 -->
<g transform="translate(12, 148)">
<rect width="108" height="56" rx="6" fill="#2d3436" opacity="0.9"/>
<text x="54" y="22" fill="#dfe6e9" font-size="11" font-weight="bold" text-anchor="middle">NAM AMP</text>
<text x="54" y="38" fill="#e17055" font-size="9" text-anchor="middle">Matchless Chime</text>
<circle cx="90" cy="12" r="4" fill="#55efc4"/>
</g>
<g transform="translate(128, 148)">
<rect width="108" height="56" rx="6" fill="#2d3436" opacity="0.9"/>
<text x="54" y="22" fill="#dfe6e9" font-size="11" font-weight="bold" text-anchor="middle">CAB IR</text>
<text x="54" y="38" fill="#e17055" font-size="9" text-anchor="middle">Vox AC30 Blue</text>
<circle cx="90" cy="12" r="4" fill="#55efc4"/>
</g>
<g transform="translate(244, 148)">
<rect width="108" height="56" rx="6" fill="#2d3436" opacity="0.9"/>
<text x="54" y="22" fill="#dfe6e9" font-size="11" font-weight="bold" text-anchor="middle">CHORUS</text>
<text x="54" y="38" fill="#636e72" font-size="9" text-anchor="middle">Rate: 0.4 Depth: 55</text>
<circle cx="90" cy="12" r="4" fill="#55efc4"/>
</g>
<g transform="translate(360, 148)">
<rect width="108" height="56" rx="6" fill="#2d3436" opacity="0.8"/>
<text x="54" y="22" fill="#636e72" font-size="11" font-weight="bold" text-anchor="middle">DELAY</text>
<text x="54" y="38" fill="#636e72" font-size="9" text-anchor="middle">Time: 420ms</text>
<circle cx="90" cy="12" r="4" fill="#636e72"/>
</g>
<!-- ═══ Footer ═══ -->
<!-- VU Meters -->
<rect x="12" y="212" width="456" height="36" rx="6" fill="#0a0a1a" opacity="0.5"/>
<text x="24" y="234" fill="#636e72" font-size="9">L</text>
<rect x="36" y="220" width="200" height="20" rx="3" fill="#1a1a2e"/>
<rect x="36" y="220" width="120" height="20" rx="3" fill="#55efc4"/>
<rect x="36" y="220" width="160" height="20" rx="3" fill="#00b894" opacity="0.3"/>
<text x="256" y="234" fill="#636e72" font-size="9">R</text>
<rect x="268" y="220" width="200" height="20" rx="3" fill="#1a1a2e"/>
<rect x="268" y="220" width="80" height="20" rx="3" fill="#55efc4"/>
<rect x="268" y="220" width="140" height="20" rx="3" fill="#00b894" opacity="0.3"/>
<!-- Knobs row -->
<g transform="translate(40, 260)">
<circle cx="0" cy="0" r="18" fill="url(#knob)" stroke="#636e72" stroke-width="1"/>
<line x1="0" y1="-14" x2="0" y2="-6" stroke="#55efc4" stroke-width="2" stroke-linecap="round"/>
<text x="0" y="28" fill="#636e72" font-size="9" text-anchor="middle">MASTER</text>
</g>
<g transform="translate(140, 260)">
<circle cx="0" cy="0" r="18" fill="url(#knob)" stroke="#636e72" stroke-width="1"/>
<line x1="0" y1="-14" x2="8" y2="-8" stroke="#55efc4" stroke-width="2" stroke-linecap="round"/>
<text x="0" y="28" fill="#636e72" font-size="9" text-anchor="middle">GAIN</text>
</g>
<g transform="translate(240, 260)">
<circle cx="0" cy="0" r="18" fill="url(#knob)" stroke="#636e72" stroke-width="1"/>
<line x1="0" y1="-14" x2="-8" y2="-8" stroke="#55efc4" stroke-width="2" stroke-linecap="round"/>
<text x="0" y="28" fill="#636e72" font-size="9" text-anchor="middle">TONE</text>
</g>
<g transform="translate(340, 260)">
<circle cx="0" cy="0" r="18" fill="url(#knob)" stroke="#636e72" stroke-width="1"/>
<line x1="0" y1="-14" x2="4" y2="-12" stroke="#55efc4" stroke-width="2" stroke-linecap="round"/>
<text x="0" y="28" fill="#636e72" font-size="9" text-anchor="middle">VOLUME</text>
</g>
<!-- Bottom nav dots -->
<circle cx="190" cy="305" r="3" fill="#55efc4"/>
<circle cx="210" cy="305" r="3" fill="#636e72"/>
<circle cx="230" cy="305" r="3" fill="#636e72"/>
<circle cx="250" cy="305" r="3" fill="#636e72"/>
<circle cx="270" cy="305" r="3" fill="#636e72"/>
<circle cx="290" cy="305" r="3" fill="#636e72"/>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

+424
View File
@@ -0,0 +1,424 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pi Multi-FX Pedal — UI Template</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #1a1a2e;
color: #e0e0e0;
padding: 40px;
display: flex;
flex-direction: column;
align-items: center;
}
h1 { font-size: 18px; font-weight: 600; color: #888; margin-bottom: 24px; letter-spacing: 2px; text-transform: uppercase; }
.pedal-frame {
width: 800px;
background: #16213e;
border: 2px solid #0f3460;
border-radius: 16px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
position: relative;
}
.pedal-frame::before {
content: "480 × 320 (touchscreen)";
position: absolute;
top: -28px;
right: 0;
font-size: 11px;
color: #555;
letter-spacing: 1px;
}
/* ── Top bar ── */
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 0 16px 0;
border-bottom: 1px solid #0f3460;
margin-bottom: 16px;
}
.topbar .preset-name {
font-size: 28px;
font-weight: 700;
color: #fff;
}
.topbar .preset-bank {
font-size: 13px;
color: #888;
margin-top: 2px;
}
.topbar .status-area {
display: flex;
align-items: center;
gap: 12px;
}
.topbar .cpu-badge {
background: #0f3460;
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
color: #53d769;
}
.topbar .bypass-btn {
background: #e94560;
color: #fff;
padding: 6px 16px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
border: none;
}
.topbar .bypass-btn.off {
background: #2d4059;
color: #888;
}
/* ── VU Meters ── */
.vu-section {
display: flex;
gap: 16px;
margin-bottom: 20px;
padding: 12px 16px;
background: #0a1628;
border-radius: 10px;
}
.vu-meter {
flex: 1;
}
.vu-meter label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
color: #666;
display: block;
margin-bottom: 4px;
}
.vu-bar {
height: 8px;
background: #1a1a2e;
border-radius: 4px;
overflow: hidden;
position: relative;
}
.vu-fill {
height: 100%;
border-radius: 4px;
width: 65%;
}
.vu-fill.left { background: linear-gradient(90deg, #53d769, #e9d34b, #e94560); width: 72%; }
.vu-fill.right { background: linear-gradient(90deg, #53d769, #e9d34b, #e94560); width: 58%; }
.vu-clip { color: #e94560; font-size: 11px; margin-left: 4px; }
/* ── FX Chain ── */
.fx-chain {
display: flex;
gap: 6px;
margin-bottom: 20px;
flex-wrap: wrap;
padding: 8px 0;
}
.fx-block {
background: #0f3460;
border: 1px solid #1a4a7a;
border-radius: 8px;
padding: 8px 12px;
font-size: 11px;
text-align: center;
min-width: 60px;
cursor: pointer;
transition: all 0.15s;
}
.fx-block:hover { background: #1a4a7a; border-color: #53d769; }
.fx-block .icon { font-size: 18px; display: block; margin-bottom: 2px; }
.fx-block .name { color: #aaa; font-size: 10px; }
.fx-block.active { border-color: #53d769; }
.fx-block.bypassed { opacity: 0.4; }
.fx-chain .arrow { color: #333; font-size: 18px; align-self: center; padding: 0 2px; }
/* ── Main content grid ── */
.main-grid {
display: grid;
grid-template-columns: 1fr 220px;
gap: 16px;
}
/* ── Knob panel ── */
.knob-panel {
background: #0a1628;
border-radius: 10px;
padding: 16px;
}
.knob-panel .header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
}
.knob-panel .header h3 { font-size: 14px; font-weight: 600; color: #ccc; }
.knob-panel .header .type { font-size: 11px; color: #53d769; }
.knob-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.knob {
text-align: center;
}
.knob .circle {
width: 48px;
height: 48px;
border: 3px solid #0f3460;
border-radius: 50%;
margin: 0 auto 6px;
position: relative;
background: #16213e;
cursor: grab;
}
.knob .circle::after {
content: "";
position: absolute;
top: 4px;
left: 50%;
width: 2px;
height: 14px;
background: #e94560;
transform: translateX(-50%) rotate(45deg);
border-radius: 1px;
}
.knob label { font-size: 10px; color: #888; display: block; }
.knob .value { font-size: 12px; color: #fff; font-weight: 600; }
/* ── Side panel (presets / browser) ── */
.side-panel {
background: #0a1628;
border-radius: 10px;
padding: 12px;
}
.side-panel .tabs {
display: flex;
gap: 4px;
margin-bottom: 10px;
}
.side-panel .tabs button {
flex: 1;
background: #0f3460;
border: none;
color: #888;
padding: 6px;
border-radius: 6px;
font-size: 11px;
cursor: pointer;
}
.side-panel .tabs button.active { background: #1a4a7a; color: #fff; }
.side-panel .preset-list { }
.preset-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 6px;
border-bottom: 1px solid #0f3460;
cursor: pointer;
}
.preset-item:hover { background: #0f3460; border-radius: 4px; }
.preset-item .p-name { font-size: 12px; }
.preset-item .p-num { color: #555; font-size: 10px; }
.preset-item.active { color: #53d769; }
.preset-item.bank-header { color: #555; font-size: 10px; letter-spacing: 1px; text-transform: uppercase; }
/* ── Bottom nav ── */
.bottom-nav {
display: flex;
gap: 4px;
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #0f3460;
}
.bottom-nav button {
flex: 1;
background: none;
border: 1px solid #0f3460;
color: #888;
padding: 8px;
border-radius: 8px;
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
text-align: center;
}
.bottom-nav button:hover { border-color: #53d769; color: #fff; }
.bottom-nav button.active { background: #0f3460; color: #53d769; border-color: #53d769; }
/* ── Tag overlay ── */
.tag {
position: absolute;
font-size: 10px;
color: #53d76955;
letter-spacing: 1px;
font-weight: 600;
}
</style>
</head>
<body>
<h1>🎛️ Figma Template — Pi Multi-FX Pedal UI</h1>
<div class="pedal-frame">
<!-- TOP BAR -->
<div class="topbar">
<div>
<div class="preset-name">Crunch Rhythm</div>
<div class="preset-bank">Bank 1 · Preset 2 of 4</div>
</div>
<div class="status-area">
<div class="cpu-badge">CPU 34%</div>
<button class="bypass-btn off">⏸ BYPASS</button>
</div>
</div>
<!-- VU METERS -->
<div class="vu-section">
<div class="vu-meter">
<label>Input</label>
<div class="vu-bar"><div class="vu-fill left"></div></div>
</div>
<div class="vu-meter">
<label>Output</label>
<div class="vu-bar"><div class="vu-fill right"></div></div>
</div>
<div class="vu-meter" style="flex: 0 0 40px;">
<label>dB</label>
<div style="font-size:22px;font-weight:700;color:#e0e0e0;">-12</div>
</div>
</div>
<!-- FX CHAIN -->
<div class="fx-chain">
<div class="fx-block active"><span class="icon">🚪</span><span class="name">Gate</span></div>
<div class="arrow"></div>
<div class="fx-block active"><span class="icon">📦</span><span class="name">Comp</span></div>
<div class="arrow"></div>
<div class="fx-block active"><span class="icon">🎸</span><span class="name">OD</span></div>
<div class="arrow"></div>
<div class="fx-block active" style="border-color:#53d769;"><span class="icon">🧠</span><span class="name">NAM</span></div>
<div class="arrow"></div>
<div class="fx-block active"><span class="icon">📡</span><span class="name">IR</span></div>
<div class="arrow"></div>
<div class="fx-block bypassed"><span class="icon">〰️</span><span class="name">Chorus</span></div>
<div class="arrow"></div>
<div class="fx-block active"><span class="icon"></span><span class="name">Delay</span></div>
<div class="arrow"></div>
<div class="fx-block active"><span class="icon">🌊</span><span class="name">Reverb</span></div>
</div>
<!-- MAIN GRID -->
<div class="main-grid">
<!-- KNOB PANEL -->
<div class="knob-panel">
<div class="header">
<h3>NAM Amp</h3>
<span class="type">feather-lstm · 1.2ms</span>
</div>
<div class="knob-grid">
<div class="knob">
<div class="circle"></div>
<label>Gain</label>
<div class="value">68</div>
</div>
<div class="knob">
<div class="circle"></div>
<label>Tone</label>
<div class="value">52</div>
</div>
<div class="knob">
<div class="circle"></div>
<label>Level</label>
<div class="value">75</div>
</div>
<div class="knob">
<div class="circle"></div>
<label>Gate</label>
<div class="value">30</div>
</div>
<div class="knob">
<div class="circle"></div>
<label>Bass</label>
<div class="value">45</div>
</div>
<div class="knob">
<div class="circle"></div>
<label>Mid</label>
<div class="value">60</div>
</div>
<div class="knob">
<div class="circle"></div>
<label>Treble</label>
<div class="value">55</div>
</div>
<div class="knob">
<div class="circle"></div>
<label>Presence</label>
<div class="value">40</div>
</div>
</div>
</div>
<!-- SIDE PANEL -->
<div class="side-panel">
<div class="tabs">
<button class="active">Presets</button>
<button>Models</button>
<button>IRs</button>
</div>
<div class="preset-list">
<div class="preset-item bank-header">Bank 1 — Classics</div>
<div class="preset-item active">
<span class="p-name">✦ Crunch Rhythm</span>
<span class="p-num">1-2</span>
</div>
<div class="preset-item">
<span class="p-name">Lead Solo</span>
<span class="p-num">1-3</span>
</div>
<div class="preset-item">
<span class="p-name">Clean Jazz</span>
<span class="p-num">1-4</span>
</div>
<div class="preset-item bank-header">Bank 2 — FX Bench</div>
<div class="preset-item">
<span class="p-name">Overdrive</span>
<span class="p-num">2-1</span>
</div>
<div class="preset-item">
<span class="p-name">Ambient Wash</span>
<span class="p-num">2-2</span>
</div>
</div>
</div>
</div>
<!-- BOTTOM NAV -->
<div class="bottom-nav">
<button class="active">🎛️ Rig</button>
<button>🔧 FX</button>
<button>📋 Presets</button>
<button>🔍 Browse</button>
<button>⚙️ Settings</button>
</div>
</div>
<p style="margin-top:32px;color:#555;font-size:12px;text-align:center;max-width:600px;">
Dark theme · 800×500 mockup (scales to 480×320 touchscreen).<br>
Sections: Top bar, VU meters, FX chain, knob panel, presets sidebar, bottom nav.<br>
Trace over the labeled blocks in Figma — swap icons, fonts, spacing to taste.
</p>
</body>
</html>