Skip to content

web-ext: rebuild on a kernel, Preact pages, YouTube-matched theme - #110

Open
DarhkVoyd wants to merge 53 commits into
mainfrom
web-ext/kernel-playlist
Open

DarhkVoyd wants to merge 53 commits into
mainfrom
web-ext/kernel-playlist

Conversation

@DarhkVoyd

@DarhkVoyd DarhkVoyd commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

Rebuilds the extension on a small kernel and retires the old path in one go, so nothing ships in a mixed state.

Architecture

  • Kernel (kernel/): a Feature contract mounted per navigation and unmounted on the next; settings slices (defineSettings) with one-shot migration from the old store; a keybinding registry with user overrides; typed background messaging; a primitive engine that keeps page tweaks applied while YouTube re-renders; DOM resolve with a capability cache; Preact mount into shadow roots; a shared toast and YouTube's own colour tokens for injected UI.
  • YouTube adapter (youtube/): routes and the navigation event, every selector the extension uses, and the catalogue of page primitives profiles are built from.
  • Features (features/): playlist runtime, Shorts, playback, profiles and segments, each a folder with its settings, keys, messages and a tested Feature. Segments got a per-video session behind a storage interface, with writes serialised.
  • Pages: popup and options on Preact (React, react-router, immer and dom-chef removed), hash routing, and a theme that is YouTube's own palette and type in both modes. The Shortcuts page renders from the key registries.

Fixes along the way

  • YouTube's speed panel is a slider with chips now; custom rates render as chips and the slider follows.
  • The desktop player has no double-tap overlay; seeks show a small overlay of our own.
  • Thumbnails loaded on scroll are blurred too (the engine re-applies on re-render).
  • Guide entry survives YouTube's Polymer re-renders; Shorts shelves and thumbnails also match YouTube's view-model markup.
  • Legacy migration no longer lets missing fields shadow defaults; covered end to end by a test.
  • webNavigation permission and the content stylesheet are gone.

Verified

  • bun run check: types clean, 66 tests.
  • Chrome MV3 and Firefox MV2 builds.
  • Playwright against live YouTube with the built extension: playback (rate, keys, chips, seek overlay), profiles (Focus on watch, gear panel, native overlay in light and dark, search primitives through scrolling), segments (button, keys, panel, marker drag, save and reload), Shorts (controls, keys, next reel), playlist runtime, options and popup pages.

Not verifiable headless: the playlist header on a signed-in account's newer layout, which has header-level fallback strategies.

Supersedes #109 and #79.

Typecheck runs again: tsconfig listed uninstalled mocha and chai types,
hiding 82 real errors. Content-script JSX is typed as DOM through a
local dom-chef jsx-runtime selected per file with a jsxImportSource
pragma.

Build: webpack and Babel replaced by Bun.build in scripts/build.ts,
Tailwind through its PostCSS API from tailwind.config.ts, CSS via the
manifest and <link> instead of JS imports. Fifteen dev dependencies go.

Audio preset finished, Audio Mode removed: profiles replaced the
feature. watch.layout no-video collapses the player and keeps playback;
the overlay, visualizer, custom images, pins, B key, popup toggle,
options page and store subtree are gone, with watch.visuals and the
unimplemented theatre layout.

Bugs: Shorts button ids started with '#', the auto-scroll toggle never
reached storage, the playback-menu marker class did not match its
query, cssText was set to "null".

Structure: app/ holds the four surfaces; features/ (playback, playlist,
profiles, segments, shorts); lib/ plumbing; ui/ shared React. core/,
data/, utils/, components/ and the primitive barrels are gone; cross-
folder imports use @/. SegmentPanel.tsx is features/segments/panel/,
one file per section.

Website copy follows the Audio preset. Storage keys, message protocol,
primitive and preset ids, default keybindings and manifest permissions
are unchanged.
…ader

The options and popup stylesheets import ui/theme.css; the PostCSS
pipeline does not resolve @import, so the compiled CSS kept a raw
import that pointed nowhere and every --color-* variable was undefined.
The build now inlines relative imports first.

The no-video layout collapsed #player-container-outer, which YouTube
empties in theater mode, moving the player into #full-bleed-container.
The container is now found by what it holds, and re-applied when the
theater attribute changes.

The playlist runtime section depended on a generated class name that
YouTube has since renamed. The header is resolved through strategies
covering both playlist layouts and reported to the capability cache.
The strangler starts here. kernel/ is the runtime features plug into:
a Feature contract mounted and unmounted per navigation, settings
slices under settings:<feature> with a one-time migration out of the
legacy store, typed request/response messaging alongside the old
string protocol, DOM resolution with capability recording (moved out
of features/profiles), and a shadow-root mount for Preact UI.
youtube/ holds route parsing on yt-navigate-finish and the per-page
DOM strategies.

playlist-runtime is the first port: the playlist page section and the
watch-page badge, rendered with Preact inside shadow roots, fetched
through a background message handler that owns the cache, with the
enable toggle on the Playlist options page reading the new slice. The
legacy context no longer carries playlist data and the old
features/playlist folder is gone.

Tests run under Bun with happy-dom: settings slices and migration,
route parsing, and the feature mounted against fixtures of both
playlist header layouts. 19 tests.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Deploying toppings-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: d2cf43e
Status: ✅  Deploy successful!
Preview URL: https://39ffaa34.toppings-website-bq3.pages.dev
Branch Preview URL: https://web-ext-kernel-playlist.toppings-website-bq3.pages.dev

View logs

kernel/keys.ts: a feature declares its actions once (defineKeys) and
binds handlers at mount (bindKeys); one keydown dispatcher, unbound on
unmount, user overrides in a settings:keybindings slice by action id.
The legacy-store mapping for bindings lives with the background's
migrations, not the kernel.

youtube/shorts.ts resolves the reel video and the action bar
separately: the newer layout reuses one renderer without is-active and
extracts the action bar beside it. The feature covers both layouts in
tests and was checked live across a reel change.

Shorts settings are typed numbers now, migrated from the old strings;
the options page reads the slice. The playlist header gets header-level
fallbacks while the logged-in layout's selector is confirmed.
Default rate, rate and seek keys, and custom rates rendered as chips inside
YouTube's speed panel (the menu-list panel it replaced no longer exists).
Keyboard seeks get a small overlay of our own since the desktop player has
no double-tap overlay to reuse. The legacy watch page keeps only segments,
profiles and the gear menu until their own ports.

The build script's output directory is overridable so a Chrome and a Firefox
build can coexist.
A primitive is now one declarative knob on YouTube's page (selectors, apply,
reset, parse), catalogued in youtube/primitives.ts; the kernel keeps a set of
values applied while the page re-renders and restores everything on unmount,
which replaces the per-primitive hidden-by-us flags and the one-shot apply
that missed thumbnails loaded on scroll.

The profile store is a local-area settings slice with the same API as before.
Gear-menu panel and native settings overlay are Preact, the overlay reading
YouTube's --yt-sys-color-baseline tokens so it follows the page theme. The
guide link is re-added when the Polymer guide re-renders. Selectors gain
YouTube's view-model markup for Shorts shelves and thumbnails.

The test harness storage stub now emits change events.
A per-video SegmentSession owns loaded config, playback state and every
change, replacing the module-level state that the old orchestration, panel
and markers shared. Persistence sits behind a SegmentStorage interface, so
the session and feature are tested against an in-memory store; writes to a
video's record are serialised so rapid edits cannot clobber each other.

Markers, control-bar button and the panel are Preact: markers re-render in
place during a drag instead of being rebuilt, the panel uses YouTube's theme
tokens in a shadow root, and toast moved into the kernel.

With segments ported the content script boots only kernel features, so the
legacy context protocol between background and content is gone.
The two pages run on Preact directly (React, react-router, immer and
dom-chef are gone), route by hash, and read every value from a settings
slice; the legacy store, its context protocol and the copied Tailwind
preflight go with it. An app slice carries the master switch and theme: the
content script gates every feature on it and the background keeps the
toolbar icon in step.

The theme is YouTube's own palette and Roboto stack in both modes, with
pill buttons and YouTube-style toggles, so the pages read as part of
YouTube. The Shortcuts page is rendered from the key registries, and the
in-page playlist section uses the same tokens as the rest of the injected
UI. Legacy migration now ignores fields the old store lacked, and is
covered end to end by a test.
Key matching and YouTube URL constants move to where they belong (kernel/,
youtube/), the manifest drops the webNavigation permission the old context
protocol needed, and AGENTS/web-ext.md describes the extension as it now is:
kernel features, Preact pages, settings slices, and the YouTube quirks the
code works around.
@DarhkVoyd DarhkVoyd changed the title web-ext: kernel, and playlist runtime as the first feature on it web-ext: rebuild on a kernel, Preact pages, YouTube-matched theme Sep 27, 2026
@DarhkVoyd
DarhkVoyd changed the base branch from web-ext/rebuild to main September 27, 2026 04:58
The README still described the old setup: a GOOGLE_SECRET the Worker never
reads (it wants YOUTUBE_DATA_API_V3_KEY), a global wrangler install that the
backend's own devDependency replaces, no step for loading the unpacked
extension, and links to Discussions and a wiki that are both disabled. It now
reads in the project's own voice, points docs at toppings.enry.ch, and says
plainly that playlist runtime is the one feature that talks to the server;
the FAQ says the same.

The backend and website agent notes pointed at web-ext files the rebuild
moved or removed.
Comment thread web-ext/app/options/routes/Profiles.tsx Fixed
Firefox only returns promises from its browser.* namespace under MV2; its
chrome.* namespace wants callbacks and returns undefined otherwise. The
rebuild calls every extension API promise-style, so in Firefox no setting,
profile or keybinding ever loaded: the options page showed no built-in
presets, and the failed storage reads left its tabs unable to switch pages.

The Firefox build now rewrites the chrome global to browser at bundle time.
Verified in Firefox 156 with the extension installed: options tabs, presets,
settings writes, popup, and every content feature on live YouTube.
The markers floated above the progress bar as tall numbered flags, with
nothing marking the exact cut and nothing showing the range between them.
Each point is now a small trapezoid head on a cut line through the bar, the
range between in and out is tinted in the segment's colour, and the grab
area is wider than the head so dragging stays easy. Segment colours no
longer start with red, which disappeared into YouTube's played fill.

Checked on a chaptered video at rest and with the bar hovered, and marker
dragging in Chromium and Firefox.
The cut line and range tint were drawn over YouTube's bar, covering the
played fill and the playhead dot, and the tint leaked through chapter gaps.
Markers now mount inside the bar's own stacking context just under the
playhead dot, and everything is drawn above the bar: a thin rail in the
segment's colour spans the range, with a trapezoid head at each end resting
on the bar's top edge. The bar itself is left exactly as YouTube draws it.

Checked on a chaptered video with the playhead on a marker, at rest and
hovered, and marker dragging in Chromium and Firefox.
Brings back the pin: the trapezoid head on a short needle, now standing on
the bar's top edge instead of piercing it, with the range rail joining the
heads above the bar. The drop shadow, stroke and hover zoom are gone; at
this size each left the shape soft once rasterised at a fractional
position. The first segment is YouTube's own red again, since the markers
no longer sit on the red played fill.
Dev builds called a local Worker at 127.0.0.1:8787, so without the backend
running every playlist lookup failed silently and nothing showed on playlist
or watch pages. Builds now use the live API unless TOPPINGS_API points them
elsewhere.

On a watch page opened from a playlist, the runtime is a line under the
panel's "channel · 2/10" row instead of falling to the bottom of the panel.
On the playlist page it is one metadata line in the header card's own type
and colour. Both use YouTube's clock format. Runtimes are cached for a day,
since they only change when the playlist does; refresh bypasses the cache.

Checked on a real playlist in Firefox with a development build.
YouTube keeps pages you navigate away from alive but hidden, and parks
unused header layouts under display:none. The kernel's element lookup took
the first selector match anywhere, so after navigating from a channel to a
playlist the runtime mounted into the hidden channel header, and on regular
playlists into a parked copy of the page header: nothing showed until a
reload, and on regular playlists not even then.

resolveTarget now accepts only rendered elements and keeps waiting until
one appears, which protects every feature, and it no longer needs the
element-ready dependency. Playlist header selectors are also scoped to the
playlist page. The runtime sits under "10 videos · Last updated" on course
playlists and under "Playlist · 34 videos · views" on regular ones.

Checked course and regular playlists, loaded directly and reached from the
channel page, plus every other feature, in Chromium and Firefox.
The runtime row and the watch-panel badge now appear as soon as their spot
is found, as a shimmering placeholder sized like the text, and fill in when
the lookup answers; a failed lookup removes them. The page no longer shifts
when the numbers arrive.
Playlist pages still come one at a time, since each page's token arrives
with the previous page, but a page's duration lookup no longer blocks the
next page request. On a 183-video playlist a lookup went from about 5.1s to
2.3-3.2s, with identical totals.
The runtime row guessed its type (12px at 80% opacity, a middle dot) and so
never quite matched the row above it. It now copies font, size, weight,
line height and colour from YouTube's own metadata text beside it, and uses
YouTube's separator where YouTube has one (the bullet on regular playlists)
and plain spacing where it does not (course cards, the watch panel). The
playlist row and the watch-panel badge are one component.

Injected UI no longer carries the Toppings logo; the credit moves into the
hover tooltip of every control we add. Refresh shows the skeleton while it
re-fetches.

Checked computed styles against YouTube's on regular and course playlists
and the watch panel.
The site moves from ink, bone and ember to YouTube's dark theme: its base,
text and outline tones, YouTube red for brand accents (the highlighted
word, eyebrow marks, cursor, selection, active docs link) and its link blue
for focus rings and link hovers. The backdrop's greys lose their blue cast
and its progress red becomes YouTube's. Tokens are renamed for what they
are now (text, text-2, text-3, red, link). Fonts are unchanged.

The custom-rates blurb now says the rates appear in the speed panel.
The extension's UI is English throughout, so a locale folder holding only
the store description and the toolbar title localised nothing. Both now sit
in the manifest, and the toolbar title reads Toppings instead of Quick
Access. Localisation, if it comes, belongs to the whole UI at once.
The pizza-slice illustration gives way to a mark built for small sizes: a
heavy lowercase t in YouTube red with a deep-red dot sitting on its stem,
the topping. assets/logo.svg is the master; the toolbar icons, the greyed
set shown while Toppings is off, the README logo files and the website
favicon (plus an SVG icon for browsers that take one) are all exported from
it. Checked at 16px on light and dark toolbars, and in the popup and options
headers.
Reverts the move to YouTube's palette. Blending in is for what Toppings
draws inside YouTube; the site and the brand keep their ink, bone and ember,
and all-over YouTube red read as too much. The custom-rates copy fix and the
corrected backdrop comment from that change stay.
Replaces the red t with the mark that won: a pizza slice that doubles as a
play button, crust along its back edge and three pepperoni, in the site's
ember with bone pepperoni. The brand keeps its own colours; YouTube red is
for what Toppings draws inside YouTube. Exported to the toolbar icons, the
greyed set, the README logo files and the website favicon from
assets/logo.svg.
Blending in is for what Toppings draws inside YouTube; the popup and options
are Toppings' own space, so they go back to the brand the website wears: ink,
bone and ember, with Gloock for headings and the wordmark and Instrument Sans
for text. Both fonts ship with the extension (OFL, licences included) rather
than loading from Google Fonts. The body now sets the font itself, because
Chrome styles extension page bodies with its own system font.
Replaces the ember slice. The crust is a solid band along the arc rather
than a ring, the drip is traced from the Recraft V4 study, and 16/32px
icons drop the drip so they stay legible.
Restores the pre-rebrand icons, favicon and brand images, and drops the
SVGs the rebrand introduced.
After navigating from a watch page, the first engagement panel in the
document is the parked watch page's, so auto-scroll advanced with the
comments open. The Next button was also matched by its English label.
Point-in-time lookups now go through findTarget, the synchronous form of
resolveTarget, and any open reel panel holds auto-scroll.
The row was matched by its English label, so outside English the custom
rate chips never appeared and the row kept YouTube's stale value. It is
now matched by its icon, reported as the watch.speedRow capability, and
1x shows YouTube's own localised word instead of a hard-coded "Normal".
findTarget returns a PrimitiveResolution, like resolveTarget.
Requiring controls to be on screen broke three things YouTube hides on
purpose: the settings menu while closed (the speed row went stale and
profiles could leave YouTube's settings panel hidden on unmount) and the
Shorts navigation buttons in a narrow window (auto-scroll stopped).

findTarget becomes findWithin: the root (the player, the Shorts page) is
the live part, and lookups inside it do not need visibility. The speed
row falls back to its English label, the 1x word check accepts any
script's digits, the menu hook runs without custom rates, reports the
row until it is found, and clears its timer on unmount.
- release.ts zips DIST rather than always dist/, and replaces an existing
  archive instead of adding to it
- type-check covers the tests, and the six errors it found are fixed
- CI installs from the lockfile with the pinned Bun
- watch mode runs one build at a time
- rates round to four places, so sixteenth steps stay exact
- index.d.ts declared image modules nothing imports any more
- The capability cache moves from IndexedDB to extension storage. Content
  scripts wrote it into youtube.com's IndexedDB, so the options page and
  background, which read the extension's, never saw an entry.
- Playlist runtime returns from mount once the skeleton is up; awaiting
  the backend held every later feature behind the network. A failed or
  thrown fetch removes the skeleton, a failed refresh keeps the last
  runtime, and a late answer after navigating away draws nothing.
- The background shares one in-flight request per playlist and does not
  cache an empty answer; Watch Later, Liked and mixes are skipped.
- A superseded navigation stops mounting further features.
- Keys typed into fields inside Toppings' shadow roots no longer trigger
  shortcuts, and stored modifier names match regardless of case.
It imported types from react, which is not a dependency. Local checks
passed only because a stale react sat in node_modules; CI's clean
install has none, so every run failed at type-check.
The manifest takes only numbers as version, so it carries 4.0.0 with
4.0.0-alpha as version_name; the pages, zip name and capability stamps
use the full name. The website keeps 3.0.3 until a store release.
- Nothing activates after dispose, so a restore that finishes after
  navigating away no longer drives the next video.
- Reaching a segment's end always advances; only a user seek adopts
  another segment, so adjacent and nested segments stop looping.
- Save as named gets its own id instead of overwriting its source.
- Segments wait for a finite duration and for pre-roll ads to end.
- Name fields keep what is typed; merge-by-drag cancels on release,
  ends the drag and clears its timer; stop only undoes a rate the
  engine set; edits no longer jump to the sequence start.
- Profile import works (the input was read after its event ended).
- Shortcut fields let Tab through, Escape cancels, and a shortcut can be
  reset to its default.
- Blank seconds and steps are rejected; the nudge step stays under its
  maximum.
- Select opens on the saved value; Profiles follows changes made
  elsewhere; a quick edit is committed on navigation.
- Profile name label, keyboard-reachable Import and field descriptions.
- Search highlights the row in the right section.
- Export revokes its blob URL after Firefox has started the download.
- Content features boot once the master switch is known, so a page load
  with Toppings off no longer applies the default rate, and a theme
  change no longer remounts every feature.
- A route's features mount together; each gets an AbortSignal, and one
  finishing after the next navigation is undone.
- mount() replaces only hosts no live mount owns, so a stale mount can
  no longer remove the new page's UI.
- Profiles act on the live page, not one YouTube keeps hidden, and try
  strategies in order.
- The resolver rechecks on a timer, which also runs in background tabs.
- Settings migrate at every background start, for sync that lands after
  install.
- Segments have their own switch, migrated from the old watch-page one;
  the kernel carries no YouTube selector for typing detection.
- The gear menu reopens on YouTube's own settings after being closed
  from the Toppings panel, instead of an empty main panel.
- A profile change that resolves after unmount no longer starts a
  primitives run nothing stops.
- A malformed playlist ID gets a 400 before any Data API call.
- Once a duration request has failed, no further pages are fetched: the
  answer is already lost and each page costs quota.
- Durations over a day (P1DT2H...) were read as 0; the day part counts.
YouTube plays a mid-roll ad in the video's own element, on the ad's
clock, so the engine read the ad's time as a playhead outside the
segment and seeked the ad. It now holds while the player shows an ad and
picks up where it was when the video returns.
AMO requires data_collection_permissions. The playlist runtime sends the
viewed playlist's ID to Toppings' server, declared as the required
browsingActivity category. web-ext lint passes with no errors.
- The engine no longer re-seeks forever toward a segment past the end of
  a trimmed video; toggling a last-used config clamps it like restore.
- Seeking into a segment outside the sequence, as when dragging its
  marker, leaves the playhead there until it leaves the segment.
- Settings writes to one slice queue, so two in the same tick both land.
- The background marks a reported feature recovered when a content
  script next records it as supported; checking on update saw only
  entries stamped with the previous version.
- Unchanged capability entries and absent legacy keys are not rewritten,
  so navigations stop waking the background for nothing.
- Content features still boot if the master switch cannot be read.
The active state drew its own square highlight and the inactive one was
dimmed, neither of which YouTube's control-bar toggles do; the filled or
outlined icon already carries the state, and hover comes from YouTube's
own .ytp-button styling.
Selectors, ids, limits and module state now sit above the functions
that use them instead of partway down the file; AGENTS.md states the
order. Values built from a file's own helpers stay after those helpers.
Collapsing the player hid its controls along with the video. Profiles get
back 3.x's Video screen (watch.visuals): real video, black, the audio
visualizer or a custom image, drawn over the picture beneath the controls
so seeking and the progress bar keep working. The Audio preset uses the
visualizer.

- Every profile can have a shortcut that switches to it and, pressed
  again, back to the profile before; Audio defaults to B, as audio mode
  was, and 3.x's key carries over.
- A headphones button in the player toggles Audio (can be turned off).
- The visualizer takes the video's audio only once its AudioContext is
  running, so a page loaded without a gesture never goes silent.
- A 3.x custom image carries over; a newly picked one shows at once.
- Search finds Re-scan features, which is now a labelled button.
Hiding the sidebar had nothing to do with listening without the picture;
Focus and custom profiles are where that belongs.
A schedule turns a profile on during a weekly time window or while at a
saved place, and at the end hands back the profile that was on before,
unless the user switched away meanwhile; it only acts at its edges.

- The background runs on a one-minute alarm; list order is priority and
  a skip from the popup lasts the current window only.
- Feedback for set-and-forget: the toolbar badge shows the profile's
  initial while a schedule is in charge and counts down the last five
  minutes before a change, and YouTube tabs toast each change and the
  heads-up before it.
- Places are checked every five minutes, only while a place schedule
  exists. Location is asked for the way websites ask, from the options
  page, so the manifest gains no location permission; Chrome's
  background reads it through an offscreen document.
- The build folds process.env.BROWSER, so Firefox bundles carry no
  Chrome-only calls.
Schedules: the Profiles page gets Schedules (profile, time window or
place, enabled, priority order) and Places (saved from the browser's own
location prompt, with a radius); a place in use cannot be deleted. The
popup shows what a schedule is doing and can skip it for the current
window. With Toppings off the runner clears its status line.

Sidebar entry: it only switched profiles and sat in YouTube's left
drawer; the options page does this more plainly. The overlay, its
setting, the open-options message and youtube/guide.ts go; the
settings-menu lookup the gear entry needs moves to youtube/player.ts.
tsconfig now reports unused locals, which caught two leftover imports.

Docs: README, FAQ and changelog describe schedules and correct the
permission list; AGENTS/web-ext.md covers the offscreen entry, where
state lives, process.env.BROWSER and why location has no permission.
A tab open across an install or update had no working Toppings until it
reloaded: the old content script is cut off, so a schedule that switched
the profile reached nothing. The background now starts the new script in
open YouTube tabs (scripting permission, no new warning), and a new copy
asks an older one to stop so shortcuts do not fire twice. Covers left by
an old copy are cleared.

Schedules switched up to a minute late: the tick was a one-minute alarm
recreated on every wake. It is now created once, and an alarm is set for
the next heads-up, start or end, which landed within 3 s in Firefox.
Both were written only when Toppings set a rate, and the row was looked
up in the page, but while a submenu is open YouTube takes the main list
out of the page. A shortcut pressed in the speed panel changed neither
the panel nor the row, which caught up only at the next change and
looked as if speeds had been skipped.

They now follow the video's ratechange: the row is kept once found and
updated even while out of the page, and an open speed panel's display,
slider and chips redraw, with or without custom rates. Tests fire
ratechange as browsers do.
The Toppings entry drew its own chevron next to the one YouTube adds,
had no icon, and its panel sat beside YouTube's instead of in its place,
with no header of YouTube's and no size.

It is now one row built from YouTube's markup (icon, Profile, the
active profile's name) that opens a Profile submenu shaped like Quality:
YouTube's header and back button, and radio rows with its checkmark.
The submenu is swapped in for YouTube's panel the way YouTube swaps its
own, sized to fit, and swapped back on back, on picking, when the menu
closes and on unmount. Clicks stop at our rows, since YouTube's own
back handling would close a menu it never opened a submenu in. The menu
grows by the row's height on the first open, which YouTube sized before
the row was there. The quick toggles are gone; profiles cover them.
YouTube draws the filled part of its speed track from a CSS variable,
--yt-slider-shape-gradient-percent, not from the input's value, so a
rate set by Toppings moved the thumb and left the fill behind. The sync
now sets it too, along with the slider's own speed text, and widens the
range at both ends for rates outside YouTube's 0.25-2.

This branch has not been deployed

No deployments
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