Cleanup old tasks
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: ML-100
|
||||
title: Inconsistent timestamp type in ScrobbleRule schema
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:58'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/74'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-02-17 · updated 2026-03-06 · closed 2026-03-06_
|
||||
|
||||
## Priority: Medium
|
||||
|
||||
## Description
|
||||
|
||||
`lib/music_library/scrobble_rules/scrobble_rule.ex:24` uses bare `timestamps()` while all other schemas explicitly use `timestamps(type: :utc_datetime)`. This means ScrobbleRule timestamps default to `:naive_datetime` instead of `:utc_datetime`.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
All schemas should consistently use `timestamps(type: :utc_datetime)`.
|
||||
|
||||
## Source
|
||||
|
||||
From technical debt audit (2026-02-17), item #1.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
id: ML-101
|
||||
title: Artists without Wikipedia page
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:58'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/73'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-02-11 · updated 2026-03-21 · closed 2026-03-21_
|
||||
|
||||
```
|
||||
ce1e4d85-3282-4f0e-8b01-a3ba91bd5719|Abydos
|
||||
e3c8b0c8-3fa0-464b-b1f5-119cb52d3817|Stevie Ray Vaughan and Double Trouble
|
||||
b9ac0c1a-59be-4a94-bc92-e92f42638fee|HRЕÐA
|
||||
afc127f9-a29b-41cb-bf97-8afb65393a2e|Jordsjø
|
||||
8e57cabb-7dc5-4eac-94c0-a3928b989af5|Bruce Soord
|
||||
6b2c1c8b-4bd4-4738-aa7d-81337a4979fb|PLANK!
|
||||
e0a829d9-211a-48c6-ab5b-24568d1ce2b3|Atmosfer
|
||||
69c4096d-b762-4b3f-ad3d-6f1f94066a1d|Manos Tacticos & His Bouzoukis
|
||||
20afbf5f-119a-47da-9903-f618ce3709ed|Spectrals
|
||||
0891d0c7-6691-4d56-b033-11c3924b8fc2|Knight Area
|
||||
9ee81a07-5edb-4bd2-9d84-4864a8ba0139|Dāmodara
|
||||
ec5fb45c-1907-4a17-9d16-2a3006ccb611|Heimdall
|
||||
5ff525f0-a7ed-41ba-ba01-f88ebd2df482|Airbag
|
||||
837626a5-e912-4dd4-b8cf-4ad8881d751f|Event
|
||||
af127443-e1c4-4ed1-a608-3991e194d07e|Isildurs Bane
|
||||
f91b5b23-c880-41fa-b6d2-d7ea41ace609|Zopp
|
||||
9d07f2ed-7bcf-4c60-86cc-0c29654120a0|Rishloo
|
||||
388a6eab-df7a-4993-b537-278a748e101a|Trifecta
|
||||
cef7cb41-f001-45e7-9e07-335a78232c05|Course of Nature
|
||||
d3bd3121-0294-4912-888c-b954fd79ea5f|The Chronicles of Father Robin
|
||||
5d006744-dd29-4560-b511-0458c1b85963|Himmellegeme
|
||||
35ac1700-84f1-4bd9-924b-3792b742e618|Tomáš Dvořák
|
||||
2b943401-39f9-4792-a17b-38393ba687b8|Freternia
|
||||
63981486-1a39-4661-ae8d-29efd62cede1|Tantz
|
||||
8049b1f4-9a19-49e5-a212-de8b0b32b250|Slow Electric
|
||||
a9283da4-fb17-41f1-bea4-d32226980b24|William
|
||||
568cb89a-e948-4295-972f-eb23f4fdf211|MEER
|
||||
10d8295c-9e82-4470-9f4d-799feb55508f|Collage
|
||||
2d2ea891-ebda-411f-aa4a-413cb31df366|Bjørn Riis
|
||||
caec8c9a-2831-4ae8-9c13-e38688a5baa5|Oak
|
||||
9fdaa16b-a6c4-4831-b87c-bc9ca8ce7eaa|The Who
|
||||
ac72187e-34ec-4c57-8c68-006e4a946e55|Calories
|
||||
5f26631e-ce74-421b-9d5d-61c034692ee9|Andy Leigh
|
||||
d4002f76-51c1-4272-8398-a6b39af66667|Persona Non Grata
|
||||
4ca66f7e-e574-46eb-99f3-d8bee033d999|Rain Tree Crow
|
||||
bccf4dd5-bb61-4ac2-9158-c67575bbdf35|Tusmørke
|
||||
e96481f7-756b-44a7-a6a3-870569a5bc3c|Tomas Bodin
|
||||
dccb8204-6654-4e7f-9587-aee467da8882|Fair Ohs
|
||||
d13cc2c0-8eb7-43a2-ab20-bcb10f222c78|Magic Pie
|
||||
02714d30-69c2-4a83-a963-2ffe6eaca39e|Flört
|
||||
59b0f968-3628-41e7-8945-121202056239|Garey Mielke
|
||||
fce3eede-7306-4d12-b5e6-2e2d272510a4|Lingua Mortis Orchestra
|
||||
08af43c6-2b3f-499b-bc17-b9f99e196cf8|Caamora
|
||||
0789a433-ec48-4dfd-9b00-675618a01352|Blind Ego
|
||||
aff74a2c-de0c-487d-b7fd-24caea74047d|BEAT
|
||||
c89fcb28-624c-4a3e-8275-a251339c071a|The Aurora Project
|
||||
75d43a2e-ba60-427c-b2c7-bb666053d2f1|Amarok
|
||||
c8c73bec-c7be-4842-9f5e-2757b9eb0e5c|Drifting Sun
|
||||
bb00ff20-4f89-407f-8207-28d75145a432|Trojka
|
||||
e1bfa520-8fad-46de-9678-10912cb26e7f|Overhead
|
||||
0152651e-5928-47ec-be9c-e4d81c143c67|Retrospective
|
||||
```
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-102
|
||||
title: Extend Universal Search to search across notes
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:58'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/72'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-01-08 · updated 2026-02-10 · closed 2026-02-10_
|
||||
|
||||
Results should include a small preview of the result.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-103
|
||||
title: Increase size of latest purchase artwork in stats on mobile
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:58'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/70'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-12-24 · updated 2025-12-24 · closed 2025-12-24_
|
||||
|
||||
Make it slightly bigger, making sure image link uses appropriate dimensions.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: ML-104
|
||||
title: Setup Litestream to backup database to Hetzner object storage
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:58'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/69'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-12-12 · updated 2026-03-20 · closed 2026-03-20_
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: ML-105
|
||||
title: Refactor MusicLibrary.ScrobbleRules
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/65'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-11-16 · updated 2026-02-10 · closed 2026-02-10_
|
||||
|
||||
- Extract sql macro for `CASE...WHEN...ELSE` update
|
||||
- Extract sql macro for `json_extract`
|
||||
- Extract sql macro for `json_set`
|
||||
- Remove extended duplication
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-106
|
||||
title: Set up Copilot instructions
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/63'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-11-11 · updated 2025-11-11 · closed 2025-11-11_
|
||||
|
||||
Configure instructions for this repository as documented in Best practices for Copilot coding agent in your repository.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
id: ML-107
|
||||
title: Regular job to cleanup asset cache
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/59'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-11-07 · updated 2025-11-07 · closed 2025-11-07_
|
||||
|
||||
This query returns the hash of all orphan assets
|
||||
|
||||
```sql
|
||||
SELECT hash
|
||||
FROM assets
|
||||
LEFT JOIN records on records.cover_hash == assets.hash
|
||||
LEFT JOIN artist_infos on artist_infos.image_data_hash == assets.hash
|
||||
WHERE records.id IS NULL AND artist_infos.id IS NULL;
|
||||
```
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
id: ML-108
|
||||
title: Support universal search navigation via keyboard
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/58'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-11-01 · updated 2025-11-09 · closed 2025-11-09_
|
||||
|
||||
The universal search modal cannot be navigated by keyboard only. It should support:
|
||||
|
||||
- Pressing down to navigate from the search input to the first result
|
||||
- Navigating results with up and down keys
|
||||
- Pressing enter to open a result
|
||||
- When pressing down on the last result, go back to the search input
|
||||
|
||||
Should be implemented as a hook for the Universal Search component.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: ML-109
|
||||
title: Improve test suite
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/57'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-10-25 · updated 2026-02-07 · closed 2026-02-07_
|
||||
|
||||
Comprehensive test coverage analysis. Key gaps identified:
|
||||
|
||||
**Critical (High Priority):**
|
||||
- 9 of 10 background workers untested (FetchArtistImage, FetchArtistInfo, RecordRefreshMusicBrainzData, RefreshCover, PopulateGenres, GenerateRecordEmbedding, PruneArtistInfo, PruneAssetCache, ExtractColors)
|
||||
- Scrobble LiveView — user-facing feature with Last.fm integration
|
||||
- LastFmController — OAuth authentication flow
|
||||
|
||||
**Moderate Priority:**
|
||||
- Universal Search LiveView
|
||||
- ArchiveController (backup download)
|
||||
- HealthController
|
||||
|
||||
**Low Priority:**
|
||||
- Online Store Templates LiveView
|
||||
- Notes management
|
||||
- Stats detail LiveViews (top albums/artists)
|
||||
- Color extraction feature
|
||||
|
||||
**Well-tested areas:** Collection/Wishlist/Artist/Stats LiveViews, core context modules (Records, Collection, Wishlist, Artists, ScrobbleActivity, ScrobbleRules), external API clients (MusicBrainz, LastFm, Discogs), AssetController, CollectionController, SessionController, Auth plug.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-110
|
||||
title: Radios?
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/56'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-10-17 · updated 2026-02-10 · closed 2026-02-10_
|
||||
|
||||
For ease of use, one could play radios directly from the library, with bonus points for scrobbling tracks.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-111
|
||||
title: Review design of ad-hoc scrobble show view
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/54'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-09-28 · updated 2026-02-10 · closed 2026-02-10_
|
||||
|
||||
Metadata part is AI-generated and not-consistent with rest of the application.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
id: ML-112
|
||||
title: Optimise scrobble rule application
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/53'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-09-28 · updated 2025-11-10 · closed 2025-11-10_
|
||||
|
||||
When applying all rules, we currently apply each rule independently on the entire database. This means that it takes a significant amount of time to apply all rules.
|
||||
|
||||
Instead we should be able to compose a single query to apply all album rules, and a single query to apply all artist rules.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
id: ML-113
|
||||
title: Scrobble groups of tracks
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/52'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-09-28 · updated 2025-11-11 · closed 2025-11-11_
|
||||
|
||||
When scrobbling a collected release from the release sheet, it's only possible to scrobble either the entire release or an individual medium.
|
||||
|
||||
The component should be updated to support scrobbling groups of tracks.
|
||||
|
||||
Specifically:
|
||||
|
||||
- each track needs a checkbox beside it, default unchecked.
|
||||
- if any track gets checked, the main "Scrobble Release" button should read "Scrobble selected tracks".
|
||||
- when pressed, only selected tracks should be scrobbled. Assume that they have been played one after the other.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-114
|
||||
title: Display progarchives links for records
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/49'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-25 · updated 2026-02-10 · closed 2026-02-10_
|
||||
|
||||
Where applicable.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-115
|
||||
title: Import Last.fm history
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/48'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-22 · updated 2025-05-31 · closed 2025-05-31_
|
||||
|
||||
When polling Last.fm, store scrobbles locally in a dedicated table. Add functionality to scrape past data as well.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: ML-116
|
||||
title: Display similar artists with an image grid
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/47'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-21 · updated 2025-05-21 · closed 2025-05-21_
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-117
|
||||
title: Move artist bio to a left side sheet
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/46'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-21 · updated 2025-05-21 · closed 2025-05-21_
|
||||
|
||||
Display summary on left-hand side, with a read more button that opens the sheet.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: ML-118
|
||||
title: Add actions to refresh artist info and artwork from artist page
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/45'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-20 · updated 2025-05-20 · closed 2025-05-20_
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-119
|
||||
title: Scrobble individual media
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/44'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-09 · updated 2025-05-10 · closed 2025-05-10_
|
||||
|
||||
So that it's possible to scrobble individual discs.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: ML-12
|
||||
title: Use Notes.change_note/2 instead of Note.changeset/2 in notes component
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:49'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/171'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-16 · updated 2026-04-17 · closed 2026-04-17_
|
||||
|
||||
## Summary
|
||||
|
||||
`lib/music_library_web/components/notes.ex` calls `Note.changeset/2` directly at three sites, bypassing the context helper `MusicLibrary.Notes.change_note/2` (which already exists and is used at line 97 of the same component).
|
||||
|
||||
## Evidence
|
||||
|
||||
- `lib/music_library_web/components/notes.ex:15`
|
||||
- `lib/music_library_web/components/notes.ex:121`
|
||||
- `lib/music_library_web/components/notes.ex:138`
|
||||
|
||||
All three could call `Notes.change_note(note, %{})` instead.
|
||||
|
||||
## Why It Matters
|
||||
|
||||
The "LiveViews / LiveComponents call the context, not schema modules" boundary is load-bearing in this project. The component already honours it at line 97 — these three spots are the outliers.
|
||||
|
||||
## Fix
|
||||
|
||||
Mechanical find-and-replace. Three one-line edits.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Tests still pass
|
||||
- No direct `Note.changeset` references in `lib/music_library_web/components/notes.ex`
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Tests still pass
|
||||
- [ ] #2 No direct `Note.changeset` references in `lib/music_library_web/components/notes.ex`
|
||||
<!-- AC:END -->
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: ML-120
|
||||
title: 'When scanning a wish listed record, set its selected release'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/43'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-09 · updated 2025-05-09 · closed 2025-05-09_
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-121
|
||||
title: Disable Last.fm http pool
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/42'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-09 · updated 2025-05-14 · closed 2025-05-14_
|
||||
|
||||
Doesn't recover well after the vm resumes from suspension, and causes the first http request to Last.fm to always fail.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: ML-122
|
||||
title: Increase size of record form save button on mobile
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/41'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-09 · updated 2025-05-09 · closed 2025-05-09_
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-124
|
||||
title: Improve display of releases in the edit record dropdown
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/39'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-07 · updated 2025-05-07 · closed 2025-05-07_
|
||||
|
||||
Sort by date descending, country
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: ML-125
|
||||
title: Display total runtime for the entire release
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/38'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-04 · updated 2025-05-05 · closed 2025-05-05_
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: ML-126
|
||||
title: 'For compilations, each track should also display artist(s)'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/37'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-04 · updated 2025-05-04 · closed 2025-05-04_
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: ML-127
|
||||
title: 'Display tracks, each one with title and duration (in mm:ss)'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/36'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-04 · updated 2025-05-05 · closed 2025-05-05_
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-128
|
||||
title: 'Display media (discs), ordered by position with total runtime'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/35'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-04 · updated 2025-05-05 · closed 2025-05-05_
|
||||
|
||||
Display media (A.K.A. discs), ordered by their `position` but identified by their label, with the total runtime
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-129
|
||||
title: Display tracklist sheet from record detail page
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/34'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-04 · updated 2025-05-04 · closed 2025-05-04_
|
||||
|
||||
In the records detail page, display a sheet on the right hand side, activated by a track list icon in the same row as the collected release information.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
id: ML-130
|
||||
title: Enrich selected release input and display
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/33'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-05-01 · updated 2025-05-25 · closed 2025-05-25_
|
||||
|
||||
- Needs to be easy to visually scan by format and date.
|
||||
- Country rendered as a flag.
|
||||
- Input needs to be searchable.
|
||||
|
||||
Optionally (if possible) group options in input so that releases of the same format are presented first.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
id: ML-131
|
||||
title: Connect a Last.fm account
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/32'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-30 · updated 2025-05-07 · closed 2025-05-07_
|
||||
|
||||
Currently communication with Last.fm is done via an API token, and the user is configured with a static username.
|
||||
|
||||
Supporting the association with a Last.fm account would support existing functionality, but also open the door for future larger integration with Last.fm.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: ML-132
|
||||
title: Scrobble a record to Last.fm
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/31'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-30 · updated 2025-05-07 · closed 2025-05-07_
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-133
|
||||
title: Show a record track list with durations
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/30'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-30 · updated 2025-05-05 · closed 2025-05-05_
|
||||
|
||||
Only for collected records with a selected release.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-134
|
||||
title: Support selecting a release per record
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/29'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-30 · updated 2025-05-01 · closed 2025-05-01_
|
||||
|
||||
While records themselves map to MusicBrainz release groups, associating a record with a release opens the door to two major future improvements.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-135
|
||||
title: Add country to artist page
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/28'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-28 · updated 2025-04-29 · closed 2025-04-29_
|
||||
|
||||
Provided by Musicbrainz data. Can be rendered via an emoji flag via flagmojis.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-136
|
||||
title: Unicode search
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/27'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-26 · updated 2025-05-25 · closed 2025-05-25_
|
||||
|
||||
Searching for "bjorn" should return records, but it doesn't.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
id: ML-137
|
||||
title: Polyfill missing data in scrobble activity
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/26'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-26 · updated 2025-09-26 · closed 2025-09-26_
|
||||
|
||||
- For tracked records, display the record artwork rather than the one provided by Last.fm.
|
||||
- For tracked records, when matching the artist try and match the album by title
|
||||
- Polyfill missing data when it's possible to find one record with the same title and artist
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-138
|
||||
title: Allow multiple plays of the same album in scrobble activity
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/25'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-24 · updated 2025-04-25 · closed 2025-04-25_
|
||||
|
||||
The logic that packs scrobbled tracks into albums currently keeps only one occurrence per album (the most recent), which ends up showing an incorrect history. An album can be played multiple non-contiguous times in the same window.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: ML-139
|
||||
title: Polyfill missing artist IDs in scrobble activity
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/24'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-23 · updated 2025-04-25 · closed 2025-04-25_
|
||||
|
||||
Given that each scrobbled track can be matched to a collected or wishlisted release, it's possible to polyfill missing artist information by copying over the data from the album artist(s).
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: ML-14
|
||||
title: 'Replace Process.sleep with :sys.get_state in error_notifier_test.exs'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:50'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/169'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-16 · updated 2026-04-17 · closed 2026-04-17_
|
||||
|
||||
## Summary
|
||||
|
||||
Five `Process.sleep(50)` calls in `test/error_tracker/error_notifier_test.exs` wait for a GenServer to process a telemetry event before `assert_email_sent`. The sleeps are redundant and are the only timing-based waits in the suite.
|
||||
|
||||
## Evidence
|
||||
|
||||
`test/error_tracker/error_notifier_test.exs` lines: `:126`, `:136`, `:155`, `:164`, `:183`
|
||||
|
||||
Each call is after `:telemetry.execute/3`, waiting for the `ErrorTracker.ErrorNotifier` GenServer to process the cast and call the mailer.
|
||||
|
||||
## Fix
|
||||
|
||||
Replace each `Process.sleep(50)` with `:sys.get_state(ErrorTracker.ErrorNotifier)` — a synchronous probe that blocks until the GenServer has drained its mailbox up to the current message. Deterministic and faster.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Zero `Process.sleep` occurrences in `test/`
|
||||
- Tests remain deterministic and pass under repeated runs
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Zero `Process.sleep` occurrences in `test/`
|
||||
- [ ] #2 Tests remain deterministic and pass under repeated runs
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
id: ML-140
|
||||
title: Get artist images
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/23'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2025-04-22 · updated 2025-04-29 · closed 2025-04-29_
|
||||
|
||||
Fetch artist images via MusicBrainz url-rels → Discogs artist endpoint. Implementation checklist (all completed):
|
||||
|
||||
- Discogs api client
|
||||
- MusicBrainz `get_artist/1` with result parser
|
||||
- `ArtistInfo` db schema and migration
|
||||
- Context function to fetch and store `ArtistInfo`
|
||||
- Background job to fetch and store `ArtistInfo`
|
||||
- Extract image URL from Discogs artist
|
||||
- Store/hash image from Discogs artist
|
||||
- Endpoint to serve artist images
|
||||
- Show artist image in artist page
|
||||
- Populate artist info when new artist is added
|
||||
- Cascade deletions
|
||||
- Tests
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
id: ML-141
|
||||
title: Support records with accents
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/7'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2024-12-07 · updated 2024-12-07 · closed 2024-12-07_
|
||||
|
||||
SQLite's collation functions don't by default collapse accented characters into their non-accented variants. For example, an artist with sort name `Åkerfeldt, Mikael` is appended at the bottom instead of being grouped with `A`.
|
||||
|
||||
The `records_search_index` table behaves correctly (searching `Aker` matches the artist), but alphabetical sorting/grouping is broken for non-ASCII sort names.
|
||||
|
||||
Fix: ensure non-ASCII artist records are slotted at the correct alphabetical position.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
id: ML-142
|
||||
title: Improve scrobble UI in the Release component
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 09:32'
|
||||
updated_date: '2026-04-22 13:27'
|
||||
labels:
|
||||
- ui
|
||||
- scrobble
|
||||
dependencies: []
|
||||
references:
|
||||
- lib/music_library_web/live/components/release.ex
|
||||
- lib/music_library_web/components/scrobble_components.ex
|
||||
- backlog/ml-142/mockups.html
|
||||
- backlog/ml-142/plan.md
|
||||
documentation:
|
||||
- backlog/ml-142/plan.md
|
||||
- backlog/ml-142/mockups.html
|
||||
priority: medium
|
||||
ordinal: 1000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The Release component's scrobble interface has several usability gaps that make scrobbling cumbersome, especially for multi-medium releases. This task improves the experience across three areas: custom scrobble time, button visual clarity, and per-medium scrobble access.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 The release-sheet header renders a `Finished at` date/time picker that displays 'Now' when unset and an explicit time when set
|
||||
- [x] #2 The release-sheet header renders a solid-primary `Scrobble release` button that scrobbles the whole release using the picker value or `DateTime.utc_now()` when unset
|
||||
- [x] #3 The release-sheet header renders a `⋯` overflow menu containing `Print tracklist`, plus `Connect Last.fm` when the session key is missing
|
||||
- [x] #4 The duplicate top-level `Tracks` master checkbox shown on single-medium releases is removed
|
||||
- [x] #5 Each medium header renders a soft-primary `Scrobble disc` button that is enabled regardless of cross-medium track selection
|
||||
- [x] #6 Each medium header renders a `⋯` overflow menu containing `Print tracklist` for that medium
|
||||
- [x] #7 A sticky bar appears at the bottom of the sheet body whenever `MapSet.size(@selected_tracks) > 0`, showing track count, medium count, aggregate duration, and a `Scrobble selected` button
|
||||
- [x] #8 The sticky bar is not rendered when no tracks are selected
|
||||
- [x] #9 `Scrobble selected` scrobbles only the ticked tracks using the picker value or `DateTime.utc_now()` when unset
|
||||
- [x] #10 Clicking a per-medium `Scrobble disc` submits that medium regardless of selection elsewhere, using the picker value or `DateTime.utc_now()`
|
||||
- [x] #11 The picker has a reset affordance that clears the value back to 'Now'
|
||||
- [x] #12 Disabled scrobble buttons are visibly distinct from enabled ones in both light and dark mode (verified in browser)
|
||||
- [x] #13 On viewports ≤ 380px the header reflows: title row above a second row containing the picker (flex-1) and the `Scrobble release` button; per-medium scrobble collapses to icon-only; sticky bar stays legible
|
||||
- [x] #14 `ScrobbleLive.Show` per-medium scrobble handler works with tracks selected (regression test)
|
||||
- [x] #15 All new user-facing strings wrapped in gettext; `.pot`/`.po` files regenerated via `mix gettext.extract --merge`
|
||||
- [x] #16 New LiveComponent tests cover: default picker state, picker value propagating to all three scrobble handlers, sticky-bar rendering, and medium-button enabled-with-selection
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Implementation complete 2026-04-22.
|
||||
|
||||
Changes:
|
||||
- `lib/music_library_web/components/release.ex` — header restructure (title + subtitle + picker + solid `Scrobble release` + ⋯ dropdown), `.medium/1` updated (removed selection-blocks-medium disable, label visible, print moved to ⋯ dropdown), new `.selection_bar/1` function component + private `selected_tracks_summary/2` helper, `finished_at` wired into the form via `parse_finished_at/1`, new `clear_finished_at` event handler, and all three scrobble handlers now resolve `socket.assigns.finished_at || DateTime.utc_now()` at call time.
|
||||
- `test/music_library_web/live/collection_live/show_test.exs` — updated assertion from "Connect your Last.fm account" to new "Connect Last.fm" link label.
|
||||
- `test/music_library_web/live/scrobble_live/show_test.exs` — regression test added: medium scrobble works with a cross-medium track selected.
|
||||
- `test/music_library_web/components/release_test.exs` — new file, 10 integration tests through `CollectionLive.Show` covering picker defaults, picker → handler arg propagation, reset-to-now, sticky bar render, cross-medium copy, and scrobble-selected using the picker value.
|
||||
- `priv/gettext/default.pot` + `priv/gettext/en/LC_MESSAGES/default.po` — regenerated via `mix gettext.extract --merge`.
|
||||
|
||||
Verification:
|
||||
- `mise run dev:precommit` — all green (credo, sobelow, formatting, translations, 823 tests passing).
|
||||
- Browser-verified at :4003: desktop 1440px (4-disc release showed new header, per-medium buttons, sticky bar with cross-medium count), mobile 360px (header stacks to title + picker + Release button, medium scrobble collapses to icon-only), picker open/select/reset cycle worked, overflow menus rendered Print tracklist.
|
||||
- `grep -n "MapSet.size(@selected_tracks) > 0" lib/music_library_web/components/release.ex` returns only the sticky-bar visibility guard, as planned.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
id: ML-15
|
||||
title: Sanitize Wikipedia bio_html in ArtistLive.Show
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:50'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/168'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-16 · updated 2026-04-16 · closed 2026-04-16_
|
||||
|
||||
## Summary
|
||||
|
||||
`lib/music_library_web/live/artist_live/show.ex:298` renders Wikipedia-sourced HTML via `Phoenix.HTML.raw(@biography.bio_html)` without sanitization or a `# sobelow_skip` annotation — inconsistent with the rest of the codebase.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `bio_html` is produced in `lib/music_library_web/live/artist_live/biography.ex:13` from `ArtistInfo.wikipedia_bio/1`, reading `wikipedia_data["intro_html"]`
|
||||
- Populated verbatim from Wikipedia's REST API in `lib/wikipedia.ex:14-15` — no sanitization before storage or rendering.
|
||||
- Every other `raw()` site in the codebase either routes through `Markdown.to_html/1` (MDEx + ammonia) or carries a justified `# sobelow_skip ["XSS.Raw"]` comment (7 such sites verified).
|
||||
- `biography.ex:76` already sanitizes the Last.fm bio path via `Markdown.to_html/1` — only the Wikipedia path is inconsistent.
|
||||
|
||||
## Risk
|
||||
|
||||
Low under the single-user threat model (Wikipedia's REST API returns sanitized HTML). Worth fixing for consistency so future reviewers don't have to re-derive the trust decision.
|
||||
|
||||
## Fix
|
||||
|
||||
Either:
|
||||
1. Pipe `bio_html` through `MDEx.safe_html/2` with `MDEx.Document.default_sanitize_options()` (strongest), or
|
||||
2. Add `# sobelow_skip ["XSS.Raw"]` at `show.ex:298` with a comment explaining Wikipedia is a trusted third-party HTML source (consistent with the other 7 annotation sites).
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Either HTML sanitization applied, or annotation + justification added
|
||||
- Sobelow scan stays clean at `--exit high`
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Either HTML sanitization applied, or annotation + justification added
|
||||
- [ ] #2 Sobelow scan stays clean at `--exit high`
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: ML-17
|
||||
title: Experimental comment left in Last.fm API client
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:50'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/162'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-05 · updated 2026-04-08 · closed 2026-04-08_
|
||||
|
||||
## Summary
|
||||
|
||||
`lib/last_fm/api.ex` lines 160-162 contains a comment marked as experimental regarding timeout tuning that was intended to be temporary but remains in production.
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/last_fm/api.ex` (lines 160-162)
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Either:
|
||||
1. Validate the timeout values work well and remove the "Experimental" comment
|
||||
2. Or revert to standard timeouts if the experiment didn't prove useful
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- No experimental/temporary comments remain in production code
|
||||
- Timeout configuration is intentional and documented
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 No experimental/temporary comments remain in production code
|
||||
- [ ] #2 Timeout configuration is intentional and documented
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: ML-2
|
||||
title: Verify or drop multipart hex dependency
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:44'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/182'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-16 · updated 2026-04-16 · closed 2026-04-16_
|
||||
|
||||
## Summary
|
||||
|
||||
`mix.exs` lists `{:multipart, "~> 0.6.0"}` but there are zero direct `Multipart.*` references in `lib/`. It is likely needed transitively for Swoosh Mailgun attachments, but worth confirming.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `mix.exs` — `{:multipart, "~> 0.6.0"}` declared as a direct dep
|
||||
- Grep for `Multipart.` in `lib/` returns no matches
|
||||
- Endpoint's `parsers: [:multipart]` is the Plug.Parsers atom, not this hex package
|
||||
|
||||
## Fix (decision tree)
|
||||
|
||||
1. Remove the dep from `mix.exs`
|
||||
2. Run `mix deps.get` + `mix compile`
|
||||
3. Run `mix test` and specifically the Mailer-sending paths (`RecordsOnThisDayEmail`, `ErrorTracker.ErrorNotifier.Email`)
|
||||
4. If something fails, restore the dep and add a comment in `mix.exs` noting what relies on it
|
||||
5. If everything passes, keep it removed
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Decision recorded: either dep removed, or dep retained with comment explaining transitive need
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
id: ML-22
|
||||
title: Search + count function pairs duplicate query logic
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:50'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/157'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-05 · updated 2026-04-09 · closed 2026-04-09_
|
||||
|
||||
## Summary
|
||||
|
||||
Multiple contexts have paired search/count functions that duplicate the same query construction logic — building the query once for results, then rebuilding it identically for the count.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `ListeningStats.list_tracks` (lines 173-186) and `search_tracks_count` (lines 237-250) repeat LIKE/JSON extraction logic
|
||||
- `Artists.search_by_name/2` (lines 83-104) and `search_by_name_count/1` (lines 106-122) repeat LIKE pattern and fragment logic
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library/listening_stats.ex`
|
||||
- `lib/music_library/artists.ex`
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Extract the shared query builder into a private function that both the search and count functions use.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Query logic is defined once per search domain
|
||||
- No regression in search results or counts
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Query logic is defined once per search domain
|
||||
- [ ] #2 No regression in search results or counts
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
id: ML-24
|
||||
title: RecordComponents is 755 lines with 40+ mixed functions
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:51'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/155'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-05 · updated 2026-04-12 · closed 2026-04-12_
|
||||
|
||||
## Summary
|
||||
|
||||
`RecordComponents` contains 40+ functions mixing display components (grids, lists), formatting helpers (badges, text), and structural elements (debug sheets, tooltips). At 755 lines it is difficult to navigate.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
- `record_list` (96 lines) and `record_grid` (262 lines) share ~50% boilerplate for dropdown/action menus
|
||||
- Formatting helpers like `format_badge/1` and `format_as_text/1` are interleaved with display components
|
||||
- Adding a new record display variant requires reading through the entire module
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library_web/components/record_components.ex`
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Split into focused component modules:
|
||||
- `RecordComponents.Grid` / `RecordComponents.List` — display layouts
|
||||
- `RecordComponents.Actions` — shared dropdown/action menus
|
||||
- Keep `RecordComponents` for simple helpers and badges
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Each module has a clear, focused purpose
|
||||
- Shared action menu markup lives in one place
|
||||
- No regression in rendering
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Each module has a clear, focused purpose
|
||||
- [ ] #2 Shared action menu markup lives in one place
|
||||
- [ ] #3 No regression in rendering
|
||||
<!-- AC:END -->
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
---
|
||||
id: ML-25
|
||||
title: recompact_positions issues per-row UPDATEs instead of bulk
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:51'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/154'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-05 · updated 2026-04-06 · closed 2026-04-06_
|
||||
|
||||
## Summary
|
||||
|
||||
`RecordSets.recompact_positions/1` fetches all items into memory and issues individual UPDATE queries for each item whose position changed. The same module already demonstrates a bulk UPDATE pattern in `reorder_records_in_set/2`.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
- For a record set with N items, this can issue up to N individual UPDATE queries
|
||||
- The same module's `reorder_records_in_set/2` (lines 142-157) uses a single bulk UPDATE with CASE expressions — proving the better pattern is already known
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library/record_sets.ex` (lines 239-254)
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Rewrite `recompact_positions/1` to use a single `UPDATE ... CASE` statement, consistent with `reorder_records_in_set/2`.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Position recompaction uses a single bulk UPDATE
|
||||
- Positions are correctly reassigned with no gaps
|
||||
- No regression in ordering behaviour
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Position recompaction uses a single bulk UPDATE
|
||||
- [ ] #2 Positions are correctly reassigned with no gaps
|
||||
- [ ] #3 No regression in ordering behaviour
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
id: ML-29
|
||||
title: Collection/Wishlist index LiveView duplication
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:51'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/150'
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-05 · updated 2026-04-13 · closed 2026-04-13_
|
||||
|
||||
## Summary
|
||||
|
||||
`CollectionLive.Index` (359 lines) and `WishlistLive.Index` (338 lines) share ~40% identical code including `apply_action/3`, `load_and_assign_records/2`, `order_path/2`, `back_path/2`, search event handling, and filter/pagination markup.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
- Any change to index behaviour (pagination, search, display modes, ordering) must be applied twice
|
||||
- Bug fixes in one may be missed in the other
|
||||
- #125 addressed the duplicated parse helpers, but the broader structural duplication remains
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library_web/live/collection_live/index.ex`
|
||||
- `lib/music_library_web/live/wishlist_live/index.ex`
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Extract shared index behaviour into a `LiveHelpers` module or use a shared base pattern that both LiveViews delegate to, parameterizing only the differences (base query filter, routes, section name).
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Shared logic lives in one place
|
||||
- Both index pages retain their current functionality
|
||||
- Adding a new shared feature (e.g., new sort option) requires changes in one location
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Shared logic lives in one place
|
||||
- [ ] #2 Both index pages retain their current functionality
|
||||
- [ ] #3 Adding a new shared feature (e.g., new sort option) requires changes in one location
|
||||
<!-- AC:END -->
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
---
|
||||
id: ML-3
|
||||
title: Add tests for ArtistLive.Form and RecordSetLive.RecordPicker LiveComponents
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:48'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/180'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-16 · updated 2026-04-19 · closed 2026-04-19_
|
||||
|
||||
## Summary
|
||||
|
||||
Two LiveComponents with real behaviour have zero dedicated test coverage:
|
||||
|
||||
- `MusicLibraryWeb.ArtistLive.Form` — 0 %. Edits artist images, integrates with Brave Image Search.
|
||||
- `MusicLibraryWeb.RecordSetLive.RecordPicker` — 0 %. Record search + add-to-set flow.
|
||||
|
||||
## Evidence
|
||||
|
||||
From `mix test --cover` in the 2026-04-16 audit. Neither module has a corresponding file under `test/music_library_web/live/`.
|
||||
|
||||
## Fix
|
||||
|
||||
Add `Phoenix.LiveViewTest`-based tests (needed because these are LiveComponents with `phx-target={@myself}` per project conventions). At minimum:
|
||||
|
||||
- `ArtistLive.Form`:
|
||||
- Opens and closes
|
||||
- Triggers Brave Search via `Req.Test` stub, displays results
|
||||
- Uploads an image
|
||||
- Submits with a picked image
|
||||
|
||||
- `RecordSetLive.RecordPicker`:
|
||||
- Opens, searches, displays results
|
||||
- Adds a record to the set
|
||||
- Handles empty search and no-results states
|
||||
|
||||
Fixture: use existing `MusicLibrary.RecordsFixtures` and `MusicLibrary.ArtistInfoFixtures`.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- New test files at `test/music_library_web/live/artist_live/form_test.exs` and `test/music_library_web/live/record_set_live/record_picker_test.exs`
|
||||
- Both components exercise happy paths and at least one error/empty state
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 New test files at `test/music_library_web/live/artist_live/form_test.exs` and `test/music_library_web/live/record_set_live/record_picker_test.exs`
|
||||
- [ ] #2 Both components exercise happy paths and at least one error/empty state
|
||||
<!-- AC:END -->
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
---
|
||||
id: ML-30
|
||||
title: Silent error suppression in Artists.refresh_lastfm_data/1
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:51'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/149'
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-05 · updated 2026-04-05 · closed 2026-04-05_
|
||||
|
||||
## Summary
|
||||
|
||||
`Artists.refresh_lastfm_data/1` returns `{:ok, artist_info}` when the Last.fm API call fails, silently swallowing the error and masking problems.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
- Line 271-272: `{:error, _reason} -> {:ok, artist_info}` hides all Last.fm failures
|
||||
- Called from `FetchArtistInfo` worker, which proceeds to the next step assuming success
|
||||
- Makes it impossible to detect persistent Last.fm API issues from job results
|
||||
- Inconsistent with the project convention that non-fatal enrichment failures should log a warning
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library/artists.ex` (lines 258-273)
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Follow the project's `best_effort_*` pattern: log a warning and return the unchanged struct, but make the suppression explicit and observable.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Last.fm API failures are logged as warnings
|
||||
- Callers can still proceed without the Last.fm data
|
||||
- Monitoring/logs reflect when Last.fm enrichment fails
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Last.fm API failures are logged as warnings
|
||||
- [ ] #2 Callers can still proceed without the Last.fm data
|
||||
- [ ] #3 Monitoring/logs reflect when Last.fm enrichment fails
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id: ML-31
|
||||
title: Deeply nested subqueries in ListeningStats
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:52'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/148'
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-05 · updated 2026-04-11 · closed 2026-04-11_
|
||||
|
||||
## Summary
|
||||
|
||||
`ListeningStats` builds deeply nested subqueries that join to Collection and Wishlist subqueries, creating multi-level query nesting that becomes expensive with high track volume.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
Three query builders create multi-level nested subqueries:
|
||||
- `tracks_with_record_info_query` (lines 344-363) — 3 left joins to subqueries containing their own subqueries
|
||||
- `top_albums_base_query` (lines 365-388) — same nested pattern for album aggregation
|
||||
- `top_artists_base_query` (lines 390-404) — third-order subqueries
|
||||
|
||||
These are used by `list_tracks`, `recent_activity`, and `get_top_albums_by_period` — high-traffic queries that will degrade as track count grows.
|
||||
|
||||
Additionally, `unique_collected_releases_query` and `unique_wishlisted_releases_query` (lines 406-424) duplicate identical grouping logic.
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library/listening_stats.ex`
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
- Simplify query nesting by flattening joins or using CTEs
|
||||
- Consider materialized views for the collection/wishlist release lookups
|
||||
- Deduplicate the `unique_*_releases_query` helpers
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Query plans for `list_tracks` and `recent_activity` show reduced nesting
|
||||
- No regression in query results
|
||||
- Benchmarks show comparable or better performance
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Query plans for `list_tracks` and `recent_activity` show reduced nesting
|
||||
- [ ] #2 No regression in query results
|
||||
- [ ] #3 Benchmarks show comparable or better performance
|
||||
<!-- AC:END -->
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
---
|
||||
id: ML-32
|
||||
title: OpenAI API uses unsafe bang methods and lacks rate limiting
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:52'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/147'
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-05 · updated 2026-04-05 · closed 2026-04-05_
|
||||
|
||||
## Summary
|
||||
|
||||
The OpenAI API client is the only integration that uses `Req.post!()` (bang methods) which raise on failure instead of returning error tuples. It also lacks rate limiting, breaking the pattern established by all other API integrations.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
- `Req.post!()` raises on HTTP errors instead of returning `{:error, reason}` tuples, risking unhandled crashes in production
|
||||
- Every other API client (MusicBrainz, Last.fm, Discogs, Wikipedia, Brave Search) attaches `Req.RateLimiter` — OpenAI does not
|
||||
- `OpenAI.Config` is missing the `api_cooldown` field present in all other Config modules
|
||||
- This is the only integration that breaks the three-module pattern (Facade/API/Config)
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/open_ai/api.ex` (lines 20, 79 — `Req.post!()` calls)
|
||||
- `lib/open_ai/config.ex` (missing `api_cooldown` field)
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
1. Replace `Req.post!()` with `Req.post()` and handle error tuples
|
||||
2. Add `api_cooldown` to `OpenAI.Config`
|
||||
3. Attach `Req.RateLimiter` in the request pipeline
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- OpenAI API calls return `{:ok, _}` / `{:error, _}` tuples like all other integrations
|
||||
- Rate limiter is attached with a configurable cooldown
|
||||
- `OpenAI.Config` follows the same structure as other API Config modules
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 OpenAI API calls return `{:ok, _}` / `{:error, _}` tuples like all other integrations
|
||||
- [ ] #2 Rate limiter is attached with a configurable cooldown
|
||||
- [ ] #3 `OpenAI.Config` follows the same structure as other API Config modules
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
id: ML-33
|
||||
title: Align Last.fm callback errors with app conventions
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:52'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/145'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-30 · updated 2026-03-30 · closed 2026-03-30_
|
||||
|
||||
## Summary
|
||||
|
||||
The Last.fm OAuth callback bypasses the project's standard user-facing error handling and interpolates raw failure reasons directly into the flash message.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
This is inconsistent with the documented convention for user-facing errors and increases the chance of exposing low-quality or overly-technical error text to users.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `MusicLibraryWeb.LastFmController.callback/2` uses `put_toast(:error, "Failed to connect your Last.fm account: #{reason}")`.
|
||||
- Project conventions say user-facing error reasons should go through `ErrorMessages.friendly_message/1` instead of raw inspection/interpolation.
|
||||
- The current test only checks the flash prefix, so the inconsistency is not exercised.
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library_web/controllers/last_fm_controller.ex`
|
||||
- `docs/project-conventions.md`
|
||||
- `test/music_library_web/controllers/last_fm_controller_test.exs`
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Align the controller with the established convention: keep the contextual prefix, translate the reason through `ErrorMessages.friendly_message/1`, ensure the resulting string remains wrapped appropriately for localization/user display.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- The Last.fm callback uses the same user-facing error formatting approach as the rest of the app.
|
||||
- Tests assert the controller does not expose raw backend error terms in the flash.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 The Last.fm callback uses the same user-facing error formatting approach as the rest of the app.
|
||||
- [ ] #2 Tests assert the controller does not expose raw backend error terms in the flash.
|
||||
<!-- AC:END -->
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: ML-34
|
||||
title: Make record creation resilient to color extraction failure
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:52'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/144'
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-30 · updated 2026-03-30 · closed 2026-03-30_
|
||||
|
||||
## Summary
|
||||
|
||||
`Records.create_record/1` inserts the record before post-processing and then hard-matches the color extraction result, so a color extraction failure can crash after the record is already persisted.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
This can leave partially-applied state: the record is committed, the caller sees a crash instead of a tuple result, and follow-up work can be skipped or interrupted.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `do_create_record/1` inserts the record first.
|
||||
- `create_record/1` then runs `{:ok, record} = maybe_extract_colors(record)`.
|
||||
- `maybe_extract_colors/1` delegates to `extract_colors/1`, which can return `{:error, term()}`.
|
||||
- The function only returns `{:ok, record}` or `{:error, changeset}` in the happy insert path, but can actually raise after commit.
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library/records.ex`
|
||||
- `test/music_library/records_test.exs`
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Refactor `create_record/1` so post-insert enrichment is failure-tolerant and consistent with the public contract. Reasonable options: treat color extraction as best-effort and continue without crashing; move the enrichment into an explicit background step; or wrap creation plus required follow-up in a transaction if failure must abort the whole operation.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- `create_record/1` never raises on color extraction failure.
|
||||
- The function returns a documented result tuple for both success and failure paths.
|
||||
- Tests cover a failed color extraction scenario.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 `create_record/1` never raises on color extraction failure.
|
||||
- [ ] #2 The function returns a documented result tuple for both success and failure paths.
|
||||
- [ ] #3 Tests cover a failed color extraction scenario.
|
||||
<!-- AC:END -->
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id: ML-35
|
||||
title: Harden the public asset endpoint against invalid payloads
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:52'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/143'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-30 · updated 2026-03-30 · closed 2026-03-30_
|
||||
|
||||
## Summary
|
||||
|
||||
The public asset endpoint uses bang-style decoding and hard matches during image transformation, so malformed payloads or transform failures can escape as 500s.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
This endpoint is public and cacheable. Invalid user input should degrade to a controlled 400/404 response, not an exception. A corrupted or unsupported asset can also crash request handling.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `AssetController.show/2` calls `Transform.decode!/1` on a path param.
|
||||
- `cached_get/3` hard-matches `{:ok, image_data}` from image resize/convert operations.
|
||||
- The current tests cover missing assets and valid transforms, but not malformed payloads or transform failures.
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library_web/controllers/asset_controller.ex`
|
||||
- `lib/music_library/assets/transform.ex`
|
||||
- `lib/music_library/assets/image.ex`
|
||||
- `test/music_library_web/controllers/asset_controller_test.exs`
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Handle invalid payloads and transform failures explicitly: replace bang-style decode with tuple-based validation in the controller boundary; convert failed image processing into a controlled 404/422/500 strategy; add regression tests for malformed payloads and failed conversion/resize paths.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Invalid payloads do not raise from the controller.
|
||||
- Failed image transforms do not crash the request path.
|
||||
- Tests cover malformed payload and transform failure scenarios.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Invalid payloads do not raise from the controller.
|
||||
- [ ] #2 Failed image transforms do not crash the request path.
|
||||
- [ ] #3 Tests cover malformed payload and transform failure scenarios.
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
id: ML-36
|
||||
title: Return controlled errors from genre population
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:52'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/142'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-30 · updated 2026-03-30 · closed 2026-03-30_
|
||||
|
||||
## Summary
|
||||
|
||||
`Records.populate_genres/1` is documented and typed as tuple-based error handling, but it hard-matches `OpenAI.gpt/1` success and therefore crashes on API failures.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
A transient OpenAI failure becomes an exception path instead of a controlled domain error. The worker and callers are written as if this function follows the normal `{:ok, ...} | {:error, ...}` contract.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `populate_genres/1` is spec'd as `{:ok, Record.t()} | {:error, Ecto.Changeset.t()}`.
|
||||
- The implementation does `{:ok, response} = OpenAI.gpt(completion)`.
|
||||
- `MusicLibrary.Worker.PopulateGenres` uses `with {:ok, updated_record} <- Records.populate_genres(record)` as if the function returns tagged tuples.
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library/records.ex`
|
||||
- `lib/music_library/worker/populate_genres.ex`
|
||||
|
||||
## Suggested Fix
|
||||
|
||||
Make `populate_genres/1` consistently return tagged tuples using `with {:ok, response} <- OpenAI.gpt(completion), {:ok, updated_record} <- Repo.update(...) do ... end`, mapping external API failures into a domain error shape the worker can handle intentionally.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- OpenAI/API failures do not raise from `populate_genres/1`.
|
||||
- The worker behavior is explicit for retryable vs non-retryable failures.
|
||||
- Tests cover an API failure path.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 OpenAI/API failures do not raise from `populate_genres/1`.
|
||||
- [ ] #2 The worker behavior is explicit for retryable vs non-retryable failures.
|
||||
- [ ] #3 Tests cover an API failure path.
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: ML-37
|
||||
title: Fix production migration strategy mismatch
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/141'
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-30 · updated 2026-03-30 · closed 2026-03-30_
|
||||
|
||||
## Summary
|
||||
|
||||
Production releases are configured to skip boot-time migrations even though the codebase and docs state that migrations run automatically on startup.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
A deploy can start successfully against an outdated schema, creating a real risk of runtime failures or subtle data corruption after deploys.
|
||||
|
||||
## Evidence
|
||||
|
||||
- `MusicLibrary.Application` wires `Ecto.Migrator` with `skip: skip_migrations?()`.
|
||||
- `skip_migrations?/0` returns `true` whenever `RELEASE_NAME` is set (the release case).
|
||||
- `rel/overlays/bin/server` only starts the release and does not run `rel/overlays/bin/migrate`.
|
||||
- `scripts/prod/deploy.hurl` triggers a deploy and health checks, but does not invoke the migrate script.
|
||||
- `docs/architecture.md` and `docs/production-infrastructure.md` both claim migrations run automatically on boot.
|
||||
|
||||
## Affected Files
|
||||
|
||||
- `lib/music_library/application.ex`
|
||||
- `rel/overlays/bin/server`
|
||||
- `rel/overlays/bin/migrate`
|
||||
- `docs/architecture.md`
|
||||
- `docs/production-infrastructure.md`
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- Production deploys cannot start on an unmigrated schema.
|
||||
- The documented migration strategy matches the implemented one.
|
||||
- There is at least one automated check covering the chosen behavior.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 Production deploys cannot start on an unmigrated schema.
|
||||
- [ ] #2 The documented migration strategy matches the implemented one.
|
||||
- [ ] #3 There is at least one automated check covering the chosen behavior.
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: ML-38
|
||||
title: Inconsistent Logger style in workers (eager vs lazy)
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/139'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
Workers use two different Logger styles:
|
||||
|
||||
**Lazy (preferred)** — `PruneAssetCache` (`lib/music_library/worker/prune_asset_cache.ex:12`):
|
||||
```elixir
|
||||
Logger.info(fn -> "Pruned #{prune_count} old cached assets..." end)
|
||||
```
|
||||
|
||||
**Eager** — `PruneAssets` (`lib/music_library/worker/prune_assets.ex:34`):
|
||||
```elixir
|
||||
Logger.info("Pruned #{count} unreferenced assets.")
|
||||
```
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Standardize on lazy logging with `fn -> ... end` for efficiency (avoids string interpolation when log level is filtered out).
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: ML-39
|
||||
title: Missing dark mode class in search_components.ex
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/138'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
At `lib/music_library_web/components/search_components.ex:51`, a paragraph has `text-zinc-500` without the corresponding `dark:text-zinc-400` class. Adjacent elements at lines 45 and 48 correctly pair light/dark classes.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Add `dark:text-zinc-400` to the class list at line 51, consistent with the project convention of always pairing dark mode variants.
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
id: ML-4
|
||||
title: Improve test coverage for LiveHelpers.RecordActions and Components.Chat
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:48'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/179'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-16 · updated 2026-04-17 · closed 2026-04-17_
|
||||
|
||||
## Summary
|
||||
|
||||
Two shared/user-facing surfaces have low test coverage despite contributing real behaviour:
|
||||
|
||||
- `MusicLibraryWeb.LiveHelpers.RecordActions` — 9.09 % coverage. Shared handler used by both Collection and Wishlist show pages.
|
||||
- `MusicLibraryWeb.Components.Chat` — 25.87 % coverage. The streaming AI chat sheet used on Collection/Wishlist/Artist pages.
|
||||
|
||||
## Evidence
|
||||
|
||||
From `mix test --cover` in the 2026-04-16 audit:
|
||||
|
||||
- `LiveHelpers.RecordActions` — 9.09 %
|
||||
- `Components.Chat` — 25.87 %
|
||||
- `StatsLive.TopAlbums` — 20-30 %
|
||||
- `StatsLive.TopArtists` — 30-39 %
|
||||
|
||||
Both `LiveHelpers.RecordActions` and `Components.Chat` contain branches for error paths (refresh failures, streaming errors) that are not exercised.
|
||||
|
||||
## Fix
|
||||
|
||||
- For `LiveHelpers.RecordActions`: unit-test each handler (`refresh_cover`, `populate_genres`, `generate_embeddings`, `refresh_musicbrainz_data`) both the success and error branches. These can be tested via `Phoenix.LiveViewTest` against either Collection or Wishlist Show.
|
||||
- For `Components.Chat`: test the streaming state transitions (starting, receiving chunks, error, completed) and the message submission path. Streaming can be faked via `Req.Test` or behaviour stubs.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- `LiveHelpers.RecordActions` reaches at least 70 % line coverage
|
||||
- `Components.Chat` reaches at least 60 % line coverage
|
||||
- Both happy and error paths covered
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 `LiveHelpers.RecordActions` reaches at least 70 % line coverage
|
||||
- [ ] #2 `Components.Chat` reaches at least 60 % line coverage
|
||||
- [ ] #3 Both happy and error paths covered
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
id: ML-40
|
||||
title: Two different pagination query styles across contexts
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/137'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
Contexts use two different patterns for handling offset/limit pagination:
|
||||
|
||||
**Style 1 — Keyword.get with defaults** (used by `Collection`, `RecordSets`):
|
||||
```elixir
|
||||
offset = Keyword.get(opts, :offset, 0)
|
||||
limit = Keyword.get(opts, :limit, @pagination[:default_page_size])
|
||||
```
|
||||
|
||||
**Style 2 — Case statements** (used by `ScrobbleRules`, `OnlineStoreTemplates`):
|
||||
```elixir
|
||||
case opts[:offset] do
|
||||
nil -> ...
|
||||
offset -> ...
|
||||
end
|
||||
```
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Standardize on one approach. The `Keyword.get` pattern is simpler and more common in the codebase.
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
id: ML-41
|
||||
title: 'ArtistInfo changeset uniquely casts :id field'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/136'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
`ArtistInfo.changeset/2` (`lib/music_library/artists/artist_info.ex:28`) includes `:id` in its cast list. No other schema in the codebase casts its primary key field.
|
||||
|
||||
This is likely intentional since artist IDs come from MusicBrainz, but the pattern is unique and undocumented. If intentional, a comment explaining why would help.
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: ML-42
|
||||
title: Missing @foreign_key_type on Note and ArtistInfo schemas
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/135'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
Two schemas have `@primary_key {:id, :binary_id, autogenerate: true}` but are missing the corresponding `@foreign_key_type :binary_id` declaration:
|
||||
|
||||
- `MusicLibrary.Notes.Note` (`lib/music_library/notes/note.ex:6`)
|
||||
- `MusicLibrary.Artists.ArtistInfo` (`lib/music_library/artists/artist_info.ex:9`)
|
||||
|
||||
All other binary_id schemas in the project correctly include both declarations. Neither schema currently has `belongs_to` associations that would be affected, but it breaks the established convention.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Add `@foreign_key_type :binary_id` after the `@primary_key` declaration in both schemas.
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: ML-43
|
||||
title: Wikipedia.Config missing @enforce_keys
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/134'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
All API Config modules use `@enforce_keys` to enforce required struct fields at compile time, but `Wikipedia.Config` (`lib/wikipedia/config.ex:7`) does not declare `@enforce_keys`.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Add `@enforce_keys [:user_agent]` (or whichever fields are required) to `Wikipedia.Config`, consistent with all other API configs.
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: ML-44
|
||||
title: Wikipedia and BraveSearch APIs missing rate limiting
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/133'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
MusicBrainz, LastFm, and Discogs API modules all attach `Req.RateLimiter` to their HTTP clients, but Wikipedia (`lib/wikipedia/api.ex`) and BraveSearch (`lib/brave_search/api.ex`) do not.
|
||||
|
||||
This is inconsistent with the pattern used by the other three APIs. While the architecture docs list these as having no rate limit, adding rate limiting would be good API citizenship and consistent with the codebase pattern.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Attach `Req.RateLimiter` with appropriate cooldown values to both Wikipedia.API and BraveSearch.API request builders.
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: ML-45
|
||||
title: Inconsistent fetch_* vs refresh_* naming in Artists context
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/132'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
The `Artists` context mixes `fetch_*` and `refresh_*` prefixes for the same semantic operation (retrieve external data and upsert locally), with no clear distinction:
|
||||
|
||||
- `fetch_artist_info/1` (line 139)
|
||||
- `refresh_musicbrainz_data/1` (line 159)
|
||||
- `refresh_discogs_data/1` (line 174)
|
||||
- `fetch_wikipedia_data/1` (line 199)
|
||||
- `refresh_wikipedia_data/1` (line 218) — literally an alias for `fetch_wikipedia_data/1`
|
||||
- `fetch_image/1` (line 249)
|
||||
- `fetch_lastfm_data/1` (line 264)
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Standardize on one naming convention. Since `Records` context uses only `refresh_*`, aligning Artists to `refresh_*` would be the consistent choice.
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: ML-46
|
||||
title: FetchArtistInfo worker contains business logic
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/131'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
`FetchArtistInfo` worker (`lib/music_library/worker/fetch_artist_info.ex:15-21`) contains a private `regenerate_record_embeddings/1` function that orchestrates cross-context work (fetching artist records, then generating embeddings for each). Per project conventions, workers should be thin wrappers that delegate to context modules.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Extract `regenerate_record_embeddings/1` into a context function (e.g. `Records.regenerate_artist_embeddings/1` or `Artists.regenerate_record_embeddings/1`).
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: ML-47
|
||||
title: PruneAssets worker has direct Repo queries
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/130'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-25 · updated 2026-03-25 · closed 2026-03-25_
|
||||
|
||||
## Description
|
||||
|
||||
`PruneAssets` worker (`lib/music_library/worker/prune_assets.ex:16-35`) builds Ecto queries and calls `Repo.delete_all` directly, violating the convention that workers are thin wrappers delegating to context modules. All other workers delegate to context functions. This logic should live in `MusicLibrary.Assets`.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Extract the query and deletion into an `Assets.prune_unreferenced/0` context function, and have the worker call it.
|
||||
|
||||
## Found during
|
||||
|
||||
Codebase consistency audit (2026-03-25)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: ML-48
|
||||
title: ScrobbleActivity context has zero test coverage
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/127'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-22 · updated 2026-03-23 · closed 2026-03-23_
|
||||
|
||||
## Description
|
||||
|
||||
`MusicLibrary.ScrobbleActivity` has complex scrobbling logic with multiple branches for release/medium/tracks but no corresponding test file. Prior issues #83 and #106 addressed code quality problems in this module but did not add test coverage.
|
||||
|
||||
## File
|
||||
|
||||
- `lib/music_library/scrobble_activity.ex`
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: ML-49
|
||||
title: BarcodeScan context has zero test coverage
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/126'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-22 · updated 2026-03-22 · closed 2026-03-22_
|
||||
|
||||
## Description
|
||||
|
||||
`MusicLibrary.BarcodeScan` is a workflow module with multiple status paths and error branches but has no corresponding test file.
|
||||
|
||||
## File
|
||||
|
||||
- `lib/music_library/barcode_scan.ex`
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
id: ML-50
|
||||
title: Duplicated parse helpers in Collection/Wishlist Index
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/125'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-22 · updated 2026-03-22 · closed 2026-03-22_
|
||||
|
||||
## Description
|
||||
|
||||
`parse_order/1` and `parse_mode/2` are identical private helper functions duplicated across both index LiveViews.
|
||||
|
||||
## Files
|
||||
|
||||
- `lib/music_library_web/live/collection_live/index.ex`
|
||||
- `lib/music_library_web/live/wishlist_live/index.ex`
|
||||
|
||||
## Suggested approach
|
||||
|
||||
Extract into a shared helper module (e.g. `MusicLibraryWeb.LiveHelpers.Params` which already exists for pagination).
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
id: ML-51
|
||||
title: ArtistLive.Show is 823 lines
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/124'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-22 · updated 2026-03-22 · closed 2026-03-22_
|
||||
|
||||
## Description
|
||||
|
||||
`ArtistLive.Show` is the largest LiveView in the codebase at 823 lines. It handles artist metadata, discography, image search/upload, notes, chat, similar artists, biography building, and multiple refresh operations.
|
||||
|
||||
Business logic like `build_biography/1` (51 lines of biography processing with link removal and content rendering) could be extracted into a helper module to improve testability.
|
||||
|
||||
## File
|
||||
|
||||
- `lib/music_library_web/live/artist_live/show.ex`
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
id: ML-52
|
||||
title: Collection/Wishlist Show LiveViews duplication
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/123'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-22 · updated 2026-03-22 · closed 2026-03-22_
|
||||
|
||||
## Description
|
||||
|
||||
`CollectionLive.Show` (611 lines) and `WishlistLive.Show` (462 lines) have near-identical `handle_event` implementations for shared operations: `refresh_musicbrainz_data`, `refresh_cover`, `populate_genres`. The error handling, success messaging, and state management patterns are functionally identical, meeting the project's 3+ duplication extraction threshold.
|
||||
|
||||
## Files
|
||||
|
||||
- `lib/music_library_web/live/collection_live/show.ex`
|
||||
- `lib/music_library_web/live/wishlist_live/show.ex`
|
||||
|
||||
## Suggested approach
|
||||
|
||||
Extract shared record action handlers into a helper module that both LiveViews can delegate to.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
id: ML-53
|
||||
title: Rewrite vertical bar chart component
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/122'
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-21 · updated 2026-03-21 · closed 2026-03-21_
|
||||
|
||||
The current vertical chart component uses SVG, which means it's not natively responsive, and it has to rely on approximated calculations which make it difficult to render nicely at every viewport.
|
||||
|
||||
It needs to be rewritten with standard html elements laid out in a responsive grid, so that it renders appropriately for every viewport.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: ML-54
|
||||
title: Standardize play icon sizing
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/121'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-16 · updated 2026-03-16 · closed 2026-03-16_
|
||||
|
||||
## Problem
|
||||
|
||||
The play/scrobble icon (`hero-play`) uses three different sizing approaches:
|
||||
|
||||
- `h-4 w-4` in `scrobble_live/show.ex:90`, `release.ex`
|
||||
- `h-5 w-5` in `collection_live/show.ex:57` (button group)
|
||||
- `class="icon"` (default) in `scrobble_rules_live/index.ex:38`
|
||||
|
||||
## Suggestion
|
||||
|
||||
The button group context (`h-5 w-5`) vs dropdown/inline context (`h-4 w-4`) might be intentional. If so, document the sizing convention. The `class="icon"` usage should align with one of these.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
id: ML-55
|
||||
title: Standardize stats dashboard gap sizes
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/120'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-16 · updated 2026-03-16 · closed 2026-03-16_
|
||||
|
||||
## Problem
|
||||
|
||||
Within the stats dashboard (`stats_live/index.ex`), multiple gap sizes are used for similar grid sections:
|
||||
|
||||
- Line 23: `gap-5` (counter cards)
|
||||
- Line 44: `gap-x-5` (formats/types)
|
||||
- Line 69: `gap-5` (top artists/albums)
|
||||
- Line 264: `gap-4` (scrobble activity)
|
||||
|
||||
## Suggestion
|
||||
|
||||
Use a single gap value (e.g. `gap-5`) across all stats grid sections for visual consistency.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
id: ML-56
|
||||
title: Standardize card border-radius and shadow
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/119'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-16 · updated 2026-03-16 · closed 2026-03-16_
|
||||
|
||||
## Problem
|
||||
|
||||
Card/panel components use inconsistent rounding and shadows:
|
||||
|
||||
- Record set cards (`record_set_live/index.ex:274`): `rounded-lg`, no shadow
|
||||
- Stats top-by-period (`stats_live/top_by_period.ex:75`): `rounded-md` with `shadow-sm`
|
||||
|
||||
## Suggestion
|
||||
|
||||
Pick one card style (e.g. `rounded-lg` without shadow, or `rounded-md` with `shadow-sm`) and apply consistently.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: ML-57
|
||||
title: Standardize grid gap values across record grids
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/118'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-16 · updated 2026-03-16 · closed 2026-03-16_
|
||||
|
||||
## Problem
|
||||
|
||||
Different record grid contexts use different vertical gap values:
|
||||
|
||||
- Main record grid (`record_components.ex:251`): `gap-y-8`
|
||||
- Similar records grid (`record_components.ex:486`): `gap-y-6`
|
||||
- Record set thumbnails (`record_set_live/index.ex:330`): `gap-3`
|
||||
|
||||
## Decision needed
|
||||
|
||||
Should all record grids share a consistent `gap-y` value, or are these intentional density differences for different contexts?
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: ML-58
|
||||
title: Standardize page header top margin
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/117'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-16 · updated 2026-03-16 · closed 2026-03-16_
|
||||
|
||||
## Problem
|
||||
|
||||
Page-level section headers use inconsistent top margins:
|
||||
|
||||
- Stats sections: `mt-5` (`stats_live/index.ex:20`)
|
||||
- Maintenance: `mt-2` (`maintenance_live/index.ex:21`)
|
||||
- TopByPeriod: no margin (`stats_live/top_by_period.ex:23`)
|
||||
|
||||
## Suggestion
|
||||
|
||||
Pick a standard margin (e.g. `mt-5`) for all page-level h1 section headers.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
id: ML-59
|
||||
title: Standardize list separation pattern (divide-y vs space-y)
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/116'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-16 · updated 2026-03-16 · closed 2026-03-16_
|
||||
|
||||
## Problem
|
||||
|
||||
Lists use two different separation approaches:
|
||||
|
||||
- **Most lists** use `divide-y divide-zinc-100 dark:divide-zinc-300/20` (visual divider lines)
|
||||
- **Scrobble rules** (`scrobble_rules_live/index.ex:71`) uses `space-y-4` (gap-based spacing, no dividers)
|
||||
|
||||
## Decision needed
|
||||
|
||||
Should all lists use `divide-y` for consistency, or is `space-y` acceptable for card-style list items where dividers would feel heavy?
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id: ML-6
|
||||
title: Align mix.exs test coverage threshold with sandbox-achievable level
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:48'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/177'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-16 · updated 2026-04-17 · closed 2026-04-17_
|
||||
|
||||
## Summary
|
||||
|
||||
`mix.exs` sets a coverage threshold of 90 % but actual coverage is 75.77 %. Most of the 14.2-point gap is modules that legitimately cannot execute in the Ecto sandbox: cron-only workers (`RepoVacuum`, `RepoOptimize`, `SendRecordsOnThisDayEmail`, `RefreshScrobbles`) and Mix tasks. Per project conventions, tests for untestable sandbox operations are not written.
|
||||
|
||||
## Evidence
|
||||
|
||||
Current coverage output: 75.77 % (720 tests, `mix test --cover`).
|
||||
|
||||
0 % modules that cannot be covered under the sandbox convention:
|
||||
- `MusicLibrary.Worker.RepoVacuum`
|
||||
- `MusicLibrary.Worker.RepoOptimize`
|
||||
- `MusicLibrary.Worker.SendRecordsOnThisDayEmail`
|
||||
- `MusicLibrary.Worker.RefreshScrobbles`
|
||||
- `MusicLibrary.Worker.BackfillScrobbledTracks`
|
||||
- Several `Mix.Tasks.*`
|
||||
|
||||
## Why It Matters
|
||||
|
||||
A red coverage check on `mix test --cover` that is structurally unachievable trains contributors to ignore the signal. Either the threshold should reflect reality, or the untestable modules should be decorated for exclusion.
|
||||
|
||||
## Fix (options)
|
||||
|
||||
1. Lower the threshold to a realistic value (e.g. 85 %) that fails when new gaps appear but passes today.
|
||||
2. Decorate the unreachable modules with `@moduledoc tags: [:skip_coverage]` (or similar) and exclude them via `test_coverage: [ignore_modules: [...]]` in `mix.exs`.
|
||||
|
||||
Option 2 is preferable because it keeps the threshold aspirational and surfaces the exclusion list.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- `mix test --cover` passes under its configured threshold
|
||||
- Untestable modules are explicitly enumerated somewhere (config, moduledoc, or both)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 `mix test --cover` passes under its configured threshold
|
||||
- [ ] #2 Untestable modules are explicitly enumerated somewhere (config, moduledoc, or both)
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
id: ML-60
|
||||
title: Unify genre tag styling between edit and display
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/115'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-16 · updated 2026-03-16 · closed 2026-03-16_
|
||||
|
||||
## Problem
|
||||
|
||||
Genre tags render differently in edit vs read contexts:
|
||||
|
||||
- **Read view** (`record_components.ex:650-652`): Uses `.badge variant="soft"` (Fluxon component)
|
||||
- **Edit form** (`record_form.ex:75-93`): Uses custom inline spans with `bg-zinc-100 dark:bg-zinc-700 px-2 py-1 text-sm` and a remove button
|
||||
|
||||
## Decision needed
|
||||
|
||||
Should the edit form use `.badge variant="soft"` with an appended remove button to match the read view? Or is the visual difference acceptable since the edit form needs the remove interaction?
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: ML-61
|
||||
title: Standardize dropdown toggle variant across pages
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/114'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-16 · updated 2026-03-16 · closed 2026-03-16_
|
||||
|
||||
## Problem
|
||||
|
||||
Show pages use `variant="soft"` for dropdown toggle buttons while index pages use `variant="ghost"`. This may be intentional (different contexts) or accidental drift.
|
||||
|
||||
**Show pages (`variant="soft"`):** `collection_live/show.ex:90`, `wishlist_live/show.ex:57`, `artist_live/show.ex:138`, `record_set_live/show.ex:28`
|
||||
|
||||
**Index pages (`variant="ghost"`):** `scrobble_rules_live/index.ex:113`, `online_store_template_live/index.ex:65`, `scrobbled_tracks_live/index.ex:160`
|
||||
|
||||
## Decision needed
|
||||
|
||||
Pick one variant for all action dropdown toggles, or document the show/index distinction as intentional.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: ML-62
|
||||
title: Route Search artist queries through Artists context
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/113'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-12 · updated 2026-03-12 · closed 2026-03-12_
|
||||
|
||||
## Problem
|
||||
|
||||
`MusicLibrary.Search.search_artists/2` and `search_artists_count/1` build their own Ecto queries directly joining `Records.ArtistRecord` to `Artists.ArtistInfo`, bypassing the `Artists` context entirely. This violates the project convention that context modules own all queries.
|
||||
|
||||
## Proposed solution
|
||||
|
||||
Add `search_by_name/2` and `search_by_name_count/1` to `MusicLibrary.Artists`, then have `Search.search_artists/2` and `Search.search_artists_count/1` delegate to them. Remove `ArtistRecord` and `ArtistInfo` aliases from `Search`.
|
||||
|
||||
## Files involved
|
||||
|
||||
- `lib/music_library/artists.ex` (add functions)
|
||||
- `lib/music_library/search.ex` (delegate, remove aliases)
|
||||
- Tests for `Artists` and `Search`
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: ML-63
|
||||
title: Break bidirectional dependency between Records and Artists
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/112'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-12 · updated 2026-03-12 · closed 2026-03-12_
|
||||
|
||||
## Problem
|
||||
|
||||
`Records` and `Artists` have a bidirectional dependency: Records → Artists (async Oban calls on create/delete) and Artists → Records (`get_collected_artist_ids/0` joins `Records.Record` directly). The direct schema query from Artists → Records is a hard compile-time dependency on another context's schema.
|
||||
|
||||
## Proposed solution
|
||||
|
||||
Move `get_collected_artist_ids/0` into `Collection` (which already owns the `purchased_at IS NOT NULL` scoping concern). Then `Artists.get_similar_artists/1` calls `Collection.collected_artist_ids()` instead. Accept `ArtistRecord` as a shared read-only schema used by both domains.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Add `collected_artist_ids/0` to `MusicLibrary.Collection`
|
||||
2. Update `Artists.get_similar_artists/1` to call `Collection.collected_artist_ids()`
|
||||
3. Remove `Records.Record` alias from `Artists`
|
||||
4. Remove the private `get_collected_artist_ids/0` from `Artists`
|
||||
5. Update tests
|
||||
|
||||
## Files involved
|
||||
|
||||
- `lib/music_library/artists.ex`
|
||||
- `lib/music_library/collection.ex`
|
||||
- Tests for `Artists` and `Collection`
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: ML-64
|
||||
title: Split ScrobbleActivity into focused modules
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:54'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/111'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-12 · updated 2026-03-12 · closed 2026-03-12_
|
||||
|
||||
## Problem
|
||||
|
||||
`MusicLibrary.ScrobbleActivity` bundles three distinct responsibilities: (1) scrobbling to Last.fm, (2) track CRUD and listing, (3) data quality diagnostics. The track listing query is nearly identical to `ListeningStats.recent_activity/2`.
|
||||
|
||||
## Proposed solution (Option A)
|
||||
|
||||
1. Move track CRUD + listing into `ListeningStats`
|
||||
2. Keep `ScrobbleActivity` focused on writes (scrobbling releases/mediums/tracks)
|
||||
3. Move diagnostics to `Maintenance`
|
||||
|
||||
## Files involved
|
||||
|
||||
- `lib/music_library/scrobble_activity.ex`
|
||||
- `lib/music_library/listening_stats.ex`
|
||||
- `lib/music_library/maintenance.ex`
|
||||
- `lib/music_library_web/live/scrobbled_tracks_live/index.ex`
|
||||
- `lib/music_library_web/live/maintenance_live/index.ex`
|
||||
- Tests for all of the above
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
id: ML-65
|
||||
title: Move scrobble queries out of Records context
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:55'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/110'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-12 · updated 2026-03-12 · closed 2026-03-12_
|
||||
|
||||
## Problem
|
||||
|
||||
`MusicLibrary.Records` directly queries `LastFm.Track` in three places: `get_last_listened_track/1`, `play_count/1`, `scrobbles_for_record_query/1`. This is a cross-domain leak — scrobble data belongs to the listening/stats domain.
|
||||
|
||||
## Proposed solution
|
||||
|
||||
Move `get_last_listened_track/1` and `play_count/1` into `MusicLibrary.ListeningStats`. Update LiveView callers (`CollectionLive.Show`, `WishlistLive.Show`) to call `ListeningStats` instead of `Records`. Remove `LastFm.Track` alias from `Records`.
|
||||
|
||||
## Files involved
|
||||
|
||||
- `lib/music_library/records.ex`
|
||||
- `lib/music_library/listening_stats.ex`
|
||||
- `lib/music_library_web/live/collection_live/show.ex`
|
||||
- `lib/music_library_web/live/wishlist_live/show.ex`
|
||||
- Tests for the above
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: ML-67
|
||||
title: Credo ModuleDoc check still disabled
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:55'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/108'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-12 · updated 2026-03-13 · closed 2026-03-13_
|
||||
|
||||
## Description
|
||||
|
||||
`.credo.exs:10` disables `Credo.Check.Readability.ModuleDoc`. The `Nesting` check was re-enabled per #84, but `ModuleDoc` remains disabled and `CyclomaticComplexity` was removed entirely (not listed).
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Consider re-enabling `ModuleDoc` (possibly with exclusions for specific module patterns like LiveComponents) and adding `CyclomaticComplexity` back with an appropriate threshold.
|
||||
|
||||
## Source
|
||||
|
||||
From technical debt audit (2026-03-12). Residual from #84.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id: ML-7
|
||||
title: Tighten vague test assertions in scrobble_activity and worker tests
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:49'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/176'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-04-16 · updated 2026-04-20 · closed 2026-04-20_
|
||||
|
||||
## Summary
|
||||
|
||||
Project convention: "Assert specific values, not just shape. Wildcard matches (`_`) in assertions are a signal the test is too vague." Several test files violate this.
|
||||
|
||||
## Evidence
|
||||
|
||||
### Bare `assert {:ok, _} = ...` (9 instances in one file)
|
||||
|
||||
`test/music_library/scrobble_activity_test.exs`: lines `43, 48, 96, 101, 154, 161, 223, 251, 272`
|
||||
|
||||
The context's entire job is to shape Last.fm payloads correctly, yet the tests don't inspect the returned struct's fields.
|
||||
|
||||
### `assert X != nil` (9 sites, 4 files)
|
||||
|
||||
- `test/music_library/worker/prune_assets_test.exs:14, 26, 38`
|
||||
- `test/music_library/worker/fetch_artist_info_test.exs:104, 105`
|
||||
- `test/music_library/barcode_scan_test.exs:23, 63, 100`
|
||||
- `test/music_library/worker/fetch_artist_image_test.exs:28`
|
||||
|
||||
### Other `{:ok, _}` (selected)
|
||||
|
||||
- `test/music_library/assets_test.exs:29, 30`
|
||||
- `test/music_library/records/similarity_test.exs:218, 229`
|
||||
- `test/music_library/chats_test.exs:162, 203`
|
||||
|
||||
## Fix
|
||||
|
||||
For each site, replace with an assertion that pins a specific value the function is responsible for producing.
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- `scrobble_activity_test.exs` tests assert specific struct fields on each `{:ok, _}` site
|
||||
- No `assert X != nil` in worker test files (replace with value assertion)
|
||||
- Suite still passes
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
- [ ] #1 `scrobble_activity_test.exs` tests assert specific struct fields on each `{:ok, _}` site
|
||||
- [ ] #2 No `assert X != nil` in worker test files (replace with value assertion)
|
||||
- [ ] #3 Suite still passes
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: ML-71
|
||||
title: page_title/2 public in two LiveViews
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:55'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/104'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-12 · updated 2026-03-12 · closed 2026-03-12_
|
||||
|
||||
## Description
|
||||
|
||||
Per project conventions, `page_title/2` should be a pattern-matched private function. Two LiveViews expose it as `def` (public):
|
||||
|
||||
- `lib/music_library_web/live/collection_live/show.ex:477`
|
||||
- `lib/music_library_web/live/wishlist_live/show.ex:421`
|
||||
|
||||
While `artist_live/show.ex:705` and `record_set_live/show.ex:259` correctly use `defp`. No external callers exist for the public versions.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Change `def page_title` to `defp page_title` in both files.
|
||||
|
||||
## Source
|
||||
|
||||
From technical debt audit (2026-03-12).
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: ML-72
|
||||
title: TopAlbums/TopArtists structural duplication
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:55'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/103'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-12 · updated 2026-03-12 · closed 2026-03-12_
|
||||
|
||||
## Description
|
||||
|
||||
`lib/music_library_web/live/stats_live/top_albums.ex` (201 LOC) and `top_artists.ex` (168 LOC) share near-identical structure: same `mount/1`, `update/2`, `handle_event/3` callbacks; same time-period tab UI; same `assign_async` pattern with `reset: true`. Only the data source and item rendering differ.
|
||||
|
||||
Additionally, `top_artists_by_period/1` is `def` (public) while `top_albums_by_period/1` is `defp` (private) — a visibility inconsistency.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Consider extracting a parameterized `TopByPeriod` component that accepts data-fetching and rendering callbacks. Fix the `def`/`defp` inconsistency.
|
||||
|
||||
## Source
|
||||
|
||||
From technical debt audit (2026-03-12).
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: ML-73
|
||||
title: Sanitize markdown HTML output in search and record set components
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:56'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/102'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-07 · updated 2026-03-07 · closed 2026-03-07_
|
||||
|
||||
The following locations render markdown descriptions via `Markdown.to_html()` + `raw()` without HTML sanitization:
|
||||
|
||||
- `lib/music_library_web/components/search_components.ex:353`
|
||||
- `lib/music_library_web/live/record_set_live/index.ex:472`
|
||||
- `lib/music_library_web/live/record_set_live/show.ex:308`
|
||||
|
||||
Same pattern as the Notes component. Should sanitize for defense in depth.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
id: ML-74
|
||||
title: Sanitize markdown HTML output in Notes component
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:56'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/101'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-07 · updated 2026-03-07 · closed 2026-03-07_
|
||||
|
||||
`lib/music_library_web/components/notes.ex:162` renders user-authored markdown via `Earmark.as_html!/2` + `raw()` without HTML sanitization.
|
||||
|
||||
Currently single-user so self-XSS only, but should sanitize for defense in depth.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
id: ML-75
|
||||
title: Add Content-Security-Policy header to browser pipeline
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:56'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/100'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-07 · updated 2026-03-07 · closed 2026-03-07_
|
||||
|
||||
Sobelow reports missing CSP on the browser pipeline (`router.ex:14`). Add a basic CSP via `put_secure_browser_headers/2`.
|
||||
|
||||
Currently the app does not set a `Content-Security-Policy` header, which is a defense-in-depth measure against XSS and other injection attacks.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: ML-77
|
||||
title: Inconsistent alphabetical ordering helpers
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:57'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/98'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-05 · updated 2026-03-06 · closed 2026-03-06_
|
||||
|
||||
## Priority: Low
|
||||
|
||||
## Description
|
||||
|
||||
`Records` defines an `order_alphabetically()` macro (used by `Collection`), but `OnlineStoreTemplates` and `RecordSets` use raw `fragment("? COLLATE NOCASE ASC", ...)` instead.
|
||||
|
||||
### Locations
|
||||
|
||||
- `lib/music_library/records.ex:39` — defines macro
|
||||
- `lib/music_library/collection.ex:52,62,73` — uses macro
|
||||
- `lib/music_library/online_store_templates.ex:14,20` — raw fragment
|
||||
- `lib/music_library/record_sets.ex:45` — raw fragment
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Parameterize the `order_alphabetically` macro to accept a field and share it across all contexts.
|
||||
|
||||
## Source
|
||||
|
||||
From technical debt audit (2026-03-05).
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: ML-78
|
||||
title: Commented-out code in error_json.ex
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:57'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/97'
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-05 · updated 2026-03-06 · closed 2026-03-06_
|
||||
|
||||
## Priority: Low
|
||||
|
||||
## Description
|
||||
|
||||
`lib/music_library_web/controllers/error_json.ex:11-13` contains a commented-out function clause for 500 error rendering. Per project conventions, dead code should be removed.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Either remove the commented-out code or restore it if needed.
|
||||
|
||||
## Source
|
||||
|
||||
From technical debt audit (2026-03-05).
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: ML-79
|
||||
title: Chat implementations have trivial test coverage
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-04-20 08:57'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'https://github.com/cloud8421/music_library/issues/96'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
_GitHub: created 2026-03-05 · updated 2026-03-05 · closed 2026-03-05_
|
||||
|
||||
## Priority: Medium
|
||||
|
||||
## Description
|
||||
|
||||
`ArtistChat` and `RecordChat` test files only verify `function_exported?/3`:
|
||||
|
||||
- `test/music_library/artist_chat_test.exs` (10 LOC) — only checks callback export
|
||||
- `test/music_library/record_chat_test.exs` (10 LOC) — only checks callback export
|
||||
|
||||
The actual business logic in `build_instructions/2`, `build_context/3`, and instruction building is untested.
|
||||
|
||||
## Expected behavior
|
||||
|
||||
Add tests for instruction building and context assembly logic.
|
||||
|
||||
## Source
|
||||
|
||||
From technical debt audit (2026-03-05).
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user