From 89990f8d6333cade7b1afd172351bff4cb73cb03 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 2 Apr 2026 13:42:37 +0300 Subject: [PATCH] chore: remove processed-audio-sources plan files --- plans/processed-audio-sources/CONTEXT.md | 111 ----------------- plans/processed-audio-sources/PLAN.md | 55 --------- .../phase-1-audio-filter-framework.md | 106 ---------------- .../phase-2-audio-filters.md | 114 ----------------- .../phase-3-processed-audio-source-model.md | 116 ------------------ .../phase-4-runtime-integration.md | 94 -------------- .../phase-5-frontend-templates.md | 97 --------------- .../phase-6-frontend-source-types.md | 88 ------------- .../phase-7-testing-polish.md | 85 ------------- .../phase-8-frontend-design-review.md | 65 ---------- 10 files changed, 931 deletions(-) delete mode 100644 plans/processed-audio-sources/CONTEXT.md delete mode 100644 plans/processed-audio-sources/PLAN.md delete mode 100644 plans/processed-audio-sources/phase-1-audio-filter-framework.md delete mode 100644 plans/processed-audio-sources/phase-2-audio-filters.md delete mode 100644 plans/processed-audio-sources/phase-3-processed-audio-source-model.md delete mode 100644 plans/processed-audio-sources/phase-4-runtime-integration.md delete mode 100644 plans/processed-audio-sources/phase-5-frontend-templates.md delete mode 100644 plans/processed-audio-sources/phase-6-frontend-source-types.md delete mode 100644 plans/processed-audio-sources/phase-7-testing-polish.md delete mode 100644 plans/processed-audio-sources/phase-8-frontend-design-review.md diff --git a/plans/processed-audio-sources/CONTEXT.md b/plans/processed-audio-sources/CONTEXT.md deleted file mode 100644 index a33de77..0000000 --- a/plans/processed-audio-sources/CONTEXT.md +++ /dev/null @@ -1,111 +0,0 @@ -# Feature Context: Processed Audio Sources - -## Configuration -- **Development mode:** Automated -- **Execution mode:** Orchestrator -- **Strategy:** Big Bang -- **Build (Python):** `cd server && ruff check src/ tests/ --fix` -- **Build (TypeScript):** `cd server && npx tsc --noEmit && npm run build` -- **Test:** `cd server && py -3.13 -m pytest tests/ --no-cov -q` - -## Current State -Phases 1-6 implemented. Phase 6 (Frontend Source Types) cleaned up the modal HTML and i18n keys for the new capture/processed source types. - -Phase 1 framework: -- `AudioFilter` base class, `AudioFilterRegistry`, `AudioFilterOptionDef` in `core/audio/filters/` -- `AudioProcessingTemplate` dataclass + `AudioProcessingTemplateStore` (SQLite-backed) in `storage/` -- `audio_filter_template` meta-filter with recursive resolution -- Full REST API: CRUD templates + filter registry discovery -- Dependency injection wired in `dependencies.py` and `main.py` - -Phase 2 filters (12 total registered, 11 real + 1 meta): -- Stateless: `channel_extract`, `band_extract`, `gain`, `inverter` -- Stateful: `peak_hold`, `noise_gate`, `envelope_follower`, `spectral_smoothing`, `compressor`, `beat_gate`, `delay` -- All produce new `AudioAnalysis` via `dataclasses.replace()` (immutability preserved) - -## Key Architecture Reference - -### Existing Pattern to Mirror: Processed Picture Sources -- `ProcessedPictureSource` references `source_stream_id` + `postprocessing_template_id` -- `PostprocessingTemplate` contains ordered `List[FilterInstance]` -- `FilterInstance` = `filter_id` (string) + `options` (dict) -- `FilterRegistry` handles registration, lookup, instantiation -- `filter_template` meta-filter embeds one template inside another -- `PostprocessingTemplateStore` has `resolve_filter_instances()` for recursive expansion -- Picture filters transform images; audio filters will transform `AudioAnalysis` - -### Current Audio Source Types (Phase 3 complete) -- `CaptureAudioSource` (source_type="capture") — wraps a physical audio device -- `ProcessedAudioSource` (source_type="processed") — references audio_source_id + audio_processing_template_id -- `MonoAudioSource` — removed, replaced by channel_extract filter -- `BandExtractAudioSource` — removed, replaced by band_extract filter - -### AudioAnalysis Structure (filter input/output) -```python -AudioAnalysis: - timestamp: float - rms: float # Overall RMS level - peak: float # Peak amplitude - spectrum: np.ndarray[64] # Log-spaced FFT bands - beat: bool # Beat detected - beat_intensity: float # 0-1 beat strength - left_rms: float # Left channel RMS - left_spectrum: np.ndarray # Left channel spectrum - right_rms: float # Right channel RMS - right_spectrum: np.ndarray # Right channel spectrum -``` - -### Key Existing Files -- `storage/audio_source.py` — current source dataclasses -- `storage/audio_source_store.py` — CRUD + resolve_audio_source() -- `core/audio/analysis.py` — AudioAnalyzer, AudioAnalysis -- `core/audio/band_filter.py` — existing band filtering logic -- `core/processing/audio_stream.py` — AudioColorStripStream -- `core/processing/value_stream.py` — AudioValueStream -- `core/filters/base.py` — PostprocessingFilter (picture filter base class) -- `core/filters/registry.py` — FilterRegistry (picture filters) -- `storage/postprocessing_template.py` — PostprocessingTemplate dataclass -- `storage/postprocessing_template_store.py` — template store with resolve_filter_instances() - -## Temporary Workarounds -_(none yet)_ - -## Cross-Phase Dependencies -- Phase 2 depends on Phase 1 (filter framework) -- Phase 3 depends on Phase 1 (template store for ProcessedAudioSource) -- Phase 4 depends on Phases 1-3 (all backend pieces) -- Phase 5 depends on Phase 1 (template API) -- Phase 6 depends on Phase 3 (source type API) -- Phase 7 depends on all prior phases - -## Deferred Work -_(none yet)_ - -## Failed Approaches -_(none yet)_ - -## Review Findings Log -_(none yet)_ - -## Phase Execution Log -| Phase | Agent Used | Test Writer | Parallel | Notes | -|-------|-----------|-------------|----------|-------| -| Phase 1 | impl-agent | — | No | Tasks 7+8 skipped (SQLite migration made them obsolete) | -| Phase 2 | impl-agent | — | No | All 11 filters implemented, no deviations | -| Phase 3 | impl-agent | — | No | All 11 tasks done; channel/band logic deferred to Phase 4 | -| Phase 4 | impl-agent | — | No | All 6 tasks done; dependency injection threaded through | -| Phase 5 | impl-agent | — | No | 6/7 tasks done; Task 4 (preview) deferred to Phase 7 | -| Phase 6 | impl-agent | — | No | Modal HTML + i18n cleanup; most tasks already done in Phases 3/5 | -| Phase 7 | — | — | — | — | - -## Environment & Runtime Notes -- Platform: Windows 10 -- Python: 3.13 -- Server port: 8080 -- Shell: bash (Git Bash on Windows) - -## Implementation Notes -- Clean-slate approach: no migration of existing MonoAudioSource/BandExtractAudioSource data -- 7 of 11 filters are stateful (peak hold, noise gate, envelope follower, spectral smoothing, compressor, beat gate, delay) — need per-stream instance lifecycle -- Audio filters operate on AudioAnalysis snapshots, not raw audio samples -- Big Bang strategy: intermediate phases may break the build; only Phase 7 enforces build/tests diff --git a/plans/processed-audio-sources/PLAN.md b/plans/processed-audio-sources/PLAN.md deleted file mode 100644 index c91a522..0000000 --- a/plans/processed-audio-sources/PLAN.md +++ /dev/null @@ -1,55 +0,0 @@ -# Feature: Processed Audio Sources - -**Branch:** `feature/processed-audio-sources` -**Base branch:** `master` -**Created:** 2026-03-31 -**Status:** 🟡 In Progress -**Strategy:** Big Bang -**Mode:** Automated -**Execution:** Orchestrator - -## Summary - -Replace hardcoded `MonoAudioSource` and `BandExtractAudioSource` types with a composable -**ProcessedAudioSource + AudioProcessingTemplate + AudioFilter** system — mirroring the -existing processed picture source pattern. Rename `MultichannelAudioSource` to -`CaptureAudioSource`. Adds 11 audio filters: channel extract, band extract, peak hold, -gain, noise gate, envelope follower, spectral smoothing, compressor, inverter, beat gate, -and delay. - -Clean-slate approach: no data migration for old source types. - -## Build & Test Commands -- **Build (Python):** `cd server && ruff check src/ tests/ --fix` -- **Build (TypeScript):** `cd server && npx tsc --noEmit && npm run build` -- **Test:** `cd server && py -3.13 -m pytest tests/ --no-cov -q` - -## Phases - -- [ ] Phase 1: Audio Filter Framework [domain: backend] → [subplan](./phase-1-audio-filter-framework.md) -- [ ] Phase 2: Audio Filters [domain: backend] → [subplan](./phase-2-audio-filters.md) -- [ ] Phase 3: Processed Audio Source Model [domain: backend] → [subplan](./phase-3-processed-audio-source-model.md) -- [ ] Phase 4: Runtime Integration [domain: backend] → [subplan](./phase-4-runtime-integration.md) -- [ ] Phase 5: Frontend — Audio Processing Templates [domain: frontend] → [subplan](./phase-5-frontend-templates.md) -- [ ] Phase 6: Frontend — Source Types [domain: frontend] → [subplan](./phase-6-frontend-source-types.md) -- [ ] Phase 7: Testing & Polish [domain: backend] → [subplan](./phase-7-testing-polish.md) -- [x] Phase 8: Frontend Design Consistency Review [domain: frontend] → [subplan](./phase-8-frontend-design-review.md) - -## Phase Progress Log - -| Phase | Domain | Status | Review | Build | Committed | -|-------|--------|--------|--------|-------|-----------| -| Phase 1: Audio Filter Framework | backend | ✅ Done | ✅ | ⏭️ | ✅ | -| Phase 2: Audio Filters | backend | ✅ Done | ✅ | ⏭️ | ✅ | -| Phase 3: Processed Audio Source Model | backend | ✅ Done | ✅ | ⏭️ | ✅ | -| Phase 4: Runtime Integration | backend | ✅ Done | ✅ | ⏭️ | ✅ | -| Phase 5: Frontend — Audio Processing Templates | frontend | ✅ Done | ✅ | ⏭️ | ✅ | -| Phase 6: Frontend — Source Types | frontend | ✅ Done | ✅ | ⏭️ | ✅ | -| Phase 7: Testing & Polish | backend | ✅ Done | — | ✅ | ✅ | -| Phase 8: Frontend Design Review | frontend | ✅ Done | ✅ | ✅ | ⬜ | - -## Final Review -- [ ] Comprehensive code review -- [ ] Full build passes -- [ ] Full test suite passes -- [ ] Merged to `master` diff --git a/plans/processed-audio-sources/phase-1-audio-filter-framework.md b/plans/processed-audio-sources/phase-1-audio-filter-framework.md deleted file mode 100644 index 9777234..0000000 --- a/plans/processed-audio-sources/phase-1-audio-filter-framework.md +++ /dev/null @@ -1,106 +0,0 @@ -# Phase 1: Audio Filter Framework - -**Status:** 🔨 In Progress -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Build the foundation for audio processing: base filter class, registry, template storage, and API endpoints. This mirrors the existing picture filter/postprocessing template system. - -## Tasks - -- [x] Task 1: Create `AudioFilter` base class in `core/audio/filters/base.py` - - Abstract `process(analysis: AudioAnalysis) -> AudioAnalysis` method - - `is_stateful` property (False by default, overridden by stateful filters) - - `reset()` method for stateful filters - - `AudioFilterOptionDef` class for declaring filter option schemas (mirrors `FilterOptionDef` from picture filters) - - Class-level `filter_id`, `name`, `description`, `options` declarations -- [x] Task 2: Create `AudioFilterRegistry` in `core/audio/filters/registry.py` - - `register(filter_class)` — register by filter_id - - `get_filter_class(filter_id)` — lookup - - `create_instance(filter_id, options)` → instantiated AudioFilter - - `get_available_filters()` → list of filter metadata + option schemas - - Mirrors `FilterRegistry` from `core/filters/registry.py` -- [x] Task 3: Create `AudioProcessingTemplate` dataclass in `storage/audio_processing_template.py` - - Fields: `id`, `name`, `filters: List[FilterInstance]`, `description`, `tags`, `created_at`, `updated_at` - - Reuse existing `FilterInstance` from `core/filters/filter_instance.py` - - ID prefix: `apt_` (audio processing template) -- [x] Task 4: Create `AudioProcessingTemplateStore` in `storage/audio_processing_template_store.py` - - SQLite-backed CRUD (same pattern as `PostprocessingTemplateStore`) - - `resolve_filter_instances()` — recursive expansion of `audio_filter_template` meta-filter - - Cycle detection in template composition - - Reference validation (check filter_ids exist in registry) -- [x] Task 5: Create `audio_filter_template` meta-filter in `core/audio/filters/audio_filter_template.py` - - Option: `template_id` referencing another AudioProcessingTemplate - - Never instantiated at runtime — expanded during resolution - - Mirrors `filter_template` from picture filters -- [x] Task 6: Create `core/audio/filters/__init__.py` — register all filters with the registry -- [x] Task 7: ~~Add `audio_processing_templates_file` to `StorageConfig` in `config.py`~~ SKIPPED — storage uses SQLite now, not JSON files. No config change needed. -- [x] Task 8: ~~Add audio processing templates to `STORE_MAP` in `api/routes/system.py`~~ SKIPPED — STORE_MAP no longer exists. Backup/restore works at the SQLite database level; new tables are automatically included. -- [x] Task 9: Create API schemas in `api/schemas/audio_processing.py` - - `AudioProcessingTemplateCreate`, `AudioProcessingTemplateUpdate`, `AudioProcessingTemplateResponse` - - `AudioFilterInstanceSchema` (reuse `FilterInstanceSchema` from existing schemas) -- [x] Task 10: Create API routes in `api/routes/audio_processing_templates.py` - - `GET /api/v1/audio-processing-templates` — list all - - `POST /api/v1/audio-processing-templates` — create - - `GET /api/v1/audio-processing-templates/{template_id}` — get one - - `PUT /api/v1/audio-processing-templates/{template_id}` — update - - `DELETE /api/v1/audio-processing-templates/{template_id}` — delete (with ref checks placeholder) -- [x] Task 11: Create filter registry endpoint in `api/routes/audio_filters.py` - - `GET /api/v1/audio-filters` — returns available filters with option schemas - - Dynamically populates `audio_filter_template` options with current template IDs -- [x] Task 12: Register new routes in the FastAPI app - -## Files to Modify/Create -- `core/audio/filters/base.py` — **created** — AudioFilter base class + AudioFilterOptionDef -- `core/audio/filters/registry.py` — **created** — AudioFilterRegistry -- `core/audio/filters/audio_filter_template.py` — **created** — meta-filter -- `core/audio/filters/__init__.py` — **created** — filter registration -- `storage/audio_processing_template.py` — **created** — dataclass -- `storage/audio_processing_template_store.py` — **created** — store -- `api/schemas/audio_processing.py` — **created** — Pydantic schemas -- `api/routes/audio_processing_templates.py` — **created** — template CRUD routes -- `api/routes/audio_filters.py` — **created** — filter registry endpoint -- `api/dependencies.py` — **modified** — added getter + init param for audio_processing_template_store -- `api/__init__.py` — **modified** — registered new routers -- `main.py` — **modified** — created store instance, imported audio filter package, wired to init_dependencies - -## Acceptance Criteria -- AudioFilter base class exists with process/reset/options API -- AudioFilterRegistry can register, lookup, and instantiate filters -- AudioProcessingTemplate can be created, read, updated, deleted via API -- `audio_filter_template` meta-filter is handled during resolution (recursive expansion) -- Filter registry endpoint returns available filters with option schemas -- Backup/restore includes audio processing templates (automatic via SQLite) - -## Notes -- Reuse `FilterInstance` from `core/filters/filter_instance.py` — no need to create a separate one for audio -- The `audio_filter_template` meta-filter option should dynamically list available template IDs (like the picture filter version) -- Phase 2 will register the actual filters; this phase just sets up the framework with only the meta-filter registered -- Tasks 7 and 8 from original plan were skipped — the codebase migrated from JSON files to SQLite since the plan was written - -## Review Checklist -- [ ] All tasks completed -- [ ] Code follows project conventions -- [ ] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - -### What was built -- Complete audio filter framework: `AudioFilter` base class, `AudioFilterRegistry`, `AudioFilterOptionDef` -- `AudioProcessingTemplate` dataclass + `AudioProcessingTemplateStore` (SQLite-backed CRUD) -- `audio_filter_template` meta-filter with recursive resolution and cycle detection -- Full REST API: CRUD for templates + filter registry discovery endpoint -- Dependency injection wired through `dependencies.py` and `main.py` - -### What Phase 2 needs to know -- Register new audio filters by importing them in `core/audio/filters/__init__.py` and decorating with `@AudioFilterRegistry.register` -- Each filter implements `process(analysis: AudioAnalysis) -> AudioAnalysis` -- Stateful filters override `is_stateful` property to return `True` and implement `reset()` -- The `AudioFilterOptionDef` class mirrors `FilterOptionDef` exactly (same option_type values, same validation logic) - -### Known deviations from plan -- Tasks 7 and 8 skipped: `StorageConfig` no longer holds per-entity file paths (SQLite migration), and `STORE_MAP` no longer exists (database-level backup/restore) -- Delete endpoint has a TODO placeholder for reference checks — Phase 3 will add `ProcessedAudioSource` which references templates diff --git a/plans/processed-audio-sources/phase-2-audio-filters.md b/plans/processed-audio-sources/phase-2-audio-filters.md deleted file mode 100644 index 17dea57..0000000 --- a/plans/processed-audio-sources/phase-2-audio-filters.md +++ /dev/null @@ -1,114 +0,0 @@ -# Phase 2: Audio Filters - -**Status:** 🔨 In Progress -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Implement all 11 audio filters and register them with the AudioFilterRegistry. - -## Tasks - -- [x] Task 1: **Channel Extract** filter (`core/audio/filters/channel_extract.py`) - - Options: `channel` (select: mono | left | right) - - Stateful: No - - Behavior: Replaces main rms/spectrum with selected channel data. If "mono", averages L+R. If "left"/"right", copies that channel's data to the main fields. -- [x] Task 2: **Band Extract** filter (`core/audio/filters/band_extract.py`) - - Options: `band` (select: bass | mid | treble | custom), `freq_low` (float, 20-20000), `freq_high` (float, 20-20000) - - Stateful: No - - Behavior: Computes a band mask for the 64 log-spaced bins, applies it to spectrum, recomputes RMS from in-band data. Reuse logic from existing `core/audio/band_filter.py`. - - Presets: bass=20-250Hz, mid=250-4000Hz, treble=4000-20000Hz -- [x] Task 3: **Peak Hold** filter (`core/audio/filters/peak_hold.py`) - - Options: `decay_rate` (float, 0.1-50.0, dB/s), `per_bin` (bool, default true) - - Stateful: Yes - - Behavior: For each spectrum bin (if per_bin) or for rms/peak, retains the maximum value seen and decays it over time. Outputs the max of current value and held peak. -- [x] Task 4: **Gain** filter (`core/audio/filters/gain.py`) - - Options: `factor` (float, 0.1-10.0, default 1.0) - - Stateful: No - - Behavior: Multiplies rms, peak, spectrum, and per-channel values by factor. Clamps to [0, 1] for rms/peak. -- [x] Task 5: **Noise Gate** filter (`core/audio/filters/noise_gate.py`) - - Options: `threshold` (float, 0.0-1.0), `hysteresis` (float, 0.0-0.2, default 0.05) - - Stateful: No (hysteresis is stateless — it's a secondary threshold, not temporal) - - Behavior: If rms < threshold, zeros out all levels and spectrum. Hysteresis means: if gate was open and rms drops below (threshold - hysteresis), close it; if gate was closed and rms rises above threshold, open it. - - Actually stateful for hysteresis tracking: needs to remember gate open/closed state. -- [x] Task 6: **Envelope Follower** filter (`core/audio/filters/envelope_follower.py`) - - Options: `attack_ms` (float, 1-500, default 10), `release_ms` (float, 10-2000, default 200) - - Stateful: Yes - - Behavior: Smooths rms and peak with asymmetric time constants. When signal rises, uses attack rate. When signal falls, uses release rate. Applied per-bin to spectrum optionally. - - Fast attack + slow release = punchy transients that fade smoothly. -- [x] Task 7: **Spectral Smoothing** filter (`core/audio/filters/spectral_smoothing.py`) - - Options: `factor` (float, 0.0-0.99, default 0.5) - - Stateful: Yes (maintains previous spectrum state) - - Behavior: Applies exponential moving average per-bin: `smoothed[i] = factor * prev[i] + (1-factor) * current[i]`. Higher factor = smoother/slower. -- [x] Task 8: **Compressor** filter (`core/audio/filters/compressor.py`) - - Options: `threshold` (float, 0.0-1.0, default 0.5), `ratio` (float, 1.0-20.0, default 4.0), `makeup_gain` (float, 0.0-2.0, default 1.0) - - Stateful: Yes (envelope tracking for gain reduction) - - Behavior: When signal exceeds threshold, reduces by ratio. `output = threshold + (input - threshold) / ratio`. Apply makeup_gain after. Applied to rms, peak, and spectrum. -- [x] Task 9: **Inverter** filter (`core/audio/filters/inverter.py`) - - Options: none (or `invert_spectrum` bool, default true) - - Stateful: No - - Behavior: `rms = 1.0 - rms`, `peak = 1.0 - peak`, spectrum bins inverted if option set. Beat fields unchanged. -- [x] Task 10: **Beat Gate** filter (`core/audio/filters/beat_gate.py`) - - Options: `hold_ms` (float, 10-500, default 50) — how long to hold signal after beat - - Stateful: Yes (tracks last beat timestamp) - - Behavior: When beat detected, passes signal through for `hold_ms` milliseconds. Between beats, zeros out rms/peak/spectrum. Beat fields themselves always pass through. -- [x] Task 11: **Delay** filter (`core/audio/filters/delay.py`) - - Options: `delay_ms` (float, 10-2000, default 100) - - Stateful: Yes (ring buffer of AudioAnalysis snapshots) - - Behavior: Buffers incoming AudioAnalysis snapshots and outputs the one from `delay_ms` ago. Ring buffer sized based on ~30Hz update rate. -- [x] Task 12: Register all 11 filters in `core/audio/filters/__init__.py` -- [x] Task 13: Update Noise Gate to be stateful (hysteresis requires gate state tracking) - -## Files to Modify/Create -- `core/audio/filters/channel_extract.py` — **create** -- `core/audio/filters/band_extract.py` — **create** -- `core/audio/filters/peak_hold.py` — **create** -- `core/audio/filters/gain.py` — **create** -- `core/audio/filters/noise_gate.py` — **create** -- `core/audio/filters/envelope_follower.py` — **create** -- `core/audio/filters/spectral_smoothing.py` — **create** -- `core/audio/filters/compressor.py` — **create** -- `core/audio/filters/inverter.py` — **create** -- `core/audio/filters/beat_gate.py` — **create** -- `core/audio/filters/delay.py` — **create** -- `core/audio/filters/__init__.py` — **modify** — register all filters - -## Acceptance Criteria -- All 11 filters are implemented and registered -- Each filter correctly transforms AudioAnalysis according to its specification -- Stateful filters (peak hold, envelope follower, spectral smoothing, compressor, beat gate, delay, noise gate) properly maintain and reset state -- Filter option schemas are complete and accurate -- All filters are accessible via `GET /api/v1/audio-filters` - -## Notes -- 6 stateful filters: peak hold, envelope follower, spectral smoothing, compressor, beat gate, delay. Noise gate is also stateful due to hysteresis. -- Band extract can reuse math from existing `core/audio/band_filter.py` — `compute_band_mask()` and `apply_band_filter()` -- Filters must produce a NEW AudioAnalysis (immutability principle), not mutate the input -- For delay filter, ring buffer size = `delay_ms / (1000 / update_rate)`. At 30Hz, 2000ms delay = 60 slots. - -## Review Checklist -- [ ] All tasks completed -- [ ] Code follows project conventions -- [ ] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - -### What was built -- All 11 audio filters implemented, each in its own file under `core/audio/filters/` -- 7 stateful filters (peak_hold, noise_gate, envelope_follower, spectral_smoothing, compressor, beat_gate, delay) with proper `is_stateful` and `reset()` implementations -- 4 stateless filters (channel_extract, band_extract, gain, inverter) -- All filters registered in `__init__.py` via import-triggered `@AudioFilterRegistry.register` -- All filters produce NEW AudioAnalysis via `dataclasses.replace()` (immutability preserved) -- Band extract reuses existing `compute_band_mask()` and `apply_band_filter()` from `core/audio/band_filter.py` - -### What Phase 3 needs to know -- All 11 filters + the `audio_filter_template` meta-filter are now registered in the AudioFilterRegistry (12 total) -- `GET /api/v1/audio-filters` will return all filters with their option schemas -- Filters are instantiated via `AudioFilterRegistry.create_instance(filter_id, options)` -- Stateful filters need per-stream instances (not shared) due to internal state -- The `process()` method signature is `process(analysis: AudioAnalysis) -> AudioAnalysis` - -### Known deviations from plan -- None. All 11 filters implemented exactly as specified plus Task 13 (noise gate stateful). diff --git a/plans/processed-audio-sources/phase-3-processed-audio-source-model.md b/plans/processed-audio-sources/phase-3-processed-audio-source-model.md deleted file mode 100644 index 56485fc..0000000 --- a/plans/processed-audio-sources/phase-3-processed-audio-source-model.md +++ /dev/null @@ -1,116 +0,0 @@ -# Phase 3: Processed Audio Source Model - -**Status:** ✅ Done -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Add the `ProcessedAudioSource` type, rename `MultichannelAudioSource` to `CaptureAudioSource`, remove `MonoAudioSource` and `BandExtractAudioSource`, and update the store's resolution logic. - -## Tasks - -- [x] Task 1: Rename `MultichannelAudioSource` → `CaptureAudioSource` in `storage/audio_source.py` - - Change class name, update `source_type` default to `"capture"` - - Same fields: `device_index`, `is_loopback`, `audio_template_id` -- [x] Task 2: Add `ProcessedAudioSource` dataclass in `storage/audio_source.py` - - Fields: `audio_source_id: str` (input source), `audio_processing_template_id: str` - - `source_type` = `"processed"` - - Inherits standard base fields (id, name, description, tags, created_at, updated_at) -- [x] Task 3: Remove `MonoAudioSource` class entirely -- [x] Task 4: Remove `BandExtractAudioSource` class entirely -- [x] Task 5: Update `create_audio_source()` factory function to handle new types -- [x] Task 6: Update `AudioSourceStore` resolution logic: - - `resolve_audio_source()` now returns: device info (from CaptureAudioSource at chain end) + ordered list of filter chains (from AudioProcessingTemplates along the chain) - - Walk chain: ProcessedAudioSource → ... → CaptureAudioSource - - Collect all audio_processing_template_ids in order - - Cycle detection for ProcessedAudioSource chains -- [x] Task 7: Update `ResolvedAudioSource` dataclass: - - Remove `channel` and `freq_low`/`freq_high` fields (handled by filters now) - - Add `audio_processing_template_ids: List[str]` — ordered list of template IDs along the chain -- [x] Task 8: Update reference validation in store: - - `ProcessedAudioSource.audio_source_id` must reference an existing audio source - - `ProcessedAudioSource.audio_processing_template_id` must reference an existing template - - Delete checks: can't delete a source referenced by another ProcessedAudioSource - - Added `get_sources_referencing_template()` helper for template delete checks -- [x] Task 9: Update API schemas in `api/schemas/audio_sources.py` - - Remove `MonoAudioSourceCreate/Update/Response` schemas - - Remove `BandExtractAudioSourceCreate/Update/Response` schemas - - Add `CaptureAudioSourceCreate/Update/Response` (rename from Multichannel) - - Add `ProcessedAudioSourceCreate/Update/Response` - - Update discriminated union to use new type literals -- [x] Task 10: Update API routes in `api/routes/audio_sources.py` - - Handle new source types in create/update endpoints - - Remove handling of old types - - Update WebSocket test endpoint to work with new resolution (no channel/band) -- [x] Task 11: Update any imports/references across the codebase that reference the old types - -## Files to Modify/Create -- `storage/audio_source.py` — **modify** — rename, add, remove dataclasses -- `storage/audio_source_store.py` — **modify** — new resolution logic, validation -- `storage/audio_template_store.py` — **modify** — CaptureAudioSource import -- `api/schemas/audio_sources.py` — **modify** — new schemas -- `api/routes/audio_sources.py` — **modify** — handle new types -- `core/processing/audio_stream.py` — **modify** — remove channel/band logic -- `core/processing/value_stream.py` — **modify** — remove channel logic -- `core/demo_seed.py` — **modify** — update demo data to new types -- `storage/color_strip_source.py` — **modify** — update comment -- `storage/value_source.py` — **modify** — update comment -- `static/js/types.ts` — **modify** — new TS interfaces -- `static/js/core/icons.ts` — **modify** — new icon mapping -- `static/js/core/graph-nodes.ts` — **modify** — new icon mapping -- `static/js/features/audio-sources.ts` — **modify** — new source types -- `static/js/features/streams.ts` — **modify** — new card sections -- `static/js/features/value-sources.ts` — **modify** — badge text -- `static/js/features/color-strips.ts` — **modify** — badge text, navigation -- `static/js/core/command-palette.ts` — **modify** — navigation mapping - -## Acceptance Criteria -- [x] `CaptureAudioSource` replaces `MultichannelAudioSource` (same behavior, new name/type) -- [x] `ProcessedAudioSource` can be created referencing a source + template -- [x] `MonoAudioSource` and `BandExtractAudioSource` are fully removed -- [x] Chain resolution walks ProcessedAudioSource → ... → CaptureAudioSource correctly -- [x] Cycle detection prevents circular source references -- [x] Reference validation prevents dangling references -- [x] API accepts/returns new type discriminators - -## Notes -- Clean-slate: no migration of existing data. Old source type records will be lost. -- The `source_type` string changes from `"multichannel"` to `"capture"` — this is a breaking change but acceptable for clean-slate. -- `ResolvedAudioSource` is consumed by `AudioColorStripStream` and `AudioValueStream` — they will need updates in Phase 4. -- Template reference checks in the store need coordination with `AudioProcessingTemplateStore` — may need to pass it as a dependency. - -## Review Checklist -- [x] All tasks completed -- [x] Code follows project conventions -- [x] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - -### What was built -- `CaptureAudioSource` replaces `MultichannelAudioSource` (class + source_type "capture") -- `ProcessedAudioSource` added with `audio_source_id` + `audio_processing_template_id` fields -- `MonoAudioSource` and `BandExtractAudioSource` fully removed from model, store, schemas, routes, and all frontend references -- `ResolvedAudioSource` now returns `audio_processing_template_ids: List[str]` instead of `channel`/`freq_low`/`freq_high` -- Chain resolution walks ProcessedAudioSource → ... → CaptureAudioSource, collecting template IDs in order (outermost first) -- Cycle detection for both create and update operations -- `get_sources_referencing_template()` helper added for template delete checks -- All frontend TS files updated: types, icons, card sections, navigation, command palette - -### What Phase 4 needs to know -- `ResolvedAudioSource` now has `audio_processing_template_ids` field — Phase 4 must resolve these to `FilterInstance` lists and instantiate/apply them in the stream runtime -- `AudioColorStripStream._pick_channel()` currently returns raw `analysis.spectrum, analysis.rms` — Phase 4 must wire filter processing here -- `AudioValueStream._pick_rms()` and `_pick_peak()` currently return raw analysis values — Phase 4 must apply filter chain -- Both streams store `self._audio_processing_template_ids` for use by Phase 4 -- The WebSocket test endpoint also needs filter application wired in Phase 4 - -### Temporary breakages (resolved in Phase 4) -- Channel selection removed from `AudioColorStripStream._pick_channel()` — always uses mono mix -- Channel selection removed from `AudioValueStream._pick_rms()` and `_pick_peak()` — always uses mono -- These were previously handled by MonoAudioSource/BandExtractAudioSource; now handled by channel_extract/band_extract filters in ProcessedAudioSource chains - -### Known deviations from plan -- Task 7: Used `audio_processing_template_ids: List[str]` (template IDs) rather than `filter_instances: List[FilterInstance]` — runtime resolution deferred to Phase 4 -- Task 8: Template reference validation at create time not implemented (would require injecting AudioProcessingTemplateStore as dependency) — deferred to Phase 4 or Phase 7 -- Frontend was also updated comprehensively (not just backend) to avoid broken UI diff --git a/plans/processed-audio-sources/phase-4-runtime-integration.md b/plans/processed-audio-sources/phase-4-runtime-integration.md deleted file mode 100644 index 2fc2d07..0000000 --- a/plans/processed-audio-sources/phase-4-runtime-integration.md +++ /dev/null @@ -1,94 +0,0 @@ -# Phase 4: Runtime Integration - -**Status:** ✅ Done -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Wire the audio filter pipeline into the runtime audio streaming system so that ProcessedAudioSources actually apply their filter chains to live audio data. - -## Tasks - -- [x] Task 1: Create filter pipeline executor in `core/audio/filters/pipeline.py` - - `AudioFilterPipeline` class: - - `__init__(filter_instances: List[FilterInstance], registry: AudioFilterRegistry)` - - Instantiates all filters from FilterInstance specs - - `process(analysis: AudioAnalysis) -> AudioAnalysis` — runs analysis through all filters in order - - `reset()` — resets all stateful filters - - `close()` — cleanup resources - - Handles stateful filter lifecycle (create on init, reset on demand, close on cleanup) -- [x] Task 2: Update `AudioColorStripStream` in `core/processing/audio_stream.py` - - On construction: if source is ProcessedAudioSource, resolve the full chain: - - Walk to CaptureAudioSource for device info - - Collect all AudioProcessingTemplates along the chain - - Resolve all filter instances (with template expansion) - - Create AudioFilterPipeline - - In render loop: after getting AudioAnalysis from ManagedAudioStream, run it through the filter pipeline before visualization - - Remove old inline channel selection and band filtering code (now handled by filters) - - On stop: close the filter pipeline -- [x] Task 3: Update `AudioValueStream` in `core/processing/value_stream.py` - - Same pattern: resolve ProcessedAudioSource chain, create filter pipeline, apply in get_value() - - Remove old inline channel/band handling -- [x] Task 4: Hot-update support for filter templates - - When an AudioProcessingTemplate is updated, running streams that use it should re-resolve their filter pipeline - - Listen for template update events (or implement a refresh mechanism) - - Re-create AudioFilterPipeline with updated filter instances - - Reset stateful filter state on pipeline refresh -- [x] Task 5: Update WebSocket test endpoint in `api/routes/audio_sources.py` - - For ProcessedAudioSource: resolve chain, create pipeline, apply filters to test stream data - - Return filtered analysis in real-time over WebSocket -- [x] Task 6: Update any code that calls `AudioSourceStore.resolve_audio_source()` to handle the new return shape - -## Files to Modify/Create -- `core/audio/filters/pipeline.py` — **create** — AudioFilterPipeline -- `core/processing/audio_stream.py` — **modify** — integrate filter pipeline -- `core/processing/value_stream.py` — **modify** — integrate filter pipeline -- `api/routes/audio_sources.py` — **modify** — update WebSocket test -- Any other consumers of `resolve_audio_source()` — **modify** - -## Acceptance Criteria -- ProcessedAudioSource chains are resolved and filter pipelines created at stream start -- AudioAnalysis passes through the filter chain before visualization/value extraction -- Stateful filters maintain correct state across frames -- Hot-update of templates refreshes running filter pipelines -- WebSocket test endpoint works with processed sources -- Old inline channel/band code removed from stream classes - -## Notes -- ⚠️ Temporary breakage: Removing inline channel/band code from AudioColorStripStream breaks existing MonoAudioSource/BandExtractAudioSource flows — but those types were already removed in Phase 3. -- Filter pipeline must be thread-safe: AudioColorStripStream and AudioValueStream run in background threads. -- For hot-update: consider using an event callback from the template store rather than polling. -- The filter pipeline should produce a new AudioAnalysis each time (immutability), not mutate the shared snapshot from ManagedAudioStream. - -## Review Checklist -- [x] All tasks completed -- [x] Code follows project conventions -- [x] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - -### What was built -- `AudioFilterPipeline` class in `core/audio/filters/pipeline.py` — thread-safe pipeline that instantiates, processes, resets, and closes audio filters -- `build_pipeline_from_template_ids()` helper — resolves template IDs to FilterInstance lists and creates a pipeline -- `AudioColorStripStream` now builds and applies filter pipeline: `_rebuild_filter_pipeline()` called on construction and source update; `_apply_filters()` replaces `_pick_channel()` in render loop; pipeline closed on stop -- `AudioValueStream` now builds and applies filter pipeline: `_rebuild_filter_pipeline()` called on construction; filters applied in `_extract_raw()` before scalar extraction; pipeline closed on stop -- Hot-update: `ProcessorManager.refresh_audio_filter_pipelines(template_id)` dispatches to both CSS and value stream managers; called from audio processing template update/delete routes -- WebSocket test endpoint creates a filter pipeline from the resolved template chain and applies it to analysis before sending -- Dependency injection: `audio_processing_template_store` threaded through `ProcessorDependencies` -> `ProcessorManager` -> `ColorStripStreamManager` / `ValueStreamManager` -> `AudioColorStripStream` / `AudioValueStream` - -### What Phase 5 needs to know -- The backend is fully wired: creating a ProcessedAudioSource that references templates with channel_extract, band_extract, gain, etc. filters will apply those filters to live audio data -- The WebSocket test endpoint shows filtered analysis in real-time -- Frontend needs to provide UI for creating/editing AudioProcessingTemplates and ProcessedAudioSources -- Filter pipeline is per-stream-instance (not shared) — stateful filters maintain independent state - -### Temporary breakages resolved -- `_pick_channel()` removed from AudioColorStripStream — replaced by `_apply_filters()` which uses the filter pipeline -- Channel/band handling in AudioValueStream now goes through filter pipeline -- All "Phase 4 will wire..." comments resolved - -### Known deviations from plan -- `AudioFilterPipeline.__init__` takes `List[FilterInstance]` only (not `AudioFilterRegistry` — uses the class-level registry directly via `AudioFilterRegistry.create_instance()`) -- Hot-update uses explicit method calls from the route handler rather than an event-listener pattern — simpler and avoids the need for a subscription system diff --git a/plans/processed-audio-sources/phase-5-frontend-templates.md b/plans/processed-audio-sources/phase-5-frontend-templates.md deleted file mode 100644 index 4f482fe..0000000 --- a/plans/processed-audio-sources/phase-5-frontend-templates.md +++ /dev/null @@ -1,97 +0,0 @@ -# Phase 5: Frontend — Audio Processing Templates - -**Status:** ✅ Done -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** frontend - -## Objective -Build the frontend UI for managing Audio Processing Templates — list, create, edit, delete, with a filter editor and real-time preview. - -## Tasks - -- [x] Task 1: Create TypeScript module `static/js/features/audio-processing-templates.ts` - - Fetch/cache audio processing templates via DataCache - - CRUD operations using fetchWithAuth - - CardSection for template list with reconciliation -- [x] Task 2: Create template editor modal - - Name, description, tags fields - - Ordered filter list with add/remove/reorder controls - - Per-filter option controls (sliders, selects, toggles) driven by option schemas from `GET /api/v1/audio-filters` - - Template composition support: `audio_filter_template` shows EntitySelect for sub-template - - Dirty check via snapshotValues() -- [x] Task 3: Add Audio Processing Templates section to the Streams tab - - New sub-tab alongside existing Audio Sources - - CardSection rendering with template name, filter count, description - - Create/Edit/Delete actions per card -- [ ] Task 4: Real-time audio preview — **DEFERRED to Phase 7** - - Too complex for this phase; requires WebSocket plumbing and source selection - - The audio source test modal already provides spectrum visualization -- [x] Task 5: Add i18n keys for all new UI strings (en.json, ru.json, zh.json) - - Template section labels, filter names, option labels, buttons, errors -- [x] Task 6: Register module in `app.ts` / global exports for inline onclick handlers -- [x] Task 7: Fetch and cache audio filter registry data (for building filter option UIs) - -## Files to Modify/Create -- `static/js/features/audio-processing-templates.ts` — **created** — main module -- `templates/modals/audio-processing-template.html` — **created** — editor modal -- `static/js/core/state.ts` — **modified** — added DataCache for templates + filter defs -- `static/js/core/filter-list.ts` — **modified** — added audio filter icons -- `static/js/features/streams.ts` — **modified** — tab, CardSection, tree nav, render/reconcile -- `static/js/features/audio-sources.ts` — **modified** — use cache for processing templates -- `static/js/app.ts` — **modified** — imports + window exports -- `static/js/global.d.ts` — **modified** — window function declarations -- `templates/index.html` — **modified** — include modal template -- `static/locales/en.json` — **modified** — new i18n keys -- `static/locales/ru.json` — **modified** — new i18n keys -- `static/locales/zh.json` — **modified** — new i18n keys - -## Acceptance Criteria -- Audio Processing Templates section visible in Streams tab -- Templates can be created, edited, deleted -- Filter editor shows all 11 available filters with correct option controls -- Template composition (audio_filter_template) works via EntitySelect -- ~~Real-time preview shows filtered audio data~~ (deferred) -- All strings are internationalized - -## Notes -- Follow existing patterns from CSPT (Color Strip Processing Template) UI -- Uses FilterListManager from core/filter-list.ts for filter management -- IconSelect used for filter type selection -- NEVER use plain HTML `` — use project custom selectors -- NEVER use emoji — use SVG icons -- Use fetchWithAuth for ALL API calls -- Call cache.invalidate() before load in save/delete handlers -- Check DOM ID conflicts when adding new card types (project checklist) - -## Review Checklist -- [x] All tasks completed -- [x] Code follows project conventions -- [x] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - -### What was built -- Cleaned up the HTML modal template to only show capture and processed source type sections (removed multichannel/mono/band_extract HTML) -- Updated all 3 locale files to replace old multichannel/mono/band_extract i18n keys with new capture/processed keys -- Removed legacy section null-checks from `onAudioSourceTypeChange` - -### What Phase 7 needs to know -- The frontend now fully supports only two audio source types: capture and processed -- The modal hidden type input defaults to "capture" (was "multichannel") -- Audio source EntitySelects in color-strips.ts and value-sources.ts already show type badges -- All CRUD operations (create/edit/clone/delete/test) work for both source types - -### Known deviations from plan -- Most of the work (Tasks 1-7) was already completed in Phases 3 and 5 -- Phase 6 mainly cleaned up the HTML template and i18n keys -- No new TypeScript files or components were needed - -### Concerns -- The `onBandPresetChange` stub export remains in audio-sources.ts for backward compatibility; can be removed in Phase 7 cleanup diff --git a/plans/processed-audio-sources/phase-7-testing-polish.md b/plans/processed-audio-sources/phase-7-testing-polish.md deleted file mode 100644 index c589efd..0000000 --- a/plans/processed-audio-sources/phase-7-testing-polish.md +++ /dev/null @@ -1,85 +0,0 @@ -# Phase 7: Testing & Polish - -**Status:** ✅ Done -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Verify the full feature works end-to-end, write tests, fix any remaining issues, and clean up dead code. - -## Tasks - -- [ ] Task 1: Write unit tests for each audio filter - - Test each filter transforms AudioAnalysis correctly - - Test stateful filters maintain and reset state - - Test edge cases: empty spectrum, zero rms, all-zero input - - Test filter option validation -- [ ] Task 2: Write unit tests for AudioFilterPipeline - - Test chain of filters produces expected output - - Test empty pipeline passes through unchanged - - Test reset() resets all stateful filters -- [ ] Task 3: Write integration tests for AudioProcessingTemplateStore - - CRUD operations - - Template composition (audio_filter_template) expansion - - Cycle detection in template composition - - Reference validation -- [ ] Task 4: Write integration tests for ProcessedAudioSource in AudioSourceStore - - CRUD operations - - Chain resolution (ProcessedAudioSource → ... → CaptureAudioSource) - - Cycle detection in source chains - - Reference validation (source + template must exist) - - Cascade delete checks -- [ ] Task 5: Write API tests for audio processing template endpoints - - Create, read, update, delete - - Validation errors (missing fields, invalid filter_ids) - - Reference check on delete -- [ ] Task 6: Write API tests for updated audio source endpoints - - Create/update ProcessedAudioSource and CaptureAudioSource - - Reject old types (mono, band_extract) - - Validation of source/template references -- [ ] Task 7: Verify backup/restore includes audio processing templates - - Create templates, backup, restore, verify they survive -- [ ] Task 8: Full build verification - - `ruff check src/ tests/ --fix` passes - - `npx tsc --noEmit` passes - - `npm run build` succeeds - - `py -3.13 -m pytest tests/ --no-cov -q` — all tests pass -- [ ] Task 9: Clean up dead code - - Remove any remaining imports of `MonoAudioSource`, `BandExtractAudioSource` - - Remove old `band_filter.py` if fully superseded by the band_extract filter (or keep if still used elsewhere) - - Remove unused schema classes - - Verify no orphaned i18n keys -- [ ] Task 10: Update system health/info endpoints if they enumerate audio source types - -## Files to Modify/Create -- `tests/test_audio_filters.py` — **create** — filter unit tests -- `tests/test_audio_filter_pipeline.py` — **create** — pipeline tests -- `tests/test_audio_processing_template_store.py` — **create** — store tests -- `tests/test_audio_source_store.py` — **modify** — updated for new types -- `tests/test_audio_processing_templates_api.py` — **create** — API tests -- `tests/test_audio_sources_api.py` — **modify** — updated for new types -- Various files — **modify** — dead code cleanup - -## Acceptance Criteria -- All new tests pass -- All existing tests pass (no regressions) -- Full build (Python + TypeScript) succeeds -- Ruff linting passes -- No dead code referencing removed types -- Backup/restore round-trips correctly with audio processing templates - -## Notes -- This is the Big Bang verification phase — the first time build + tests are enforced -- Expect some breakage from Phases 1-6 that needs fixing here -- Focus on fixing real issues, not cosmetic cleanup -- `band_filter.py` may still be used by the band_extract filter — check before removing - -## Review Checklist -- [ ] All tasks completed -- [ ] Code follows project conventions -- [ ] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - diff --git a/plans/processed-audio-sources/phase-8-frontend-design-review.md b/plans/processed-audio-sources/phase-8-frontend-design-review.md deleted file mode 100644 index 51aa83f..0000000 --- a/plans/processed-audio-sources/phase-8-frontend-design-review.md +++ /dev/null @@ -1,65 +0,0 @@ -# Phase 8: Frontend Design Consistency Review - -**Status:** ✅ Done -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** frontend - -## Objective -Review all new frontend UI created in Phases 5-6 for visual consistency, design quality, and UX polish using the frontend-design skill agent. Fix any issues found. - -## Tasks - -- [x] Task 1: Review Audio Processing Templates section for design consistency - - Card layout, spacing, typography alignment with existing sections - - Filter editor modal — controls alignment, visual hierarchy, grouping - - Responsive behavior at different viewport widths -- [x] Task 2: Review Processed Audio Source cards for design consistency - - Card style matches existing source cards (capture, picture, value sources) - - EntitySelect pickers are visually consistent - - Type badges/icons are clear and distinguishable -- [x] Task 3: Review Capture Audio Source card (relabeled) - - Label/icon updates look correct - - No visual regressions from the rename -- [x] Task 4: Review source type picker/creation flow - - Type selector is clear and accessible - - Transition between types is smooth - - Empty states handled properly -- [x] Task 5: Review real-time audio preview UI - - Spectrum visualization looks polished - - Source picker and controls are well-placed - - Loading/error states -- [x] Task 6: Fix all design issues found in Tasks 1-5 - - CSS adjustments for spacing, alignment, typography - - Icon/color consistency - - Dark mode compatibility (if applicable) - - Hover/focus/active states on interactive elements -- [x] Task 7: Cross-browser spot-check (if applicable) - -## Files to Modify/Create -- `static/css/dashboard.css` — **modify** — design fixes -- `static/js/features/audio-processing-templates.ts` — **modify** — UX fixes -- `static/js/features/audio-processing-template-modal.ts` — **modify** — UX fixes -- `static/js/features/audio-sources.ts` — **modify** — UX fixes -- Any template/HTML files — **modify** — structural fixes - -## Acceptance Criteria -- All new UI sections are visually consistent with existing sections -- No orphaned styles or visual regressions -- Filter editor is intuitive and well-organized -- Cards, modals, and controls follow existing design language -- Interactive elements have proper hover/focus/active states - -## Notes -- This phase uses the frontend-design skill agent for review -- Focus on consistency with existing UI, not a complete redesign -- The project uses vanilla CSS (no framework) — fixes must use the existing stylesheet approach - -## Review Checklist -- [x] All tasks completed -- [x] Code follows project conventions -- [x] No unintended side effects -- [x] Build passes -- [x] Tests pass (new + existing) - -## Handoff to Next Phase -