- Replace .separator with .dropdown_separator in 4 dropdown menus
- Change gray-* to zinc-* color classes in online store templates
- Wrap hardcoded page titles in gettext() in online store templates
- Add consistent dark:text-zinc-200 to maintenance h3 subsections
- Add text-zinc-900 dark:text-zinc-200 to scrobble section headers
- Fix heading hierarchy (h1/h2 → h2/h3) in record set items
- Align artist form modal header to text-lg font-semibold
- Use size="xs" prop instead of class override on format badge
- Add external link icon to wishlist online store buttons
Extends the same optimization from the previous commit to
all remaining show page handlers across collection, wishlist,
and artist LiveViews. Also simplifies bare JS.push() calls
to plain phx-click strings.
The Index modules were calling Show.page_title/2 for modal edit
routes. Inlined the logic as private functions in each Index
module and removed the now-unused Show aliases.
Closes#104
Have just a bin icon, and rely on drag and drop.
This solves a massive performance issue with the page, which is
traceable to the Fluxon dropdowns, particularly the Class.merge logic
used to merge Tailwind classes
⏺ Here's a breakdown of each directive:
┌─────────────────┬────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Directive │ Value │ Purpose │
├─────────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ default-src │ 'self' │ Fallback for any resource type not explicitly listed — only allow from the app's own origin │
├─────────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ script-src │ 'self' │ JavaScript can only be loaded from the app's origin (no external scripts, no inline) │
├─────────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ style-src │ 'self' 'unsafe-inline' https://rsms.me │ Stylesheets from the app's origin, inline styles (needed by LiveView), and the https://rsms.me/inter/ hosted on rsms.me │
├─────────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ font-src │ 'self' https://rsms.me │ Font files from the app's origin and rsms.me (Inter font) │
├─────────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ img-src │ 'self' data: https://lastfm.freetls.fastly.net │ Images from the app's origin, data: URIs (inline base64 images), and Last.fm's CDN for album/artist artwork │
├─────────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ connect-src │ 'self' │ XHR/fetch/WebSocket connections only to the app's origin (covers LiveView's WebSocket) │
├─────────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ frame-ancestors │ 'self' │ The app can only be embedded in iframes by itself (clickjacking protection) │
├─────────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ base-uri │ 'self' │ Restricts <base> tag to the app's origin (prevents base tag injection attacks) │
└─────────────────┴────────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Notable things not allowed by this policy:
- No inline <script> tags or eval()
- No external JavaScript from CDNs
- No embedding in third-party iframes
- No connections to external APIs from the browser (all external API calls go through the server)