Skip to main content

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):

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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-rail component tree replacing track-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 + Map CTA
  • 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 + Map CTA (this ADR only wires the button)
  • ADR-015: signalPulseStore (T2) data consumed by compact mode metrics and live event dots
  • ADR-014: mappingFireState / mappingFireCount consumed 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 ComponentPurposeV3 Fate
SignalFlowView.svelteTop-level container — iterates devices, renders track rowsRewrite: iterate device-group components instead of track rows
TrackRow.svelteOne row per device: source → rail → destinationsReplace with DeviceGroup.svelte
TrackRail.svelteHorizontal rail with junction nodesReplace with MappingBranch.svelte (one per mapping)
TrackJunction.svelteIndividual junction on a railRemove — functionality absorbed into MappingBranch inline badges
CrossTrackConnector.svelteSVG overlay connecting junctions across devicesRemove — replaced by inline ↗ cross-track badge
LoopDetector.svelteSVG overlay drawing loop arcsRemove — replaced by inline ⚠ loop badge
SignalFlowToolbar.svelteMode/device/type filter bar + density toggleKeep — minor modifications to host ADR-019 filter components
signal-flow-store.jsLocal state (selected junction, hovered mapping, etc.)Refactor — replace junction-centric state with branch-centric state
signal-flow.test.ts125 tests covering V2 renderingMigrate — 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 ApproachV3 ReplacementBenefit
CrossTrackConnector.svelte (SVG path between track rows)<span class="cross-track-badge">↗ cross-track</span> inside mb-actionNo 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 railClick opens loop detail tooltip; no SVG path management
Junction type indicators (◆ fan-out, ▶ seq, ◇ if) on TrackJunctionInlineBadge component inside mb-railSame 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 + + Map CTA 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:

ElementSourcePurpose
Device colour dotdeviceBindingsStoreVisual device identification
Device namedeviceBindingsStore.namePrimary label
Port namedeviceBindingsStore.portMIDI port context
Channel activity barssignalPulseStore.devices[id].channelsMini bar chart per active channel
Mapping count pillmappingsStore (filtered by mode)"6 mappings"
Unmapped countsignalPulseStore.unmapped_by_device[id] (T2)Amber "2 unmapped"
Connection statusdeviceBindingsStore.connectedGreen 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.svelte renders V3 components behind a feature flag (v3Rendering in signal-flow-store.js)
  • V2 components (TrackRow, TrackRail, TrackJunction) remain in place

Phase 2 — Wire interactions and live data:

  • Connect MappingBranch to mappingFireState for fired highlighting
  • Connect DeviceHeader to signalPulseStore for channel activity and unmapped counts
  • Wire InlineBadge click handlers to preserve Chat bridge dispatch
  • Implement event dot animation on mb-rail
  • Wire UnmappedRow + Map CTA to MappingEditor (ADR-017)

