From 7781e0a116ea54fe0bbf088ea979056a6dfaa3c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 06:41:50 +0000 Subject: [PATCH] Add startup, search and play timing marks plus yt-dlp duration logs --- frontend/app.js | 21 +++++++++++++++++++ plans/INDEX.md | 2 +- .../001-perf-timing-marks-105acc.md | 6 ++++++ server/server.js | 4 ++++ 4 files changed, 32 insertions(+), 1 deletion(-) rename plans/{active => done}/001-perf-timing-marks-105acc.md (88%) diff --git a/frontend/app.js b/frontend/app.js index c82771a..c5491c2 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -23,6 +23,18 @@ const ZERO = window.zero && typeof window.zero.invoke === 'function' ? window.ze const WEB = !TAURI && !ZERO; const APP_VERSION = '1.0.0'; +// Timing marks for the speed work (plans/). performance.measure entries are +// visible in DevTools → Performance; __ytpPerf() prints the latest ones. +function perfMark(name) { try { performance.mark(name); } catch { /* old browser */ } } +function perfMeasure(name, start) { + try { performance.measure(name, start); } catch { /* start mark missing */ } +} +window.__ytpPerf = () => { + const out = {}; + try { for (const m of performance.getEntriesByType('measure')) if (m.name.startsWith('ytp:')) out[m.name] = Math.round(m.duration); } catch { /* none */ } + return out; +}; + // call(zeroName, tauriName, payload) — routes to whichever native shell is present. async function call(zeroName, tauriName, payload = {}) { if (TAURI) return await TAURI.invoke(tauriName, payload); @@ -1649,6 +1661,7 @@ const Player = { }, async loadVideo(videoObj, { preferStream = false, resume = true, reveal = true, nocache = false } = {}) { + perfMark('ytp:tap'); if (!this._handoff) Transition.cancel(); // When false (auto-advance / prev), skip resuming the saved timestamp and // start from the beginning (or the A marker, if an A-B loop is set). @@ -2286,6 +2299,10 @@ function wirePlayerEvents() { }); el.addEventListener('playing', () => { if (!masterIs(el)) return; + if (performance.getEntriesByName('ytp:tap').length) { + perfMeasure('ytp:tap-to-play', 'ytp:tap'); + try { performance.clearMarks('ytp:tap'); } catch { /* ignore */ } + } showSpinner(false); Player.clearBufferGrace(); if (Player.secondary && !el.paused && Player.secondary.paused) { @@ -8584,6 +8601,7 @@ function wireUI() { q = (q || '').trim(); if (!q) return; const mySeq = ++searchSeq; + perfMark('ytp:search-start'); RecentSearches.add(q); hideRecentSearches(); view = { type: 'search' }; @@ -8607,6 +8625,7 @@ function wireUI() { searchAttempted = true; renderList(); RecentSearches.cacheResults(q, results); + perfMeasure('ytp:search', 'ytp:search-start'); } catch (err) { if (mySeq !== searchSeq) return; if (instant) return; // keep showing the cached results; fail silently @@ -9747,6 +9766,7 @@ function bootSearchFromUrl() { // Boot // ============================================================================ async function boot() { + perfMark('ytp:boot-start'); wirePlayerEvents(); wireUI(); wireShortcutHelp(); @@ -9795,6 +9815,7 @@ async function boot() { // first, which left the sidebar empty for as long as the network took. renderSmartSidebar(); render(); + perfMeasure('ytp:boot', 'ytp:boot-start'); // …then reconcile with the server in the background, with the spinner up, // and re-render only if something actually changed. diff --git a/plans/INDEX.md b/plans/INDEX.md index 96896cd..1294d4d 100644 --- a/plans/INDEX.md +++ b/plans/INDEX.md @@ -8,7 +8,7 @@ green, app boots with no JS errors, P2P on by default, offline boot works). | Seq | ID | Title | Status | Commit | Notes | |-----|----|-------|--------|--------|-------| -| 001 | 001-perf-timing-marks-105acc | Add startup, search and play timing marks plus yt-dlp duration logs | in-progress | | baseline numbers for the rest | +| 001 | 001-perf-timing-marks-105acc | Add startup, search and play timing marks plus yt-dlp duration logs | done | Add startup, search and play timing marks plus yt-dlp duration logs | baseline numbers for the rest | | 002 | 002-compress-and-etag-shell-bd459c | Serve the app shell gzip/brotli-compressed with ETags | queued | | app.js 426 KB → 94 KB (br) | | 003 | 003-self-host-fonts-89466b | Self-host the three web fonts and drop the render-blocking Google Fonts CSS | queued | | needs network once; CSP font-src fix | | 004 | 004-coalesce-stream-resolves-a92d40 | Coalesce concurrent resolveStreams calls for the same video | queued | | | diff --git a/plans/active/001-perf-timing-marks-105acc.md b/plans/done/001-perf-timing-marks-105acc.md similarity index 88% rename from plans/active/001-perf-timing-marks-105acc.md rename to plans/done/001-perf-timing-marks-105acc.md index 4528dd9..4d17fe8 100644 --- a/plans/active/001-perf-timing-marks-105acc.md +++ b/plans/done/001-perf-timing-marks-105acc.md @@ -121,3 +121,9 @@ Output ONLY the following, no other prose: skipped and why. Do not commit. Do not push. Do not touch files outside the Steps. + +## Execution log + +- Executor: in-session Agent (haiku), gateway `delegate.sh` not installed. Attempts: 1. Fix rounds: 0. +- Orchestrator re-ran Verification: syntax OK, `SERVER_OK`, `node --test frontend/*.test.js` 52 pass / 0 fail, mark count 8. +- Executor Findings (verbatim): All 6 plan steps applied successfully. Frontend timing marks added for boot (start->first paint), search (start->cached), tap-to-play (tap->playing event). Server logs yt-dlp duration on every invocation. Verification: syntax OK, server builds, unit tests pass (0 failures), 8 perfMark/perfMeasure occurrences (exceeds required 8). No deviations or skips. diff --git a/server/server.js b/server/server.js index 5e5ae6c..1ef029b 100644 --- a/server/server.js +++ b/server/server.js @@ -143,6 +143,9 @@ const CHANNEL_LIMIT = 60; function runYtdlp(args, { signal } = {}) { return new Promise((resolve, reject) => { const child = spawn(YTDLP, args, { stdio: ['ignore', 'pipe', 'pipe'] }); + const t0 = Date.now(); + const kind = String(args.find((a) => /^ytsearch|^https?:/.test(String(a))) || args[0] || '') + .replace(/^ytsearch\d*:.*/, 'search').replace(/^https?:\/\/[^/]+\/watch.*/, 'video').slice(0, 40); // Kill the download when the requesting client goes away — otherwise an // aborted/retried save leaves yt-dlp running to completion (8 copies of // one video were found pulling in parallel after the client retried). @@ -158,6 +161,7 @@ function runYtdlp(args, { signal } = {}) { child.stderr.on('data', (d) => { err += d; }); child.on('error', (e) => reject(new Error('yt-dlp not found: ' + e.message))); child.on('close', (code) => { + console.log(`[ytdlp] ${kind} ${Date.now() - t0}ms ${code === 0 ? 'ok' : 'fail'}`); if (code !== 0) reject(new Error(err.trim() || 'yt-dlp exited with code ' + code)); else resolve(out); });