ad43df87c0
Web Store submission target. Mirrors rangerhq-radio's track-history pattern (inc/history.php) so the family stays coherent across surfaces. Highlights - New Tab Page override (Tier 2.5) — Chrome's default new tab replaced with a RangerHQ-branded landing showing the player, current track, quick chips, searchable browse list, and now history + favourites tabs. - Track history + favourites — capped FIFO 500, dedup against last entry, skip "(unknown)" artist (SomaFM dead-air). Stored in chrome.storage.local under tuner.history + tuner.favourites. - 4-button search per entry — Spotify / YouTube / Apple Music / Bandcamp. Pure public-search-URL link-outs in a new tab, NO auth, NO API keys, NO quota, NO third-party SDK embedded. - Options page (chrome://extensions → details → options) — live stats, history cap slider (50-500), Clear history / Clear favourites / Clear EVERYTHING buttons, About panel with Gitea + davidtkeane.com links. - Popup nav row — Open in tab / History (#hash deep link) / Settings, using chrome.tabs.create + chrome.runtime.openOptionsPage. No new perms. - Cross-surface sync — popup ↔ newtab listen on chrome.storage.onChanged for tuner.currentStationId / tuner.isPlaying / history / favourites. - Storage gateway — offscreen doc can't reliably reach chrome.storage in some Chrome versions, so it sends LOG_TRACK_REQUEST to the SW which does the write. history.js also defensively guards every storage call. - Metadata latency fix — polling now starts immediately on PLAY, in parallel with audio buffer fill. First track display drops from ~10-15s to ~1-2s. Permissions unchanged - Still ["offscreen", "storage"] + somafm.com host only. - chrome.tabs.create works on our own extension URLs without "tabs" perm. - No webRequest, no <all_urls>, no third-party SDK. Bumped from 0.1.0 (last tag on Gitea) directly to 0.3.0. v0.2.0 (newtab + clock) was a working local build but never tagged; its features ship together with v0.3.0 in this single commit.
207 lines
14 KiB
Markdown
207 lines
14 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to **RangerHQ Tuner** are documented here.
|
||
Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versioning: [SemVer](https://semver.org/).
|
||
|
||
---
|
||
|
||
## [Unreleased]
|
||
|
||
### Planned — Tier 3 (Chrome Web Store submission)
|
||
- Listing assets: 1280x800 screenshots, 440x280 promo tile
|
||
- Privacy policy URL published at `davidtkeane.com/rangerhq-tuner/privacy`
|
||
- 2-Step Verification on Google account
|
||
- $5 dev fee + submission
|
||
|
||
---
|
||
|
||
## [0.3.0] — 2026-06-08
|
||
|
||
### Added — Track history + 4-button search + Options page (Web Store target)
|
||
|
||
This is the version that goes to the Chrome Web Store. Mirrors the rangerhq-radio WordPress plugin's history feature so the family stays coherent across surfaces.
|
||
|
||
#### Track history
|
||
- **`src/lib/history.js`** — helpers for history/favourites/search-URL/dedup/cap/clear, mirroring `inc/history.php` in the WP plugin.
|
||
- **Storage:** `tuner.history` (FIFO, capped at 500 by default) and `tuner.favourites` (uncapped) in `chrome.storage.local`. Cap configurable 50–500 on the Options page.
|
||
- **Entry shape** (mirror WP plugin exactly): `{ artist, title, station, stationId, at }` where `at = Date.now()`.
|
||
- **Dedup against the LAST entry only** so consecutive identical now-playing reads don't pile up.
|
||
- **Skip `(unknown)` artist** — SomaFM's dead-air/promo placeholder doesn't pollute the log.
|
||
|
||
#### Metadata polling
|
||
- **`src/offscreen/offscreen.js`** — the offscreen document (where audio lives) now polls SomaFM's per-channel song endpoint every 25 s while playing. On `PLAY`, polling starts (immediate first fetch + interval). On `PAUSE` or `ERROR`, polling stops. Within-session dedup prevents redundant logs when the same song stays current across polls.
|
||
- **Why the offscreen doc, not the SW:** the SW gets killed when idle; the offscreen doc is alive while audio plays. This is the right home for the loop.
|
||
- New message types in `src/lib/messages.js`: `TRACK_LOGGED`, `FAVOURITE_TOGGLED`, `STORAGE_WIPED`.
|
||
|
||
#### 4-button search (the headline feature)
|
||
- Every track row in History and Favourites gets four search-link buttons that open the major services' public search pages in a new tab:
|
||
|
||
| Service | URL pattern |
|
||
|---|---|
|
||
| Spotify | `https://open.spotify.com/search/{enc}` |
|
||
| YouTube | `https://www.youtube.com/results?search_query={enc}` |
|
||
| Apple Music | `https://music.apple.com/search?term={enc}` |
|
||
| Bandcamp | `https://bandcamp.com/search?q={enc}` |
|
||
|
||
Where `enc = encodeURIComponent(artist + " " + title)`. **Zero auth, zero API keys, zero quota, zero ToS gray area.** RangerHQ Tuner doesn't play these services — it opens the search results page and the user picks. Same approach as the rangerhq-radio WP plugin.
|
||
|
||
- Each link is brand-coloured (muted on the dark palette) — Spotify green, YouTube red, Apple pink, Bandcamp cyan. `target="_blank"` + `rel="noopener noreferrer"`.
|
||
|
||
#### Tabbed New Tab Page
|
||
- The New Tab Page's bottom section is now a **3-tab control**: **Stations · History · Favourites**, plus a ⚙ icon that opens the Options page.
|
||
- History tab: newest first, with track title, artist (accent green), `station · 2m ago` meta line, star toggle, and the 4 search links.
|
||
- Favourites tab: same shape, all your starred tracks.
|
||
- Each tab has its own "Clear …" button in the toolbar (confirm dialog).
|
||
|
||
#### Options page (NEW)
|
||
- Registered via `"options_page": "src/options/options.html"` in the manifest. Opens via `chrome://extensions` → details → Extension options OR via the ⚙ on the New Tab Page.
|
||
- **Local data card:** live stats (History size, Favourites size, Stations cached, Total `tuner.*` bytes), history-cap slider (50–500 step 50), and three buttons:
|
||
- `Clear history` — drops `tuner.history` only.
|
||
- `Clear favourites` — drops `tuner.favourites` only.
|
||
- `Clear EVERYTHING` (danger styling) — wipes every `tuner.*` key in `chrome.storage.local`.
|
||
- **Playback card:** current volume %, last-played station name (read-only).
|
||
- **About card:** version, Gitea repo link, davidtkeane.com link, SomaFM link, licence reminder.
|
||
- A small toast message confirms each destructive action.
|
||
- The page listens for `chrome.storage.onChanged` so stats stay live if you wipe from somewhere else.
|
||
|
||
#### Manifest
|
||
|
||
- Version: `0.2.0` → `0.3.0`.
|
||
- Description updated to mention the 4-button search affordance (helps Web Store reviewers see the single-purpose statement matches the listing).
|
||
- Added `"options_page": "src/options/options.html"`.
|
||
- **No new permissions, no new host permissions.** Still `["offscreen", "storage"]` + `somafm.com` only. Search-link clicks open in a new browser tab — that's the user's navigation, not an API call from the extension.
|
||
|
||
#### Cross-surface state sync
|
||
|
||
The popup and the New Tab Page now stay in lockstep without manual reloads. Both surfaces subscribe to `chrome.storage.onChanged` and re-render when:
|
||
|
||
- `tuner.currentStationId` changes (someone in the other surface picked a station)
|
||
- `tuner.isPlaying` changes (play/pause from anywhere)
|
||
- `tuner.history` or `tuner.favourites` mutates (re-render the relevant tab)
|
||
|
||
Single source of truth = `chrome.storage.local`. No more drift between popup and New Tab.
|
||
|
||
#### Storage gateway architecture (MV3 safety net)
|
||
|
||
Offscreen documents don't reliably have access to `chrome.storage` across Chrome versions, so all storage writes from the offscreen audio host are routed through the service worker via a new `LOG_TRACK_REQUEST` message type. The SW (which always has `chrome.storage`) does the actual `tuner.history` write and broadcasts `TRACK_LOGGED` back to the UI surfaces.
|
||
|
||
As belt-and-braces, every function in `src/lib/history.js` now checks for `chrome.storage` availability before using it and returns sensible defaults if absent — so the module is safe to import from any extension context.
|
||
|
||
#### Metadata latency fix (parallelised audio + polling)
|
||
|
||
When PLAY arrives at the offscreen doc, metadata polling now starts **immediately** in parallel with the audio HTTP buffer fill, rather than waiting for `audio.play()` to resolve. First-track display time drops from ~10-15 seconds (audio buffer + then poll) to ~1-2 seconds (poll racing in parallel with buffer fill).
|
||
|
||
#### Popup quick-link nav
|
||
|
||
The toolbar popup now has a three-button nav row above the footer:
|
||
|
||
- **⎘ Open in tab** — `chrome.tabs.create({url: chrome.runtime.getURL('src/newtab/newtab.html')})` opens the full Tuner UI as a regular pinnable tab.
|
||
- **♪ History** — same as Open in tab but with `#history` hash so the History pane is pre-selected. `newtab.js` reads `location.hash` on init and respects it.
|
||
- **⚙ Settings** — `chrome.runtime.openOptionsPage()` opens the Options page directly.
|
||
|
||
No new permissions: `chrome.tabs.create()` works on our own extension URLs without the `tabs` permission. The Web Store permission profile stays `["offscreen", "storage"]` + `somafm.com` host only.
|
||
|
||
#### Why this is the Web Store version
|
||
|
||
- Clear single-purpose statement now obvious: *"Plays SomaFM internet radio, logs heard tracks, and provides search-link shortcuts to Spotify/YouTube/Apple Music/Bandcamp."*
|
||
- Privacy story stays clean: "collects nothing, stores everything locally, no remote storage" — every Options-page checkbox stays "does not collect" on the Dashboard.
|
||
- The four service buttons are pure `<a href>` link-outs — Web Store reviewers can verify they don't embed third-party SDKs.
|
||
- All inline styles are gone (lessons from `rangerhq-buddy v0.1.5`).
|
||
- Accessibility: every button has an `aria-label`, every interactive element is reachable by keyboard, focus rings visible, contrast ≥ 4.5:1.
|
||
- Storage operations are SW-gated, so even Chrome versions with the offscreen-doc storage quirk run cleanly.
|
||
|
||
---
|
||
|
||
## [0.2.0] — 2026-06-08
|
||
|
||
### Added — New Tab Page override (Tier 2.5)
|
||
|
||
Replace Chrome's default New Tab Page with a RangerHQ-branded landing that surfaces the player, current track, a quick-station chip row, and a searchable browse list. Same `chrome.offscreen` audio pipeline as the popup — the New Tab Page is just a second view onto the same playback state. Live ticking clock (HH:MM in bold + small dim seconds) updates every second; date renders in the user's locale.
|
||
|
||
Note: v0.2.0 was a working local build but was never tagged on Gitea — its features ship to the world together with v0.3.0 in a single commit.
|
||
|
||
- **`manifest.json`**: added
|
||
```json
|
||
"chrome_url_overrides": { "newtab": "src/newtab/newtab.html" }
|
||
```
|
||
Description string updated to mention the optional New Tab Page replacement (Web Store reviewers prefer overrides to be disclosed in the description).
|
||
- **`src/newtab/newtab.html`** — full-viewport landing with header (helmet + clock), large now-playing block, generous Play/Volume controls, quick-station chips, searchable browse list, footer linking to `davidtkeane.com` + Gitea.
|
||
- **`src/newtab/newtab.css`** — same earthy palette as the popup but laid out for a full screen. Subtle helmet watermark (2.5% opacity) in the background. Responsive shrink-to-fit at `max-height: 700px`.
|
||
- **`src/newtab/newtab.js`** — reuses `src/lib/messages.js`, `src/sources/index.js`, and the same `chrome.storage.local` keys as the popup. State stays consistent: pick a station in the popup, refresh new tab → same station selected, same play state. Adds a clock that updates every 30 s.
|
||
|
||
**Quick-pick stations:** the chip row surfaces the 8 most-loved SomaFM channels (Groove Salad, Drone Zone, Indie Pop Rocks!, Secret Agent, Space Station Soma, Lush, Deep Space One, Fluid). One click → playing.
|
||
|
||
**Why this feature exists:** validates [[user_browser_resident_tools]] — David has the browser open all day, every new tab is a touchpoint. Every `Cmd+T` now opens a RangerHQ-branded landing that's one click from playing radio. The Web Store reviewer is also more likely to grant a clear single-purpose override than a vague one — having a useful New Tab Page is good signal alongside the toolbar player.
|
||
|
||
**Permissions unchanged:** no new permissions or hosts added. The override is a pure UI choice; no expanded API surface.
|
||
|
||
---
|
||
|
||
## [0.1.0] — 2026-06-08
|
||
|
||
### Added — Tier 1 MVP (Buddy is alive — er, Tuner is alive)
|
||
|
||
**Buddy's browser cousin lives.** First release of RangerHQ Tuner — a Chrome Manifest V3 extension that plays SomaFM internet radio from your toolbar. Sibling to [`rangerhq-radio`](https://git.davidtkeane.com/ranger/rangerhq-radio) (the WordPress version live on wp.org since 2026-06-04). Same brand idea, different surface: WP version lives in admin pages, Chrome version lives one toolbar-click away no matter what you're doing.
|
||
|
||
#### Architecture
|
||
|
||
- **Manifest V3** — uses the `chrome.offscreen` API to host the `<audio>` element in a hidden document. Service workers can't host audio in MV3 (they get killed when idle), so a one-off offscreen document is the only supported pattern. Working code in `src/offscreen/offscreen.js` + `src/background/service-worker.js`.
|
||
- **Source-adapter pattern** at `src/sources/` — every radio network is a single file conforming to the `RadioSource` interface in `base-source.js`. Adding a new network = drop a new file + register one line in `src/sources/index.js`. Popup, SW, and offscreen never know which network is which.
|
||
- **Vanilla JS, no build step** — pure ES modules loaded directly. No webpack, no Vite, no `npm install`. Same ethos as the RangerHQ WP family (hand-rolled PHP).
|
||
- **No telemetry, no third-party JS** — everything is local. Only outbound network calls are to SomaFM's public endpoints (catalogue + stream metadata).
|
||
|
||
#### Features
|
||
|
||
- **Toolbar popup** showing a searchable SomaFM station list (~150 channels) with artwork, name, and genre.
|
||
- **Play / Pause / Volume** controls. Volume persists across browser restarts.
|
||
- **Last station** picked is remembered — Tuner remembers where you left off.
|
||
- **Now-playing metadata** displayed from SomaFM's per-channel song history endpoint.
|
||
- **Catalogue cached** in `chrome.storage.local` for 6 hours to make popup re-opens instant.
|
||
- **Audio continues after popup closes** — the offscreen document owns the playback, popup is just a view.
|
||
- **RangerHQ helmet** icon in the Chrome toolbar (resized from `src/assets/img/ranger.png` to 16/32/48/128 PNGs with a dark `#1a221c` padded background that matches the popup palette).
|
||
|
||
#### Files (22 created)
|
||
|
||
```
|
||
rangerhq-tuner/
|
||
├── manifest.json
|
||
├── README.md
|
||
├── CHANGELOG.md
|
||
├── .gitignore
|
||
└── src/
|
||
├── assets/
|
||
│ ├── icons/icon-{16,32,48,128}.png (toolbar icons)
|
||
│ └── img/ranger.png (master helmet logo, 257×275)
|
||
├── background/service-worker.js (message router only)
|
||
├── offscreen/offscreen.{html,js} (audio host — the MV3 gotcha solved)
|
||
├── popup/popup.{html,css,js} (the toolbar UI)
|
||
├── sources/
|
||
│ ├── base-source.js (RadioSource interface contract)
|
||
│ ├── somafm.js (first concrete adapter)
|
||
│ └── index.js (registry)
|
||
└── lib/
|
||
├── messages.js (type + target constants)
|
||
└── playlist-parser.js (.pls parser)
|
||
```
|
||
|
||
#### Manifest permissions (narrow on purpose)
|
||
|
||
```json
|
||
"permissions": ["offscreen", "storage"],
|
||
"host_permissions": ["https://somafm.com/*", "https://*.somafm.com/*"]
|
||
```
|
||
|
||
No `tabs`, no `<all_urls>`, no `webRequest`. Smallest permission ask possible = easiest Web Store review.
|
||
|
||
#### Architecture / status
|
||
|
||
- **Tier 1 MVP** — shipped this commit.
|
||
- **Tier 2** (planned) — full UX polish, favourites via `chrome.storage.sync`, now-playing polling loop, `.m3u` parser, `.crx` packaging, second source adapter stub.
|
||
- **Tier 3** (planned) — Chrome Web Store submission. See `~/.ranger-memory/docs/` for the per-family submission checklist; Chrome Web Store specifics tracked in the user's memory under `reference_chrome_web_store_rules`.
|
||
|
||
#### Why this exists
|
||
|
||
David has Chrome open essentially all day. The WP version of RangerHQ Radio requires going to admin pages to use; the toolbar version is one click away regardless of context. Same code-level effort, dramatically higher daily-use payoff. The "best thing we done so far" verdict on first sound (2026-06-08 evening) confirmed the bet.
|
||
|
||
#### Rangers lead the way 🪖☕🎵
|