# Lightify UX ROI Polish Plan

> Goal: ship a focused polish pass that makes Lightify feel more responsive,
> more self-explanatory, and more intentional without redesigning the shell or
> removing any existing feature surface.

---

## §0 - Framing

This is the curated subset of the broader UX backlog. The job here is not to
replace the current visual language or rebuild the runtime model. The current
Tauri app already has the right shell, the right tabs, and the right interaction
surfaces. The value is in making the work the app already does more legible,
reducing repeated backend/API work, and tightening the visual affordances that
carry the most user attention.

This plan is split into three workstreams:

1. Download feedback and visibility.
2. High-leverage runtime and state improvements.
3. Visual polish and interaction clarity.

Planning rules for this pass:

1. Preserve the current dark emerald Lightify identity.
2. Extend existing state and render paths instead of creating parallel systems.
3. Land feedback and caching wins before broader polish.
4. Rebuild the Tauri release artifact before claiming delivery.

---

## §1 - Recommended order

Recommended execution order:

### Phase A - Fast trust wins

1. Legible download progress.
2. Newly queued download surfacing.
3. Device selection caching.
4. Search TTL cache.

### Phase B - Runtime and state consistency

1. Smarter track page cache refresh policy.
2. Sidebar memoization per mode.
3. Like and save state materialization across surfaces.
4. Typed error classification and recovery messaging.

### Phase C - High visual ROI polish

1. Play button depth and press feedback.
2. Empty-state hierarchy.
3. Responsive now-playing layout.
4. Scrollbar affordance.
5. Focus and active states in settings and dropdown controls.

Why this order:

1. Download and search feedback remove immediate user uncertainty.
2. Device, cache, and sidebar work improve repeated interactions everywhere.
3. Visual polish compounds the earlier wins instead of masking workflow friction.

---

## §2 - Download feedback and visibility

### 2.1 Legible download progress

Problem:

- The downloads surface works, but progress is still too dependent on raw text
  status. Users should not need to parse summary strings to know whether a file
  is moving.

Primary touch points:

- `lightify-tauri/src/app.js`:
  - `loadSidebarDownloads()`
  - `renderSidebarDownloads()`
- `lightify-tauri/src/styles.css`:
  - sidebar download row styling

Plan:

1. Preserve the current textual status line.
2. Add a visible progress treatment for items that report progress.
3. Show numeric percent when available.
4. Keep the treatment subtle enough that the sidebar does not become noisy.

Acceptance criteria:

1. A downloading item can be distinguished from a waiting item at a glance.
2. Users can tell whether the item is near completion without reading the full
   summary text.
3. Completed, failed, cancelled, and unavailable states still read clearly.

Constraints:

- Progress values may be missing or `0` early in the lifecycle.
- The current polling cadence should remain bounded; no aggressive repaint loop.

### 2.2 Newly queued downloads should surface immediately

Problem:

- The app can open the downloads sidebar when a new download is queued, but the
  new item is not clearly marked. Users still have to scan the list to confirm
  what just happened.

Primary touch points:

- `lightify-tauri/src/app.js`:
  - `ensureDownloadsSidebar()`
  - `loadSidebarDownloads()`
  - `renderSidebarDownloads()`
  - download enqueue call sites
- `lightify-tauri/src/styles.css`:
  - transient row highlight treatment

Plan:

1. Track the most recently enqueued download item locally in frontend state.
2. When downloads render, scroll the new item into view if it is present.
3. Apply a short visual confirmation treatment so users do not need to hunt for
   the row.
4. Clear the transient state after first confirmation.

Acceptance criteria:

1. Queueing a download produces an immediate visible confirmation in the
   downloads list.
2. The user does not need to guess which row is new.
3. Re-polling the downloads list does not keep re-highlighting old rows.

Constraints:

- The row highlight must compose cleanly with later status changes.
- Do not permanently pin or reorder the queue just to surface new items.

