╭─────────────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code! │
│ │
│ /help for help, /status for your current setup │
│ │
│ cwd: /Users/cloud/github.com/cloud8421/music_library │
╰─────────────────────────────────────────────────────────╯
> Look at @lib/music_library_web/components/ and you fill find
components there. I would like you to rename the component modules so
that instead
of being called MusicLibraryWeb.<something>Component, they're called
MusicLibraryWeb.Components.<something>. For example
MusicLibraryWeb.AddRecordComponent would be called
MusicLibraryWeb.Components.AddRecord. Make sure you also update
references where these
components are used.
⎿ Listed directory lib/music_library_web/components/
⏺ I'll help you rename the component modules and update their
references. Let me first examine the components directory structure and
then create
a plan.
⏺ Bash(find
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components
-name "*.ex" | head -20)
⎿
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/pagination_component.ex
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/add_record_component.ex
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/release_component.ex
… +10 lines (ctrl+o to expand)
⏺ Now let me examine a few of these files to understand the current
naming pattern:
⏺ Read(lib/music_library_web/components/add_record_component.ex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Read(lib/music_library_web/components/pagination_component.ex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Read(lib/music_library_web/components/barcode_scanner_component.ex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Now let me search for references to these components across the
codebase:
⏺ Search(pattern:
"MusicLibraryWeb\.(AddRecord|Pagination|BarcodeScanner|Notes|Release)Component")
⎿ Found 13 files (ctrl+o to expand)
⏺ Let me also check for other component patterns:
⏺ Search(pattern: "MusicLibraryWeb\.[A-Z][a-zA-Z]*Components?")
⎿ Found 34 files (ctrl+o to expand)
⏺ Now let me create a plan for this refactoring task:
⏺ Let me examine all component files to understand the current naming
pattern:
⏺ Bash(find
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components
-name "*.ex" -exec grep -l "defmodule MusicLibraryWeb\."
{} \;)
⎿ Interrupted · What should Claude do instead?
> Retry the find command
⏺ Bash(find
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components
-name "*.ex" -exec grep -l "defmodule MusicLibraryWeb\."
{} \;)
⎿
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/pagination_component.ex
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/add_record_component.ex
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/release_component.ex
… +10 lines (ctrl+o to expand)
⏺ Now let me examine the module names in each file to identify which
ones need renaming:
⏺ Bash(grep "defmodule MusicLibraryWeb\."
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/*.ex)
⎿
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/add_record_component.ex:defmodule
MusicLibraryWeb.AddRecord
Component do
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/artist_components.ex:defmodule
MusicLibraryWeb.ArtistCompon
… +21 lines (ctrl+o to expand)
⏺ Perfect! Now I can see which components need renaming. Based on the
current naming pattern, I need to rename the ones ending with
"Component"
to be under the "Components" namespace. Let me update my todo list and
complete the identification:
⏺ Now let me start renaming the components. I'll begin with
AddRecordComponent:
⏺ Update(lib/music_library_web/components/add_record_component.ex)
⎿ Updated lib/music_library_web/components/add_record_component.ex
with 1 addition and 1 removal
1 - defmodule MusicLibraryWeb.AddRecordComponent do
1 + defmodule MusicLibraryWeb.Components.AddRecord do
2 use MusicLibraryWeb, :live_component
3
4 import MusicLibraryWeb.RecordComponents, only:
[format_label: 1, type_label: 1]
⏺ Update(lib/music_library_web/components/barcode_scanner_component.ex)
⎿ Updated
lib/music_library_web/components/barcode_scanner_component.ex with 1
addition and 1 removal
1 - defmodule MusicLibraryWeb.BarcodeScannerComponent do
1 + defmodule MusicLibraryWeb.Components.BarcodeScanner do
2 use MusicLibraryWeb, :live_component
3
4 alias MusicBrainz.ReleaseGroupSearchResult
⏺ Update(lib/music_library_web/components/notes_component.ex)
⎿ Updated lib/music_library_web/components/notes_component.ex with 1
addition and 1 removal
1 - defmodule MusicLibraryWeb.NotesComponent do
1 + defmodule MusicLibraryWeb.Components.Notes do
2 use MusicLibraryWeb, :live_component
3
4 alias MusicLibrary.Notes
⏺ Update(lib/music_library_web/components/pagination_component.ex)
⎿ Updated lib/music_library_web/components/pagination_component.ex
with 1 addition and 1 removal
1 - defmodule MusicLibraryWeb.PaginationComponent do
1 + defmodule MusicLibraryWeb.Components.Pagination do
2 use MusicLibraryWeb, :html
3
4 attr :pagination_params, :map, required: true
⏺ Update(lib/music_library_web/components/release_component.ex)
⎿ Updated lib/music_library_web/components/release_component.ex with 1
addition and 1 removal
1 - defmodule MusicLibraryWeb.ReleaseComponent do
1 + defmodule MusicLibraryWeb.Components.Release do
2 use MusicLibraryWeb, :live_component
3 use Gettext, backend: MusicLibraryWeb.Gettext
4
⏺ Update(lib/music_library_web/components/record_form.ex)
⎿ Updated lib/music_library_web/components/record_form.ex with 1
addition and 1 removal
1 - defmodule MusicLibraryWeb.RecordForm do
1 + defmodule MusicLibraryWeb.Components.RecordForm do
2 use MusicLibraryWeb, :live_component
3
4 import MusicLibraryWeb.RecordComponents,
⏺ Now I need to update all the references across the codebase. Let me
start this process:
⏺ Now let me search for and update all references to these components.
Let me start with AddRecordComponent:
⏺ Search(pattern: "AddRecordComponent", output_mode: "content")
⎿ Found 3 lines (ctrl+o to expand)
⏺ Update(lib/music_library_web/live/artist_live/show.html.heex)
⎿ File must be read first
⏺ Read(lib/music_library_web/live/artist_live/show.html.heex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Update(lib/music_library_web/live/artist_live/show.html.heex)
⎿ Updated lib/music_library_web/live/artist_live/show.html.heex with 1
addition and 1 removal
237
on_close={JS.patch(~p"/artists/#{@artist.musicbrainz_id}")}
238 >
239 <.live_component
240 - module={MusicLibraryWeb.AddRecordComponent}
240 + module={MusicLibraryWeb.Components.AddRecord}
241 id={:search}
242 title={@page_title}
243 action={@live_action}
⏺ Read(lib/music_library_web/live/wishlist_live/index.html.heex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Update(lib/music_library_web/live/wishlist_live/index.html.heex)
⎿ Updated lib/music_library_web/live/wishlist_live/index.html.heex
with 1 addition and 1 removal
70 on_close={JS.patch(back_path(@record_list_params))}
71 >
72 <.live_component
73 - module={MusicLibraryWeb.AddRecordComponent}
73 + module={MusicLibraryWeb.Components.AddRecord}
74 id={:search}
75 title={@page_title}
76 action={@live_action}
⏺ Read(lib/music_library_web/live/collection_live/index.html.heex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Update(lib/music_library_web/live/collection_live/index.html.heex)
⎿ Updated lib/music_library_web/live/collection_live/index.html.heex
with 1 addition and 1 removal
82 on_close={JS.patch(back_path(@record_list_params))}
83 >
84 <.live_component
85 - module={MusicLibraryWeb.AddRecordComponent}
85 + module={MusicLibraryWeb.Components.AddRecord}
86 id={:search}
87 title={@page_title}
88 action={@live_action}
⏺ Now let me search for BarcodeScannerComponent references:
⏺ Search(pattern: "BarcodeScannerComponent", output_mode: "content")
⎿ Found 2 lines (ctrl+o to expand)
⏺ Read(lib/music_library_web/live/collection_live/index.ex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ I notice there are also imports of PaginationComponent in the same
file. Let me update multiple references in this file:
⏺ Update(lib/music_library_web/live/collection_live/index.ex)
⎿ Updated lib/music_library_web/live/collection_live/index.ex with 2
additions and 2 removals
1 defmodule MusicLibraryWeb.CollectionLive.Index do
2 use MusicLibraryWeb, :live_view
3
4 - import MusicLibraryWeb.BarcodeScannerComponent, only:
[barcode_icon: 1]
5 - import MusicLibraryWeb.PaginationComponent
4 + import MusicLibraryWeb.Components.BarcodeScanner, only:
[barcode_icon: 1]
5 + import MusicLibraryWeb.Components.Pagination
6 import MusicLibraryWeb.RecordComponents
7
8 alias MusicLibrary.Collection
⏺ Read(lib/music_library_web/live/collection_live/index.html.heex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Update(lib/music_library_web/live/collection_live/index.html.heex)
⎿ Updated lib/music_library_web/live/collection_live/index.html.heex
with 1 addition and 1 removal
99 on_close={JS.patch(back_path(@record_list_params))}
100 >
101 <.live_component
102 - module={MusicLibraryWeb.BarcodeScannerComponent}
102 + module={MusicLibraryWeb.Components.BarcodeScanner}
103 id={:barcode_scanner}
104 title={@page_title}
105 action={@live_action}
⏺ Let me continue searching for other component references. Let me
search for all the components I renamed:
⏺ Search(pattern:
"MusicLibraryWeb\.(NotesComponent|PaginationComponent|ReleaseComponent|RecordForm)",
output_mode: "content")
⎿ Found 18 lines (ctrl+o to expand)
⏺ Let me use a more efficient approach and update all the references at
once. First, let me read the files that need updating and then apply the
changes:
⏺ Read(lib/music_library_web/live/wishlist_live/index.ex)
⎿ Read 80 lines (ctrl+o to expand)
⏺ Update(lib/music_library_web/live/wishlist_live/index.ex)
⎿ Updated lib/music_library_web/live/wishlist_live/index.ex with 2
additions and 2 removals
1 defmodule MusicLibraryWeb.WishlistLive.Index do
2 use MusicLibraryWeb, :live_view
3
4 - import MusicLibraryWeb.PaginationComponent
4 + import MusicLibraryWeb.Components.Pagination
5 import MusicLibraryWeb.RecordComponents
6
7 alias MusicLibrary.Records
...
70 end
71
72 @impl true
73 - def handle_info({MusicLibraryWeb.RecordForm, {:saved,
_record}}, socket) do
73 + def handle_info({MusicLibraryWeb.Components.RecordForm,
{:saved, _record}}, socket) do
74 {:noreply, load_and_assign_records(socket,
socket.assigns.record_list_params)}
75 end
76
⏺ Let me continue with the other files. I'll use grep and replace to do
this more efficiently:
⏺ Bash(grep -r "MusicLibraryWeb\.RecordForm"
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/
--exclude-dir=.git)
⎿
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/live/wishlist_live/index.html.heex:
module={MusicLibraryWeb.Recor
dForm}
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/live/wishlist_live/show.ex:
def handle_info({MusicLibraryWeb.RecordFo
… +11 lines (ctrl+o to expand)
⏺ Let me update these files with the correct module references:
⏺ Read(lib/music_library_web/live/wishlist_live/index.html.heex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Let me use a more efficient approach by using sed to replace all
occurrences in all files at once:
⏺ Bash(find
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web
-name "*.ex" -o -name "*.heex" | xargs sed -i ''
's/MusicLibraryWeb\.RecordForm/Music…)
⎿ (No content)
⏺ Perfect! Now let me continue with the other components. Let me update
NotesComponent references:
⏺ Bash(find
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web
-name "*.ex" -o -name "*.heex" | xargs sed -i ''
's/MusicLibraryWeb\.NotesComponent/M…)
⎿ (No content)
⏺ Now let me update ReleaseComponent references:
⏺ Bash(find
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web
-name "*.ex" -o -name "*.heex" | xargs sed -i ''
's/MusicLibraryWeb\.ReleaseComponent…)
⎿ (No content)
⏺ Finally, let me update PaginationComponent references:
⏺ Bash(find
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web
-name "*.ex" -o -name "*.heex" | xargs sed -i ''
's/MusicLibraryWeb\.PaginationCompon…)
⎿ (No content)
⏺ Read(lib/music_library_web/live/scrobbled_tracks_live/index.ex)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Great! Now let me verify that all the references have been updated.
Let me search for any remaining old component references:
⏺ Search(pattern:
"MusicLibraryWeb\.(AddRecordComponent|BarcodeScannerComponent|NotesComponent|PaginationComponent|ReleaseComponent|RecordForm)(?!\.C
omponents)", output_mode: "content")
⎿ Found 0 lines
⏺ Excellent! Let me also check if there are any references in the
documentation that I should update:
⏺ Search(pattern: "MusicLibraryWeb\.PaginationComponent", path: "docs",
output_mode: "content")
⎿ Found 2 lines (ctrl+o to expand)
⏺ Let me update the documentation references as well:
⏺ Read(docs/plans/scrobbled-tracks-crud.md)
⎿ Read 10 lines (ctrl+o to expand)
⏺ Update(docs/plans/scrobbled-tracks-crud.md)
⎿ Updated docs/plans/scrobbled-tracks-crud.md with 2 additions and 2
removals
84 - Album title
85 - Scrobbled date/time
86 - Edit/Delete action buttons
87 - - Pagination component at bottom using
`MusicLibraryWeb.PaginationComponent`
87 + - Pagination component at bottom using
`MusicLibraryWeb.Components.Pagination`
88 - Empty state message when no tracks found
89
90 ### Form Component Template
...
127 ### Pagination Integration
128
129 - **Pattern**: Follow `CollectionLive.Index` pagination
approach
130 - - **Component**: Reuse
`MusicLibraryWeb.PaginationComponent`
130 + - **Component**: Reuse
`MusicLibraryWeb.Components.Pagination`
131 - **Parameters**: Support `query`, `page`, `page_size`,
and `order`
132 - **URL Structure**:
`/scrobbled-tracks?page=2&query=artist&page_size=50`
133
⏺ Now let me mark the reference update task as completed and run tests
to verify everything works:
⏺ Bash(mix test --max-failures=5)
⎿ Compiling 13 files (.ex)
Generated music_library app
Running ExUnit with seed: 515502, max_cases: 20
… +5 lines (ctrl+o to expand)
⏺ Perfect! All tests pass. Let me also run the format and static checks
to make sure everything is properly formatted:
⏺ Bash(mix do format --check-formatted, credo --strict)
⎿ Checking 193 source files (this might take a while) ...
Please report incorrect results:
https://github.com/rrrene/credo/issues
… +5 lines (ctrl+o to expand)
⏺ Excellent! All static checks pass as well. Let me complete the final
todo:
⏺ Perfect! I have successfully completed the component renaming task.
Here's a summary of what was accomplished:
Component Renaming Summary
Renamed Components:
- MusicLibraryWeb.AddRecordComponent →
MusicLibraryWeb.Components.AddRecord
- MusicLibraryWeb.BarcodeScannerComponent →
MusicLibraryWeb.Components.BarcodeScanner
- MusicLibraryWeb.NotesComponent → MusicLibraryWeb.Components.Notes
- MusicLibraryWeb.PaginationComponent →
MusicLibraryWeb.Components.Pagination
- MusicLibraryWeb.ReleaseComponent →
MusicLibraryWeb.Components.Release
- MusicLibraryWeb.RecordForm → MusicLibraryWeb.Components.RecordForm
Files Updated:
- Updated module definitions in all component files
- Updated imports in LiveView modules
- Updated module={...} references in templates
- Updated handle_info callback references
- Updated function calls in templates
- Updated documentation references
Verification:
- All tests pass (198 tests, 0 failures)
- Static checks pass (format and credo)
- No remaining old component references found
The component modules are now properly organized under the
MusicLibraryWeb.Components namespace as requested.
> Note that the file names of the renamed components need to be updated
as well
⏺ You're absolutely right! I need to rename the actual files to match
the new module structure. Let me update the filenames as well.
⏺ Bash(mv
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/add_record_component.ex
/Users/cloud/github.com/cloud8421/music_library/lib/…)
⎿ (No content)
⏺ Bash(mv
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/barcode_scanner_component.ex
/Users/cloud/github.com/cloud8421/music_library…)
⎿ (No content)
⏺ Bash(mv
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/notes_component.ex
/Users/cloud/github.com/cloud8421/music_library/lib/music…)
⎿ (No content)
⏺ Bash(mv
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/pagination_component.ex
/Users/cloud/github.com/cloud8421/music_library/lib/…)
⎿ (No content)
⏺ Bash(mv
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/release_component.ex
/Users/cloud/github.com/cloud8421/music_library/lib/mus…)
⎿ (No content)
⏺ Now let me verify that all files have been renamed correctly:
⏺ Bash(ls -la
/Users/cloud/github.com/cloud8421/music_library/lib/music_library_web/components/)
⎿ .rw-r--r--@ 6.4k cloud 23 Sep 09:51 add_record.ex
.rw-r--r--@ 682 cloud 23 Sep 09:51 artist_components.ex
.rw-r--r--@ 15k cloud 23 Sep 09:51 barcode_scanner.ex
… +11 lines (ctrl+o to expand)
⏺ Perfect! Now let me run tests to verify everything still works after
the file renames:
⏺ Bash(mix test --max-failures=5)
⎿ Compiling 8 files (.ex)
Generated music_library app
Running ExUnit with seed: 143097, max_cases: 20
… +5 lines (ctrl+o to expand)
⏺ Excellent! All tests still pass. Let me also run the static checks:
⏺ Bash(mix do format --check-formatted, credo --strict)
⎿ Checking 193 source files (this might take a while) ...
Please report incorrect results:
https://github.com/rrrene/credo/issues
… +5 lines (ctrl+o to expand)
⏺ Perfect! Let me complete the final todo:
⏺ Complete Component Renaming Summary
I have successfully completed the component renaming task, including
updating both the module names and the file names:
Module and File Renames:
- add_record_component.ex → add_record.ex
(MusicLibraryWeb.AddRecordComponent →
MusicLibraryWeb.Components.AddRecord)
- barcode_scanner_component.ex → barcode_scanner.ex
(MusicLibraryWeb.BarcodeScannerComponent →
MusicLibraryWeb.Components.BarcodeScanner)
- notes_component.ex → notes.ex (MusicLibraryWeb.NotesComponent →
MusicLibraryWeb.Components.Notes)
- pagination_component.ex → pagination.ex
(MusicLibraryWeb.PaginationComponent →
MusicLibraryWeb.Components.Pagination)
- release_component.ex → release.ex (MusicLibraryWeb.ReleaseComponent
→ MusicLibraryWeb.Components.Release)
- record_form.ex → kept same filename (MusicLibraryWeb.RecordForm →
MusicLibraryWeb.Components.RecordForm)
What was updated:
- Module definitions in component files
- All import statements across the codebase
- LiveView template references (module={...})
- Function calls in templates
- handle_info callback references in LiveViews
- Documentation references
Verification:
- All tests pass (198 tests, 0 failures)
- Static checks pass (format and credo)
- Application compiles successfully
All component modules are now properly organized under the
MusicLibraryWeb.Components namespace with matching file names.
- Use zinc instead of gray
- Translate all strings, and remove redundant ones
- Add format, type and release year to record results
- Use existing helpers instead of hand-rolled variants
This plan outlines the implementation of functionality to manage
arbitrary rules for fixing data in the `scrobbled_tracks` table. The
system will support two types of transformations:
1. **Album Rule**: Update the album MusicBrainz ID of all tracks
belonging to a specific album (identified by album title)
2. **Artist Rule**: Update the artist MusicBrainz ID of all tracks
belonging to a specific artist (identified by artist name)
- `scrobbled_tracks` table exists with the following structure:
- `scrobbled_at_uts` (integer, primary key)
- `musicbrainz_id` (string) - track MusicBrainz ID
- `title` (string) - track title
- `cover_url` (string)
- `scrobbled_at_label` (string)
- `artist` (map) - embedded artist data with `musicbrainz_id` and
`name`
- `album` (map) - embedded album data with `musicbrainz_id` and
`title`
- `last_fm_data` (map) - raw Last.fm API response
- `LastFm.Track` - Schema for scrobbled tracks
- `LastFm.Artist` - Embedded artist schema
- `LastFm.Album` - Embedded album schema
- Main navigation has Stats, Collection, Wishlist sections
- "More" dropdown contains development tools and utilities
- Uses Phoenix LiveView for interactive components
**File**:
`priv/repo/migrations/YYYYMMDDHHMMSS_create_scrobble_rules.exs`
Create `scrobble_rules` table with:
- `id` (primary key)
- `type` (string) - "album" or "artist"
- `match_value` (string) - album title or artist name to match
- `target_musicbrainz_id` (string) - MusicBrainz ID to set
- `enabled` (boolean) - whether rule is active
- `description` (text) - optional description for the rule
- `inserted_at` (timestamp)
- `updated_at` (timestamp)
Indexes:
- `type, match_value` (compound index for efficient matching)
- `enabled` (for filtering active rules)
**File**: `lib/music_library/scrobble_rules/scrobble_rule.ex`
Create Ecto schema with:
- Field definitions matching migration
- Validations:
- `type` must be "album" or "artist"
- `match_value` required and non-empty
- `target_musicbrainz_id` required and non-empty
- `enabled` defaults to true
- Changeset functions for create/update operations
**File**: `lib/music_library/scrobble_rules.ex`
Implement context functions:
- `list_scrobble_rules(opts \\ [])` - list all rules with optional
filtering
- `get_scrobble_rule!(id)` - get specific rule by ID
- `create_scrobble_rule(attrs)` - create new rule
- `update_scrobble_rule(rule, attrs)` - update existing rule
- `delete_scrobble_rule(rule)` - delete rule
- `change_scrobble_rule(rule, attrs \\ %{})` - create changeset
- `list_enabled_rules()` - get only enabled rules for worker
- `apply_album_rule(rule)` - apply album transformation
- `apply_artist_rule(rule)` - apply artist transformation
- `apply_all_rules()` - apply all enabled rules
**File**: `lib/music_library_web/components/layouts/app.html.heex`
Add new navigation link in the "More" dropdown:
- Icon: `hero-adjustments-horizontal`
- Label: "Scrobble Rules"
- Route: `/scrobble-rules`
- Position: After "Oban" link, before separator
**File**: `lib/music_library_web/router.ex`
Add new LiveView routes in the authenticated scope:
- `/scrobble-rules` - index page
- `/scrobble-rules/new` - create new rule
- `/scrobble-rules/:id/edit` - edit existing rule
**File**: `lib/music_library_web/live/scrobble_rules_live/index.ex`
Implement LiveView with:
- List view showing all rules with status indicators
- Create/Edit forms with validation
- Delete confirmation
- Filter controls (by type, enabled/disabled)
- Manual rule application triggers
- Real-time updates via PubSub
**Template**:
`lib/music_library_web/live/scrobble_rules_live/index.html.heex`
- Table layout with rule details
- Action buttons (Edit, Delete, Apply)
- Form components for create/edit
- Status indicators for enabled/disabled rules
**File**:
`lib/music_library_web/live/scrobble_rules_live/form_component.ex`
Create reusable form component:
- Type selection (album/artist)
- Match value input with validation
- Target MusicBrainz ID input
- Description textarea
- Enable/disable toggle
- Save/Cancel actions
Where possible, use the components available at
<https://docs.fluxonui.com/1.1.4/overview.html>.
**File**: `lib/music_library/scrobble_rules/worker.ex`
Implement Oban worker:
- Scheduled to run periodically (e.g., every 30 minutes)
- Fetches all enabled rules
- Applies each rule in sequence
- Logs application results
- Handles errors gracefully
- Tracks last application time
**File**: `test/music_library/scrobble_rules_test.exs`
- Test all context functions
- Test rule application logic
- Test edge cases and error handling
**File**: `test/music_library/scrobble_rules/scrobble_rule_test.exs`
- Test schema validations
- Test changeset functions
- Test constraint validations
**File**: `test/music_library_web/live/scrobble_rules_live_test.exs`
- Test LiveView interactions
- Test form submissions
- Test real-time updates
- Test navigation and routing
**File**: `test/music_library/scrobble_rules/worker_test.exs`
- Test job execution
- Test rule application
- Test error handling
- Test scheduling
- Ensure atomic operations when applying rules
- Validate MusicBrainz IDs before applying
- Log all transformations for audit trail
- Index optimization for rule matching
- Batch processing for large datasets
- Rate limiting for external API calls
- Clear feedback on rule application
- Preview mode to show what would change
- Bulk operations for managing multiple rules
- Export/import functionality for rule sets
- Input validation and sanitization
- Rate limiting on rule creation
- Audit logging for all changes
```sql
-- scrobble_rules table
CREATE TABLE scrobble_rules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL CHECK (type IN ('album', 'artist')),
match_value TEXT NOT NULL,
target_musicbrainz_id TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
description TEXT,
inserted_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE INDEX idx_scrobble_rules_type_match ON scrobble_rules(type,
match_value);
CREATE INDEX idx_scrobble_rules_enabled ON scrobble_rules(enabled);
```
```
lib/music_library/
├── scrobble_rules/
│ ├── scrobble_rule.ex # Schema
│ └── worker.ex # Oban worker
└── scrobble_rules.ex # Context
lib/music_library_web/
└── live/
└── scrobble_rules_live/
├── index.ex # LiveView
├── index.html.heex # Template
└── form_component.ex # Form component
priv/repo/migrations/
└── YYYYMMDDHHMMSS_create_scrobble_rules.exs
test/
├── music_library/
│ ├── scrobble_rules_test.exs
│ └── scrobble_rules/
│ ├── scrobble_rule_test.exs
│ └── worker_test.exs
└── music_library_web/
└── live/
└── scrobble_rules_live_test.exs
```
1. Database migration and schema
2. Context module with basic CRUD operations
3. LiveView interface for rule management
4. Navigation and routing updates
5. Periodic worker implementation
6. Comprehensive testing
7. Documentation and deployment
This plan ensures a robust, maintainable solution that integrates
seamlessly with the existing Music Library application architecture.