[web-ext]: add-audio-mode-and-revamp-popup-and-options - #79
Open
DarhkVoyd wants to merge 14 commits into
Open
Conversation
DarhkVoyd
force-pushed
the
claude/exciting-keller-a18866
branch
from
May 14, 2026 21:37
f2054cb to
dfa60f6
Compare
DarhkVoyd
force-pushed
the
claude/exciting-keller-a18866
branch
from
May 17, 2026 21:32
d039cdb to
b3e383c
Compare
| if (currentCustomImageUrl) { | ||
| // Wrap in double quotes; CSS.escape() is for identifiers (class | ||
| // names/selectors) and would mangle the URL's : ; / + = characters. | ||
| const escapedUrl = currentCustomImageUrl.replace(/"/g, '\\"'); |
DarhkVoyd
force-pushed
the
claude/exciting-keller-a18866
branch
from
June 21, 2026 11:48
0074568 to
3aa7a07
Compare
clean up wait wait feat: Segments — multi-segment playback sequencer (#102) Replaces the single loop-segment system with a fully extensible Segments engine. Users can define multiple time-range markers on any video and configure exactly how they play back. Core capabilities - Multiple segments per video, each shown as a colored pentagon marker pair on the YouTube progress bar - Per-segment loop count (0 = infinite) and playback rate override - Per-iteration rate overrides: [1.0, 1.5] plays 1× first, 1.5× second - Advanced Sequence editor: compose steps with any custom segment order; duplicates allowed — [seg3, seg3, seg1] plays seg3 twice then seg1 - Segment merge: hold adjacent markers within 1.5% for 500 ms → merge with pulse animation Save tiers - Last-used: auto-updated volatile slot, never requires explicit save - Default slot: one-click quick save - Named configs: unlimited, each with an optional keyboard shortcut for instant activation (e.g. Ctrl+1) Keyboard shortcuts - Z — load last-used config / toggle off (replaces toggleLoopSegment) - Shift+Z — always start a fresh slate (full video, infinite loop) - Q / E — set start / end of the active segment (one containing playhead) - Shift+Q / Shift+E — nudge with acceleration (unchanged) - Named config combos — user-assignable per saved config New modules (SOLID boundaries) - utils/segments/types.ts — data types only - utils/segments/factories.ts — pure config factory functions - utils/segments/engine.ts — playback state machine, no DOM - utils/segments/markers.tsx — DOM marker controller, N colored pairs - utils/storage/segmentStore.ts — IndexedDB CRUD + transparent migration - components/SegmentButton.tsx — control bar button - components/SegmentPanel.tsx — below-video panel (simple + advanced) - components/Segments.tsx — coordinator; public API for watch.tsx Migration Existing SavedLoopSegment records are transparently converted to a single-segment SegmentConfig on first access. IndexedDB bumped to v4 with a new `segment_data` object store. Backward compat All existing preference keys (toggleLoopSegment, setLoopSegmentBegin, setLoopSegmentEnd, nudgeLoopSegment.*) are preserved unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> segment fixes marker update changes updates
DarhkVoyd
force-pushed
the
claude/exciting-keller-a18866
branch
from
June 21, 2026 11:49
3aa7a07 to
1774554
Compare
remove
DarhkVoyd
force-pushed
the
claude/exciting-keller-a18866
branch
from
June 21, 2026 11:50
1774554 to
9e63c09
Compare
Comment on lines
+24
to
+31
| import { | ||
| setSidebarVisible, | ||
| setCommentsVisible, | ||
| setEndCardsVisible, | ||
| resetSidebar, | ||
| resetComments, | ||
| resetEndCards, | ||
| } from "../../primitives/watch"; |
| "Show or hide the right-side recommendations", | ||
| !sidebarEl || sidebarEl.style.display !== "none", | ||
| async (next) => { | ||
| const { setSidebarVisible, resetSidebar } = await import( |
| @@ -0,0 +1,249 @@ | |||
| import React, { useEffect, useState } from "react"; | |||
| import { Link } from "react-router-dom"; | |||
Colocate docs UI under app/docs, consolidate site config into constants/site.ts, flatten route data files, and remove the old website/lib layer. update
DarhkVoyd
force-pushed
the
claude/exciting-keller-a18866
branch
from
June 23, 2026 06:15
cea95b3 to
111c312
Compare
| @@ -0,0 +1,27 @@ | |||
| import type { Metadata } from "next"; | |||
| import DocsPageHeader from "./components/DocsPageHeader"; | |||
…a files - Move hooks/ into core/ (useSyncStore, useLocalStorage, useCapabilityCache, useScope, useTheme) - Inline useVideoPreference into AudioMode.tsx (single caller, 3 lines) - Merge data/contract.ts + data/extension.ts into data/core.ts - Move data/extension.ts constants into core/ then into data/core.ts - Delete data/player.ts; export PlayerLayout/PlayerVisuals from data/profiles.ts - Fix stale ./contract import in data/store.ts - Rename useChromeStorageLocal → useLocalStorage, useStoreUpdater → useSyncStore
…E_IDB, consistent hooks - Centralise all chrome.storage.local keys into CHROME_STORAGE_LOCAL in data/core.ts - Add BROWSER_STORAGE_IDB to data/core.ts; drop scattered store name exports from indexedDb.ts - Rename useChromeLocal → useChromeStorageLocal, useSyncStore → useChromeStorageSync - Dissolve context/ directory; move StoreContext into core/storeContext.tsx - Fix popup/App.tsx to use getAllProfiles/getActiveProfile from core/profileStore instead of raw local reads - Replace all raw "toppings:*" string literals with CHROME_STORAGE_LOCAL constants
- CHROME_STORAGE_LOCAL → CHROME_STORAGE_LOCAL_KEY - BROWSER_STORAGE_IDB → BROWSER_STORAGE_IDB_STORE
The guard checked three conditions, two of which were unreachable: the recursion only descended into non-null plain objects, so `defaults` could never be nullish or a primitive. Recursing unconditionally lets the guard handle primitives and arrays itself, removing the duplicated type checks in the loop. Also iterate `defaults` keys instead of `stored` keys. Previously any key in storage passed through untouched, and `syncStorageWithDefaults` writes the merge back, so settings removed from DEFAULT_STORE would live in chrome.storage.sync forever. Safe here: DEFAULT_STORE is a static shape and every storage.sync.set in the repo writes that shape. Extracts isPlainObject as an exported guard and adds bun tests covering fallback, array replacement, unknown-key dropping, and type mismatches.
Root AGENTS.md now holds only what applies repo-wide and delegates project-specific rules to AGENTS/<scope>.md, so an agent reads the hub plus the one scope it touches. Previously the root carried a three-line repo layout and two sections that both said "read this file". The comment policy is the substance of this change and lives in the root so every agent reads it: code explains what and how through names and execution order, so a comment only earns its place when it answers why — a decision, constraint, or external cause the code cannot state. Applies on-touch rather than as a sweep mandate. AGENTS/web-ext.md is the first scoped file: commands, the four webpack entries, the React vs dom-chef split, the three storage layers and how mergeDefaults treats DEFAULT_STORE as schema, the MV3-to-MV2 manifest transform, and the background-dispatched navigation model.
…tions
Strips comments that restate the code — section banners, JSDoc repeating the
signature, and narration of the next line — from the twelve densest files.
What survives is the why: YouTube DOM constraints, ordering requirements,
magic-value encodings, and the reasons behind deliberate deviations.
Three comments were deleted as actively wrong rather than merely redundant:
- Segments.tsx claimed markers must not be rebuilt in the panel change
handler while the call passes skipMarkerRebuild = false, which rebuilds them.
- engine.ts documented getSegmentAt tie-breaking as nearest end time; the
code compares distance to the segment midpoint.
- markers.tsx carried two geometry blocks that contradicted each other, one
of which also contradicted the transform it sat above.
Comments dropped from key bindings ("// Z — ...") because those bindings are
user-configurable and the labels are already wrong for anyone who rebound them.
No behaviour change: bun run build passes at every step.
Second and final pass, covering profiles, audio mode, primitives, options routes, form components and the small utils. Same rule as the first pass: a comment stays only when it answers why — an external constraint, an ordering requirement, a magic-value encoding, or the reason a deliberate deviation exists. Notable keeps, rewritten rather than removed: the CSS.escape hazard in the audio-mode custom background, the createMediaElementSource routing constraint in the visualizer, the forced reflow in pageToast, the stuck tooltip on DOM-mutating clicks, and the search index's coupling to rendered UI text. Also drops a comment in watch.tsx claiming getAllProfiles returns only custom profiles. It returns presets plus custom, which means cycleProfilesShortcut prepends BUILT_IN_PRESETS to a list that already contains them and every preset appears twice in the cycle. The bug is left in place to be fixed on its own; only the comment hiding it is gone. web-ext comment density is now 330 of 13,256 lines (2.5%), down from 1,306 of 14,314 (9.1%). SegmentPanel.tsx, background/context.ts and utils/indexedDb.ts are untouched — they carry uncommitted work. bun run build passes; bun test utils/object.test.ts passes.
The cycle was built as [null, ...BUILT_IN_PRESETS, ...getAllProfiles()], but getAllProfiles already returns the presets followed by the custom profiles. Each preset therefore appeared twice, so the shortcut ran Default, Audio, Focus, Audio, Focus, then the user's own profiles, and two presses in a row could land on the same profile. Switches to getCustomProfiles, which returns only the user's profiles and leaves the explicit [none, presets, custom] ordering intact. Other getAllProfiles callers are unaffected: the popup, the gear menu and the native settings overlay render the returned list directly, and the options Profiles page partitions it on isPreset.
| import { withStore } from "../../../utils/indexedDb"; | ||
| import { BROWSER_STORAGE_IDB_STORE } from "../../../data/core"; | ||
| import type { VideoSegmentData, SegmentConfig, SegmentAutoLoadPin } from "./types"; | ||
| import { createFreshConfig } from "./factories"; |
| import { useToast } from "../../../components/feedback/ToastProvider"; | ||
| import { useCapabilityCache } from "../../../core/useCapabilityCache"; | ||
| import { setExtensionIcon } from "../../../utils/browser"; | ||
| import { ThemePreference } from "../../../core/useTheme"; |
Comment on lines
+21
to
+26
| import { | ||
| BUILT_IN_PRESETS, | ||
| type Profile, | ||
| type ProfilePrimitiveConfig, | ||
| type ThumbnailMode, | ||
| } from "../../../data/profiles"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
This PR introduces Audio Mode for the YouTube watch page, then revamps the popup and options page on top of a new shared component library and theme system.
Bshortcut that hides the video and plays audio-only, with three screen modes (Black, Visualizer, Custom Image). Includes a full custom UI overlay with title, channel, play/pause, progress bar, volume, mode switcher, per-video pin, and explicit close button. Persists across SPA navigation. Per-video pins stored inchrome.storage.local. Visualizer sensitivity is configurable. Auto-disables during ads.chrome.storage.local.prefers-color-scheme.options/tosrc/shared/so popup, options, and any future surface share primitives, form, feedback (Popup → ActionPopup → ConfirmPopup), layout, hooks, and utils.src/shared/features.ts) is a single source of truth for feature metadata + "What's New" feed; popup renders from it.web-ext/CLAUDE.md) document the new layout, theming, features-registry contract, and storage split.Why is this change necessary?
The original popup was a static welcome screen; the options page was a single monolithic file with hardcoded colors and limited components, and the watch page had no audio-only mode for users who want to listen without the visuals (office use, multitasking, NSFW visuals, etc.).
Putting popup + options on a shared, themable foundation also unblocks future surfaces — every primitive (Popup → ActionPopup → ConfirmPopup, Toast +
useToast(), ConfirmProvider +useConfirm(), Select, Slider, FilePicker, etc.) is now reusable, and adding a feature requires only an entry insrc/shared/features.tsfor it to appear in the popup.How was this change implemented?
web-ext/src/content_scripts/components/AudioMode/)#movie_playeratz-[9000+], with a class on the player that hides YouTube's chrome (.ytp-chrome-bottom, gradients, end-screen cards) viaweb-ext/src/content_scripts/index.csswhile active.createMediaElementSource(video)reroutes audio through Web Audio. Keepsource → destinationconnected at all times so audio plays in every screen mode; the analyser is an optional side-branch attached only when the visualizer runs.sqrtperceptual curve and a configurable sensitivity multiplier.userWantsAudioMode) persists across SPA navigations; per-video pins override.chrome.storage.syncfor preferences,chrome.storage.localfor per-video pins and uploaded background images (which exceedsync's 8KB per-item cap).web-ext/src/shared/theme.css)--color-bg,--color-surface,--color-fg,--color-accent, plus tonal pairs for success/info/warning/danger).web-ext/scripts/build.jsexposes them as semantic utilities (tw-bg-surface,tw-text-fg, etc.).useTheme()resolves the user's preference and writesdata-themeon<html>; re-applies on OS preference changes when "system" is selected.web-ext/src/shared/)options/components/and reorganized intoprimitives/,form/,feedback/,layout/,hooks/,utils/.Fieldwrapper for consistent label/description/error layout.Popup(portal + focus trap + backdrop) →ActionPopup(title + body + footer actions) →ConfirmPopup(configures actions for the confirm pattern) →useConfirm()for an imperativeawait confirm({…})API.web-ext/src/options/)chrome.storage.local).SectionNavusesIntersectionObserverto highlight the section in view; sticky positioning required removingoverflow-x-hiddenfrom<main>(any non-visibleoverflow ancestor silently becomes the sticky's scroll container).getStorage()deep-mergesDEFAULT_STOREinto existingchrome.storage.syncdata so installs get new keys (audio mode, theme, visualizer sensitivity) without manual migration.web-ext/src/popup/)options_pending_routetochrome.storage.local;options/App.tsxreads and consumes it on mount.web-ext/src/shared/features.ts)FEATURESandWHATS_NEWarrays with a maintainer-rules header. Popup renders from this; future surfaces (search, onboarding) can too.Screenshots / Visual Changes
NA (please grab while testing — the diff is large enough that screenshots would be helpful in the PR description before merging).
Related Issues
NA
Additional Considerations
getStorage()(web-ext/src/background/store.ts) means existing users won't lose their settings or hit "undefined" errors when the new keys (ui.theme,audioMode.*,audioMode.visualizerSensitivity) appear. The strategy is recursive merge of defaults into stored data with stored values taking precedence.createMediaElementSource(video)permanently reroutes the video's audio throughAudioContext. Once attached, audio plays only ifsource → destinationis connected. We keep that connection at all times and treat the analyser as an optional branch — switching out of visualizer mode no longer kills audio (this was an early bug that the new structure prevents).web-ext/scripts/build.js: addeddarkMode: ["class", '[data-theme="dark"]']and atheme.extend.colorsblock that maps semantic names to the CSS variables. No effect on production behavior, but reviewers should be aware.web-ext/CLAUDE.mdand the header ofweb-ext/src/shared/features.tsboth document that any new user-facing feature should be added toFEATURES(andWHATS_NEW) so the popup picks it up automatically.options/components/{Card,Input,Keybinding,Navbar,Switch,Tooltip}.tsxandpopup/components/Navbar.tsxare deleted in favor of the shared library; the previousoptions/store.tsx,options/hooks/,options/utils/moved toshared/.🤖 Generated with Claude Code