---

## §3 - High-leverage runtime and state improvements

### 3.1 Device selection caching

Problem:

- The Spotify device path appears to do more repeated work than necessary.
  Playback commands should not repeatedly re-discover the same active device if
  nothing has changed.

Primary touch points:

- `lightify-tauri/src-tauri/src/main.rs`:
  - `ensure_active_device_id()`
  - `put_player()`
  - `post_player()`
  - `cmd_set_device()`

Plan:

1. Add a short-lived active-device cache in backend state.
2. Invalidate it when the user explicitly changes devices.
3. Refresh it on no-device or stale-device failures.
4. Keep the cache window short enough that manual device changes are still
   picked up quickly.

Acceptance criteria:

1. Repeated transport actions avoid unnecessary full device-list fetches.
2. Device change and recovery behavior remains correct.
3. No user-visible regression occurs when Spotify loses the active device.

Constraints:

- Cache must not survive known-invalid device errors.
- Device selection still has to recover cleanly after 404/no-device conditions.

### 3.2 Smarter track page cache refresh policy

Problem:

- The playlist page cache is structurally sound, but the page-zero refresh path
  likely performs redundant validation work that users pay for repeatedly.

Primary touch points:

- `lightify-tauri/src/app.js`:
  - `loadNextTrackPage()`
- `lightify-tauri/src-tauri/src/main.rs`:
  - `CachedTrackPage`
  - `read_cached_track_page()`
  - `write_cached_track_page()`

Plan:

1. Keep cache hits fast by default.
2. Replace unconditional page-zero refresh with a freshness heuristic.
3. Use saved-at timing or a similar age check to decide when a background or
   immediate refresh is actually warranted.
4. Preserve correctness for obviously stale data.

Acceptance criteria:

1. Reopening the same playlist shortly after a prior load avoids redundant page
   zero validation.
2. Old cache entries still refresh when needed.
3. Large playlists feel faster on repeat entry.

Constraints:

- Do not weaken correctness for clearly stale caches.
- Background refresh should not create flicker in the rendered list.

### 3.3 Search should have a short-lived result cache

Problem:

- Search is already responsive, but repeated refinements of similar terms likely
  hit Spotify more often than needed during one session.

Primary touch points:

- `lightify-tauri/src/app.js`:
  - `doSearch()`
  - `state.lastSearchResults`
- `lightify-tauri/src-tauri/src/main.rs`:
  - `cmd_search_all`

Plan:

1. Add a short-lived frontend cache keyed by normalized query.
2. Reuse results for exact or safe refinement cases when still fresh.
3. Keep cache lifetime intentionally short to avoid stale-feeling results.
4. Preserve explicit search refresh behavior where needed.

Acceptance criteria:

1. Repeating a recent search avoids unnecessary backend work.
2. Small query refinements feel more immediate.
3. Search results do not become misleadingly stale.

Constraints:

- Cache policy must stay simple and observable.
- Avoid speculative caching that complicates invalidation more than it helps.

### 3.4 Error handling should be classified, not flattened

Problem:

- The current catch-and-status pattern works but collapses distinct failures into
  raw strings. That lowers trust and makes recovery harder than it needs to be.

Primary touch points:

- `lightify-tauri/src-tauri/src/main.rs`:
  - Spotify request helpers and error mapping
- `lightify-tauri/src/app.js`:
  - command call sites that currently do `setStatus(String(err))`

Plan:

1. Introduce a small set of typed backend error kinds.
2. Preserve readable message text, but route the frontend through category-aware
   recovery messaging.
3. Give users direct guidance for no-device, auth expiry, rate limit, and
   network conditions.
4. Keep the contract backward-compatible where practical.

Acceptance criteria:

1. Device, auth, and rate-limit failures produce distinct user guidance.
2. Raw opaque backend text no longer dominates routine error messaging.
3. Existing call sites remain maintainable rather than growing ad hoc string
   heuristics.

