Commit Graph
9 Commits
Author SHA1 Message Date
paulandClaude Opus 4.8 e06c983b16 feat: optional direct Bandcamp/Apple/YouTube links via Odesli (flag-gated) (closes task/odesli-direct-cross-platform-links)
Source: task/odesli-direct-cross-platform-links (plan/improvement-backlog) — bandcamp was only a Google search; Odesli gives direct cross-platform links for Spotify-resolved names.

- service/odesli.js: makeOdesli({fetch,baseUrl}) -> getLinks(spotifyUrl); pure parseOdesli core; bounded AbortSignal.timeout; non-200/timeout/garbage -> {} (graceful).
- resolver.js: opt-in ODESLI / ODESLI_URL flag (default OFF). When ON, enrich each Spotify-resolved result with additive bandcamp/appleMusic/youtube fields (isHttpUrl-validated), cached per Spotify url. OFF -> no call, byte-identical behavior. /health + startup log surface the flag.
- extension: a tiny additive "bc" link to a direct Bandcamp page when result.bandcamp is present (reuses isSafeHttpUrl); primary artist/album/spotify/google rendering unchanged. styles.css: bandcamp-teal secondary link.
- odesli.test.js + odesli.fixture.json: parseOdesli unit-tested against a real captured Odesli response (Blood Incantation album -> direct bandcamp; absent apple/youtube omitted; junk -> {}); getLinks 200/non-200/throw/parse-error paths.
- docs: .env.example, README, AGENTS.md note the opt-in flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 02:41:30 +00:00
paulandClaude Opus 4.8 8ac0666a71 fix: scope getRoot to the comment tree so the shadow walk doesn't collect reddit chrome (closes task/getroot-comment-tree-scope)
Source: task/getroot-comment-tree-scope (plan/steward-linker-design) — getRoot's union querySelector resolved to <main> on new reddit, so the shadow-piercing walk over-collected reddit chrome.

- getRoot now tries shreddit-comment-tree, then .commentarea, then main/body sequentially (narrow-first) instead of one union querySelector, so the comment tree wins when present.
- collectInto takes an allowShadow flag: a narrow comment root pierces every open shadow host; a broad fallback root descends only into comment-component hosts (tagName SHREDDIT-COMMENT*), so chrome shadow roots (nav/sidebar/composer) are never walked.
- README + AGENTS shadow-DOM notes updated to describe the two-layer scoping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:38:11 +00:00
paulandClaude Opus 4.8 071cc3f85a fix: pierce new-reddit (shreddit) shadow DOM when collecting comment text (closes task/handle-shreddit-shadow-dom)
Source: task/handle-shreddit-shadow-dom (plan/steward-linker-design) — the TreeWalker only walked light DOM, so new-reddit comment bodies inside shadow roots were missed.

- collectTextNodes now walks the comment-tree root's light DOM AND recursively descends into the open shadow roots of elements within that subtree, so comment text is reached whether reddit slots it (light DOM) or encapsulates it (shadow DOM).
- Scoped to getRoot()'s comment tree (never the whole document's shadow roots); bounded by a depth cap; a no-op on old reddit (no shadow roots).
- Slotted light-DOM nodes are not double-collected (a shadow walker sees the shadow tree's own nodes, not a <slot>'s assigned light nodes); a Set de-dupes by node identity belt-and-braces.
- All existing guards preserved (inSkippable / rsl-link skip). README + AGENTS document the support.
- Limitation: CLOSED shadow roots are unreachable from a content script and stay unlinked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:28:31 +00:00
paulandClaude Opus 4.8 2b4babf955 feat: detect & link album mentions, not just artists (closes task/album-detection)
Source: task/album-detection (objective/linker-improvements) — project brief asks for artist OR album linking; MVP linked artists only.

- service/lib.js: pickAlbum (the exact-name gate, named for the album path) + linkResult now carries kind:"artist"|"album" (additive; defaults to artist so older clients keep working).
- service/resolver.js: one Spotify type=artist,album search per candidate, exact-match against either; returns {url,kind}; cache key includes the kind hint. resolveOne/resolveAll thread the LLM kind hint through.
- service/llm.js: parseClassification now returns Map(name -> "artist"|"album") so album-kind candidates route to the album path (.has() still works like the old Set).
- extension/extract.js: albumMatchesIn over two precise signals (quoted strings + album cue phrases "the album X", "X LP/EP"), folded into allMatchesIn.
- extension/content.js + styles.css: album links render italicised (.rsl-album) with an album tooltip; artist behavior unchanged.
- Tests: new pure-logic coverage in lib.test.js / llm.test.js / extract.test.cjs (album shaping, Map-kind routing, album candidate extraction). scripts/check.sh green.
- README.md + AGENTS.md: document album linking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:33:32 +00:00
paulandClaude Opus 4.8 ef57761859 feat: spotify-only matching + optional local-LLM gate + google→bandcamp fallback (closes task/drop-musicbrainz-google-fallback, closes task/ollama-classifier)
Source: human-feedback note 01KVDYV3A35B57N15CRBQDXPCH (project/reddit-spotify-linker,
2026-06-18) — "remove the musicbrainz integration and just use spotify"; a google search
for "<name> bandcamp" fallback; and "would a local llm be better able to categorise these
... include instructions for running something suitable via ollama (16GB M2)".

- Remove MusicBrainz entirely (viaMusicBrainz + url-rels + rate-gate; lib relUrls/mbResult).
  Spotify is now the only music API; it both gates by exact-name match and supplies the
  direct artist link.
- Optional ollama LLM gate (OLLAMA_URL enables it, OLLAMA_MODEL default qwen2.5:3b) called
  over plain HTTP -> stays zero npm-dep. It classifies which candidates are real
  artists/albums (the matching-quality lever). Pure prompt/parse logic in new llm.js
  (unit-tested in llm.test.js); the fetch + resolveAll gate wiring live in resolver.js.
- A confirmed name links to its direct Spotify page when available, else a Google search for
  "<name> bandcamp". New "google" primary platform rendered by the extension (styles.css
  blue accent + platform-aware link title in content.js).
- README: drop MusicBrainz; document the two gates + a runnable local-LLM setup for a 16GB
  MacBook M2. AGENTS.md: flip the old "no ML/NER" standing decision per the human override.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:50:07 +00:00
paulandClaude Opus 4.8 c2618ce0ea feat: cue-phrase candidate extraction + testable extract.js (task/smarter-candidate-extraction)
Catch artist names written in lowercase right after a cue phrase ("check out devourment",
"by cattle decapitation", "fan of ...") — the Title-Case/ALL-CAPS pass missed those.

- extension/extract.js (new): pure extraction (matchesIn + new cueMatchesIn + allMatchesIn),
  UMD so it loads as a content script AND require()s in node for tests. The cue pass uses the
  regex `d` flag to map matches back to positions for in-place wrapping; a small cue-stoplist
  drops "the/this/new/..." right after the cue.
- extension/content.js: extraction now comes from globalThis.RSLExtract.allMatchesIn
  (Title-Case + cue union), keeping the existing position-based wrapping.
- extension/manifest.json: load extract.js before content.js.
- extension/extract.test.cjs (new) + scripts/check.sh: 6 node:test cases for the extraction
  logic; check.sh now runs the extension tests too (16 total, green).
- README: updated the detection limitation note.

Deferred (design item 2): merging names split across inline formatting (cross-node) — more
involved; left as a follow-up.

Verified: scripts/check.sh green (16 tests). In-browser behavior is logic-verified only (no
headless Firefox); additive to the pending background-fix re-test — the [rsl] logs distinguish
"candidates found" from "resolve failed".

Source: task/smarter-candidate-extraction (objective/linker-improvements).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 06:36:28 +00:00
paulandClaude Opus 4.8 607a4ab45e fix: background-script fetch (CSP/mixed-content), prefer-Spotify links, toolbar icon (task/fix-extension-and-linkui)
The extension linked nothing and the resolver saw no requests: the content script
fetched http://localhost:8787 directly from the HTTPS reddit page, which reddit's CSP /
mixed-content blocks. Move the network call to a background script (extension origin,
exempt) and add stage logging so failures are visible.

- extension/background.js (new): does the resolver fetch on message; sets the toolbar badge.
- extension/content.js: talks to background via runtime messaging (no page-context fetch);
  [rsl] console logs at boot/root/candidates/resolve/scan; single primary link.
- extension/manifest.json: background.scripts + browser_action (icon.svg, title).
- extension/{styles.css,icon.svg}: platform-coloured link (green=spotify, teal=bandcamp) + toolbar icon.
- service/lib.js: primaryOf()/isSearchUrl() — link Spotify when the artist is on Spotify,
  else Bandcamp, else Spotify search; spotifyResult/mbResult attach `primary` (unit-tested).
- README: link behaviour, toolbar, Troubleshooting.

Verified: scripts/check.sh green (10 tests); resolver returns `primary` end-to-end. The
in-browser fix is logic-verified only (no headless Firefox here) — reload the temp add-on
and watch the page console `[rsl]` logs to confirm.

Source: project human-feedback 2026-06-16 (notes 01KV8VC58QMYBDY6VVVJHR8YNY + 01KV91AMJCN2C4GFCKMKQRGNXE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 00:36:55 +00:00
paulandClaude Opus 4.8 7c695be590 feat: direct Spotify/Bandcamp links via MusicBrainz url-relations (task/odesli-direct-links)
The Odesli/Songlink API rejects artist URLs (405 UNSUPPORTED_URL — it is song/album
only), so the planned Odesli hop is not viable for artist linking. Pivoted to
MusicBrainz `inc=url-rels`, which returns the artist's official Bandcamp + Spotify
URLs directly, zero-cred:
- service/lib.js: relUrls() extracts direct bandcamp/spotify from MB relations;
  mbResult() prefers them, falls back to search URLs per-platform (replaces searchResult).
- service/resolver.js: viaMusicBrainz now does search -> url-rels enrichment (both
  throttled through one gate); resolveOne calls it directly.
- service/lib.test.js: +3 tests (relUrls + mbResult); 9 total, green.
- README: document the direct-link behavior.

Verified zero-cred end to end: "Cattle Decapitation" -> open.spotify.com/artist/... +
cattledecapitation.bandcamp.com/ (both direct, not search); nonsense -> null.

Source: task/odesli-direct-links (objective/linker-improvements); approach changed from
Odesli to MB url-rels after the API probe; goal (direct Bandcamp) achieved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:35:33 +00:00
paulandClaude Opus 4.8 779eba6cb1 feat: reddit -> spotify/bandcamp artist linker MVP (extension + resolver service)
Firefox MV2 content script that detects artist/band names in reddit comment
threads and rewrites them inline into Spotify + Bandcamp links, backed by a tiny
zero-dependency Node resolver that confirms each candidate against the Spotify Web
API (direct links) or MusicBrainz (zero-setup, search links). Includes build +
install instructions in the README.

Source: project/reddit-spotify-linker (user 2026-06-12) + user message 2026-06-14
"Full vibe code, small service, minimal code."

Closes task/resolver-service, closes task/firefox-extension, closes task/docs-and-publish
(plan/mvp-build, objective/ship-mvp-linker).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:33:33 +00:00