Phase 3 — Compact mode:

  • Create DeviceSummary.svelte for compact rendering
  • Wire to deviceMetrics store (ADR-019) for aggregate metrics
  • Implement type distribution bar, sparkline, stat cells
  • Density toggle switches between DeviceGroup (expanded) and DeviceSummary (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

FilePurposeLOC (est.)
components/signal-flow/DeviceGroup.svelteContainer: header + mapping branches + unmapped rows~100
components/signal-flow/DeviceHeader.svelteDevice metadata bar (name, port, channels, counts, status)~80
components/signal-flow/MappingBranch.svelteSingle mapping row: trigger → rail → action~90
components/signal-flow/InlineBadge.svelteRail junction badge (fan-out, seq, cond, loop, fired, cross-track)~60
components/signal-flow/FanOutRow.svelteFan-out sub-row (index + connector + action)~40
components/signal-flow/UnmappedRow.svelteUnmapped trigger row (dashed, 50% opacity, + Map CTA)~50
components/signal-flow/DeviceSummary.svelteCompact mode device summary (metrics, sparkline, type bar)~100
components/signal-flow/EventDot.svelteAnimated event dot on mb-rail (CSS animation, self-removing)~30

Modified Files

FileChange
components/signal-flow/SignalFlowView.svelteRewrite iteration: from trackRows array to deviceGroups array. Feature flag for incremental rollout.
components/signal-flow/signal-flow-store.jsReplace junction-centric state (selectedJunction, hoveredJunction) with branch-centric state (selectedMapping, hoveredMapping, expandedFanOuts, collapsedDevices). Add v3Rendering feature flag.
components/signal-flow/SignalFlowToolbar.svelteMinor: ensure density toggle dispatches to signalFlowDensity store (ADR-019). No structural changes.

Removed (Phase 4)

FileReplacement
TrackRow.svelteDeviceGroup.svelte
TrackRail.svelteMappingBranch.svelte × N
TrackJunction.svelteInlineBadge.svelte (positioned in MappingBranch)
CrossTrackConnector.svelteInline ↗ cross-track badge in MappingBranch action column
LoopDetector.svelteInline ⚠ 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 + Map CTA
  • #659 (1D): Wire SignalFlowView.svelte to render V3 components behind v3Rendering feature flag; refactor signal-flow-store.js for branch-centric state

Phase 2: Interactions & Live Data — 6–8h (Issues #660–#662)

  • #660 (2A): Wire fired state (mappingFireState → full-row highlight) + create EventDot.svelte (CSS animation, 200ms throttle per mapping, self-remove on animationend)
  • #661 (2B): Wire InlineBadge click handlers for Chat bridge dispatch (signal-flow:loop-click, signal-flow:mapping-select) + UnmappedRow + Map CTA → mapping-editor:open
  • #662 (2C): Wire DeviceHeader to signalPulseStore for 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 to deviceMetrics store (ADR-019 #641)
  • #664 (3B): Wire density toggle (▤ expanded / ▬ compact) to switch DeviceGroupDeviceSummary. 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. Remove v3Rendering flag. Clean up orphaned CSS. Remove selectedJunction, hoveredJunction from 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

  1. ADR-019 Phase 1 (#633–#635) before ADR-020 Phase 1 (#656–#659): The V3 DeviceGroup and MappingBranch components should consume signalFlowDeviceFilter and activeModeFilter from day one. Building them against V2's ad-hoc filter state and then retrofitting would create unnecessary rework.

  2. 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.

  3. ADR-019 Phase 5 (#641–#642) before ADR-020 Phase 3 (#663–#664): Compact mode's DeviceSummary consumes deviceMetrics from signalFlowMetrics.js (ADR-019 D8). The metrics store must exist before compact mode can render real data.

  4. #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.

  5. 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 + Map CTA 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 DeviceGroupDeviceSummary density 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), animationend cleanup.
  • R3: ADR-019 Phase 5 delay: If signalFlowMetrics.js is delayed, compact mode (Phase 3) cannot render real data. Mitigation: DeviceSummary gracefully degrades — shows structural metrics (mapping count, type distribution) from mappingsStore directly, 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 consumes deviceMetrics
  • ADR-015 (signalPulseStore): Consumed by DeviceHeader (channel activity, unmapped counts) and EventDot (event matching). Graceful degradation if not yet implemented.
  • ADR-014 (mappingFireState): Consumed by MappingBranch for fired state highlighting. Already implemented in #583.
  • ADR-017 (MappingEditor): Opened by + Map CTA on UnmappedRow. 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 resolve SendMidi/MidiForward action 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. See implementation-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 components
  • conductor-gui/ui/src/lib/components/signal-flow/SignalFlowView.svelte: V2 top-level container to be rewritten
  • conductor-gui/ui/src/lib/components/signal-flow/TrackRow.svelte: V2 track row to be replaced by DeviceGroup
  • conductor-gui/ui/src/lib/components/signal-flow/TrackRail.svelte: V2 track rail to be replaced by MappingBranch
  • conductor-gui/ui/src/lib/components/signal-flow/TrackJunction.svelte: V2 junction to be replaced by InlineBadge
  • conductor-gui/ui/src/lib/components/signal-flow/signal-flow-store.js: Local state store to be refactored
  • conductor-gui/ui/src/lib/components/signal-flow/signal-flow.test.ts: 125 V2 tests to be migrated
  • conductor-gui/ui/src/lib/stores/events.js: mappingFireState, eventBuffer consumed by V3 components
  • ADR-014: Mapping Feedback & Simulate (mappingFireState, fire count, toast notifications)
  • ADR-015: LLM Signal Awareness (signalPulseStore T2, signalAlerts T3)
  • ADR-017: Unified Mapping Experience (MappingEditor opened by + Map CTA)
  • 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)