Constraints:

- This is the most cross-cutting item in the plan and should be staged.
- Avoid inventing a large error framework for a small set of real cases.

### 3.5 Like and save state should be materialized across surfaces

Problem:

- Like state is still fetched piecemeal. That creates repeated checks and allows
  the same track to read differently across Library, Search, and Sidebar.

Primary touch points:

- `lightify-tauri/src/app.js`:
  - `checkLiked()`
  - track menu generation and track rendering paths
- `lightify-tauri/src-tauri/src/main.rs`:
  - like-check commands and any batch extension if added

Plan:

1. Introduce a shared frontend cache keyed by track ID.
2. Prime it for visible track batches where the user is actively browsing.
3. Reuse that cache for Library, Search, Sidebar, and current-track surfaces.
4. Invalidate or update the cache immediately on like/unlike actions.

Acceptance criteria:

1. The same track reports the same like state across surfaces.
2. Repeated context-menu opens do not trigger redundant checks.
3. Like and unlike actions update visible state immediately.

Constraints:

- A batch endpoint may be worth adding if the current single-track path becomes
  the bottleneck.
- Keep the visible-batch scope bounded; do not eagerly fetch the entire library.

### 3.6 Sidebar data should be memoized per mode

Problem:

- Queue and Recent are useful, but toggling between them likely causes more
  reload and dedupe work than necessary. That is repeated friction on a core
  browse surface.

Primary touch points:

- `lightify-tauri/src/app.js`:
  - `toggleSidebar()`
  - `loadSidebarQueue()`
  - `loadSidebarRecent()`
  - `refreshPersistedRecentHistory()`
  - `recentHistoryForDisplay()`

Plan:

1. Add a short-lived memoized sidebar view model per mode.
2. Reuse recent queue/recent results when switching back quickly.
3. Keep explicit invalidation for queue mutations, playback-driven history
   changes, and manual refresh paths.
4. Simplify recent-history dedupe by operating on a more unified normalized
   list.

Acceptance criteria:

1. Rapid Queue/Recent toggling does not feel like reloading the same view.
2. Sidebar data stays correct after actions that actually mutate it.
3. Recent-history display logic becomes simpler, not more layered.

Constraints:

- Memoization must not hide real queue or history updates.
- Explicit invalidation rules need to stay easy to reason about.

---

## §4 - Visual polish and interaction clarity

### 4.1 The play button needs more physicality

Problem:

- The central transport control is already clean, but it still reads flatter
  than its importance warrants.

Primary touch points:

- `lightify-tauri/src/styles.css`:
  - `.ctrl-btn.play-btn`
  - hover, active, and pressed states

Plan:

1. Add depth on hover.
2. Add a pressed state that feels tactile rather than just color-swapped.
3. Preserve the current silhouette and emerald emphasis.

Acceptance criteria:

1. Hover and press both read more intentional.
2. The button still feels like Lightify, not a foreign control.
3. No surrounding transport control becomes visually subordinate in a noisy way.

### 4.2 Empty states need real visual hierarchy

Problem:

- Empty, loading, and failed list states are too close to ordinary rows. They
  read more like content glitches than intentional product states.

Primary touch points:

- `lightify-tauri/src/app.js`:
  - `renderListMessage()`
- `lightify-tauri/src/styles.css`:
  - list item styling and any empty-state variant

Plan:

1. Distinguish loading, empty, and failed states from normal content rows.
2. Add clearer spacing and hierarchy.
3. Keep the design lightweight; no full-screen overlays for ordinary empty
   states.

Acceptance criteria:

1. Users can distinguish empty, loading, and failed states immediately.
2. Empty-state rows no longer look like missing list data.
3. The treatment works across Library, Search, Sidebar, and any other list
   surface that reuses `renderListMessage()`.

### 4.3 The now-playing area needs a narrow-width responsive pass

