Skip to content

Commit 0c5e2bf

Browse files
jvillegasdclaude
andauthored
refactor: options-page: Refactor options UI/UX, add new configurable modules, added app settings object for using configurables variables (#34)
* feat: redesign options page with sidebar nav, history view, and S3 config - Replace flat options layout with a sidebar + multi-view layout (Download Settings, Cloud Providers, History) - Add History view with search/format/status/date filters, bulk delete, per-item actions (re-download, copy URL, manifest health check, delete) - Add S3/compatible cloud provider form alongside existing Google Drive panel - Add history button (clock icon) in popup top bar that opens options directly to History view - Add CHECK_URL message type so options page can probe manifest URLs via service worker (bypasses CORS) - Add bulkDeleteDownloads() IDB utility for efficient multi-item deletion - Extend StorageConfig with historyEnabled and s3 fields - Auto-delete terminal downloads from IDB when historyEnabled is false - All views centered; CSS tooltips on history action icon buttons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: limit popup downloads tab to in-progress downloads only Completed/failed/cancelled downloads now appear exclusively in the History view on the options page, not in the popup Downloads tab. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: move Open File action from popup to history view Completed downloads no longer appear in the popup Downloads tab, so the Open File button is now in the History view (options page). Only shown for COMPLETED items with a known localPath. Removes dead completed/failed action button code from render-downloads. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(options): use extension icon in sidebar header, clean up download settings icon Replaces SVG placeholder logo with the actual extension icon (icon-32.png). Swaps the messy gear/path icon on Download Settings for a clean sliders icon. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(history): replace inline action buttons with a dropdown menu Collapses Open file / Re-download / Copy URL / Check manifest / Delete into a single ··· button per row. Menu closes on outside click or action selection. Check manifest state feedback is shown inline on the menu item. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(history): show check manifest result as toast notification Replaces the inline button state feedback with a bottom-center toast. "Checking…" appears immediately, then resolves to success/warning/error with the HTTP status or a CORS note. Toast auto-dismisses after 3 seconds. Removes now-unused check-btn CSS from the menu item. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(history): show toast when copying URL to clipboard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(history): toast on re-download and refresh history list Shows "Download queued" toast on success or an error toast if the service worker rejects. Refreshes the history list in place so the user stays on the history view without a full page reload. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(history): use stored metadata for readable filename on re-download Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(options): restore active section on page refresh switchView() now calls history.replaceState to keep the URL hash in sync with the active section. On refresh, init() reads the hash and opens the correct view instead of always falling back to download-settings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(history): infinite scroll with IntersectionObserver Render first 50 items on load; append subsequent batches as the user scrolls near the bottom via a sentinel div watched by IntersectionObserver. Filters and search reset to the first page on every change. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(options): rename terminal → finished stages and add live history updates - Rename TERMINAL_STAGES → FINISHED_STAGES and handleTerminalDownload → handleFinishedDownload to align with DownloadStage terminology used elsewhere in the codebase - Update service-worker.ts JSDoc comment to match ("terminal" → "finished") - Add real-time history updates via DOWNLOAD_PROGRESS message listener — newly finished downloads appear/update without requiring a page reload - Flash animation (history-item--new) highlights items as they arrive Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(options): remove redundant check mark from manifest live toast Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(options): add Recording, Notifications, and Advanced settings pages - Add three new options sections: Recording (poll intervals), Notifications (OS notify + auto-open), and Advanced (retries, backoff, caches, IDB sync) - Wire all new settings through DownloadManager → BasePlaylistHandler → fetch-utils so retryDelayMs and retryBackoffFactor now take effect at runtime - Read detectionCacheSize/masterPlaylistCacheSize from chrome.storage in content script init so detection cache limits are configurable - Add cross-field validation: min poll interval must be < max poll interval - Add notifications permission to manifest for OS completion alerts - Add post-download actions handler (notify on completion, reveal in Finder) - Fix re-recording: clean up finished IDB entry before starting fresh - Fix CHECK_URL: use GET for HLS/DASH manifests to avoid CDN 403s on HEAD - Fix history re-download: send START_RECORDING for live stream entries - Add live badge to history items for recordings from live streams - Add history-item flash animation on completion while history tab is open Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(config): introduce loadSettings() and consolidate maxConcurrent - Add src/core/storage/settings.ts with AppSettings interface and loadSettings() — single function that reads StorageConfig and applies all defaults, eliminating scattered ?? DEFAULT_X fallbacks - Migrate all read-only config calls in service-worker.ts and options.ts load functions to use loadSettings(); save functions keep raw ChromeStorage reads to preserve merge-and-write-back pattern - Consolidate maxConcurrent into StorageConfig (was stored under a separate "max_concurrent" key); remove MAX_CONCURRENT_KEY constant - loadSettings() now does a single storage read instead of Promise.all with two keys Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(options): consolidate constants and unify notifications via toast - Add src/options/constants.ts for options-page-only validation bounds and UI timing constants (TOAST_DURATION_MS, MS_PER_DAY, MIN/MAX clamps) - Add DEFAULT_GOOGLE_DRIVE_FOLDER_NAME to shared/constants (used by both options.ts and settings.ts) - Replace showStatus(elementId, msg, type) with showStatus(msg, type) that delegates to showToast — all save confirmations now use the same bottom toast as history actions - Remove all status-msg div elements and their CSS from options.html - Replace every hardcoded clamp literal in saveRecordingSettings and saveAdvancedSettings with named constants Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(options): unify all time fields to seconds in the UI All time inputs now display in seconds; internal storage remains ms throughout. Conversion happens only at the options page boundary (divide on load, multiply on save). - ffmpeg-timeout: minutes → seconds (15 min = 900 s, range 300–3 600) - poll-min/max: ms → seconds (1 000 ms = 1 s, 10 000 ms = 10 s) - retry-delay: ms → seconds (100 ms = 0.1 s) - db-sync-interval: ms → seconds (500 ms = 0.5 s) Remove DEFAULT_FFMPEG_TIMEOUT_MINUTES, MIN/MAX_FFMPEG_TIMEOUT_MINUTES, and MS_PER_MINUTE from shared/constants (options-only). Replace with _S equivalents in options/constants.ts alongside updated _S clamp bounds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(options): remove dead code Remove makeIconBtn and iconSpinner — defined but never called. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: update CLAUDE.md and README to reflect refactor/options-page changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(styles): extract shared design tokens and badge styles into shared.css Move duplicated @font-face declarations, CSS custom properties, reset, scrollbar, and badge styles out of popup.html and options.html into a new public/shared.css. Both pages now reference it via /shared.css. Also unifies token drift between the two files: options gains --recording, --space-*, and --radius-lg; badge colors are harmonised; options gets light-mode badge overrides it was missing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(options): add inline field validation before saving Replace silent Math.max/Math.min clamping in all save handlers with explicit validation. Invalid fields get a red border and an inline error message; the button stays enabled and no write is attempted until all fields pass. Helpers: validateField(), markInvalid(), clearInvalid() in options.ts. The recording cross-field check (min < max) now marks the specific field invalid instead of falling back to the toast. Toast is reserved for storage/network errors. Documents the pattern in CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 94d5efb commit 0c5e2bf

27 files changed

+3246
-854
lines changed

CLAUDE.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Media Bridge is a Manifest V3 Chrome extension. It has five distinct execution c
3939

4040
4. **Popup** (`src/popup/``dist/popup/`): Extension action UI — Videos tab (detected videos), Downloads tab (progress), Manifest tab (manual URL input with quality selector).
4141

42-
5. **Options Page** (`src/options/``dist/options/`): FFmpeg timeout and max concurrent downloads configuration.
42+
5. **Options Page** (`src/options/``dist/options/`): Full settings UI with sidebar navigation. Sections: Download (FFmpeg timeout, max concurrent), History (completed/failed/cancelled download log with infinite scroll), Google Drive, S3, Recording (HLS poll interval tuning), Notifications, and Advanced (retries, backoff, cache sizes, fragment failure rate, IDB sync interval). All settings changes notify via bottom toast. History button in the popup header opens the options page directly on the `#history` anchor.
4343

4444
### Download Flow
4545

@@ -62,7 +62,7 @@ Download state is persisted in **IndexedDB** (not `chrome.storage`), in the `med
6262
- `downloads`: Full `DownloadState` objects keyed by `id`
6363
- `chunks`: Raw `Uint8Array` segments keyed by `[downloadId, index]`
6464

65-
Configuration (FFmpeg timeout, max concurrent) lives in `chrome.storage.local` via `ChromeStorage` (`core/storage/chrome-storage.ts`).
65+
Configuration lives in `chrome.storage.local` under the `storage_config` key (`StorageConfig` type). Always access config through `loadSettings()` (`core/storage/settings.ts`) which returns a fully-typed `AppSettings` object with all defaults applied — never read `StorageConfig` directly. `AppSettings` covers: `ffmpegTimeout`, `maxConcurrent`, `historyEnabled`, `googleDrive`, `s3`, `recording`, `notifications`, and `advanced`.
6666

6767
IndexedDB is used as the shared state store because the five execution contexts don't share memory. The service worker writes state via `storeDownload()` (`core/database/downloads.ts`), which is a single IDB `put` upsert keyed by `id`. The popup reads the full list via `getAllDownloads()` on open. The offscreen document reads raw chunks from the `chunks` store during FFmpeg processing. `chrome.storage` is only used for config because it has a 10 MB quota and can't store `ArrayBuffer`.
6868

@@ -86,7 +86,7 @@ Progress updates use two complementary channels:
8686

8787
### Message Protocol
8888

89-
All inter-component communication uses the `MessageType` enum in `src/shared/messages.ts`. When adding new message types, add them to this enum and handle them in the service worker's `onMessage` listener switch statement.
89+
All inter-component communication uses the `MessageType` enum in `src/shared/messages.ts`. When adding new message types, add them to this enum and handle them in the service worker's `onMessage` listener switch statement. `CHECK_URL` is used by the options page manifest-check feature to probe a URL's content-type via the service worker (bypassing CORS).
9090

9191
### Build System
9292

@@ -123,6 +123,31 @@ The fix uses `chrome.declarativeNetRequest` dynamic rules (`src/core/downloader/
123123

124124
HLS, M3U8, and DASH handlers support saving partial downloads when cancelled. If `shouldSaveOnCancel()` returns true, the handler transitions to the `MERGING` stage with whatever chunks were collected, runs FFmpeg, and saves a partial MP4. The abort signal is cleared before FFmpeg processing to prevent immediate rejection.
125125

126+
### Constants Ownership
127+
128+
- `src/shared/constants.ts` — only constants used across **multiple** modules (runtime defaults, pipeline values, storage keys)
129+
- `src/options/constants.ts` — constants used exclusively within the options UI (toast duration, UI bounds for all settings inputs in seconds, validation clamp values)
130+
131+
**Time representation**: All runtime/storage values use **milliseconds** (`StorageConfig`, `AppSettings`, all handlers). The options UI uses **seconds** exclusively. Conversion happens only in `options.ts`: divide by 1000 on load, multiply by 1000 on save.
132+
133+
### Options Page Field Validation
134+
135+
All numeric inputs are validated **before** saving via three helpers in `options.ts`:
136+
137+
- `validateField(input, min, max, isInteger?)` — parses the value, returns the number on success or `null` on failure. Calls `markInvalid` automatically.
138+
- `markInvalid(input, message)` — adds `.invalid` class (red border) and inserts a `.form-error` div after the input. Registers a one-time `input` listener to auto-clear when the user edits.
139+
- `clearInvalid(input)` — removes `.invalid` and the `.form-error` div.
140+
141+
Each save handler validates all fields upfront and returns early if any are invalid — the button is never disabled and no write is attempted. Cross-field constraints (e.g. `pollMin < pollMax`) call `markInvalid` on the relevant field directly rather than relying on the toast. The toast is reserved for storage/network errors.
142+
143+
### History
144+
145+
Completed, failed, and cancelled downloads are persisted in IndexedDB when `historyEnabled` (default `true`) is set. The options page History section renders all finished downloads with infinite scroll (`IntersectionObserver`). From history, users can re-download (reuses stored metadata for filename), copy the original URL, or delete entries. `bulkDeleteDownloads()` (`core/database/downloads.ts`) handles batch removal. The popup "History" button navigates to `options.html#history`.
146+
147+
### Post-Download Actions
148+
149+
After a download completes, `handlePostDownloadActions()` in the service worker reads `AppSettings.notifications` and optionally fires an OS notification (`notifyOnCompletion`) or opens the file in Finder/Explorer (`autoOpenFile`).
150+
126151
### DASH-Specific Notes
127152

128153
- No `-bsf:a aac_adtstoasc` bitstream filter — DASH segments are already in ISOBMF container format
@@ -177,7 +202,8 @@ src/
177202
│ │ ├── downloads.ts # storeDownload(), getDownload(), etc.
178203
│ │ └── chunks.ts # storeChunk(), deleteChunks(), getChunkCount()
179204
│ ├── storage/
180-
│ │ └── chrome-storage.ts
205+
│ │ ├── chrome-storage.ts
206+
│ │ └── settings.ts # AppSettings interface + loadSettings() — always use this
181207
│ ├── cloud/ # ⚠️ Planned — not wired up yet
182208
│ │ ├── google-auth.ts
183209
│ │ ├── google-drive.ts
@@ -207,6 +233,7 @@ src/
207233
│ └── utils.ts
208234
├── options/
209235
│ ├── options.ts / options.html
236+
│ └── constants.ts # Options-page-only constants (UI bounds, toast duration)
210237
├── offscreen/
211238
│ ├── offscreen.ts / offscreen.html
212239
└── types/

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ A Manifest V3 Chromium extension that detects and downloads videos from the web
1414
- **Partial Save on Cancel**: Save whatever segments were collected before cancellation
1515
- **AES-128 Decryption**: Decrypts encrypted HLS segments transparently
1616
- **Header Injection**: Injects `Origin`/`Referer` headers via `declarativeNetRequest` for CDNs that require them
17+
- **Download History**: Completed, failed, and cancelled downloads are persisted and browsable in the options page History section with infinite scroll
18+
- **Notifications**: Optional OS notification and auto-open file on download completion
19+
- **Configurable Settings**: Recording poll intervals, fetch retry behaviour, detection cache sizes, IDB sync rate — all tunable from the options page
1720

1821
## ⚠️ Output File Size Limit
1922

@@ -103,8 +106,8 @@ Media Bridge has five distinct execution contexts that communicate via `chrome.r
103106
1. **Service Worker** (`src/service-worker.ts`): Central orchestrator. Routes messages, manages download lifecycle, keeps itself alive via heartbeat.
104107
2. **Content Script** (`src/content.ts`): Runs on all pages. Detects videos via DOM observation and network interception. Proxies fetch requests through the service worker to bypass CORS.
105108
3. **Offscreen Document** (`src/offscreen/`): Hidden page that runs FFmpeg.wasm. Reads segment data from IndexedDB, muxes into MP4, returns a blob URL.
106-
4. **Popup** (`src/popup/`): Extension action UI — Videos tab, Downloads tab, Manifest tab.
107-
5. **Options Page** (`src/options/`): Configuration (FFmpeg timeout, max concurrent).
109+
4. **Popup** (`src/popup/`): Extension action UI — Videos tab (detected videos), Downloads tab (in-progress only), Manifest tab (manual URL + quality selector). A History button opens the options page directly on the history section.
110+
5. **Options Page** (`src/options/`): Full settings UI with sidebar navigation — Download, History, Google Drive, S3, Recording, Notifications, and Advanced sections. All settings changes are confirmed via a bottom toast notification.
108111

109112
### Download Flow
110113

@@ -123,8 +126,8 @@ Media Bridge has five distinct execution contexts that communicate via `chrome.r
123126

124127
| Store | Data | Reason |
125128
|-------|------|--------|
126-
| **IndexedDB** (`media-bridge` v3) | `downloads` (state), `chunks` (segments) | Survives restarts; supports large `ArrayBuffer` |
127-
| **`chrome.storage.local`** | Config (FFmpeg timeout, concurrency) | Simple K/V; 10 MB quota |
129+
| **IndexedDB** (`media-bridge` v3) | `downloads` (state + history), `chunks` (segments) | Survives restarts; supports large `ArrayBuffer` |
130+
| **`chrome.storage.local`** | All config via `loadSettings()` / `AppSettings` | Simple K/V; 10 MB quota |
128131

129132
### Project Structure
130133

@@ -168,7 +171,8 @@ src/
168171
│ │ ├── downloads.ts # Download state CRUD
169172
│ │ └── chunks.ts # Segment chunk storage
170173
│ ├── storage/
171-
│ │ └── chrome-storage.ts # Config via chrome.storage.local
174+
│ │ ├── chrome-storage.ts # Raw chrome.storage.local access
175+
│ │ └── settings.ts # AppSettings interface + loadSettings() — always use this
172176
│ ├── cloud/ # ⚠️ Planned — infrastructure exists, not yet wired up
173177
│ │ ├── google-auth.ts
174178
│ │ ├── google-drive.ts
@@ -189,7 +193,7 @@ src/
189193
│ ├── logger.ts
190194
│ └── url-utils.ts
191195
├── popup/ # Popup UI (Videos / Downloads / Manifest tabs)
192-
├── options/ # Options page (FFmpeg timeout, concurrency)
196+
├── options/ # Options page (Download, History, Drive, S3, Recording, Notifications, Advanced)
193197
├── offscreen/ # Offscreen document (FFmpeg.wasm processing)
194198
└── types/
195199
└── mpd-parser.d.ts # Type declarations for mpd-parser
@@ -251,7 +255,7 @@ npm run type-check
251255

252256
### FFmpeg Merge Fails
253257
- File may exceed the ~2 GB limit — try a shorter clip or lower quality
254-
- Increase FFmpeg timeout in Options if processing is slow
258+
- Increase FFmpeg timeout in **Options → Download Settings** if processing is slow
255259
- Check the offscreen document console for FFmpeg error output
256260

257261
### Extension Not Detecting Videos

manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"unlimitedStorage",
1111
"storage",
1212
"downloads",
13+
"notifications",
1314
"tabs",
1415
"activeTab",
1516
"offscreen",

public/shared.css

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/* ---- Fonts ---- */
2+
@font-face {
3+
font-family: 'Inter';
4+
font-weight: 400;
5+
font-style: normal;
6+
font-display: swap;
7+
src: url('/fonts/inter-regular.woff2') format('woff2');
8+
}
9+
@font-face {
10+
font-family: 'Inter';
11+
font-weight: 500;
12+
font-style: normal;
13+
font-display: swap;
14+
src: url('/fonts/inter-medium.woff2') format('woff2');
15+
}
16+
@font-face {
17+
font-family: 'Inter';
18+
font-weight: 600;
19+
font-style: normal;
20+
font-display: swap;
21+
src: url('/fonts/inter-semibold.woff2') format('woff2');
22+
}
23+
24+
/* ---- Design Tokens (Dark Default) ---- */
25+
:root {
26+
/* Surfaces */
27+
--surface-0: #0f1117;
28+
--surface-1: #181a22;
29+
--surface-2: #1e2028;
30+
--surface-3: #252830;
31+
32+
/* Text */
33+
--text-primary: #e8eaf0;
34+
--text-secondary: #8b8fa3;
35+
--text-tertiary: #5c6070;
36+
37+
/* Accent */
38+
--accent: #3b82f6;
39+
--accent-hover: #2563eb;
40+
--accent-subtle: rgba(59, 130, 246, 0.10);
41+
42+
/* Semantic */
43+
--success: #34d399;
44+
--warning: #fbbf24;
45+
--error: #f87171;
46+
--info: #60a5fa;
47+
--recording: #ef4444;
48+
49+
/* Borders */
50+
--border: rgba(255, 255, 255, 0.06);
51+
--border-hover: rgba(255, 255, 255, 0.12);
52+
53+
/* Spacing */
54+
--space-1: 4px;
55+
--space-2: 8px;
56+
--space-3: 12px;
57+
--space-4: 16px;
58+
--space-5: 20px;
59+
--space-6: 24px;
60+
--space-8: 32px;
61+
62+
/* Radii */
63+
--radius-sm: 6px;
64+
--radius-md: 10px;
65+
--radius-lg: 14px;
66+
--radius-pill: 9999px;
67+
}
68+
69+
:root.light-mode {
70+
--surface-0: #f4f5f7;
71+
--surface-1: #ffffff;
72+
--surface-2: #f0f1f4;
73+
--surface-3: #e8e9ed;
74+
75+
--text-primary: #1a1c24;
76+
--text-secondary: #5c5f72;
77+
--text-tertiary: #8b8fa3;
78+
79+
--accent: #2563eb;
80+
--accent-hover: #1d4ed8;
81+
--accent-subtle: rgba(37, 99, 235, 0.08);
82+
83+
--border: rgba(0, 0, 0, 0.08);
84+
--border-hover: rgba(0, 0, 0, 0.15);
85+
}
86+
87+
/* ---- Reset ---- */
88+
* { margin: 0; padding: 0; box-sizing: border-box; }
89+
90+
/* ---- Base ---- */
91+
body {
92+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
93+
background: var(--surface-0);
94+
color: var(--text-primary);
95+
}
96+
97+
/* ---- Scrollbar ---- */
98+
::-webkit-scrollbar { width: 5px; }
99+
::-webkit-scrollbar-track { background: transparent; }
100+
::-webkit-scrollbar-thumb {
101+
background: var(--surface-3);
102+
border-radius: 3px;
103+
}
104+
::-webkit-scrollbar-thumb:hover {
105+
background: var(--text-tertiary);
106+
}
107+
108+
/* ---- Badges ---- */
109+
.badge {
110+
display: inline-flex;
111+
align-items: center;
112+
padding: 1px 7px;
113+
border-radius: var(--radius-pill);
114+
font-size: 10px;
115+
font-weight: 500;
116+
letter-spacing: 0.01em;
117+
white-space: nowrap;
118+
}
119+
120+
.badge-resolution {
121+
background: rgba(59, 130, 246, 0.15);
122+
color: var(--info);
123+
}
124+
125+
.badge-format {
126+
background: rgba(139, 92, 246, 0.15);
127+
color: #a78bfa;
128+
}
129+
130+
.badge-link-type {
131+
background: rgba(251, 191, 36, 0.12);
132+
color: var(--warning);
133+
}
134+
135+
.badge-duration {
136+
color: var(--text-tertiary);
137+
font-size: 10px;
138+
}
139+
140+
.badge-completed {
141+
background: rgba(52, 211, 153, 0.12);
142+
color: var(--success);
143+
}
144+
145+
.badge-failed {
146+
background: rgba(248, 113, 113, 0.12);
147+
color: var(--error);
148+
}
149+
150+
.badge-cancelled {
151+
background: rgba(251, 191, 36, 0.12);
152+
color: var(--warning);
153+
}
154+
155+
.badge-live {
156+
background: rgba(248, 113, 113, 0.15);
157+
color: #f87171;
158+
}
159+
160+
/* Light-mode badge overrides */
161+
:root.light-mode .badge-resolution {
162+
background: rgba(37, 99, 235, 0.08);
163+
color: #2563eb;
164+
}
165+
166+
:root.light-mode .badge-format {
167+
background: rgba(124, 58, 237, 0.08);
168+
color: #7c3aed;
169+
}
170+
171+
:root.light-mode .badge-link-type {
172+
background: rgba(217, 119, 6, 0.08);
173+
color: #d97706;
174+
}
175+
176+
:root.light-mode .badge-completed {
177+
background: rgba(22, 163, 74, 0.08);
178+
color: #16a34a;
179+
}
180+
181+
:root.light-mode .badge-failed {
182+
background: rgba(220, 38, 38, 0.08);
183+
color: #dc2626;
184+
}
185+
186+
:root.light-mode .badge-cancelled {
187+
background: rgba(217, 119, 6, 0.08);
188+
color: #d97706;
189+
}

src/content.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
*/
55

66
import { MessageType } from "./shared/messages";
7-
import { VideoMetadata, VideoFormat } from "./core/types";
7+
import { VideoMetadata, VideoFormat, StorageConfig } from "./core/types";
88
import { DetectionManager } from "./core/detection/detection-manager";
99
import { normalizeUrl } from "./core/utils/url-utils";
1010
import { logger } from "./core/utils/logger";
11+
import { STORAGE_CONFIG_KEY } from "./shared/constants";
1112

1213
let detectedVideos: Record<string, VideoMetadata> = {};
1314
let detectionManager: DetectionManager;
@@ -158,21 +159,26 @@ function addDetectedVideo(video: VideoMetadata) {
158159
* Initialize content script
159160
* Sets up detection manager, performs initial scan, and monitors DOM changes
160161
*/
161-
function init() {
162+
async function init() {
162163
// Reset icon to gray on page load (only from top frame)
163164
if (!inIframe) {
164165
safeSendMessage({
165166
type: MessageType.SET_ICON_GRAY,
166167
});
167168
}
168169

170+
const stored = await chrome.storage.local.get(STORAGE_CONFIG_KEY);
171+
const config: StorageConfig | undefined = stored[STORAGE_CONFIG_KEY];
172+
169173
detectionManager = new DetectionManager({
170174
onVideoDetected: (video) => {
171175
addDetectedVideo(video);
172176
},
173177
onVideoRemoved: (url) => {
174178
removeDetectedVideo(url);
175179
},
180+
detectionCacheSize: config?.advanced?.detectionCacheSize,
181+
masterPlaylistCacheSize: config?.advanced?.masterPlaylistCacheSize,
176182
});
177183

178184
// Initialize all detection mechanisms

0 commit comments

Comments
 (0)