Skip to main content

ADR-017: Unified Mapping Experience — Learn, Configure & LLM-Assisted Workflow

Status

Draft (For Discussion)

Context

Problem Statement

Conductor's mapping workflow is fragmented across three disconnected paths that each do part of the job but don't compose into a coherent experience:

Path 1 — MCP/Chat MIDI Learn (30s dialogue) The LLM calls conductor_start_midi_learn, a 30-second timed dialogue opens (MidiLearnDialog.svelte), the user plays their controller, pattern detection identifies triggers (DoubleTap, Chord, LongPress, etc.), and the user selects an interpretation. The dialogue generates a TOML snippet or feeds a ConfigPlan. This path has several problems:

  • The 30-second countdown is inconsistent with the rest of the UI/UX — it feels like a modal interruption, not an integrated workflow
  • Pattern detection works well for DoubleTap and VelocityRange but is inconsistent for Chords (the auto-select-largest-chord heuristic at MidiLearnDialog lines 64-84 doesn't always pick what the user intended)
  • The result is a config snippet — it doesn't smoothly flow into mapping creation or editing
  • "Learn" is labelled as "MIDI Learn" throughout, but the system also supports gamepad events (GamepadButton, GamepadAnalogStick, GamepadTrigger, GamepadButtonChord, GamepadChord)

Path 2 — Events Panel Learn (highlighting + per-event Learn button) The Events panel has a "Start Learn" toggle that highlights incoming events with accent tinting. Each raw event row also has a hover-reveal "Learn" button that calls daemonMidiLearnStore.selectEvent(event)eventToTrigger() → opens MidiLearnRefinement.svelte (RefinementCard) in the workspace. This provides the best raw data visibility but:

  • The per-event Learn button opens RefinementCard, which only configures the trigger — not the full mapping (trigger + action + description)
  • There's no path from RefinementCard to creating/saving a complete mapping without the LLM
  • Pattern detection (DoubleTap, Chord) runs in the daemon but the Events panel doesn't surface patterns inline — the user must recognise patterns themselves from the raw stream
  • Bulk Learn mode (Start Learn toggle) only highlights events; it doesn't interact with pattern detection at all

Path 3 — ConfigDiff Plan/Apply When the LLM calls conductor_create_mapping or conductor_update_mapping, a ConfigPlan appears in the workspace (ConfigDiffView.svelte) with Apply/Edit/Reject. This works well for LLM-proposed changes but:

  • There's no way for the user to manually create, edit, move, or delete mappings through the UI
  • The only manual path is editing raw TOML in RawConfigView
  • ConfigDiffView is approval-only — it doesn't allow the user to modify the proposed mapping before applying

What users have told us works well:

  1. Events panel capture and highlighting — seeing the full data stream
  2. Double-tap / pattern detection config card — interactive parameter editing
  3. Chat plan-and-approve flow — natural language description → review → apply
  4. The workspace as a configuration surface — visual, not text-based

What doesn't work:

  1. The 30-second MIDI Learn dialogue feels disconnected and rushed
  2. "Learn" is not consistent — MCP Learn and Events Learn produce different outcomes
  3. No manual mapping CRUD without the LLM or raw TOML editing
  4. Chord detection is unreliable through the dialogue path
  5. No way to extend trigger interpretation (e.g., pressure curves, aftertouch zones, combined gestures)
  6. The LLM can't see or interact with what the user is currently configuring in the workspace

Current State — What Actually Exists

Before making design decisions, we must accurately catalogue what's implemented.

Pattern detection — implemented in midi_learn.rs:

PatternImplementationNotes
DoubleTaplast_note_times HashMap, ≤500ms gapWorking, tested
Chordheld_notes Vec, 100ms window, per-channel debounce timersWorking, but auto-select-largest heuristic is unreliable
LongPressnote_press_times HashMap, ≥1000ms holdWorking, tested
Encoder directionCC value delta from history (clockwise/counterclockwise)Working
CC streamCaptured as CC with optional value_rangeWorking
AftertouchCaptured with (note, pressure_range) — simple threshold, no zone detectionBasic only
PitchBendCaptured with (bend_range) — simple range, no zone detectionBasic only
Gamepad variantsGamepadButton, GamepadButtonChord, GamepadAnalogStick, GamepadTriggerWorking

Pattern detection — NOT implemented:

PatternStatusNotes
AftertouchZoneDoes not existNo zone-based pressure detection; Aftertouch is simple threshold
PitchBendZoneDoes not existNo zone-based bend detection; PitchBend is simple range
CompoundTriggerDoes not existNo combinator type in Trigger enum; no daemon evaluation logic
CC Stream groupingDoes not existCC events are captured individually; no inline "12 ev/s" aggregation in Events panel

Runtime PatternType enum (in event_types.rs): LongPress, DoubleTap, Chord, GamepadChord — only 4 variants.

Client-side eventToTrigger() (in stores.js): Converts daemon-detected patterns to trigger suggestions. Adds VelocityRange suggestion if 3+ presses with >30 velocity spread. Falls back to raw event type for non-pattern events.

Existing sub-editor components (these must be accommodated in any MappingEditor design):

ComponentLOCComplexityWhat It Does
VelocityMappingSelector.svelte694HighSVG curve editor: Fixed/PassThrough/Linear/Curve modes with Exponential/Logarithmic/S-Curve types. Real-time graph rendering. Used by SendMidiActionEditor for note_on velocity transforms.
ConditionalActionEditor.svelte889HighNested condition builder: 10 condition types (TimeRange, DayOfWeek, AppRunning, AppFrontmost, ModeIs, And/Or/Not). Recursive ActionSelector embedding.
SendMidiActionEditor.svelte808Medium-HighMIDI output config: 6 message types, channel selection, pitch bend range, embeds VelocityMappingSelector. Real-time validation via API.
RefinementCard.svelte357MediumPost-capture refinement: alternative interpretation chips, editable parameter fields, channel selector, collapsible advanced section with RangeSlider for velocity ranges.
RangeSlider.svelte129LowDual-thumb range slider (0-127). Reusable.
ActionSelector.svelte~400MediumAction type selector with embedded sub-editors (SendMidi, Conditional). Modifier grid for keystrokes.
TriggerSelector.svelte~350Medium13 trigger types with type-specific config fields. MIDI Learn button. Device selector (ADR-009).

Key implication: The MappingEditor cannot be a flat form of simple field-rows. It must delegate to TriggerSelector (which handles 13+ trigger types with type-specific fields) and ActionSelector (which embeds SendMidiActionEditor, ConditionalActionEditor, VelocityMappingSelector). The mockup's simplified view is the collapsed/summary state — editing expands to these sub-editors.

Existing MappingList.svelte CRUD: Already dispatches addMapping, editMapping, duplicateMapping, deleteMapping events with a confirmation modal. MappingRow.svelte renders coloured dots, → arrows, Sim button (hover-reveal), fire counts, LED glow, and row pulse animations. The gap is: these are embedded in dialogs, not promoted to a workspace view, and "Edit" has no editor to open.

Events panel Learn button: The hover-reveal "Learn" button on each EventRow IS wired. It calls daemonMidiLearnStore.selectEvent(event)eventToTrigger() → opens RefinementCard in the workspace. The gap is that RefinementCard only configures the trigger, not a full mapping.

What We Want

An integrated experience where:

  1. Learning is continuous, not modal. Events flow, patterns are detected in real-time, and the user can capture triggers at any time without a countdown dialogue
  2. The workspace is the configuration surface. The user sees, edits, creates, and manages mappings visually — not just approves LLM proposals
  3. The LLM participates in workspace configuration. It can see what's displayed, suggest modifications, and project configuration for review — extending ADR-013's artifact projection model
  4. Trigger types are extensible. DoubleTap, Chord, LongPress, VelocityRange, pressure/aftertouch zones, and compound gestures share a common refinement UI
  5. Manual and LLM paths converge. The same workspace components serve both manual editing and LLM-proposed changes
  6. Existing sub-editor complexity is preserved. VelocityMappingSelector curves, ConditionalActionEditor logic trees, SendMidiActionEditor MIDI output — these remain accessible through the unified editor, not replaced by simplified forms
  • ADR-002: MIDI Learn event streaming architecture — Tauri event push model, daemon session state machine
  • ADR-007: LLM integration — tool risk tiers, Plan/Apply workflow, agentic loop
  • ADR-013: LLM Canvas — artifact projection, component registry, bidirectional editing (Proposed, not implemented)
  • ADR-014: Mapping Feedback — mapping_fired events, action visibility
  • ADR-015 (in repo): Async action execution
  • ADR-016 (Signal Awareness, in outputs): LLM signal context — T1 topology, T2 pulse, T3 alerts, Performance Mode

Decision

D1: Eliminate the Timed MIDI Learn Dialogue

Replace the 30-second MidiLearnDialog.svelte modal with a continuous learn mode integrated into the Events panel and workspace.

Current flow:

Chat "learn CC7" → LLM calls conductor_start_midi_learn → 30s modal opens →
user plays controller → pattern detected → select interpretation → TOML snippet

New flow:

User clicks Learn in Events panel (or LLM activates via tool) →
Events panel enters Learn mode (highlighted stream, inline pattern badges) →
User clicks event, pattern group, or per-event Learn button → Trigger captured →
Workspace shows MappingEditor with trigger pre-filled →
User configures action (manual or via LLM) → Save

The key changes:

  • No countdown timer. Learn mode stays active until the user explicitly stops it or captures a trigger. The daemon session stays open.
  • Pattern detection surfaces inline. The existing daemon-side detection (DoubleTap, Chord, LongPress — already in midi_learn.rs and PatternType enum) is displayed as pattern groups in the Events stream, rather than only in MidiLearnDialog's event list.
  • Capture is a click, not a timeout. The user clicks an event or pattern group. This extends the existing per-event Learn button (which already calls selectEvent() → RefinementCard) to route to the full MappingEditor instead.
  • The MCP tool changes. conductor_start_midi_learn no longer opens a modal. It activates Learn mode in the Events panel and returns immediately. A new tool conductor_capture_trigger lets the LLM select a specific event/pattern from the stream.

Implementation note: The existing daemonMidiLearnStore.selectEvent()eventToTrigger()showMidiLearnRefinement() pipeline is preserved. The change is: (a) showMidiLearnRefinement() routes to MappingEditor instead of RefinementCard, and (b) inline pattern badges in the Events panel give users a second capture path beyond the per-event Learn button.

D2: Unified Mapping Editor (Workspace View)

A new workspace view — MappingEditor — that serves as the single configuration surface for creating, editing, and reviewing mappings. This replaces the current split between RefinementCard (trigger only), ConfigDiffView (approval only), and RawConfigView (TOML editing).

MappingEditor structure:

The editor has a collapsed summary view (shown in mockups) and an expanded editing view. The collapsed view shows field-row summaries; the expanded view delegates to the existing sub-editors:

┌─────────────────────────────────────────────────────────┐
│ MAPPING EDITOR Mode: Default │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─ TRIGGER ──────────────────── [● Captured] ────────┐ │
│ │ Summary: DoubleTap · N60 · 300ms · ch.any · Mikro │ │
│ │ [▶ Expand to edit] │ │
│ │ │ │
│ │ (Expanded: embeds TriggerSelector.svelte — │ │
│ │ 13 trigger types with type-specific fields, │ │
│ │ device selector, channel, RangeSlider for vel) │ │
│ │ │ │
│ │ ○ Capture from Events ○ LLM Suggest │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─ ACTION ──────────────────────────────────────────┐ │
│ │ Summary: Keystroke ⌘C │ │
│ │ [▶ Expand to edit] │ │
│ │ │ │
│ │ (Expanded: embeds ActionSelector.svelte — │ │
│ │ includes SendMidiActionEditor for MIDI output, │ │
│ │ ConditionalActionEditor for if-then-else, │ │
│ │ VelocityMappingSelector SVG curves for SendMidi, │ │
│ │ modifier grid for Keystroke) │ │
│ │ │ │
│ │ + Add Action (Sequence fan-out) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─ DESCRIPTION ──────────────────────────────────────┐ │
│ │ "Double-tap pad 1 to copy" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─ PREVIEW ──────────────────────────────────────────┐ │
│ │ trigger.type = "DoubleTap" ... │ │
│ │ ↕ Diff (vs current config) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ [ Save ] [ Save & New ] [ Cancel ] [ Ask LLM ] │
└─────────────────────────────────────────────────────────┘

Key behaviours:

  • Trigger section embeds TriggerSelector.svelte (expanded) or shows a summary line (collapsed). TriggerSelector already handles all 13 trigger types with device selector (ADR-009), channel, and type-specific fields (note preview, velocity ranges via RangeSlider, chord window, encoder direction).
  • Action section embeds ActionSelector.svelte (expanded) or shows a summary line (collapsed). ActionSelector already handles Keystroke (modifier grid), Text, Launch, Shell, MouseClick, VolumeControl, ModeChange, Delay, SendMidi (→ SendMidiActionEditor → VelocityMappingSelector curves), Conditional (→ ConditionalActionEditor with nested And/Or/Not).
  • "Capture from Events" enters Learn mode. Events panel highlights. User clicks event/pattern. Trigger section populates.
  • "LLM Suggest" sends current editor state to LLM. LLM responds with ConfigPlan that populates action section.
  • Save creates a ConfigPlan locally (same TOCTOU protection as LLM-generated plans) and applies it.
  • Save & New applies and immediately opens a fresh editor.
  • Diff section (collapsible) shows what will change vs current config, reusing DiffBlock from ConfigDiffView.

The MappingEditor is both a manual UI component and (in Phase 3) an ADR-013 artifact type (mapping-editor), meaning the LLM can project it with pre-filled data.

D3: Mapping List with CRUD Operations

The existing MAPPINGS workspace view currently renders MappingList.svelte with full event dispatching for CRUD (addMapping, editMapping, duplicateMapping, deleteMapping with confirmation modal) and MappingRow.svelte with fire visualization (coloured dots, Sim button, fire count, LED glow, row pulse).

This ADR promotes the mapping list to a first-class workspace view and adds:

OperationCurrent StateExtension
CreateaddMapping event dispatched, but opens no editor"+" button opens MappingEditor (empty)
ReadMappingRow with dot, arrow, sim, fire countAdd drag-reorder handles, device colour on dot
UpdateeditMapping event dispatched, but opens no editorClick row or "Edit" context → opens MappingEditor (pre-filled)
DeletedeleteMapping with confirmation modalPreserved as-is
DuplicateduplicateMapping event dispatchedOpens MappingEditor pre-filled with copy
Move to modeNot possibleContext menu → move mapping to different mode
ReorderNot possibleDrag-to-reorder (changes execution priority)

D4: Extended Trigger Types & Compound Gestures

The current TriggerSuggestion enum in midi_learn.rs supports 13 trigger types. Several extensions are proposed:

New trigger types (require Rust implementation):

TriggerCurrent StateWhat's Needed
AftertouchZoneAftertouch captured with simple (note, pressure_range) — no zone classificationNew TriggerSuggestion variant. Pattern detection in midi_learn.rs: classify sustained pressure into zones (soft: 0-40, medium: 41-80, hard: 81-127). New Trigger variant in types.rs with zone_min/zone_max fields.
PitchBendZonePitchBend captured with simple (bend_range) — no zone classificationNew TriggerSuggestion variant. Pattern detection: classify bend direction and magnitude (up/down/centered). New Trigger variant in types.rs with direction and range fields.
CompoundTriggerDoes not exist anywhere in the codebaseNew Trigger variant with combinator enum (And/Sequence/Any), sub-triggers Vec, and optional window_ms. Requires daemon-side evaluation logic for matching multiple conditions. Largest backend change in this ADR.

Existing trigger improvements (no new Rust types needed):

TriggerImprovement
NoteChordShow all detected chord variants as selectable chips (reuse AlternativeChips from RefinementCard). Allow manual note addition/removal. Fixes auto-select-largest unreliability.
CC StreamSurface inline in Events panel during Learn mode — group consecutive CC events with same (cc, channel) and show rate (ev/s). Client-side only, no Rust change.

Compound gestures enable:

  • "Press pad hard" = Note 60 AND Aftertouch > 80
  • "Fader then knob" = CC7 followed by CC1 within 500ms (sequence)
  • "Either device" = Note 36 from Mikro OR GamepadButton A from Xbox (any)
interface CompoundTrigger {
type: 'Compound';
combinator: 'and' | 'sequence' | 'any';
triggers: Trigger[]; // 2-4 sub-triggers
window_ms?: number; // for 'sequence': max time between triggers
}

D5: LLM Workspace Awareness (Extending ADR-013 + ADR-016)

ADR-013 proposes artifact projection: the LLM pushes structured content to the workspace. ADR-016 gives the LLM signal awareness (T1 topology, T2 pulse, T3 alerts). This ADR adds: T4 workspace state — the LLM can see what the user is currently configuring.

T4 context injection:

When the user has the MappingEditor open, inject its current state into the LLM's context (~50-80 tokens, refreshed on each user message):

## Active Workspace: Mapping Editor
Editing: New mapping in mode "Default"
Trigger: DoubleTap, note=60, timeout=300ms, channel=any, device=Mikro MK3
Action: (not yet configured)
Description: (empty)

New MCP tools:

ToolRisk TierDescription
conductor_get_workspace_stateReadOnlyReturns what the user is currently viewing/editing
conductor_set_mapping_editorArtifactRender (ADR-013) or Stateful (pre-ADR-013)Opens MappingEditor with pre-filled trigger/action/description
conductor_update_mapping_editorArtifactRender or StatefulUpdates specific fields of currently open MappingEditor
conductor_capture_triggerStatefulIn Learn mode, selects a specific event/pattern as trigger

Pre-ADR-013 fallback: If ADR-013 artifact infrastructure isn't implemented yet, conductor_set_mapping_editor populates a store and calls showMappingEditor() to switch the workspace view — the same pattern as showConfigDiff() today.

D6: Events-to-Mapping Bridge

The bridge between "seeing an event" and "configuring a mapping" is partially wired today. This ADR completes it:

Current flow (partially connected):

Events panel → Learn button on hover → selectEvent() → eventToTrigger() →
RefinementCard (trigger only, no action, no save path without LLM)

New flow (fully connected):

Events panel → Learn mode + inline pattern badges →
Click event Learn button OR click pattern group →
selectEvent() → eventToTrigger() → MappingEditor (trigger pre-filled) →
User configures action (manual dropdowns OR "Ask LLM") →
Save (creates ConfigPlan, applies)

Pattern grouping in the Events panel (Learn mode):

During Learn mode, the event list groups related events with pattern badges inline. This is a client-side enhancement using daemon-detected PatternType values (LongPress, DoubleTap, Chord, GamepadChord) that are already present on InputEvent:

┌─ Events (Learn Mode Active) ──────────────────┐
│ │
│ ● CC 7 = 96 ch.1 Mikro 0.2s ago │
│ ● CC 7 = 101 ch.1 Mikro 0.1s ago │
│ ● CC 7 = 108 ch.1 Mikro just now │
│ ├ Pattern: CC stream (12 ev/s) │
│ │ [ Use as CC ] [ Use as Encoder ] │
│ │
│ ◆ Note 36 vel=98 ch.1 Mikro 1.0s ago │
│ ◆ Note 36 vel=92 ch.1 Mikro 0.7s ago │
│ ├ Pattern: DoubleTap (280ms gap) │
│ │ [ Use as DoubleTap ] [ Use as Note ] │
│ │
│ ◆ Note 60 vel=110 ch.1 Mikro 2.1s ago │
│ ◆ Note 64 vel=105 ch.1 Mikro 2.1s ago │
│ ◆ Note 67 vel=108 ch.1 Mikro 2.0s ago │
│ ├ Pattern: Chord [C4 E4 G4] (90ms window) │
│ │ [ Use as Chord ] [ Edit Notes ] │
│ │
│ ■ Stop Learn │
└────────────────────────────────────────────────┘

What's already detected vs what's new:

  • DoubleTap, Chord, LongPress, GamepadChord → daemon detects these and sets pattern_type on InputEvent. The Events panel just needs to render them inline.
  • CC Stream grouping → client-side aggregation (group consecutive CC events with same cc/channel, show rate). No Rust change.
  • AftertouchZone, PitchBendZone badges → require new PatternType variants and detection logic in Rust (see D4). These are deferred to Phase 4.

D7: Rename "MIDI Learn" to "Learn"

Throughout the UI, documentation, tool names, and code:

CurrentNew
MIDI LearnLearn
conductor_start_midi_learnconductor_start_learn (alias old name)
conductor_stop_midi_learnconductor_stop_learn (alias old name)
MidiLearnDialog.svelteRemove (replaced by Events panel Learn mode + MappingEditor)
MidiLearnRefinement.svelteRemove (trigger refinement moves into MappingEditor trigger section)
daemonMidiLearnStorelearnStore (or keep internal name, change UI labels)

D8: Implementation Approach — Revised Convergent Plan

The plan accounts for: existing sub-editor complexity, minimum ADR-016 dependencies, ADR-013 readiness, and honest scoping of new Rust work.

Assumption: ADR-016 (Signal Awareness) will be substantially implemented before this ADR. ADR-013 (LLM Canvas) will NOT be implemented first — Phase 3 of this ADR delivers the minimum ADR-013 infrastructure needed.

Phase 1: MappingEditor + Events Bridge + Learn Rename           (10-14h)
1A: MappingEditor component — embeds TriggerSelector +
ActionSelector (with their sub-editors), preview,
diff, save/cancel (5-7h)
1B: Events-to-MappingEditor bridge — rewire selectEvent() →
MappingEditor instead of RefinementCard. Inline pattern
badges for existing PatternType values (DoubleTap,
Chord, LongPress, GamepadChord). CC stream grouping
(client-side). (3-4h)
1C: Rename "MIDI Learn" → "Learn" throughout UI + tools (1-2h)
1D: Remove MidiLearnDialog.svelte + MidiLearnRefinement.svelte (1h)

Phase 2: Mapping List CRUD + T4 Context (6-8h)
2A: Promote MappingList to workspace view — register in
workspace.js, wire addMapping/editMapping to open
MappingEditor, add drag-reorder + "Move to Mode" (3-4h)
2B: T4 workspace state injection — build
getWorkspaceContext() in signalContext.js, inject
alongside T1/T2/T3 from ADR-016. New
conductor_get_workspace_state tool (ReadOnly). (2-3h)
2C: conductor_set_mapping_editor +
conductor_update_mapping_editor tools (Stateful tier,
pre-ADR-013 — populate store, switch workspace view) (1-2h)

Phase 3: ADR-013 Minimum + Artifact Migration (5-7h)
3A: Implement ADR-013 Phase 1 minimum — ArtifactDescriptor
format, ARTIFACT_REGISTRY, artifactStack store,
renderArtifact()/dismissArtifact(), workspace host
with <svelte:component> (3-4h)
3B: Register MappingEditor as artifact type
('mapping-editor'). Migrate conductor_set/update_
mapping_editor to ArtifactRender tier. Register
existing ConfigDiffView as 'config-diff' artifact. (2-3h)

Phase 4: Extended Triggers + Chord Refinement (Rust work) (6-9h)
4A: AftertouchZone — new TriggerSuggestion variant +
detection in midi_learn.rs + new Trigger variant in
types.rs + UI in TriggerSelector (2-3h)
4B: PitchBendZone — same pattern as 4A (2-3h)
4C: Chord refinement — selectable chord variants with
AlternativeChips, manual note add/remove in
TriggerSelector (2-3h)

Phase 5: Compound Gestures (largest new Rust work) (7-10h)
5A: CompoundTrigger type — new Trigger variant with
combinator enum, daemon evaluation logic with
sequential/parallel matching, window_ms for
SEQUENCE combinator (4-6h)
5B: CompoundTrigger UI — sub-trigger rows in
TriggerSelector/MappingEditor, combinator selector,
"Add Condition" button, per-condition edit/remove (3-4h)

Total: 34-48h across 5 phases.

Phase 1-2 can run in parallel with remaining ADR-016 implementation. Phase 4 can run in parallel with Phase 3.

D9: Relationship to ADR-013 (LLM Canvas)

ADR-013 is Proposed but not implemented. This ADR proceeds independently for Phases 1-2:

  • Phases 1-2 don't need ADR-013. The MappingEditor is a standard workspace view (registered in WORKSPACE_VIEWS, switched via showMappingEditor()), not an artifact. LLM tools use the Stateful tier and populate stores directly.
  • Phase 3 implements the minimum ADR-013 infrastructure needed: ArtifactDescriptor, component registry, artifact stack store, and workspace host. This is a subset of ADR-013's full scope — enough for MappingEditor and ConfigDiffView to become registered artifact types.
  • Phase 3 also migrates LLM tools to the ArtifactRender tier, enabling the LLM to project MappingEditor content without manual workspace switching.

D10: Relationship to ADR-016 (Signal Awareness)

ADR-016 gives the LLM ambient signal awareness. This ADR extends it:

  • T1 topology → LLM knows existing mappings → can suggest modifications in MappingEditor
  • T2 pulse → LLM sees runtime activity → can suggest triggers ("CC74 is unmapped, want to map it?")
  • T3 alerts → unmapped activity alerts naturally lead into MappingEditor: "Create a mapping for CC74?"
  • T4 workspace state (this ADR) → completes the picture: LLM knows what the user is currently configuring

Minimum ADR-016 dependency: Phase 2B (T4 injection) requires the signalContext.js module and message injection mechanism from ADR-016. T1 and T2 should be implemented before Phase 2B starts. T3 alerts are desirable but not blocking.


Consequences

Positive

  • Eliminates the modal Learn dialogue. The most-criticised UX element is replaced by continuous Events panel Learn mode — the same daemon infrastructure, better interaction model.
  • Manual mapping CRUD for the first time. Users no longer need the LLM or raw TOML.
  • Single configuration surface. The MappingEditor handles creation, editing, Learn-based capture, and LLM-proposed changes. Existing sub-editors (VelocityMappingSelector, ConditionalActionEditor, SendMidiActionEditor) are embedded, not replaced.
  • LLM becomes a co-configurator. T4 workspace awareness + editor tools let it participate in real-time.
  • Chord detection becomes reliable. User sees all variants and picks; no auto-select heuristic.
  • Extended trigger types. AftertouchZone, PitchBendZone, CompoundTrigger unlock new gestures.
  • "Learn" naming reflects reality. Supports MIDI, gamepad, and future input types.
  • ADR-013 minimum is delivered. Phase 3 implements artifact infrastructure that benefits the entire GUI, not just mapping.

Negative

  • Large scope. 34-48h across 5 phases. Mitigated by phased delivery — each phase delivers standalone value.
  • MappingEditor complexity. Must embed TriggerSelector + ActionSelector (+ their sub-editors: 2700+ LOC combined). Mitigated by collapsed/expanded pattern — summary view for quick edits, expanded view delegates to existing components.
  • Phase 5 CompoundTrigger is the riskiest Rust change. Daemon evaluation of compound conditions requires new matching logic. Mitigated by limiting to 4 sub-triggers with short-circuit evaluation.
  • Migration overhead. MidiLearnDialog, RefinementCard, and their store wiring must be removed/refactored.

Risks

RiskLikelihoodImpactMitigation
MappingEditor too complex for one componentMediumHighCollapsed/expanded pattern; each section delegates to existing sub-editors
Existing sub-editors don't compose cleanlyLowMediumTriggerSelector and ActionSelector already work as embedded components — no re-architecture needed
CompoundTrigger evaluation is expensiveLowMediumLimit to 4 sub-triggers; daemon evaluates sequentially with short-circuit
ADR-013 minimum in Phase 3 diverges from full ADR-013LowLowPhase 3 implements a subset; full ADR-013 extends it
Pattern detection false positives in Learn modeMediumLowKeep daemon-side detection thresholds unchanged; inline badges are visual, not auto-applied

Implementation Estimate

PhaseWorkHoursDependencies
1MappingEditor + Events bridge + Learn rename10-14hNone
2Mapping list CRUD + T4 context + LLM editor tools6-8hPhase 1, ADR-016 T1+T2
3ADR-013 minimum + artifact migration5-7hPhase 2
4AftertouchZone + PitchBendZone + chord refinement6-9hPhase 1 (UI), none (Rust)
5CompoundTrigger (Rust + UI)7-10hPhase 4
Total34-48h

Phases 1-2 can run in parallel with remaining ADR-016 implementation. Phase 4 can run in parallel with Phase 3.


References

  • ADR-002: MIDI Learn Event Streaming
  • ADR-007: LLM Integration Architecture
  • ADR-013: LLM Canvas — Artifact Projection
  • ADR-014: Mapping Feedback & Simulate
  • ADR-016: LLM Signal Awareness (numbered ADR-016 on GitHub)
  • Mockup: adr-017-unified-mapping-mockups.html — companion mockups (v3)
  • conductor-gui/ui/src/lib/components/MidiLearnDialog.svelte — current 30s Learn dialogue (to be removed)
  • conductor-gui/ui/src/lib/components/RefinementCard.svelte — current trigger refinement (to be absorbed into MappingEditor)
  • conductor-gui/ui/src/lib/components/TriggerSelector.svelte — 13 trigger types (embedded in MappingEditor)
  • conductor-gui/ui/src/lib/components/ActionSelector.svelte — action config with sub-editors (embedded in MappingEditor)
  • conductor-gui/ui/src/lib/components/VelocityMappingSelector.svelte — SVG curve editor (embedded via ActionSelector → SendMidiActionEditor)
  • conductor-gui/ui/src/lib/components/ConditionalActionEditor.svelte — nested conditions (embedded via ActionSelector)
  • conductor-gui/ui/src/lib/components/SendMidiActionEditor.svelte — MIDI output config (embedded via ActionSelector)
  • conductor-gui/ui/src/lib/workspace/ConfigDiffView.svelte — current plan approval (migrated to artifact in Phase 3)
  • conductor-gui/ui/src/lib/stores/workspace.js — workspace view management
  • conductor-gui/src-tauri/src/midi_learn.rs — pattern detection engine
  • conductor-core/src/config/types.rs — Trigger enum (13 variants + proposed extensions)
  • conductor-core/src/event_types.rs — PatternType enum (4 variants)