# Changelog All notable changes to **Radio** are documented here. Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versioning: [SemVer](https://semver.org/). --- ## [Unreleased] --- ## [0.5.0] — 2026-05-29 — Track history + favourites SomaFM plays deep cuts you'll never hear again. v0.5.0 quietly logs every track that scrolls past so you can find it again later — and a star button keeps the ones worth keeping forever. ### Added — `Radio → History` admin page (per-user) - **History tab** — capped FIFO list of the last 500 played tracks. Each row shows when (relative time, full timestamp on hover), station, *artist — title*, four search links, and a favourite-star toggle. - **Favourites tab** — uncapped list of starred tracks. Same row layout. Survives even when the history rolls over. - **Filter** by artist/title (live, client-side) and by station (dropdown). **Clear history** button on the History tab — favourites preserved. - **Four search providers** per row, brand-tinted on hover: **Spotify** (green), **YouTube** (red), **Apple Music** (pink), **Bandcamp** (teal). Deep-link search URLs only — no API keys, no third-party JS. - Empty-state messages on both tabs. ### Added — automatic logging during playback - `fetchTrack` (the existing 30s SomaFM polling loop) now hands every new track to a new `logTrackIfNew` helper that POSTs it to `wp_ajax_radio_log_track`. - **Dedup**: client-side via `lastLoggedSig` so the 30s polling doesn't re-log the same song; server-side against the last entry in user_meta as belt-and-braces. - **Junk filtering**: `(unknown)` artists and entries missing artist *or* title are dropped server-side in `radio_sanitize_entry`. ### Added — per-user storage - Two new `user_meta` keys, separate from `radio_state` so frequent track inserts don't churn the player-state blob: - `radio_history` — capped at **500 entries** (~50–80 KB max). - `radio_favourites` — uncapped, expected to stay small (user-curated). ### Added — three new AJAX endpoints - `wp_ajax_radio_log_track` — append a track (nonce: `radio_save_state`; player-page only). - `wp_ajax_radio_toggle_favourite` — toggle a track in favourites (nonce: `radio_history`; History-page only). - `wp_ajax_radio_clear_history` — clear the history list (nonce: `radio_history`; History-page only). Favourites untouched. ### Notes - Per-user — nothing is shared, nothing leaves the site. Just your own listening history on your own WP account. - **No PII concern** — entries are public station/artist/title strings from SomaFM's own JSON. - Search-link UI tints toward each provider's brand colour on hover only — keeps the row visually calm in the default state. **Files changed:** `radio.php` (version, require, submenu, asset enqueue hook, three AJAX endpoints, three new localized strings), **new** `inc/history.php` (storage helpers + page renderer), `assets/css/radio.css` (history-page table, toolbar, search-link pills, favourite star, dark-theme overrides), `assets/js/radio.js` (`logTrackIfNew` wired into `fetchTrack`; `bindHistoryPage` for filter/favourite/clear), `inc/about.php` (history entry). --- ## [0.4.0] — 2026-05-29 — Now-playing indicator: dancing bars + Web Audio visualizer A small visual that instantly says *"this is playing right now."* Two layers — a reliable CSS-only indicator that always works, and a progressive Web Audio upgrade that draws actual frequency data when the browser allows. ### Added — dancing bars (always on, CSS only) - Four tiny vertical bars next to the "Now Playing" label that pulse with a staggered `@keyframes` animation while the audio is playing, settling to a low static state when paused. Pure CSS — no JS dependency, no audio analysis. - Bars use `var(--wp-admin-theme-color)` so they tint to whichever WP admin colour scheme the user has chosen. - Driven by a single `.is-playing` class toggled on the `.radio-player` surface from the existing `play` / `pause` / `error` audio handlers. ### Added — Web Audio frequency visualizer (progressive upgrade) - On first `play`, `tryVisualizer` builds an `AudioContext` + `AnalyserNode` chain on the `