feat: channel blocklist, search duration filter, feed-wide live filtering #51

Merged
365DevNet merged 0 commits from refs/pull/51/head into main 2026-08-03 22:02:36 +02:00
365DevNet commented 2026-08-03 21:57:41 +02:00 (Migrated from codeberg.org)

Summary

Three gaps found while surveying the existing ~90 settings, plus the audit follow-up from #50. No version bump — nothing is being packaged in this PR, so the CHANGELOG entry sits under ## Unreleased.

Channel blocklist (Dashboard → Blocked channels)

There genuinely was no way to block a channel. The only workaround was the Keyword Blocklist, which matches title and channel in a single string — so blocking a channel called "Vox" also hid every video with "vox" anywhere in its title. That's why it felt like the feature existed: typing a channel name mostly works, right up until a title collision eats something unrelated.

This matches the channel itself via name, @handle, or URL, reusing the existing getFeedItemChannelIdentifiers() resolution. It applies everywhere including search — a blocked channel is an explicit "never show me this", not a feed-tidiness preference — and wins over the Trusted channels allowlist if a channel somehow lands on both.

Search duration filter (off by default)

A range control above the search results: "videos between 30 and 45 minutes", which YouTube's own filter cannot express — it offers only under 4 min, 4–20 min, or over 20 min.

Search-only, deliberately. As a standing home-feed rule a narrow band would hide almost the entire feed; a range like 30–45 is a discovery query, not a preference. Duration comes from the thumbnail time badge, which is numeric and language-independent — no relative-date guessing and no localization problem. Items with no duration (live, upcoming, playlists, channel rows) are never hidden.

Live / premiere / upcoming on every feed

These were CSS-scoped to ytd-browse[page-subtype="subscriptions"]. Moved to Home & Feeds → Feed filtering, labels now read "(all feeds)".

⚠️ Behaviour change: anyone who already has either toggle on will now have those videos hidden everywhere, not just on Subscriptions. That matches the intent of the setting, but it is a change on upgrade. Storage keys keep their historical hideSubscriptions* names so existing synced settings need no migration — the mismatch between key name and scope is documented at both the CSS and the settings definition.

Audit + tests (60 → 68)

Added the single-selector features to the audit registry: watched-progress (which hangs off .ytThumbnailOverlayProgressBarHostWatchedProgressBarSegment, one long private class name), the duration badge, the live/upcoming overlays, and the search results host.

Fixed a TDZ crash I introduced: the audit registry referenced selector constants declared after it in defaults.js, which would have thrown on load in the browser and taken the whole content script down. node --check passes on that file happily — it only surfaced because I ran it. Worth remembering that syntax-checking a file proves very little about load order.

The audit test also caught the DOM fixture being unrepresentative twice — first no video titles, then no duration badges. An unrepresentative fixture is worse than no fixture, so that guard is doing real work.

Review notes

  • Two managed-hide keys on one element was again the subtle risk: applyMembershipFeedFilters now skips elements the blocklist already claimed, otherwise the second key records display: none as the element's "original" and the item never comes back. Same class of bug as in #49.
  • The duration filter re-evaluates only when the range changes. The main observer pass runs on every mutation; a blanket clear-and-reapply would unhide and rehide results continuously.
  • The injected control is new territory — it is the first UI CleanWatch adds to a YouTube page rather than hiding. It falls back through ytd-search ytd-section-list-rendererytd-search#page-manager if YouTube reshuffles the search chrome, and is in the audit registry.

Test plan

  • npm test — 68/68
  • node --check on every touched script, plus actually loading defaults.js (which is what caught the TDZ)
  • i18n: new labels, hints, dashboard card and on-page control strings resolve in EN/NL/DE/FR
  • Load unpacked: block a channel, confirm it disappears from home, related and search
  • Search page: confirm the control renders, filters, persists across queries, and vanishes when the setting is off
  • Confirm the live/upcoming scope change looks right on home rather than over-hiding
  • __CW_AUDIT_SELECTORS__() on home, watch and search
