Skip to content

feat: Spotter design system + full frontend redesign - #360

Merged
joestump merged 4 commits into
mainfrom
feat/spotter-design-system
Jul 18, 2026
Merged

feat: Spotter design system + full frontend redesign#360
joestump merged 4 commits into
mainfrom
feat/spotter-design-system

Conversation

@joestump

Copy link
Copy Markdown
Owner

Spotter Design System + full frontend redesign

Introduces the Spotter design system and restyles the entire frontend onto it.

Identity — evokes a modern music app, legally its own thing

  • Spotter Green #1FDF6E (primary) — deliberately not Spotify's #1DB954/#1ED760
  • Vibe Violet #8B5CF6 (secondary) — reserved for AI / Vibes features; breaks the green-only monochrome
  • Near-black surfaces, Figtree (open SIL OFL, geometric), pill controls, art shelves
  • Also removed the docs site's hardcoded Spotify green + "Spotify-Inspired" naming

What changed

  • Custom spotter DaisyUI theme (tailwind.config.js) — now the default (internal/config), wired into Base()/switcher. Cascades through every view via DaisyUI semantic classes.
  • Global polish in static/css/input.css (Figtree, green CTA glow, card hover-lift, green active-nav, violet Vibes utilities).
  • All 40 internal/views/**/*.templ redesigned into the design language — hero headers, art shelves w/ hover lift, now-playing rows, stat blocks, violet Vibes panels, pill CTAs.
  • Docs (docs-site) re-themed to match.
  • ds-bundle/ — the design system published to a claude.ai/design project.

Guardrails (markup-only redesign)

Every htmx/SSE/hyperscript/Alpine attribute, form field name/id, type=, templ.URL/templ.KV, and @components.* call was preserved verbatim — verified via net-zero functional-attribute diff analysis. No templ signatures or Go logic changed.

Verification

  • templ generate + go build ./... + npm run build:css clean
  • go test ./... green (updated one login copy assertion)
  • ✅ Live-rendered the running app (login screen) to confirm

Known low-severity notes (non-blocking)

  • Violet (secondary) is used broadly; each usage should sit on a genuinely AI/Vibes surface (spot-check recommended).
  • The artist-page bio card was intentionally left un-restyled to avoid desyncing its toggleBio() JS — a minor cosmetic lag.

🤖 Generated with Claude Code

🤖 Posted on behalf of @joestump by Claude.

joestump and others added 2 commits July 18, 2026 10:28
Ownable Spotify-evoking identity (green #1FDF6E + Vibe Violet #8B5CF6,
deliberately not Spotify's #1DB954/#1ED760). Adds the custom `spotter`
DaisyUI theme (default), Figtree type + polish utilities, re-themes the
Docusaurus docs off the hardcoded Spotify green, and publishes a
class-based design-system bundle (ds-bundle/) synced to claude.ai/design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restyle all 40 templ views into the Spotter design language (green leads,
Vibe Violet for AI/Vibes, near-black surfaces, pill CTAs, art shelves,
hero headers, now-playing rows, Figtree). Markup-only: all htmx/SSE/
hyperscript/Alpine wiring, form fields, templ control flow, and component
signatures preserved. Updates one login copy assertion to match new text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joestump

Copy link
Copy Markdown
Owner Author

Security review — clean ✅

Adversarial security pass over the full main...HEAD diff for #360, weighting XSS / attribute-injection / URL-injection / CSRF for this templ markup redesign. No security regressions introduced. Not blocking.

What I checked

  • templ.Raw / unescaped output — none introduced. Zero templ.Raw, @templ.Raw, Unsafe, or innerHTML in the diff. All dynamic text/attribute interpolations use templ's auto-escaping { x } form ({ navidromeURL }, { errorMsg }, { props.Name }, title={ rec.Reason }, alt={ pl.Name }).
  • URL / href injection — every dynamic href remains wrapped in templ.URL(...). The 18 removed href={ templ.URL(...) } lines each map to an added href={ templ.URL(...) } line (class-only changes); no downgrade to raw string interpolation. No hx-* attributes take interpolated/user-controlled values.
  • style= interpolation — the one dynamic style, style={ \"color: \" + props.IconColorStyle + \";\" } in provider_tile.templ, is pre-existing (only wrapped in a new container span) and runs through templ's CSS-context sanitizer.
  • img src<img src={ pl.ImageURL }> in playlists/index.templ is pre-existing; only the class changed.
  • Inline scripts / on* handlers — no <script> added. The two onchange=\"this.form.submit()\" handlers in preferences/events.templ are static, pre-existing, and had only class changes.
  • Forms / CSRF — no form action or method changed. The login form is still action=\"/login\" method=\"POST\"; username/password inputs keep autocomplete + required. The preferences form change is class-only. No CSRF token markup was removed (the app carries none in templates today — unchanged by this PR).
  • Secrets / sensitive data — nothing new rendered into markup. navidromeURL on the login page is still shown (auto-escaped, text context) exactly as before — pre-existing behavior, not a regression.
  • Non-templ changesconfig.go / config_test.go / auth_test.go are theme-string and copy-string edits only. base.templ swaps the default DaisyUI theme name (darkspotter). ds-bundle/, docs-site/, static/css, tailwind.config.js are static design assets with no injection surface.

Verdict

No high/critical (or lower) security findings. The redesign preserved every existing escaping and templ.URL boundary.

🤖 Posted on behalf of @joestump by Claude.

@joestump

Copy link
Copy Markdown
Owner Author

Test / coverage review — design-system redesign

TL;DR: Not blocking. templ generate && go test ./... is fully green. This is a markup + copy redesign, and the two tests that assert on changed surfaces were updated correctly. One weak assertion is worth tightening, plus a pre-existing theme-init bug the theme-default change makes newly relevant.

What's good

  • config_test.go tracked the theme-default change honestly — TestLoadDefaults, TestAvailableThemesDefaultsWhenNotSet now assert the real new list (spotter,night,synthwave,dracula,dark,light) and spotter default. The one piece of logic that actually changed (theme defaults in config.go) stays covered — good.
  • auth_test.go updated the stale login copy assertion ("Spotter uses your Navidrome credentials""Uses your Navidrome credentials") to match login.templ. Correct, not weakened.
  • Default theme spotter is a real registered DaisyUI theme in tailwind.config.js and is present in the available list, so the switcher can't land on an undefined theme.

Finding 1 — weak/near-tautological assertion (please tighten)

TestAlbumShow_WithRecommendations (internal/handlers/albums_test.go:403):

assert.Contains(t, body, "AI") // AI attribution badge

AlbumShow renders the full layouts.Dashboard, and the substring "AI" now appears all over that page independent of recommendations — the AI menu, the "AI Summary"/"AI Insights" card, "Regenerate AI Insights", and (new in this PR) the sidebar badge badge-secondary AI chip on the Vibes nav item. So this assertion passes whether or not the recommendation attribution badge renders — it verifies nothing about what the comment claims. The redesign didn't create the weakness but it added yet another always-present source of "AI". Suggest asserting on something specific to the recommendations block (e.g. the Recommended Albums badge context, a recommendation's Reason, or a data-testid) so the test actually guards the attribution feature.

Finding 2 — pre-existing theme-init bug, now more relevant (non-blocking)

layouts/dashboard.templ:206 (unchanged by this PR, but this PR is all about theme defaults so flagging it):

const defaultTheme = '{ cfg.Theme.Default }';

This sits inside a <script> block, which templ treats as raw text — it does not interpolate { ... } there. The generated dashboard_templ.go emits the literal string { cfg.Theme.Default }, so on a first visit (no localStorage) this inline script overwrites the correctly server-rendered data-theme (set via BaseWithTheme(title, cfg.Theme.Default)) with the invalid literal "{ cfg.Theme.Default }". It happens to look fine today only because spotter is first in the theme list and DaisyUI falls back to it — change theme.default to anything else and first-load theming breaks. Worth fixing while theme handling is fresh (pass the value through a templ.JSExpression/attribute or an interpolated data-attribute instead of raw script text). There's no test for this because the view layer has no view tests.

Coverage

  • The view layer (internal/views/**) has no view-level tests — this is a pre-existing gap and, given this is a pure markup redesign, I'm not asking for new templ tests here.
  • Handler tests that assert on stable data/copy (Recommended Albums, No mixtapes yet, No playlists found, progress-warning/progress-success, Enhance Vibes, etc.) still pass, which confirms the redesign kept those hooks intact — no silently-stale string assertions beyond Finding 1.

🤖 Posted on behalf of @joestump by Claude.

@joestump

Copy link
Copy Markdown
Owner Author

LINT review — feat/spotter-design-system

Ran the repo's linters in check-only mode (no files rewritten), matching what CI's make lint executes.

Linter Command Result
templ fmt templ fmt -fail . ✅ clean (41 files, 0 changed)
go vet go vet ./... ✅ clean
golangci-lint golangci-lint run ./... (v1.64.8, CI-pinned) ✅ clean
markdownlint npx markdownlint "**/*.md" ... 2 failures

❌ Markdown (BLOCKING — CI make lint-md would fail)

ds-bundle/README.md has two table rows with a missing cell (header declares 3 columns, these rows supply 2):

ds-bundle/README.md:62:116  MD056/table-column-count  Expected: 3; Actual: 2
ds-bundle/README.md:63:34   MD056/table-column-count  Expected: 3; Actual: 2
  • Line 62| Type | \sp-display` `sp-h1` ... |` — only 2 cells.
  • Line 63| Layout | \sp-stack` · `sp-row` |` — only 2 cells.

Each needs a third pipe-delimited cell (or the table structure adjusted) to match the 3-column header.

Notes

  • Local golangci-lint is v2.11.4, which cannot parse this repo's v1-format .golangci.yml. To match CI exactly I installed and ran the pinned v1.64.8 — that run is clean.
  • templ fmt in this environment does not support -l; used -fail (read-only, same check CI's lint-templ uses).
  • No YAML or Dockerfile changes in this PR, so lint-yaml / lint-docker are unaffected.

BLOCKING: yes — the 2 markdownlint MD056 errors in ds-bundle/README.md will fail CI until fixed.

🤖 Posted on behalf of @joestump by Claude.

- ds-bundle/README.md: fix MD056 table column counts (CI-blocking markdownlint)
- dashboard.templ: fix theme-init script that relied on templ interpolating
  { cfg.Theme.Default } inside <script> (templ does not); server-rendered
  data-theme is the default, JS now only applies a saved override.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joestump

Copy link
Copy Markdown
Owner Author

Adversarial review round — resolved

Three parallel adversarial reviews ran (security / testing / lint):

  • 🔒 Security — clean, non-blocking. Pure visual refactor; all XSS/URL/CSRF boundaries preserved (every templ.URL maps 1:1, no templ.Raw introduced, no dynamic hx-*/on*).
  • 🧪 Testing — suite green. Flagged two pre-existing items; fixed the relevant one below.
  • 🧹 Lint — found 2 CI-blocking markdownlint MD056 failures (now fixed). templ fmt, go vet, golangci-lint (v1.64.8, CI-pinned) all clean.

Fixes applied (1924760)

  • ds-bundle/README.md — corrected 3-column table rows (MD056).
  • internal/views/layouts/dashboard.templ — the theme-init <script> relied on templ interpolating { cfg.Theme.Default } inside <script>, which templ does not do (it emitted the literal string, "working" only because spotter is first in the theme list). Now the server-rendered data-theme is the default and JS only applies a saved override.

Deferred (pre-existing, non-blocking): TestAlbumShow_WithRecommendations asserts on the generic string "AI" — worth tightening to a recommendation-specific assertion in a follow-up.

🤖 Posted on behalf of @joestump by Claude.

…spotter-design-system

# Conflicts:
#	internal/config/config.go
@joestump
joestump merged commit 7260224 into main Jul 18, 2026
4 checks passed
@joestump
joestump deleted the feat/spotter-design-system branch July 18, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant