ADR-020: V3 Structural Rendering Migration
Status
Draft (For Discussion)
Context
Problem Statement
Conductor's Signal Flow workspace was rebuilt in #583 (PR #595 + #629, 125 tests) using a V2 rendering model: track-row / track-rail / track-junction. This model places all of a device's mappings on a single horizontal rail, with junction nodes marking each mapping point. While this works for 2–3 mappings per device, it breaks down at real-world density (6–12 mappings per device):
-
Junction overlap: Junctions are positioned along the rail by mapping index. At >5 mappings, labels collide and become unreadable. The mockups' V2→V3 Problem tab demonstrates this with 8 junctions on one rail.
-
SVG overlay complexity: Cross-track connectors (routing between devices) and feedback loop arcs are rendered as SVG overlays. These require z-order management, intersection calculations, and careful coordinate tracking as the DOM layout changes. Any re-render risks stale SVG positions.
-
Fan-out stacking: V2 renders fan-out (1:N) mappings as vertically stacked destinations below the junction, pushing the track row height to 120px+ and creating irregular row heights.
-
Undifferentiated unmapped events: V2 shows a single dashed-circle terminus dot at the end of the track rail for all unmapped events — no breakdown by type, no event rate, no creation affordance.
-
Fired state ambiguity: V2 pulses only the junction dot, which is small and positioned among overlapping siblings. The user must scan the rail to find which junction fired.
The V3 Signal Flow mockups (signal-flow-v3-mockups.html, Combined tab — the RECOMMENDED implementation) define a new component structure that addresses all five problems. This ADR governs the structural migration from V2 to V3.
What This ADR Covers vs What It Doesn't
In scope — V3 structural rendering:
device-group/mapping-branch/mb-railcomponent tree replacingtrack-row/track-rail/track-junction- Per-mapping branch rows with individual trigger → rail → action columns
- Inline badges for fan-out, sequence, conditional, loop warning, and cross-track routing
- Individual unmapped trigger rows with event rate and
+ MapCTA - Fan-out sub-rows with count badges
- Fired state as full row highlight
- Compact mode (aggregate device summaries with type distribution bar, sparkline, metrics)
- Device header bar with channel activity, mapping count, unmapped count, connection status
- Live event dot animation on per-mapping rails (not device rail)
Out of scope — covered by other ADRs:
- ADR-019: Shared filter stores (
workspaceFilters.js), link/unlink toggle, collapsible filter panes,TypeFilterChips, mode filter,signalFlowMetrics.js, Events sidebar enhancements - ADR-017: MappingEditor opened by
+ MapCTA (this ADR only wires the button) - ADR-015:
signalPulseStore(T2) data consumed by compact mode metrics and live event dots - ADR-014:
mappingFireState/mappingFireCountconsumed by fired state highlighting
V2 Component Inventory (#583 Output)
The V2 implementation from #583 created these components (all in conductor-gui/ui/src/lib/components/signal-flow/):
| V2 Component | Purpose | V3 Fate |
|---|---|---|
SignalFlowView.svelte | Top-level container — iterates devices, renders track rows | Rewrite: iterate device-group components instead of track rows |
TrackRow.svelte | One row per device: source → rail → destinations | Replace with DeviceGroup.svelte |
TrackRail.svelte | Horizontal rail with junction nodes | Replace with MappingBranch.svelte (one per mapping) |
TrackJunction.svelte | Individual junction on a rail | Remove — functionality absorbed into MappingBranch inline badges |
CrossTrackConnector.svelte | SVG overlay connecting junctions across devices | Remove — replaced by inline ↗ cross-track badge |
LoopDetector.svelte | SVG overlay drawing loop arcs | Remove — replaced by inline ⚠ loop badge |
SignalFlowToolbar.svelte | Mode/device/type filter bar + density toggle | Keep — minor modifications to host ADR-019 filter components |
signal-flow-store.js | Local state (selected junction, hovered mapping, etc.) | Refactor — replace junction-centric state with branch-centric state |
signal-flow.test.ts | 125 tests covering V2 rendering | Migrate — tests must be rewritten for V3 component APIs |
Decision
D1: Device-Group / Mapping-Branch / MB-Rail Component Tree
Replace the V2 track-row → track-rail → track-junction tree with a V3 tree:
SignalFlowView
└── DeviceGroup (one per device)
├── DeviceHeader (device name, port, channels, mapping count, unmapped count, status)
└── MappingBranch[] (one per mapping in active mode)
├── mb-stripe (device colour left border)
├── mb-trigger (trigger type dot + type label + trigger identifier)
├── mb-rail (thin horizontal connector line + optional inline badge)
│ └── InlineBadge? (fan-out ◆×N / sequence ▶ / conditional ◇ / loop ⚠ / cross-track ↗)
└── mb-action (action icon + action label)
Rationale: Each mapping gets its own horizontal row. This eliminates junction overlap entirely — there is no horizontal position competition because each mapping is a discrete row. The visual weight shifts from the rail (which is now a thin 1px connector) to the trigger and action columns (which is where the user actually needs to read).
Data flow: SignalFlowView receives the filtered mapping list from signalFlowDeviceFilter (ADR-019) and groups by device. Each DeviceGroup receives its device info and mapping array. Each MappingBranch receives a single mapping object and renders it.
D2: Inline Badges Replace SVG Overlays
V2's SVG-based cross-track connectors and loop arcs are replaced by inline badge elements positioned within the mb-rail of the affected MappingBranch:
| V2 Approach | V3 Replacement | Benefit |
|---|---|---|
CrossTrackConnector.svelte (SVG path between track rows) | <span class="cross-track-badge">↗ cross-track</span> inside mb-action | No z-order, no coordinate tracking, no stale SVG |
LoopDetector.svelte (SVG arc from junction to junction) | <span class="loop-badge">⚠ loop</span> inside mb-action + mb-junction.loop-warning on rail | Click opens loop detail tooltip; no SVG path management |
Junction type indicators (◆ fan-out, ▶ seq, ◇ if) on TrackJunction | InlineBadge component inside mb-rail | Same visual, but positioned in its own row — no overlap with adjacent badges |
InlineBadge variants (all positioned at ~40% of rail width):
<!-- Fan-out: blue diamond + count -->
<div class="mb-junction fan-out"><span>◆</span><span class="mb-fan-count">{count}</span></div>
<!-- Sequence: purple triangle + label -->
<div class="mb-junction sequence"><span>▶</span> Seq</div>
<!-- Conditional: amber diamond + label -->
<div class="mb-junction conditional"><span>◇</span> If…</div>
<!-- Loop warning: red warning -->
<div class="mb-junction loop-warning"><span>⚠</span></div>
<!-- Fired state: green bolt -->
<div class="mb-junction fired"><span>⚡</span></div>
Click targets preserved: #583 Phase C built click interaction handlers for loop warning badges, unmapped terminus dots, and error junctions. These handlers wire into the Chat bridge (#568). The V3 inline badges must preserve the same data-mapping-id and click event dispatch so the Chat bridge continues to work.
D3: Fan-Out Sub-Rows
V2 stacks fan-out destinations vertically below the junction, creating irregular row heights. V3 renders fan-out as explicit sub-rows:
MappingBranch (trigger → ◆×3 → primary action)
FanOutRow (2: secondary action)
FanOutRow (3: tertiary action)
Structure: Each FanOutRow is a thinner (22px min-height vs 32px) row aligned with the action column. It shows: index number, a thin connector line, and the action. Fan-out rows are visually indented (padding-left aligns with the action column, ~143px).
Collapse behaviour: Fan-out sub-rows for mappings with ×3 or more destinations are collapsed by default — the primary action shows "Volume + 2 more". Clicking the fan-out badge expands the sub-rows. This keeps the default view compact while allowing drill-down.
State: Expanded/collapsed state per mapping is tracked in the local signal-flow-store.js as expandedFanOuts: Set<mappingId>. This is ephemeral (memory only).
D4: Individual Unmapped Trigger Rows
V2 shows a single dashed terminus dot for all unmapped events. V3 shows each unmapped trigger type as its own row below the mapped branches, within the device group:
DeviceGroup
DeviceHeader (… 2 unmapped)
MappingBranch (mapped trigger 1)
MappingBranch (mapped trigger 2)
…
UnmappedRow (CC74 ch.1 — 12 ev/s — [+ Map])
UnmappedRow (PitchBend ch.1 — 2 ev/s — [+ Map])
UnmappedRow structure:
- Dashed border (1px dashed, not solid)
- 50% opacity by default, 80% on hover
- Trigger column: dashed dot + type label + trigger identifier (from T1 structural topology)
- Rail: dashed line (no junction)
- Action column: "no mapping" label +
+ MapCTA button
+ Map CTA: Clicking opens the MappingEditor (ADR-017) pre-filled with the unmapped trigger's device, type, channel, and number. The CTA passes a triggerPrefill object:
{ device_id: 'mikro-mk3-midi', trigger_type: 'cc', channel: 1, number: 74 }
Data source: Unmapped triggers are derived from signalPulseStore.unmapped_by_device (ADR-015 T2). If T2 is not yet implemented, unmapped rows are hidden and only the device header's unmapped count shows "—".
Compact mode: Unmapped rows are NOT shown in compact mode. Instead, the device summary header shows an aggregate amber unmapped count. The type distribution bar excludes unmapped triggers (shows mapped types only).
Alert promotion: When ADR-015 T3 fires a "Sustained Unmapped" alert (>50 events of same type in 30s), the corresponding unmapped row promotes from 50% to 80% opacity with an amber pulse animation. This draws attention without being disruptive.
D5: Fired State — Full Row Highlight
V2 pulses only the junction dot. V3 highlights the entire MappingBranch row:
.mapping-branch.fired {
background: var(--green-08); /* subtle green tint */
}
.mapping-branch.fired .mb-trigger-label {
color: var(--green);
}
.mapping-branch.fired .mb-junction {
border-color: var(--green);
color: var(--green);
background: var(--green-08);
}
Data source: mappingFireState from events.js (ADR-014) provides the set of recently-fired mapping IDs. The MappingBranch component checks if its mapping.id is in this set and applies the .fired class.
Animation: A live event dot (6px circle, trigger type colour) animates along the mb-rail from left to right (CSS @keyframes travel-branch, 1.2s duration). The junction badge shows ⚡ during the fired window (default 3s per ADR-014). This is more prominent than V2's junction-only pulse because the full row background changes.
D6: Device Header Bar
Each DeviceGroup has a header bar showing device metadata:
| Element | Source | Purpose |
|---|---|---|
| Device colour dot | deviceBindingsStore | Visual device identification |
| Device name | deviceBindingsStore.name | Primary label |
| Port name | deviceBindingsStore.port | MIDI port context |
| Channel activity bars | signalPulseStore.devices[id].channels | Mini bar chart per active channel |
| Mapping count pill | mappingsStore (filtered by mode) | "6 mappings" |
| Unmapped count | signalPulseStore.unmapped_by_device[id] (T2) | Amber "2 unmapped" |
| Connection status | deviceBindingsStore.connected | Green dot + "connected" / dim "disconnected" |
Collapsible: The device header is clickable — clicking collapses/expands the mapping branches within the group. State tracked in signal-flow-store.js as collapsedDevices: Set<deviceId>. Default: expanded.
D7: Compact Mode — Aggregate Device Summaries
When density is set to compact (▬), each DeviceGroup renders as a single summary row instead of individual branches:
DeviceSummary
├── ▸ expand chevron
├── Device dot + name
├── Type distribution bar (micro-bar with coloured segments per trigger type)
├── Stats: mappings count, unmapped count, fire rate, warning count
└── Sparkline (8-sample fire rate history)
Component: DeviceSummary.svelte replaces the DeviceHeader + MappingBranch[] when compact. Clicking the expand chevron (▸) opens the full expanded view for that specific device, even while other devices remain compact.
Data: All metrics consumed from deviceMetrics derived store (ADR-019 D8 / signalFlowMetrics.js).
Type distribution bar: A 48px-wide micro-bar divided into coloured segments proportional to the count of each trigger type (Note=green, CC=blue, AT=amber, Bend/Enc=purple). This gives an at-a-glance trigger type breakdown without expanding.
Sparkline: 8 bars, each 2px wide, representing the last 8 fire rate samples (4-minute trend). Consumed from the fireRateHistory ring buffer in signalFlowMetrics.js.
D8: Live Event Dot Animation on Per-Mapping Rails
V2 animated event dots along the device rail. V3 animates them on the specific mapping's mb-rail:
@keyframes travel-branch {
0% { left: 0%; opacity: 0; }
5% { opacity: 0.8; }
80% { opacity: 0.8; }
100% { left: 90%; opacity: 0; }
}
Matching logic: When a raw event arrives in eventBuffer, the system identifies which mapping(s) it matches (by trigger signature: type + device + channel + number). The matched MappingBranch spawns a <div class="mb-event-dot"> with the trigger type's colour. The dot animates left-to-right over 1.2s, then is removed from the DOM.
Performance: Event dots are CSS-only animations (no JS per frame). The DOM node is created on match and garbage-collected after animation completes. At high event rates (>30/s on one mapping), dots are throttled to max 1 per 200ms per mapping to prevent DOM buildup.
D9: Migration Strategy — Incremental, Not Big-Bang
The V2→V3 migration follows an incremental approach within the existing SignalFlowView.svelte:
Phase 1 — Scaffold V3 components alongside V2 (can import V2 interactions):
- Create
DeviceGroup.svelte,MappingBranch.svelte,DeviceHeader.svelte,UnmappedRow.svelte,FanOutRow.svelte,InlineBadge.svelte SignalFlowView.svelterenders V3 components behind a feature flag (v3Renderinginsignal-flow-store.js)- V2 components (
TrackRow,TrackRail,TrackJunction) remain in place
Phase 2 — Wire interactions and live data:
- Connect
MappingBranchtomappingFireStatefor fired highlighting - Connect
DeviceHeadertosignalPulseStorefor channel activity and unmapped counts - Wire
InlineBadgeclick handlers to preserve Chat bridge dispatch - Implement event dot animation on
mb-rail - Wire
UnmappedRow+ MapCTA to MappingEditor (ADR-017)
Phase 3 — Compact mode:
- Create
DeviceSummary.sveltefor compact rendering - Wire to
deviceMetricsstore (ADR-019) for aggregate metrics - Implement type distribution bar, sparkline, stat cells
- Density toggle switches between
DeviceGroup(expanded) andDeviceSummary(compact)
Phase 4 — Remove V2 components and tests:
- Remove
TrackRow.svelte,TrackRail.svelte,TrackJunction.svelte,CrossTrackConnector.svelte,LoopDetector.svelte - Migrate or rewrite the 125 V2 tests for V3 component APIs
- Remove feature flag — V3 becomes the only rendering path
- Clean up orphaned CSS classes (
.track-row,.track-rail,.track-junction)
Specification: Component Files
New Files
| File | Purpose | LOC (est.) |
|---|---|---|
components/signal-flow/DeviceGroup.svelte | Container: header + mapping branches + unmapped rows | ~100 |
components/signal-flow/DeviceHeader.svelte | Device metadata bar (name, port, channels, counts, status) | ~80 |
components/signal-flow/MappingBranch.svelte | Single mapping row: trigger → rail → action | ~90 |
components/signal-flow/InlineBadge.svelte | Rail junction badge (fan-out, seq, cond, loop, fired, cross-track) | ~60 |
components/signal-flow/FanOutRow.svelte | Fan-out sub-row (index + connector + action) | ~40 |
components/signal-flow/UnmappedRow.svelte | Unmapped trigger row (dashed, 50% opacity, + Map CTA) | ~50 |
components/signal-flow/DeviceSummary.svelte | Compact mode device summary (metrics, sparkline, type bar) | ~100 |
components/signal-flow/EventDot.svelte | Animated event dot on mb-rail (CSS animation, self-removing) | ~30 |
Modified Files
| File | Change |
|---|---|
components/signal-flow/SignalFlowView.svelte | Rewrite iteration: from trackRows array to deviceGroups array. Feature flag for incremental rollout. |
components/signal-flow/signal-flow-store.js | Replace junction-centric state (selectedJunction, hoveredJunction) with branch-centric state (selectedMapping, hoveredMapping, expandedFanOuts, collapsedDevices). Add v3Rendering feature flag. |
components/signal-flow/SignalFlowToolbar.svelte | Minor: ensure density toggle dispatches to signalFlowDensity store (ADR-019). No structural changes. |
Removed (Phase 4)
| File | Replacement |
|---|---|
TrackRow.svelte | DeviceGroup.svelte |
TrackRail.svelte | MappingBranch.svelte × N |
TrackJunction.svelte | InlineBadge.svelte (positioned in MappingBranch) |
CrossTrackConnector.svelte | Inline ↗ cross-track badge in MappingBranch action column |
LoopDetector.svelte | Inline ⚠ loop badge in MappingBranch action + InlineBadge on rail |
Specification: Data Flow
Expanded Mode Rendering
mappingsStore (all mappings from config)
↓ filter by activeModeFilter (ADR-019)
filteredMappings
↓ group by device_id
deviceGroups: Map<deviceId, Mapping[]>
↓ filter by signalFlowDeviceFilter (ADR-019)
visibleDeviceGroups
↓ for each device
DeviceGroup
├── DeviceHeader ← deviceBindingsStore[id] + signalPulseStore[id]
├── MappingBranch[] ← mapping + mappingFireState[mapping.id]
│ ├── InlineBadge ← mapping.action_type (fan-out/seq/cond) + loopDetection
│ └── EventDot[] ← eventBuffer (matched by trigger signature)
├── FanOutRow[] ← mapping.actions[1..N] (when expanded)
└── UnmappedRow[] ← signalPulseStore.unmapped_by_device[id]
Compact Mode Rendering
deviceMetrics (ADR-019 D8)
↓ for each device in visibleDeviceGroups
DeviceSummary
├── Type distribution bar ← deviceMetrics[id].typeDistribution
├── Mapping count ← deviceMetrics[id].mappingCount
├── Unmapped count ← deviceMetrics[id].unmappedCount
├── Fire rate ← deviceMetrics[id].fireRate
├── Sparkline ← deviceMetrics[id].fireRateHistory
└── Warning count ← deviceMetrics[id].warningCount
Event Dot Lifecycle
eventBuffer.push(rawEvent)
↓ for each visible MappingBranch
check: rawEvent.trigger matches mapping.trigger?
↓ yes
MappingBranch → spawn EventDot(colour = triggerTypeColour)
↓ CSS animation: travel-branch (1.2s)
↓ animationend → remove from DOM
MappingBranch → add mapping.id to recentlyFiredSet (3s TTL)
↓ .fired class applied to row
Implementation Plan
Phase 1: V3 Component Scaffold — 8–10h (Issues #656–#659)
- #656 (1A): Create
DeviceGroup.svelte+DeviceHeader.svelte— device colour dot, name, port, channel bars, mapping count, unmapped count, status, collapse toggle - #657 (1B): Create
MappingBranch.svelte+InlineBadge.svelte— trigger → rail → action row; badge variants (fan-out, sequence, conditional, loop-warning, fired, cross-track) - #658 (1C): Create
FanOutRow.svelte+UnmappedRow.svelte— fan-out sub-rows with expand/collapse; unmapped trigger rows with+ MapCTA - #659 (1D): Wire
SignalFlowView.svelteto render V3 components behindv3Renderingfeature flag; refactorsignal-flow-store.jsfor branch-centric state
Phase 2: Interactions & Live Data — 6–8h (Issues #660–#662)
- #660 (2A): Wire fired state (
mappingFireState→ full-row highlight) + createEventDot.svelte(CSS animation, 200ms throttle per mapping, self-remove onanimationend) - #661 (2B): Wire
InlineBadgeclick handlers for Chat bridge dispatch (signal-flow:loop-click,signal-flow:mapping-select) +UnmappedRow+ MapCTA →mapping-editor:open - #662 (2C): Wire
DeviceHeadertosignalPulseStorefor channel activity bars + unmapped counts (graceful degradation when T2 unavailable)
Phase 3: Compact Mode — 4–6h (Issues #663–#664)
- #663 (3A): Create
DeviceSummary.svelte— expand chevron, type distribution micro-bar, sparkline, stat cells (mapping count, unmapped, fire rate, warnings). Wire todeviceMetricsstore (ADR-019 #641) - #664 (3B): Wire density toggle (▤ expanded / ▬ compact) to switch
DeviceGroup↔DeviceSummary. Per-device expand-in-compact. Smart default (auto-compact when >15 mappings)
Phase 4: V2 Removal & Test Migration — 4–6h (Issues #665–#666)
- #665 (4A): Delete
TrackRow.svelte,TrackRail.svelte,TrackJunction.svelte,CrossTrackConnector.svelte,LoopDetector.svelte. Removev3Renderingflag. Clean up orphaned CSS. RemoveselectedJunction,hoveredJunctionfrom store. - #666 (4B): Migrate 125 V2 tests to V3 component APIs (target: 165+ tests). Verify Chat bridge 27 tests pass without modification (non-regression against #568).
Total estimated effort: 22–30 hours across 4 phases.
Tracking issue: #667
Phase 1 (#656–#659) depends on ADR-019 Phase 1 (#633). Phase 2 (#660–#662) depends on Phase 1. Phase 3 (#663–#664) depends on Phase 2 and ADR-019 Phase 5 (#641–#642). Phase 4 (#665–#666) depends on Phases 1–3.
Implementation Order — Where ADR-020 Fits
Global Sequencing
#583 (Signal Flow V2) ✅ MERGED (PR #595 + #629, 125 tests)
#568 (Chat bridge) ✅ MERGED (PR #628 + #629, 27 tests)
#569 (Token budget) 🟡 OPEN — independent, can run in parallel
│
STAGE 1 ─ ADR-019 Phase 1 │
#633 → #634 → #635 Shared filter stores (workspaceFilters.js)
│
STAGE 2 ─ ADR-020 Phase 1 │
#656 → #657 → #658 → #659 V3 component scaffold + feature flag
│
STAGE 3 ─ Parallel │
Track A (ADR-020 P2): │
#660, #661, #662 Fired state, click handlers, pulse data
Track B (ADR-019 P2–4): │
#636, #637, #638, Collapsible panes, link/unlink,
#639, #640 type chips, mode filter
│
STAGE 4 ─ ADR-019 Phase 5 │
#641 → #642 signalFlowMetrics.js (compact mode needs this)
│
STAGE 5 ─ ADR-020 Phase 3 │
#663 → #664 Compact mode (DeviceSummary + density toggle)
│
STAGE 6 ─ Parallel │
Track A (ADR-020 P4): │
#665 → #666 V2 removal + test migration (125 → 165+)
Track B (ADR-019 P6–7): │
#643 → #644 Unmapped tagging + integration tests
Tracking issues: #645 (ADR-019), #667 (ADR-020)
Key Sequencing Rationale
-
ADR-019 Phase 1 (#633–#635) before ADR-020 Phase 1 (#656–#659): The V3
DeviceGroupandMappingBranchcomponents should consumesignalFlowDeviceFilterandactiveModeFilterfrom day one. Building them against V2's ad-hoc filter state and then retrofitting would create unnecessary rework. -
ADR-020 Phase 2 (#660–#662) parallel with ADR-019 Phases 2–4 (#636–#640): The V3 interaction wiring and the filter UI components (collapsible panes, link/unlink, type chips) touch different file sets. Two contributors can work simultaneously.
-
ADR-019 Phase 5 (#641–#642) before ADR-020 Phase 3 (#663–#664): Compact mode's
DeviceSummaryconsumesdeviceMetricsfromsignalFlowMetrics.js(ADR-019 D8). The metrics store must exist before compact mode can render real data. -
#569 is independent: Token budget and multi-provider work (#569) has no dependency on V3 rendering. It can proceed in parallel with all ADR-019 and ADR-020 work.
-
ADR-020 Phase 4 (#665–#666) is the final step: Only remove V2 components after all V3 phases are complete and tested. The feature flag provides a safe rollback path during development.
Consequences
Positive
- Unlimited mapping density: Each mapping is its own row — adding a 13th mapping to a device just adds a 13th row, with no overlap or readability degradation
- No SVG overlay management: Inline badges eliminate coordinate tracking, z-order management, and stale-position bugs that come with SVG overlays
- Better fired state visibility: Full row highlight is immediately visible even in a long list — the user doesn't need to scan a rail for a small pulsing dot
- Unmapped event actionability: Individual unmapped rows with
+ MapCTA turn a passive indicator into a creation affordance — users can map unmapped triggers in one click - Preserves Chat bridge: Click handlers on inline badges maintain the same dispatch events that #568 wired, so the Chat bridge doesn't break
- Compact mode ready: The
DeviceGroup→DeviceSummarydensity toggle gives users a one-click way to manage visual density for large configurations
Negative
- Vertical space increase: 8 mappings × 32px = ~260px vs V2's ~80px single rail (mitigated by compact mode, filter-based device hiding, and device group collapsing)
- Test migration cost: 125 existing V2 tests must be rewritten for V3 component APIs — this is real work, not just find-and-replace
- Two rendering paths during migration: The feature flag means both V2 and V3 code exist simultaneously during Phases 1–3, increasing bundle size temporarily
Risks
- R1: Chat bridge regression: V3 components must dispatch the same click events (#568 expects). Mitigation: explicit non-regression test suite that verifies each click target (loop badge, unmapped dot, error junction) dispatches the correct event with the correct payload.
- R2: Event dot performance: At high event rates (>30 events/sec on one mapping), DOM node creation for event dots could cause jank. Mitigation: 200ms throttle per mapping, CSS-only animation (no JS per frame),
animationendcleanup. - R3: ADR-019 Phase 5 delay: If
signalFlowMetrics.jsis delayed, compact mode (Phase 3) cannot render real data. Mitigation:DeviceSummarygracefully degrades — shows structural metrics (mapping count, type distribution) frommappingsStoredirectly, with "—" for runtime metrics.
Dependencies
- #583 (Signal Flow V2): ✅ MERGED — provides the V2 components being replaced and the test baseline
- #568 (Chat bridge): ✅ MERGED — provides click dispatch infrastructure that V3 must preserve
- ADR-019 Phase 1 (#633, #634, #635 —
workspaceFilters.js): Required before Phase 1 (#656) — V3 components consume shared filters - ADR-019 Phase 5 (#641, #642 —
signalFlowMetrics.js): Required before Phase 3 (#663) — compact mode consumesdeviceMetrics - ADR-015 (
signalPulseStore): Consumed byDeviceHeader(channel activity, unmapped counts) andEventDot(event matching). Graceful degradation if not yet implemented. - ADR-014 (
mappingFireState): Consumed byMappingBranchfor fired state highlighting. Already implemented in #583. - ADR-017 (MappingEditor): Opened by
+ MapCTA onUnmappedRow. If not yet implemented, CTA is disabled with tooltip "MappingEditor not yet available". - ADR-021 (Input/Output Device Qualification, #669–#676): Phase 4 (#674) modifies
MappingBranch.svelte(#657) to resolveSendMidi/MidiForwardaction targets to device aliases with colour dots. Must land after ADR-020 Phase 1 (#656–#659). Phase 3 (#672–#673) adds direction indicators to device pills — parallel with ADR-020 Phase 2. Seeimplementation-order-adr-019-020-021.md.
References
signal-flow-v3-mockups.html: Combined tab (RECOMMENDED target), S1 Branched Layout, S3 Compact Mode — visual specification for all V3 componentsconductor-gui/ui/src/lib/components/signal-flow/SignalFlowView.svelte: V2 top-level container to be rewrittenconductor-gui/ui/src/lib/components/signal-flow/TrackRow.svelte: V2 track row to be replaced byDeviceGroupconductor-gui/ui/src/lib/components/signal-flow/TrackRail.svelte: V2 track rail to be replaced byMappingBranchconductor-gui/ui/src/lib/components/signal-flow/TrackJunction.svelte: V2 junction to be replaced byInlineBadgeconductor-gui/ui/src/lib/components/signal-flow/signal-flow-store.js: Local state store to be refactoredconductor-gui/ui/src/lib/components/signal-flow/signal-flow.test.ts: 125 V2 tests to be migratedconductor-gui/ui/src/lib/stores/events.js:mappingFireState,eventBufferconsumed by V3 components- ADR-014: Mapping Feedback & Simulate (
mappingFireState, fire count, toast notifications) - ADR-015: LLM Signal Awareness (
signalPulseStoreT2,signalAlertsT3) - ADR-017: Unified Mapping Experience (MappingEditor opened by
+ MapCTA) - ADR-019: Unified Workspace Filtering & Aggregate Metrics (shared filters,
signalFlowMetrics.js) - PR #595 + #629: Signal Flow V2 implementation (125 tests)
- PR #628 + #629: Chat bridge implementation (27 tests)