Problem:

- The now-playing section still compresses too hard on narrower windows. The
  structure is correct, but spacing and balance degrade earlier than they
  should.

Primary touch points:

- `lightify-tauri/src/index.html`:
  - now-playing layout structure
- `lightify-tauri/src/styles.css`:
  - `#now-playing`
  - `#progress-row`
  - control rows and spacing

Plan:

1. Add a narrow-width responsive pass for spacing and density.
2. Keep the current component order.
3. Reduce squeeze before resorting to bigger structural moves.

Acceptance criteria:

1. Progress, controls, and metadata remain comfortable below standard desktop
   width.
2. No row starts to look cramped or misaligned before it has to.
3. The layout still feels like the same product, not a separate mobile design.

### 4.4 Scrollbars need stronger affordance

Problem:

- The current scrollbar treatment matches the theme but hides affordance instead
  of supporting it, especially in dense library and sidebar lists.

Primary touch points:

- `lightify-tauri/src/styles.css`:
  - shared scrollbar rules for scrollable panels and lists

Plan:

1. Increase contrast enough that scroll position is obvious.
2. Keep the treatment subtle and in-family with the accent palette.
3. Preserve thin scrollbars; this is about legibility, not thickness.

Acceptance criteria:

1. Users can immediately tell a panel is scrollable.
2. Scroll thumb visibility improves in long dark lists.
3. The result feels intentional rather than default-browser.

### 4.5 Focus and active states in settings and dropdown controls need work

Problem:

- Settings and select controls are usable, but focus-visible and active states
  are underpowered. That reduces both polish and keyboard clarity.

Primary touch points:

- `lightify-tauri/src/styles.css`:
  - `.btn-primary`
  - `.custom-select-trigger`
  - settings inputs and select surfaces

Plan:

1. Add strong but in-family focus-visible states.
2. Improve pressed and active feedback for settings buttons and dropdowns.
3. Keep focus treatment consistent across controls.

Acceptance criteria:

1. Keyboard focus is obvious without looking foreign to the theme.
2. Button and dropdown interaction states feel more deliberate.
3. Settings surfaces feel less flat without structural redesign.

---

## §5 - Validation plan

### Download feedback

1. Queue a new download from a track context menu.
2. Confirm the downloads sidebar surfaces the new item immediately.
3. Confirm downloading rows expose progress in a legible way.
4. Confirm completed, failed, and cancelled items still read clearly.

### Runtime and state work

1. Repeat transport commands against the same active device and confirm device
   discovery work is reduced.
2. Reopen the same playlist repeatedly and confirm page-zero cache behavior is
   measurably less redundant.
3. Repeat adjacent searches and confirm cached search reuse works without stale
   confusion.
4. Toggle Queue and Recent repeatedly and confirm the sidebar stops feeling like
   it is reloading the same data.
5. Open the same track in Library, Search, and Sidebar and confirm like state
   stays consistent.
6. Trigger known error classes and confirm the user guidance is specific.

### Visual polish

1. Verify the play button hover and press states in the built app.
2. Verify empty/loading/error list states across Library, Search, and Sidebar.
3. Resize the window narrower and confirm the now-playing area holds up.
4. Confirm scrollbars are more legible in long lists without becoming loud.
5. Confirm settings and dropdown focus states work for both mouse and keyboard
   navigation.

### Delivery gate

1. Rebuild `lightify-tauri/src-tauri` with `cargo build --release`.
2. Verify the exact built executable path and freshness before smoke testing.
3. Smoke test the built artifact rather than relying on source-only validation.

---

## §6 - Out of scope for this plan

This plan does not include:

1. Replacing the current shell or layout model.
2. A broad visual redesign of tabs, chrome, or page structure.
3. New major product surfaces unrelated to the curated list above.
4. Feature cuts made only to make polish work easier.

The job is to improve feedback, leverage, and polish inside the product that is
already here.
