Files
rangerhq-radio/CHANGELOG.md
T
ranger 2bd501d610 feat(0.5.0): track history + favourites with four search providers
Quietly log every track that scrolls past the player to a per-user
history (capped 500). A star button promotes the good ones to a
separate Favourites tab that doesn't age out. Each row has four
search-provider deep links so you can find the track on whichever
service you use.

Storage
  Two new user_meta keys (separate from radio_state so frequent
  inserts don't churn the player-state blob):
    - radio_history    — capped FIFO of last 500 played tracks
    - radio_favourites — uncapped user-starred list

New page: Radio → History
  - History tab: capped FIFO list, newest first.
  - Favourites tab: starred tracks (uncapped).
  - Each row: when (relative + full timestamp on hover) / station /
    artist — title / four search links / favourite-star toggle.
  - Filter by artist/title (live, client-side) + by station (dropdown).
  - Clear history button (favourites preserved).
  - Search providers: Spotify, YouTube, Apple Music, Bandcamp.
    Deep-link URLs only — no API keys, no third-party JS.

Auto-logging during playback
  fetchTrack (existing 30s SomaFM poll loop) now hands new tracks to
  logTrackIfNew → POST to wp_ajax_radio_log_track. Dedup client-side
  (lastLoggedSig) AND server-side (against last entry in user_meta).
  Junk filtered server-side: (unknown) artist, missing artist/title.

New AJAX endpoints
  - radio_log_track          (nonce radio_save_state — player pages)
  - radio_toggle_favourite   (nonce radio_history — history page only)
  - radio_clear_history      (nonce radio_history — history page only)

Files
  - radio.php (version, require new include, submenu page, asset
    enqueue hook adds radio_page_radio-history, three AJAX endpoints,
    three new localized strings)
  - inc/history.php (NEW — storage helpers + admin page renderer)
  - assets/css/radio.css (history table, toolbar, search-link pills,
    favourite star, dark-theme overrides)
  - assets/js/radio.js (logTrackIfNew wired into fetchTrack;
    bindHistoryPage handles filter/favourite/clear)
  - inc/about.php (history entry)
  - CHANGELOG.md (full entry)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 23:55:18 +01:00

192 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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** (~5080 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 `<audio>` element and starts drawing live frequency bars on a `<canvas>` next to "Now Playing."
- `<audio>` now carries `crossorigin="anonymous"` so the Web Audio analyser can actually read the stream data (SomaFM serves the CORS headers).
- **Graceful fallback:** if `AudioContext` isn't available, or `createMediaElementSource` throws, or the analyser returns all-zeros for 2 s (CORS silently blocking), the visualizer state flips to `cors-blocked` / `init-failed` / `no-webaudio` and the CSS dancing bars remain — the plugin never loses its indicator.
- Canvas is sized to its CSS box × `devicePixelRatio` so it stays crisp on retina screens.
### State machine on `player._vizState`
| Value | Meaning |
|---|---|
| _undefined_ | not yet attempted |
| `no-webaudio` | browser lacks `AudioContext` |
| `init-failed` | `createMediaElementSource` threw |
| `cors-blocked` | analyser returned zeros for >2 s |
| `ok` | live frequency data flowing → canvas shown, bars hidden |
**Files changed:** `radio.php` (version), `inc/admin-page.php` + `inc/dashboard-widget.php` (added `.radio-player__indicator` with `.radio-player__bars` + `<canvas data-radio-viz>`, plus `crossorigin="anonymous"` on the `<audio>`), `assets/css/radio.css` (indicator container, bars + `radio-bars-dance` keyframes, canvas size), `assets/js/radio.js` (`tryVisualizer` / `startVizLoop` / `stopVizLoop`, `play/pause/error` handlers toggle `is-playing` class and drive the loop), `inc/about.php` (history entry).
---
## [0.3.2] — 2026-05-29 — Play-button glyph baseline fix
The dashicon used for the play/pause icon was rendering visibly below the button text baseline — the dashicon font sits the glyph low inside its own box, and even with `inline-flex` centering the result looked like the symbol was on a separate row from the word "Play".
### Fixed
- **Play/pause icon now sits on the text baseline.** Swapped the dashicon (`dashicons-controls-play` / `dashicons-controls-pause`) for a plain Unicode glyph (▶ / ‖) which renders on the text baseline like any other character.
- Flex container changed from `align-items: center` to `align-items: baseline` for the same reason.
- `font-variant-emoji: text` set so platforms that might otherwise pick up a colour-emoji variant for ▶ keep it as monochrome text.
**Files changed:** `radio.php` (version), `inc/admin-page.php` + `inc/dashboard-widget.php` (dashicon span → glyph span), `assets/css/radio.css` (drop the dashicon-sizing rule, add `.radio-player__play-glyph` styling, change play-button flex alignment to baseline), `assets/js/radio.js` (`setPlayIcon` now swaps glyph `textContent` instead of dashicon `className`), `inc/about.php` (history entry).
---
## [0.3.1] — 2026-05-29 — My Radio layout polish + drop dark-auto
Quick patch after a real-screen review of the My Radio admin page. v0.3.0 added a lot of features but stretched the player to fill the full admin width and — embarrassingly — introduced a real contrast bug via the dark-auto CSS.
### Fixed
- **Dropped `@media (prefers-color-scheme: dark)` for `theme=auto`.** WordPress admin has no native dark mode, so when the OS was dark, our dark text rendered on the still-white WP postbox = unreadable. `auto` now behaves as light (matching WP's actual scheme); `dark` is still available as an explicit choice.
- **`theme=dark` now actually reads.** The player surface goes dark (`#1d2327` background + subtle border + padding) so the light text has somewhere to sit, instead of fighting the white WP postbox.
- **Player no longer stretches edge-to-edge.** `.radio-wrap { max-width: 880px; }` keeps the player a focused settings-page card.
- **Intro paragraph one-line on normal widths.** Removed the `max-width: 720px` cap that was forcing the wrap.
- **Volume slider no longer dominates the row.** Fixed width 220px (`flex: 0 0 auto`) — the percent label now sits next to the slider instead of pinned to the far right edge.
- **Station dropdown** capped at 360px (was full-width) — typical WP form-control width.
- **Play button icon** shrunk from 18px to 14px so the ▶ glyph sits on the button-text baseline instead of looking like a separate row.
**Files changed:** `radio.php` (version), `assets/css/radio.css` (wrap width, intro, play icon, volume, station-select, dark-auto block deleted, dark-surface added), `inc/about.php` (history entry).
---
## [0.3.0] — 2026-05-29 — Dark theme + mute + media keys + current-track display
A polish pass that closes the gaps surfaced by a UI review. Two categories: **2nd-look fixes** (things the previous release implied but didn't actually deliver) and **nice-to-haves** (small upgrades that lift the admin-player feel).
### Added — features
- **Mute toggle on the speaker icon.** The icon next to the volume slider is now a button. Click to mute (icon flips to `dashicons-controls-volumeoff`, tinted red); click again to restore the prior volume. Remembers volume across mute/unmute via `data-prev-volume`.
- **MediaSession API integration.** OS media keys (F8/F9 on Mac, Bluetooth headphone buttons, lock-screen widget on supported platforms) now play/pause the radio. The currently-playing station name, "SomaFM" as artist, and the genre as album are exposed as `MediaMetadata` so they show on the OS overlays.
- **Current-track display.** Polls `https://somafm.com/songs/{code}.json` every 30 seconds **while playing only** and shows the track as `♪ Title — Artist` under the station description. Best-effort: silently hidden if the endpoint is unreachable / CORS-blocked, so the plugin keeps working regardless.
### Fixed — 2nd-look
- **Dark theme is now actually wired through.** v0.2.0 saved the Theme dropdown (auto / light / dark) but had no CSS to render anything other than light. v0.3.0 adds `admin_body_class` filter → `radio-theme-{auto,light,dark}` body class → corresponding dark-palette CSS for the player + about-cards. `auto` follows the OS via `prefers-color-scheme: dark`.
- **Settings-page volume slider** no longer uses an inline `oninput=""` handler — the listener moved into `assets/js/radio.js` (`bindSettingsSlider`). Cleaner under strict-CSP environments.
- **Save errors are surfaced.** AJAX state-save failures were previously swallowed silently — the local UI updated but the user had no signal if the server dropped the request. The plugin now shows a brief notice ("Preferences not saved — check your connection.") in the player's error slot for 3.5 s, then auto-clears.
- **Hardcoded Gitea URL** in the About page replaced with a `RADIO_GITEA_URL` constant defined in `radio.php`. One place to update if the repo ever moves.
- **Genre badge layout fix.** Was using `margin-left: auto` inside a wrap-enabled flex row, which caused it to land on its own line on narrow widget widths. Now styled as a small inline pill (rounded `rgba(0,0,0,0.06)` background) that flows naturally next to the station name.
### Other
- Plugin version bumped to **0.3.0**.
- New localized strings: `mute`, `unmute`, `saveError` (for the JS-driven UI).
- The mute button has a visible focus ring (`outline: 2px solid var(--wp-admin-theme-color)`) for keyboard navigation.
- Volume slider input now also exits mute state (sets `audio.muted = false` on drag), so dragging the slider always overrides a prior mute click.
**Files changed:** `radio.php` (version, constant, strings, `admin_body_class` filter), `inc/about.php` (constant for changelog URL), `inc/settings.php` (removed inline `oninput`), `inc/admin-page.php` + `inc/dashboard-widget.php` (speaker icon → mute button, added track slot), `assets/css/radio.css` (genre badge pill, mute button, track display, dark-theme rules incl. `prefers-color-scheme` for `auto`), `assets/js/radio.js` (full rewrite incl. `bindMute`, `bindSettingsSlider`, `startTrackPolling`/`stopTrackPolling`, `updateMediaSession`, save-error surfacing).
---
## [0.2.0] — 2026-05-26
### Changed — UI rebuilt to WordPress admin standards
v0.1.0 worked but looked like a third-party React widget sitting on top of WordPress rather than part of it. v0.2.0 rebuilds the player UI to use WP-native patterns end-to-end.
- **Main page** now uses the standard **`.postbox` container** (gray header bar + `.inside` body) instead of a custom rounded-shadow card.
- **Play button** is now a standard **`.button .button-primary`** with both icon AND text label (Play / Pause), matching every other admin button. Replaces the giant blue circular icon-only button.
- **Now Playing** uses left-aligned default body text with `.description` muted-gray for the station tagline. Replaces the centered large-typography card.
- **Genre badge** moved to a small `.radio-player__station-genre` text label aligned right of the station name. Replaces the custom pill.
- **Volume slider** now uses `accent-color: var(--wp-admin-theme-color)` — adapts to whichever admin colour scheme the user has chosen (Default / Light / Modern / Blue / Coffee / Ectoplasm / Midnight / Ocean / Sunrise).
- **All link colours** likewise adapt to the user's admin theme via `var(--wp-admin-theme-color, #2271b1)`.
- **Dashboard widget** content sits bare inside its `.inside` — WordPress already wraps it in a postbox. v0.1.0 was rendering a card inside a card.
- **About page** cards now use postbox-style gray header bars + WP-standard 1px border + subtle shadow. Replaces the custom rounded grid.
- **Credit footer** uses `.description` class, smaller and more native.
### Net effect
The plugin feels like part of WordPress now, not bolted onto it. Picks up your admin colour scheme automatically. Closer to WP.org submission criteria — they look for native-styled plugins during plugin review.
### Not changed
- Functionality identical to v0.1.0 — same 44 stations, same audio path, same user_meta persistence, same updater, same AJAX endpoint.
- No behaviour change for end users; this is purely visual.
- About page version-history card promotes v0.2.0 to "latest", demotes v0.1.0.
---
## [0.1.0] — 2026-05-26
**Radio is born.** First release of a new standalone WordPress plugin extracted-and-rebuilt from the radio feature that lives inside RangerPlex. Radio stands on its own as a focused, friendly companion plugin for the WordPress dashboard — a tab of background music while you work.
### Added — Phase A complete (player exists)
- **Dashboard widget** at WP Admin → Dashboard showing a compact mini-player with play/pause, station select grouped by genre, and a volume slider.
- **Dedicated admin page** at WP Admin → Radio → My Radio showing the same controls in a larger format with the station's genre badge and description.
- **44 SomaFM stations** across **10 genres** (Ambient, Electronic, Lounge, Rock, Metal, Jazz, World, Reggae, Holiday, Specials). Stream URLs use SomaFM's public 128kbps MP3 endpoints — no proxy server required.
- **Per-user state storage** via `user_meta` (key: `radio_state`). Each WordPress admin remembers their own station choice, volume, and theme preference.
- **Settings page** at WP Admin → Radio → Settings with default station, default volume, theme (auto/light/dark), and an opt-out for the dashboard widget. Updates panel shown to admins with `manage_options`.
- **About page** at WP Admin → Radio → About with plain-language explanation of what the plugin does, who it's for, version history, and credits to SomaFM.
- **Self-hosted update checker** wired up to the Gitea repo (`ranger/a-radio`) from commit 1. Polls `/api/v1/repos/ranger/a-radio/releases/latest` with a `/tags?limit=1` fallback. 12h success cache, 1h negative cache.
- **AJAX endpoint** `radio_save_state` for persisting station/volume changes without a page reload. Nonce-protected, capability-checked.
- **Custom admin-menu icon** (`dashicons-format-audio`).
- **Direct HTML5 `<audio>` playback** — no Node proxy, no PHP stream-passthrough, no server-side resource cost per listener. SomaFM's CORS headers make this work out of the box in modern browsers.
### Architecture (locked from day one)
- **Single-word brand name `Radio`** — no "WP" prefix, no marketplace trademark hurdle.
- **Public GPL v2+ Gitea repo** at `ranger/a-radio` on `git.davidtkeane.com`.
- **Per-user state in `user_meta`** under key `radio_state`.
- **Vanilla JS only** — no React, no build step, no bundler. ~200 lines of JS controlling all interactions.
- **CSS-only animations, all assets local** — bundle stays sub-100KB.
- **Single H1 per admin page**, no nested toggle boxes — Tier-1 discipline carried forward from the Logbook + Buddy lineage.
- **Sanitize on input, escape on output** throughout. Every AJAX endpoint nonce-protected and capability-checked.
### Compliance
- Station list and stream URLs are SomaFM's public, freely-published endpoints. Their terms allow redistribution with attribution.
- "Powered by SomaFM" credit displayed in both player surfaces, linking to somafm.com.
- The About page invites users to donate to SomaFM directly.
### Not in this release (planned)
- Phase B — Settings polish + README for WP.org submission + retry logic for transient stream errors.
- Phase C — Now-playing metadata via SomaFM's per-station song-history endpoint.
- Phase D — `[ranger_radio]` shortcode so the player can be embedded in posts/pages.
- Phase E — Favorites system.
- Phase F — Multi-provider (Radio Paradise, NTS Radio, KEXP, BBC) with a provider abstraction.