--- id: ML-150 title: Extract Records sub-contexts to reduce module size status: Done assignee: [] created_date: '2026-04-30 10:47' updated_date: '2026-04-30 16:08' labels: - refactor - records dependencies: [] references: - lib/music_library/records.ex priority: medium --- ## Description The `Records` context module (450+ lines in `lib/music_library/records.ex`) handles CRUD, FTS5 search, MusicBrainz import, cover management, genre population, color extraction, PubSub notifications, and similarity embedding dispatch — too many responsibilities for a single module. Extract focused sub-contexts: - `Records.Search` — FTS5 search, `SearchParser` integration, search result formatting - `Records.Import` — MusicBrainz release/group import, barcode scan integration - `Records.Enrichment` — genre population, color extraction, cover management, embedding dispatch Keep the public `Records` module as a facade that re-exports key functions for backward compatibility with all existing callers (LiveViews, workers, controllers). The `SearchIndex` schema, `Record` schema, `Similarity` module, `TracklistPdf`, and `Batch` sub-modules stay as-is. Only `records.ex` itself is being split. ## Acceptance Criteria - [x] #1 `Records.Search`, `Records.Import`, and `Records.Enrichment` modules exist with focused responsibilities - [x] #2 Public `Records` module re-exports all previously-public functions through delegation - [x] #3 All callers (LiveViews, workers, controllers, tests) continue to work without changes to their import/alias lines - [x] #4 Full test suite passes with no regressions - [x] #5 `@moduledoc` for each new module explains its responsibility ## Implementation Notes ## Implementation Summary ### New modules created **`Records.Search`** (`lib/music_library/records/search.ex`): - `essential_fields/0`, `search_records/3`, `search_records_count/2`, `list_genres/0` - Private: `build_search/3`, `fts_escape/1`, `fts_query_escape/1` - Imports `order_alphabetically` macro from `Records` (one-direction dependency, no cycle) **`Records.Import`** (`lib/music_library/records/import.ex`): - `get_release_status/2`, `get_artist_records/1` - `import_from_musicbrainz_release/2`, `import_from_musicbrainz_release_group/2` - Private: `get_cover_art_or_default/1`, `build_record_attrs/2` - Calls `Records.create_record/1` and `Records.Search.essential_fields/0` **`Records.Enrichment`** (`lib/music_library/records/enrichment.ex`): - `populate_genres/1`, `populate_genres_async/1` - `refresh_cover/1`, `refresh_cover_async/1` - `extract_colors/1`, `resize_cover/1` - `refresh_musicbrainz_data/1`, `refresh_musicbrainz_data_async/1` - `best_effort_extract_colors/1` (public, called from `Records.create_record/1`) - Private: `maybe_extract_colors/1`, `enqueue_worker/3`, `record_meta/1` - Calls `Records.update_record/2` ### Facade (`lib/music_library/records.ex`) - 15 `defdelegate` calls covering all moved functions (compile-time safety) - `order_alphabetically` macro stays in `Records` for backward compatibility - CRUD (`get_record`, `create_record`, `update_record`, `delete_record`, `change_record`) and PubSub (`subscribe`, `notify_update`) stay directly in `Records` ### Dependency graph (one direction only): ``` Records.Search → imports macro from Records Records.Import → calls Records, Records.Search Records.Enrichment → calls Records Records → delegates to Search, Import, Enrichment (compile-time: defdelegate only, no runtime cycle) ``` ### Tests - `test/music_library/records_test.exs` — CRUD tests (6 tests) - `test/music_library/records/search_test.exs` — search tests (13 tests) - `test/music_library/records/import_test.exs` — import tests (3 tests) - `test/music_library/records/enrichment_test.exs` — enrichment tests (3 tests)