From ceb3d0255e00c8c78890d4024efb3ee7bf1ad7c8 Mon Sep 17 00:00:00 2001 From: Kotmin <70173732+Kotmin@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:30:02 +0200 Subject: [PATCH 1/3] fix(queue-overlay): match menuitem role on popup item's inner button YouTube moved role="menuitem" off yt-list-item-view-model onto its inner button/anchor (wrapper is now role="presentation"), so the queue popup opened but the "Add to queue" item was never found, silently no-opping. Select the descendant instead of the wrapper. --- docs/probes/add-to-queue-dom-findings.md | 22 ++++++++++++++++++++++ src/ui/queue-overlay.js | 7 +++++-- tests/fixtures/youtube-watch-basic.html | 4 +++- tests/unit/queue-overlay.test.js | 2 +- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/docs/probes/add-to-queue-dom-findings.md b/docs/probes/add-to-queue-dom-findings.md index 9ae9ab6..53dd2e8 100644 --- a/docs/probes/add-to-queue-dom-findings.md +++ b/docs/probes/add-to-queue-dom-findings.md @@ -292,3 +292,25 @@ genuinely never replaces the node — it falls back to clicking whatever's there pre-existing behavior rather than failing outright. Covered by two new unit tests in `tests/unit/queue-overlay.test.js`: one simulating a poll that returns the stale node for a couple of ticks before the fresh one appears, one simulating a node that never changes at all. + +## Follow-up: YouTube moved `role="menuitem"` off the item wrapper (2026-08-16) + +User reported the queue overlay regressing to a no-op: the "..." trigger still opened a popup +(visible only as a brief touch-feedback ripple), but no video was ever added. Captured a fresh +live DOM (`playwright` launched directly per [[feedback_firefox_testing]] — the `mcp__playwright__*` +tools still fail to launch a browser in this sandbox) against a real watch page. + +Root cause: `yt-list-item-view-model`, which previously carried `role="menuitem"` itself, now +carries `role="presentation"` — the role moved to its inner interactive child +(` + diff --git a/tests/unit/queue-overlay.test.js b/tests/unit/queue-overlay.test.js index ea165c3..bb9bf34 100644 --- a/tests/unit/queue-overlay.test.js +++ b/tests/unit/queue-overlay.test.js @@ -77,7 +77,7 @@ describe('activateQueueTarget', () => { querySelector: (sel) => { assert.equal( sel, - 'ytd-popup-container yt-list-item-view-model[role="menuitem"], ' + 'ytd-popup-container yt-list-item-view-model [role="menuitem"], ' + 'ytd-popup-container ytd-menu-service-item-renderer', ); queried = true; From cbd91d215498c2c552f1f831a9b24fe0165f3398 Mon Sep 17 00:00:00 2001 From: Kotmin <70173732+Kotmin@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:34:49 +0200 Subject: [PATCH 2/3] feat(playlist): add best-effort Shorts fallback for save-to-playlist trigger findSaveToPlaylistTrigger only looked for the /watch page's action-row button, so Ctrl+A Shift+P silently no-op'd on the Shorts standalone player (issue #20). A captured Shorts-page JSON snapshot confirms YouTube offers "Save to playlist" there via the header "..." menu, so add ytd-reel-player-header-renderer ytd-menu-renderer button as a fallback, derived from JSON renderer names and this codebase's established naming convention but not yet confirmed against a live DOM. --- README.md | 2 +- docs/ai/questions-for-K.md | 24 ++++++++++ docs/keyboard-quickstart.md | 2 +- docs/probes/save-trigger-dom-findings.md | 46 +++++++++++++++++++ .../youtube/youtube-site-adapter.js | 19 +++++++- tests/fixtures/youtube-shorts-basic.html | 29 ++++++++++++ tests/unit/youtube-site-adapter.test.js | 24 +++++++++- 7 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/youtube-shorts-basic.html diff --git a/README.md b/README.md index 11f6fc9..5cea55e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Press `Ctrl+A`, then a command key (tmux-style prefix — outside that window ev | --- | --- | | `Ctrl+A` `o` | Open jump overlay (labels clickable elements) | | `Ctrl+A` `p` | Open queue overlay (add a video to the Up Next queue) | -| `Ctrl+A` `Shift+P` | Open playlist picker for the current video (fuzzy-search, multi-add, create new) — watch page only, requires being logged in | +| `Ctrl+A` `Shift+P` | Open playlist picker for the current video (fuzzy-search, multi-add, create new) — watch page, and Shorts on a best-effort basis, requires being logged in | | `Ctrl+A` `y` | Go home | | `Ctrl+A` `v` | Set speed to preset 1 (default `1x`) | | `Ctrl+A` `b` | Set speed to preset 2 (default `1.5x`) | diff --git a/docs/ai/questions-for-K.md b/docs/ai/questions-for-K.md index 1bcce8e..4649fbe 100644 --- a/docs/ai/questions-for-K.md +++ b/docs/ai/questions-for-K.md @@ -162,3 +162,27 @@ to whichever matches first). signal was found in probes to key on instead. Flag if this turns out to matter in practice (e.g. your account actually has duplicate-named playlists). + +--- + +Dated 2026-08-16. Surfaced while implementing issue #20 (Shorts playlist- +picker gap). + +## Q17 — Shorts "Save to playlist" trigger selector, JSON-inferred only + +`findSaveToPlaylistTrigger` now falls back to +`ytd-reel-player-header-renderer ytd-menu-renderer button` when the +existing `/watch`-page selector finds nothing, to cover the Shorts +standalone player (`/shorts/`). This is derived only from a captured +Shorts-page JSON snapshot's renderer names (`reelPlayerHeaderRenderer`, +`menuRenderer`, both confirming a "Save to playlist" item) plus this +codebase's own already-confirmed `` -> `ytd-foo-renderer` +naming convention — it has **not** been checked against a live/rendered +Shorts DOM (no working headless browser in this sandbox this session +either; see `docs/probes/save-trigger-dom-findings.md` follow-up section +for the exact reasoning and what would falsify it). +**Default:** ship the selector above. If wrong, same failure mode as every +other best-effort selector in this file: `findSaveToPlaylistTrigger` +returns `null`, `openPlaylistOverlay` no-ops, safe no-op with no user- +visible error, just `Shift+P` staying silently unavailable on Shorts. +Please confirm the real selector live when convenient. diff --git a/docs/keyboard-quickstart.md b/docs/keyboard-quickstart.md index 5e68569..ee38a33 100644 --- a/docs/keyboard-quickstart.md +++ b/docs/keyboard-quickstart.md @@ -13,7 +13,7 @@ want via the extension's stored settings, same as any other override. | `Ctrl+A` | Prefix — arms the next key as a command (default binding, configurable) | Global | | `Ctrl+A` then `o` | Open jump overlay (labels clickable elements) | Global | | `Ctrl+A` then `p` | Open queue overlay (labels videos with an "Add to queue" option; typing a label adds that video next) | Global | -| `Ctrl+A` then `Shift+P` | Open playlist picker for the current video (fuzzy-search playlists, checkbox multi-add, create new) — watch page only, requires being logged in | Watch page | +| `Ctrl+A` then `Shift+P` | Open playlist picker for the current video (fuzzy-search playlists, checkbox multi-add, create new) — requires being logged in | Watch page, and Shorts on a best-effort basis (see issue #20) | | `Ctrl+A` then `y` | Go home (click YouTube logo, or navigate to configured home URL) | Global | | `Ctrl+A` then `v` | Set playback speed to preset 1 (default `1×`) | Global | | `Ctrl+A` then `b` | Set playback speed to preset 2 (default `1.5×`) | Global | diff --git a/docs/probes/save-trigger-dom-findings.md b/docs/probes/save-trigger-dom-findings.md index f876d30..f8ad395 100644 --- a/docs/probes/save-trigger-dom-findings.md +++ b/docs/probes/save-trigger-dom-findings.md @@ -67,3 +67,49 @@ Live capture only (headless Firefox via the `playwright` devDependency, run as a throwaway script, not committed) — no HTML fragment saved alongside this doc since the page contains no personal/account data in the logged-out state captured. + +## Follow-up 2026-08-16: Shorts trigger, JSON-inferred only (issue #20) + +Resolves the "Shorts / other layouts" gap flagged above. A captured live +JSON snapshot of a real Shorts URL (`docs/probes/*short_page.html`) +confirms Shorts genuinely offers "Save to playlist" — the JSON path +`overlay.reelPlayerOverlayRenderer.reelPlayerHeaderSupportedRenderers +.reelPlayerHeaderRenderer.menu.menuRenderer.items` contains a +`listItemViewModel` with `title.content == "Save to playlist"`, alongside +"Description", "Ambient mode", etc. This is the "..." menu near the +channel name/timestamp at the top of the Shorts player, not the right-hand +like/comment/share/remix action rail — that rail (`reelActionBarViewModel` +in the same JSON) only exposes those four actions and has no menu. + +`reelPlayerHeaderRenderer` and the sibling `menuRenderer` are both +`Renderer`-suffixed (legacy, non-"ViewModel") JSON node types. This +codebase has already confirmed, independently, that this JSON node family +maps 1:1 to a `` custom element (see +`ytd-menu-service-item-renderer`, `ytd-video-renderer` in +`src/ui/queue-overlay.js`, and `ytd-menu-renderer` in the "Action row +structure" section above). Combined with the "..." trigger being the +extremely common, well-established `ytd-menu-renderer button` pattern +already used elsewhere in this exact codebase (see above), the selector +shipped for `findSaveToPlaylistTrigger`'s Shorts fallback is: + +``` +ytd-reel-player-header-renderer ytd-menu-renderer button +``` + +**Still unverified — this is JSON-inferred, not live-DOM-confirmed.** No +headless browser worked in this sandbox for this session either (same +`navigator.webdriver` / hydration ceiling documented in +`add-to-queue-dom-findings.md`). What would falsify this: + +- `ytd-reel-player-header-renderer` not existing as a real custom element + tag at all (the naming convention breaking for this particular renderer). +- The header's "..." trigger not being wrapped in `ytd-menu-renderer`, or + not being a plain ` + + + + + + + + + + + + + + diff --git a/tests/unit/youtube-site-adapter.test.js b/tests/unit/youtube-site-adapter.test.js index 79fdaf7..f5f3a7a 100644 --- a/tests/unit/youtube-site-adapter.test.js +++ b/tests/unit/youtube-site-adapter.test.js @@ -105,6 +105,7 @@ describe('isLoggedIn', () => { describe('findSaveToPlaylistTrigger', () => { const SAVE_SELECTOR = '#flexible-item-buttons > yt-button-view-model button[aria-label]'; + const SHORTS_SELECTOR = 'ytd-reel-player-header-renderer ytd-menu-renderer button'; it('returns the button matching the Save wrapper selector', () => { const btn = {}; @@ -112,10 +113,31 @@ describe('findSaveToPlaylistTrigger', () => { assert.equal(findSaveToPlaylistTrigger(doc), btn); }); - it('returns null when the Save wrapper is absent', () => { + it('returns null when neither the watch-page nor the Shorts selector matches', () => { const doc = { querySelector: () => null }; assert.equal(findSaveToPlaylistTrigger(doc), null); }); + + it('prefers the watch-page selector over the Shorts fallback when both match', () => { + const watchBtn = {}; + const shortsBtn = {}; + const doc = { + querySelector: (sel) => { + if (sel === SAVE_SELECTOR) return watchBtn; + if (sel === SHORTS_SELECTOR) return shortsBtn; + return null; + }, + }; + assert.equal(findSaveToPlaylistTrigger(doc), watchBtn); + }); + + it('falls back to the Shorts header menu button when the watch-page selector finds nothing', () => { + const shortsBtn = {}; + const doc = { + querySelector: (sel) => (sel === SHORTS_SELECTOR ? shortsBtn : null), + }; + assert.equal(findSaveToPlaylistTrigger(doc), shortsBtn); + }); }); function makeWindow(href) { From 52dfef3064cb218dd0ecdcf6901c98b7f82a043e Mon Sep 17 00:00:00 2001 From: Kotmin <70173732+Kotmin@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:36:46 +0200 Subject: [PATCH 3/3] docs(changelog): note queue-overlay and Shorts save-trigger fixes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f4bca4..087ea84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html) ## [Unreleased] +### Fixed +- Queue overlay ("Add to queue") stopped working everywhere: YouTube moved `role="menuitem"` off the popup item wrapper onto its inner button/link, so the item was never found after the trigger opened the menu. +- Playlist picker (`Ctrl+A, Shift+P`) no-op'd on the Shorts standalone player; it now also checks the Shorts header "..." menu for the Save-to-playlist trigger (best-effort, not yet live-confirmed — see `docs/ai/questions-for-K.md`). + ## [0.3.0] - 2026-08-10 ### Added