Skip to content

[web-ext]: add-audio-mode-and-revamp-popup-and-options#79

Open
DarhkVoyd wants to merge 3 commits into
mainfrom
claude/exciting-keller-a18866
Open

[web-ext]: add-audio-mode-and-revamp-popup-and-options#79
DarhkVoyd wants to merge 3 commits into
mainfrom
claude/exciting-keller-a18866

Conversation

@DarhkVoyd

Copy link
Copy Markdown
Member

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.

  • Audio Mode: a player button + B shortcut 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 in chrome.storage.local. Visualizer sensitivity is configurable. Auto-disables during ads.
  • Options page revamp: hybrid sidebar layout with anchored section navigation, confirm modals, toast notifications, file picker with thumbnail preview, custom dropdown, and a visualizer-sensitivity slider.
  • Popup revamp: replaces the old welcome screen with a real dashboard — master toggle, at-a-glance stats, feature list, "What's New", and footer actions. Deep-links into specific options routes via chrome.storage.local.
  • Theme system: semantic CSS variable tokens (System / Dark / Light), live system follow via prefers-color-scheme.
  • Shared component library moved from options/ to src/shared/ so popup, options, and any future surface share primitives, form, feedback (Popup → ActionPopup → ConfirmPopup), layout, hooks, and utils.
  • Features registry (src/shared/features.ts) is a single source of truth for feature metadata + "What's New" feed; popup renders from it.
  • Maintainer docs (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 in src/shared/features.ts for it to appear in the popup.

How was this change implemented?

  • Audio Mode (web-ext/src/content_scripts/components/AudioMode/)
    • CSS overlay inside #movie_player at z-[9000+], with a class on the player that hides YouTube's chrome (.ytp-chrome-bottom, gradients, end-screen cards) via web-ext/src/content_scripts/index.css while active.
    • Audio routing: createMediaElementSource(video) reroutes audio through Web Audio. Keep source → destination connected at all times so audio plays in every screen mode; the analyser is an optional side-branch attached only when the visualizer runs.
    • Visualizer uses a peak-of-low-spectrum loudness signal with a sqrt perceptual curve and a configurable sensitivity multiplier.
    • User intent (userWantsAudioMode) persists across SPA navigations; per-video pins override.
    • chrome.storage.sync for preferences, chrome.storage.local for per-video pins and uploaded background images (which exceed sync's 8KB per-item cap).
  • Theme tokens (web-ext/src/shared/theme.css)
    • Semantic CSS variables (--color-bg, --color-surface, --color-fg, --color-accent, plus tonal pairs for success/info/warning/danger).
    • Tailwind config in web-ext/scripts/build.js exposes them as semantic utilities (tw-bg-surface, tw-text-fg, etc.).
    • useTheme() resolves the user's preference and writes data-theme on <html>; re-applies on OS preference changes when "system" is selected.
  • Shared library (web-ext/src/shared/)
    • Moved from options/components/ and reorganized into primitives/, form/, feedback/, layout/, hooks/, utils/.
    • Form components compose a single Field wrapper for consistent label/description/error layout.
    • Feedback components form a composition chain: Popup (portal + focus trap + backdrop) → ActionPopup (title + body + footer actions) → ConfirmPopup (configures actions for the confirm pattern) → useConfirm() for an imperative await confirm({…}) API.
  • Options page (web-ext/src/options/)
    • Sidebar with iconified routes, collapsible to a 60px icon-only mode (state persisted in chrome.storage.local).
    • Right-rail SectionNav uses IntersectionObserver to highlight the section in view; sticky positioning required removing overflow-x-hidden from <main> (any non-visible overflow ancestor silently becomes the sticky's scroll container).
    • Migration: getStorage() deep-merges DEFAULT_STORE into existing chrome.storage.sync data so installs get new keys (audio mode, theme, visualizer sensitivity) without manual migration.
  • Popup (web-ext/src/popup/)
    • New 360×520 dashboard built entirely from shared components.
    • Deep-links into options routes by writing options_pending_route to chrome.storage.local; options/App.tsx reads and consumes it on mount.
  • Features registry (web-ext/src/shared/features.ts)
    • FEATURES and WHATS_NEW arrays 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

  • Storage migration is automatic. The deep-merge in 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.
  • Web Audio caveat: createMediaElementSource(video) permanently reroutes the video's audio through AudioContext. Once attached, audio plays only if source → destination is 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).
  • Tailwind config change in web-ext/scripts/build.js: added darkMode: ["class", '[data-theme="dark"]'] and a theme.extend.colors block that maps semantic names to the CSS variables. No effect on production behavior, but reviewers should be aware.
  • Maintainer contract: web-ext/CLAUDE.md and the header of web-ext/src/shared/features.ts both document that any new user-facing feature should be added to FEATURES (and WHATS_NEW) so the popup picks it up automatically.
  • Old files removed: options/components/{Card,Input,Keybinding,Navbar,Switch,Tooltip}.tsx and popup/components/Navbar.tsx are deleted in favor of the shared library; the previous options/store.tsx, options/hooks/, options/utils/ moved to shared/.

🤖 Generated with Claude Code

Comment thread web-ext/app/content_scripts/components/AudioMode/index.ts Fixed
Comment thread web-ext/src/popup/App.tsx Fixed
Comment thread web-ext/src/options/routes/General.tsx Fixed
Comment thread web-ext/src/popup/App.tsx Fixed
@DarhkVoyd
DarhkVoyd force-pushed the claude/exciting-keller-a18866 branch from f2054cb to dfa60f6 Compare May 14, 2026 21:37
Comment thread .github/workflows/consistency.yml Fixed
Comment thread scripts/bump-version.ts Fixed
Comment thread scripts/lib/releases-file.ts Fixed
Comment thread web-ext/src/shared/utils/youtubeTab.ts Fixed
Comment thread scripts/lib/releases-file.ts Fixed
@DarhkVoyd
DarhkVoyd force-pushed the claude/exciting-keller-a18866 branch from d039cdb to b3e383c Compare May 17, 2026 21:32
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, '\\"');
Comment thread web-ext/app/options/routes/General.tsx Fixed
@DarhkVoyd
DarhkVoyd force-pushed the claude/exciting-keller-a18866 branch from 0074568 to 3aa7a07 Compare June 21, 2026 11:48
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
DarhkVoyd force-pushed the claude/exciting-keller-a18866 branch from 3aa7a07 to 1774554 Compare June 21, 2026 11:49
@DarhkVoyd
DarhkVoyd force-pushed the claude/exciting-keller-a18866 branch from 1774554 to 9e63c09 Compare June 21, 2026 11:50
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(
import { useToast } from "../../../components/feedback/ToastProvider";
import { useCapabilityCache } from "../../../hooks/useCapabilityCache";
import { setExtensionIcon } from "../../../utils/browser";
import { ThemePreference } from "../../../utils/theme";
@@ -0,0 +1,249 @@
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
Comment on lines +21 to +26
import {
BUILT_IN_PRESETS,
type Profile,
type ProfilePrimitiveConfig,
type ThumbnailMode,
} from "../../../data/profiles.data";
Comment thread web-ext/app/popup/App.tsx
Comment on lines +19 to +22
import {
getCustomProfiles,
setActiveProfileId,
} from "../../utils/storage/profileStore";
withStore,
} from "./indexedDb";
import type { VideoSegmentData, SegmentConfig, SegmentAutoLoadPin } from "../segments/types";
import { createFreshConfig } from "../segments/factories";
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
DarhkVoyd force-pushed the claude/exciting-keller-a18866 branch from cea95b3 to 111c312 Compare June 23, 2026 06:15
@@ -0,0 +1,27 @@
import type { Metadata } from "next";
import DocsPageHeader from "./components/DocsPageHeader";
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.

2 participants