## Summary Three gaps found while surveying the existing ~90 settings, plus the audit follow-up from #50. No version bump — nothing is being packaged in this PR, so the CHANGELOG entry sits under `## Unreleased`. ## Channel blocklist (Dashboard → Blocked channels) There genuinely was no way to block a channel. The only workaround was the Keyword Blocklist, which matches title **and** channel in a single string — so blocking a channel called "Vox" also hid every video with "vox" anywhere in its title. That's why it *felt* like the feature existed: typing a channel name mostly works, right up until a title collision eats something unrelated. This matches the channel itself via name, `@handle`, or URL, reusing the existing `getFeedItemChannelIdentifiers()` resolution. It applies **everywhere including search** — a blocked channel is an explicit "never show me this", not a feed-tidiness preference — and wins over the Trusted channels allowlist if a channel somehow lands on both. ## Search duration filter (off by default) A range control above the search results: *"videos between 30 and 45 minutes"*, which YouTube's own filter cannot express — it offers only under 4 min, 4–20 min, or over 20 min. **Search-only, deliberately.** As a standing home-feed rule a narrow band would hide almost the entire feed; a range like 30–45 is a discovery query, not a preference. Duration comes from the thumbnail time badge, which is numeric and language-independent — no relative-date guessing and no localization problem. Items with no duration (live, upcoming, playlists, channel rows) are never hidden. ## Live / premiere / upcoming on every feed These were CSS-scoped to `ytd-browse[page-subtype="subscriptions"]`. Moved to Home & Feeds → Feed filtering, labels now read "(all feeds)". ⚠️ **Behaviour change:** anyone who already has either toggle on will now have those videos hidden everywhere, not just on Subscriptions. That matches the intent of the setting, but it is a change on upgrade. Storage keys keep their historical `hideSubscriptions*` names so existing synced settings need no migration — the mismatch between key name and scope is documented at both the CSS and the settings definition. ## Audit + tests (60 → 68) Added the single-selector features to the audit registry: watched-progress (which hangs off `.ytThumbnailOverlayProgressBarHostWatchedProgressBarSegment`, one long private class name), the duration badge, the live/upcoming overlays, and the search results host. **Fixed a TDZ crash I introduced:** the audit registry referenced selector constants declared *after* it in `defaults.js`, which would have thrown on load in the browser and taken the whole content script down. `node --check` passes on that file happily — it only surfaced because I ran it. Worth remembering that syntax-checking a file proves very little about load order. The audit test also caught the DOM fixture being unrepresentative **twice** — first no video titles, then no duration badges. An unrepresentative fixture is worse than no fixture, so that guard is doing real work. ## Review notes - **Two managed-hide keys on one element** was again the subtle risk: `applyMembershipFeedFilters` now skips elements the blocklist already claimed, otherwise the second key records `display: none` as the element's "original" and the item never comes back. Same class of bug as in #49. - **The duration filter re-evaluates only when the range changes.** The main observer pass runs on every mutation; a blanket clear-and-reapply would unhide and rehide results continuously. - **The injected control is new territory** — it is the first UI CleanWatch adds to a YouTube page rather than hiding. It falls back through `ytd-search ytd-section-list-renderer` → `ytd-search` → `#page-manager` if YouTube reshuffles the search chrome, and is in the audit registry. ## Test plan - [x] `npm test` — 68/68 - [x] `node --check` on every touched script, plus actually loading `defaults.js` (which is what caught the TDZ) - [x] i18n: new labels, hints, dashboard card and on-page control strings resolve in EN/NL/DE/FR - [ ] Load unpacked: block a channel, confirm it disappears from home, related and search - [ ] Search page: confirm the control renders, filters, persists across queries, and vanishes when the setting is off - [ ] Confirm the live/upcoming scope change looks right on home rather than over-hiding - [ ] `__CW_AUDIT_SELECTORS__()` on home, watch and search
365DevNet commented 2026-08-03 22:01:12 +02:00 (Migrated from codeberg.org)

Update — this PR now also cuts the release and refreshes the site download.

The original description said "no version bump — nothing is being packaged in this PR". That is no longer accurate; please read this comment as superseding it.

Added in 1083fcc:

  • v1.18.0manifest.json + package.json. Minor bump because this branch adds user-facing features (channel blocklist, search duration filter, feed-wide live filtering), matching the 1.15.0 → 1.16.0 convention for SponsorBlock. Without it the download would have gone out labelled 1.17.0 while containing features 1.17.0 does not have — same version string, different contents.
  • CHANGELOG## Unreleased## 1.18.0.
  • website/downloads/cleanwatch-latest.zip rebuilt via npm run build:store.

Zip verified: manifest reports 1.18.0, 30 files, manifest.json at the zip root, byte-identical to CleanWatch-store-upload.zip, and no test/, node_modules/, website/, package*.json, scripts/ or .DS_Store leakage.

No website markup changes were needed — index.html links the fixed path /downloads/cleanwatch-latest.zip, so the refreshed file is picked up automatically.

npm test still 68/68. The manual browser checks in the test plan above remain outstanding.

**Update — this PR now also cuts the release and refreshes the site download.** The original description said "no version bump — nothing is being packaged in this PR". That is no longer accurate; please read this comment as superseding it. Added in `1083fcc`: - **v1.18.0** — `manifest.json` + `package.json`. Minor bump because this branch adds user-facing features (channel blocklist, search duration filter, feed-wide live filtering), matching the 1.15.0 → 1.16.0 convention for SponsorBlock. Without it the download would have gone out labelled 1.17.0 while containing features 1.17.0 does not have — same version string, different contents. - **CHANGELOG** — `## Unreleased` → `## 1.18.0`. - **`website/downloads/cleanwatch-latest.zip` rebuilt** via `npm run build:store`. Zip verified: manifest reports `1.18.0`, 30 files, `manifest.json` at the zip root, byte-identical to `CleanWatch-store-upload.zip`, and no `test/`, `node_modules/`, `website/`, `package*.json`, `scripts/` or `.DS_Store` leakage. No website markup changes were needed — `index.html` links the fixed path `/downloads/cleanwatch-latest.zip`, so the refreshed file is picked up automatically. `npm test` still 68/68. The manual browser checks in the test plan above remain outstanding.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
365DevNet/CleanWatch!51
No description provided.