Skip to content

release: v0.3.0 — playlist picker (issue #16) - #19

Merged
Kotmin merged 26 commits into
mainfrom
dev
Aug 10, 2026
Merged

release: v0.3.0 — playlist picker (issue #16)#19
Kotmin merged 26 commits into
mainfrom
dev

Conversation

@Kotmin

@Kotmin Kotmin commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Playlist picker (Ctrl+A, Shift+P): fuzzy-search overlay to add/remove the current watch-page video from playlists or create a new one, without leaving the keyboard. Gated to logged-in users; membership pre-checked; toggle/check/uncheck keys and scroll-follow configurable.
  • Playlist orchestration extracted into src/ui/playlist-controller.js, previously untested logic in content.js's closure, now covered by tests/unit/playlist-controller.test.js (20 new tests).
  • README and CONTRIBUTING updated: keyboard shortcut table, controller extraction pattern, and a new default versioning policy (minor bump for features, patch for fixes).
  • Version bumped 0.2.0 → 0.3.0 across all three manifest editions per that policy.

Test plan

  • bash scripts/test.sh — 336/336 passing
  • bash scripts/check.sh — syntax, edition-dir cleanliness, manifest parity all green
  • CI green on this PR (secret-scan)
  • Firefox/Chrome release pipelines succeed post-merge

Kotmin added 26 commits August 10, 2026 13:20
Trimmed to the relevant yt-sheet-view-model fragment (dropped the 19MB
full-page asset save) and redacted real playlist names before committing
to this public repo. Confirms the newer sheet-based popup renders all
playlists directly in the DOM with no continuation tokens, and the
create-new control lives in the sheet footer rather than the list.
Diffing an unselected vs. selected playlist row across the two captures
found aria-pressed on the row's inner button as the real locale-
independent selection signal, replacing the earlier assumption that only
locale-text aria-label was available. Also documents the create-new-
playlist footer button's structural (non-text) selector path.
Chords previously matched key case-insensitively with no shift distinction.
Introduces a separate shiftChords map matched via evt.shiftKey so
Shift+P (playlist picker, issue #16) stays unambiguous from plain p
(queue overlay) regardless of layout-dependent key casing.
Case/space-insensitive substring match plus edit-distance-2 typo
tolerance against the whole playlist name, per issue #16's resolved
match algorithm (deliberately not a full fzf-style scorer).
Scrapes/toggles the native yt-sheet-view-model "Save to playlist" sheet
off-screen (issue #16), same DOM-driving trick queue-overlay.js uses for
"Add to queue". Row/state/footer selectors are verified against the
captured DOM in docs/probes/save-to-playlist-dom-findings.md. How the
watch page's own action row opens this sheet was never captured in the
probes, so the caller supplies the trigger element rather than the
driver guessing a selector for it (flagged in docs/ai/questions-for-K.md).
browser.storage.local-backed, 5 min TTL per issue #16's resolved
default. Caches the playlist name catalog only, not per-video
membership, since aria-pressed state reflects the currently-open
video and would be wrong to serve to a different one from cache.
Pure reducer module (playlist-overlay-state.js, fully unit tested) drives
query typing, up/down highlight, space-checkbox toggling capped at 5,
enter-confirm resolution (checked set, implicit single-select, or
create-new), and the nested create-new sub-dialog per issue #16's
resolved UX. playlist-overlay.js is the DOM painter consuming that state,
left untested at the unit level same as jump-overlay.js's createJumpOverlay.
Ctrl+A,Shift+P now opens the playlist overlay: gated behind a
best-effort isLoggedIn signal, loads the catalog from the shared
cache or a live native-popup scrape, drives query/highlight/checkbox/
enter through the existing overlay-state reducer, and adds to
playlists sequentially (idempotent per row, since the native button
is a toggle) with a progress badge reused from jump-overlay's
BADGE_STYLE. Create-new is wired through driveCreateNewPlaylist.

findSaveToPlaylistTrigger stays an explicit null stub — the watch
page's native Save button was never captured in DOM probes, so the
whole feature self-heals to a silent no-op rather than driving a
fabricated selector. Tracked as the blocking open question for M7.
Extends keyboard-quickstart.md and keyboard-shortcuts.md with the
Ctrl+A,Shift+P playlist picker (behavior, config shape, shiftChords
merge semantics, verification coverage). Adds an Unreleased changelog
entry.

Files four open questions in questions-for-K.md surfaced while
building issue #16: the still-uncaptured native Save-trigger button
(blocking — feature currently no-ops on the live site), the
best-effort login-detection selector, the best-effort create-new
post-click UI assumption, and the name-collision limitation of
keying playlists by name with no stable id available.
Live DOM probe of the watch page's action row found the real trigger:
Save is the sole yt-button-view-model-wrapped child of
#flexible-item-buttons, distinct from Download's wrapper. Replaces the
null stub that kept Ctrl+A, Shift+P inert, resolving Q13.
The logged-out path silently no-op'd, giving no feedback that the
chord was even recognized. Reuses the existing badge pattern to tell
the user to sign in instead.
The sheet was genuinely visible on screen for the whole add/remove
sequence, not just briefly. Hides it via inline style as soon as rows
are found, instead of relying solely on the unverified Escape-close
to make it disappear afterward.
The picker never showed which playlists the video was already in
(loadPlaylistCatalog dropped the scraped selected flag, and the
cache-hit path skipped scraping entirely). Now always scrapes fresh,
pre-checks membership, and unchecking a pre-checked row on confirm
removes the video from that playlist instead of only ever adding.
hideOpenSheet's !important overrides were never undone, so YouTube's
reused sheet DOM node stayed permanently hidden after the first
drive, breaking the native Save button on subsequent opens.
Escape wasn't reliably closing YouTube's Save-to-playlist sheet,
leaving it open on screen at the end of a drive sequence. Close now
polls for the sheet actually disappearing and re-clicks the trigger
button as a fallback instead of trusting Escape blindly.
Toggle every row in a single open/close session instead of N
open+verify-close round trips, and skip re-clicking an already-open
trigger (which was toggling it shut and returning stale rows on a
same-cycle reopen). Also stop unhiding the sheet before confirming
close, so the verification wait is no longer visible on screen.
…scroll

Space sometimes leaks through to YouTube's own play/pause, so the
playlist overlay's toggle key is now sourced from
shortcuts.config.json (playlistKeys.toggle) instead of hardcoded.
Adds ArrowRight/ArrowLeft as a non-conflicting check/uncheck
alternative, also configurable. The highlighted row now scrolls into
view as the selector moves past the panel's visible area.

Issue #16.
…ield

Create-new never worked live: the native "Create new playlist" click
opens a separate yt-dialog-view-model with a <textarea> title field,
not the <input>/[contenteditable] the driver looked for inside the
original sheet, so nothing was ever typed and the empty dialog was
left sitting open. Submits via the dialog's own primary button
instead of an Enter keydown, since Enter just inserts a newline in a
textarea. Leaves the visibility dropdown untouched (defaults to
Private already).

Issue #16.
Creating a playlist worked but left the native dialog visibly open.
Submitting is async so a click doesn't guarantee it closes itself in
time; verifies the close the same way as the save-to-playlist sheet
(poll, then fall back to Escape) rather than trusting the click.

Issue #16.
Escape didn't close the create-playlist dialog either (reported live
after the previous fix), same unreliability already seen with the
save-to-playlist sheet. Falls back to clicking the dialog's own
Cancel button instead of a synthetic keydown.

Issue #16.
createNewPlaylistOnSite closed the save-to-playlist sheet then
addVideoToPlaylists immediately reopened it to check the new playlist,
since the new entry always lands in toAdd. That close/reopen race left
the sheet visibly stuck open. Only close here on failure now; success
leaves it hidden-but-open for the immediate reopen to reuse.
content.js's IIFE closure held loadPlaylistCatalog/addVideoToPlaylists/
createNewPlaylistOnSite/handlePlaylistKey with zero test coverage - every
behavior fix in this area (30ef16b, 7cf4888, 7e9fc6a, a41f324, ...) only
got verified by a live YouTube re-test. Moved into
src/ui/playlist-controller.js as a doc/win-injectable factory, same
pattern as playlist-popup-driver.js and queue-overlay.js; content.js now
just constructs it and forwards events. Behavior is unchanged.

20 new tests pin down the sequences that took multiple live-debug rounds
to get right: idempotent per-row toggling, one open/close per add batch,
and the close-only-on-failure fix from 30ef16b so a create-then-add
reopen doesn't race a redundant close.
…attern

README's shortcut table never got the queue (Ctrl+A p) or playlist
picker (Ctrl+A Shift+P) chords that docs/keyboard-quickstart.md already
documented since they shipped. CONTRIBUTING's Tests section now points
new DOM-driving code at src/ (doc/win-injectable factory) instead of
content.js's closure, per the gap playlist-controller.js just fixed.
Formalizes the default: minor bump for features/shortcuts/layout
changes, patch bump for fixes. Referenced when scoping release PRs.
Playlist picker (issue #16) is fully live and covered by unit tests;
per the versioning policy this is a minor bump.
@Kotmin
Kotmin merged commit 055d86a into main Aug 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant