Skip to content

web-ext work branch (superseded by #109) - #79

Closed
DarhkVoyd wants to merge 41 commits into
mainfrom
claude/exciting-keller-a18866
Closed

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

Conversation

@DarhkVoyd

@DarhkVoyd DarhkVoyd commented May 14, 2026 •

Copy link
Copy Markdown
Member

Superseded by #109, which carries the same extension work as one commit on top of main. This branch's history includes the website and backend commits that already landed via #107, which is why it conflicts. Safe to close once #109 is merged.

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
Comment thread web-ext/app/content_scripts/components/AudioMode/index.ts Fixed
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 thread web-ext/app/content_scripts/components/GearMenuPanel/index.ts Fixed
Comment thread web-ext/app/content_scripts/pages/nativeSettings.ts Fixed
Comment thread web-ext/app/options/routes/General.tsx Fixed
Comment thread web-ext/app/options/routes/Watch.tsx Fixed
Comment thread web-ext/app/options/routes/Profiles.tsx Fixed
Comment thread web-ext/app/popup/App.tsx Fixed
Comment thread web-ext/utils/storage/segmentStore.ts Fixed
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
Comment thread website/app/docs/layout.tsx Fixed
…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.
It belongs in the changelog, not in the pitch.
"Sponsor" reads corporate. The label is now a small on-brand gesture,
like "buy me a coffee"; the key is renamed to match.
The Worker served two routes through eleven files of layers, barrels
and constants, plus itty-router. It is now src/index.ts (routing, CORS,
error JSON), src/playlist.ts (the feature) and src/http.ts (shared
response plumbing), with no runtime dependencies. The contract with
the extension is unchanged.

Move to Bun and wrangler.jsonc, trim the config to what is used, add a
deploy workflow mirroring the website's, and add backend/AGENTS.md with
the contract and the types-regeneration gotcha.
Comment on lines +16 to +29
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
working-directory: backend
- run: bun run check
working-directory: backend
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: backend
command: deploy
The exported function now states the algorithm: page through the
playlist, fetch each batch's durations, total them. Paging is an async
generator, the YouTube call builds its URL with URLSearchParams, and
the duration parser and sum are their own functions.
getPlaylistRuntime, videoIdsByPage, fetchDurationSeconds, fetchYouTube,
toSeconds. The durations fetch returns a total, so the sum helper goes,
and the ISO parser sits under its only caller.
…ror text

Without .dev.vars the key was sent as "undefined" and the only clue
was YouTube's bare 400. Now a missing key is a 500 naming the variable,
and YouTube failures carry its own message.
wrangler dev regenerates worker-configuration.d.ts and drops secrets it
cannot see, which broke the typecheck. The secret is now declared by
hand and merges into Env whatever Wrangler generates.
youtubeErrorMessage parses Google's error envelope, so it stays beside
fetchYouTube rather than in http.ts, which is about our own responses.
The route regex is PLAYLIST_PATH, and the id is no longer decoded:
playlist ids are [A-Za-z0-9_-].
It reads a message out of any JSON error body and falls back to the
status text; nothing in it is YouTube-specific.
The live Worker currently catches the whole hostname, which is why the
Pages site cannot be served there. Pinning the route in wrangler.jsonc
leaves everything outside /api to Pages.
@DarhkVoyd DarhkVoyd changed the title [web-ext]: add-audio-mode-and-revamp-popup-and-options Ship the rebuilt website and backend to toppings.enry.ch Sep 26, 2026
@DarhkVoyd DarhkVoyd changed the title Ship the rebuilt website and backend to toppings.enry.ch web-ext: restructure and comment sweep (not for release yet) Sep 26, 2026
tsconfig listed mocha and chai types that were never installed, so tsc
stopped before reporting anything. With that fixed it found 82 errors.
60 of them came from content scripts writing dom-chef JSX under React's
JSX types: elements typed as ReactNode, handlers as SyntheticEvent. A
local jsx-runtime (app/content_scripts/jsx) now types those files as
HTMLElement with native events; each opts in with a jsxImportSource
pragma, which also replaces the import React from "dom-chef" lines.
Babel moves to the automatic runtime for the same reason.

The rest were stale guards for store keys that are always present,
readonly nav items, an untyped modifier list, and two dead functions.
@cloudflare-workers-and-pages

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

Copy link
Copy Markdown

Deploying toppings-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: b307bab
Status: ✅  Deploy successful!
Preview URL: https://4e98bfa7.toppings-website-bq3.pages.dev
Branch Preview URL: https://claude-exciting-keller-a1886.toppings-website-bq3.pages.dev

View logs

scripts/build.ts bundles the four entries with Bun.build (IIFE, no code
splitting), runs Tailwind through its PostCSS API from a real
tailwind.config.ts, copies the static files and applies the same
manifest transform for Firefox. CSS no longer goes through JS: the
content stylesheet is listed in the manifest and the popup and options
pages link theirs. Dev mode watches the source directories.

scripts/release.ts zips dist with the system zip. The four dynamic
imports become static, since nothing is code-split. Fifteen build
dependencies go; 112 packages install instead of several hundred, and
a production build takes about 300ms.
Profiles replaced Audio Mode, but the replacement was half-built: the
Audio preset set watch.layout to no-video and applyWatchProfile ignored
it, while 1,100 lines of the old feature stayed in the tree with no
caller since the v3.0.3 restructure.

A watch.layout primitive now collapses the player container for
no-video and keeps playback running; the preset works. The old feature
goes: the overlay UI, visualizer, custom image, per-video pins, the B
key, the popup toggle, its options page, its CSS and the audioMode
store subtree. watch.visuals and the unimplemented theatre layout go
with it, from the types, the Profiles editor and profile import.
…l; drop aliases

Bugs: the Shorts buttons had ids starting with '#', so the guard that
stops double-injection never matched; the auto-scroll toggle changed
memory only and never reached storage; the playback-rate menu items
were created with a tw- prefixed marker class but queried without it;
the double-tap circle reset its style to the string "null".

The typing-target check that watch and shorts each carried is one
function. Dead exports go (getActivePrimitives, createEmptyConfig,
isSegmentsActive, getActiveConfig), and the toolbar icon uses whichever
of chrome.action / browserAction exists instead of sniffing the UA.

Constants that only renamed literals go with their files: HTTP_*, ERROR,
NODE_ENV, EXTENSION_INSTALL_REASON, EXTENSION_MESSAGE_BODY, KEYBOARD_KEY,
NUMBER, and the template-interpolating API client, which is now one
fetchPlaylist function.
…mponents/

app/ keeps only the four surfaces. Content-script logic moves into
features/ (playback, playlist, profiles, segments, shorts) next to the
data and DOM strategies each feature owns. Shared plumbing is lib/ and
the React components are ui/. data/core.ts splits into lib/youtube.ts,
lib/protocol.ts and lib/storageKeys.ts; the two store files and the two
version files merge. The four primitive barrels go; callers import the
primitive they use. Cross-folder imports use the @/ root alias.

No storage key, message type, primitive id or manifest field changes.
import { useToast } from "@/ui/feedback/ToastProvider";
import { useCapabilityCache } from "@/features/profiles/useCapabilityCache";
import { setExtensionIcon } from "@/lib/browser";
import { ThemePreference } from "@/ui/useTheme";
@@ -0,0 +1,266 @@
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 "@/features/profiles/profiles";
getActiveProfile,
setActiveProfileId,
} from "./profileStore";
import { setSidebarVisible, resetSidebar } from "./primitives/watch/sidebar";
setActiveProfileId,
} from "./profileStore";
import { setSidebarVisible, resetSidebar } from "./primitives/watch/sidebar";
import { setCommentsVisible, resetComments } from "./primitives/watch/comments";
} from "./profileStore";
import { setSidebarVisible, resetSidebar } from "./primitives/watch/sidebar";
import { setCommentsVisible, resetComments } from "./primitives/watch/comments";
import { setEndCardsVisible, resetEndCards } from "./primitives/watch/endCards";
import { withStore } from "@/lib/indexedDb";
import { BROWSER_STORAGE_IDB_STORE } from "@/lib/storageKeys";
import type { VideoSegmentData, SegmentConfig, SegmentAutoLoadPin } from "./types";
import { createFreshConfig } from "./factories";
One 1,696-line file becomes a public module (panel.tsx) and one file
per concern: state, menu wiring, styles, formatting, mutations, header,
config switcher, pin button, save menu, segment list, sequence editor,
config manager. Sections re-render through state.render so none of
them imports the module that composes them.

The trigger-and-dropdown wiring that three menus copied is attachMenu;
the panel's private toast is the shared page toast; mutations no
longer take a config argument they ignored.
The extension replaced the standalone feature and its B key with
Profiles. The feature list, description, greetings page, install page,
FAQ and shortcut table follow; the 3.0.3 changelog stays as history
and the next release notes the change.
The previous commit left the question without its comma and the old
answer in place; the site did not build.
@DarhkVoyd DarhkVoyd changed the title web-ext: restructure and comment sweep (not for release yet) web-ext: Bun build, feature folders, Audio preset, bug fixes Sep 26, 2026
@DarhkVoyd DarhkVoyd changed the title web-ext: Bun build, feature folders, Audio preset, bug fixes web-ext work branch (superseded by #109) Sep 26, 2026
@DarhkVoyd

Copy link
Copy Markdown
Member Author

Superseded by #110.

@DarhkVoyd DarhkVoyd closed this Sep 27, 2026
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