diff --git a/CONTEXT.md b/CONTEXT.md index 9ef7e117..cb87df5f 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,3 +1,25 @@ -Node Version: v22.22.0 -NPM Version: 11.7.0 -Date: Thu Feb 12 14:32:45 UTC 2026 +# CONTEXT - bitvid-perf-agent + +## Goal +Daily, measurable improvement of app responsiveness by identifying and remediating background CPU/network work that degrades UX — while also ensuring user-facing docs (/content) reflect the actual runtime behavior of upload/contribution flows. + +## Scope +- Repo: `PR0M3TH3AN/bitvid` (unstable branch). +- Focus Areas: + - P0: Login/auth, relay initialization, decryption (hashtags, subscriptions, blocks, watch history). + - P1: Initial UI responsiveness (profile cache, relay load). + - P2: User-initiated background features (WebTorrent). + - Docs: `/content` (user-facing docs), ensuring upload/contribution docs match code. + +## Constraints +- Never invent files, APIs, libraries, or behaviors. +- Prefer minimal incremental changes. +- Keep build/tests green. +- Preserve repo style and architecture. +- Security-critical changes require human signoff. + +## Definition of Done +- Performance issues identified and fixed with small, safe PRs. +- Docs in `/content` matched to code. +- `daily-perf-report` generated. +- Artifacts (`TODO.md`, `DECISIONS.md`, `TEST_LOG.md`) updated. diff --git a/DECISIONS.md b/DECISIONS.md index e2c91902..b743244a 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -23,3 +23,14 @@ Ran a daily dependency audit. Found `esbuild` (0.27.2 -> 0.27.3) as a safe candi - **Outcome:** BLOCKED (Major Version). - **Reason:** Major version bump (v3 -> v4) carries significant risk of breaking changes. - **Action:** Existing issue `issues/upgrade-tailwindcss.md` is sufficient. + +## 2026-02-18: Performance - Bounded Concurrency in AuthService + +### Context +`loadOwnProfile` in `js/services/authService.js` was using `Promise.allSettled` on all background relays simultaneously. For users with many relays (e.g., 50+), this caused a massive spike in network requests and CPU usage during login/startup. + +### Decision: Use `pMap` with Concurrency Limit +- **Proposal:** Replace `Promise.allSettled` with `pMap` for background relays. +- **Outcome:** IMPLEMENTED. +- **Reason:** To prevent network saturation and UI freeze. +- **Details:** Used `RELAY_BACKGROUND_CONCURRENCY` (3) to limit concurrent background requests. Fast relays (top 3) are still fetched in parallel (unbounded, but small set). diff --git a/TODO.md b/TODO.md index 5857bd57..5a2f77c4 100644 --- a/TODO.md +++ b/TODO.md @@ -9,3 +9,6 @@ ## Priority: Low (Maintenance) - [ ] Batch upgrade dev dependencies (`stylelint`, `postcss-import`). + +## Priority: High (Performance) +- [x] Bound concurrency in `authService.js` for profile hydration (P0). diff --git a/daily-perf-report-2026-02-18.md b/daily-perf-report-2026-02-18.md new file mode 100644 index 00000000..c290da7e --- /dev/null +++ b/daily-perf-report-2026-02-18.md @@ -0,0 +1,15 @@ +# Daily Performance Report - 2026-02-18 + +**Summary:** Bounded concurrency in `authService` profile hydration; verified upload docs. + +## Findings & Fixes +- **P0 Fix:** `js/services/authService.js` was launching unbounded concurrent requests for profile hydration. + - **Fix:** Implemented `pMap` with `RELAY_BACKGROUND_CONCURRENCY` (3) for background relays. + - **Impact:** Prevents network saturation for users with many relays. +- **Docs Audit:** Verified `content/docs/guides/upload-content.md` correctly specifies port 3000 for CORS. No changes needed. + +## Metrics +- **Tests:** `tests/authService.test.mjs` and `tests/auth-service.test.mjs` passed. + +## Artifacts +- Updated `CONTEXT.md`, `TODO.md`, `DECISIONS.md`, `TEST_LOG.md`. diff --git a/js/services/authService.js b/js/services/authService.js index 76cba80e..1a7326ab 100644 --- a/js/services/authService.js +++ b/js/services/authService.js @@ -43,6 +43,8 @@ import { normalizeProviderId, normalizeAuthType, } from "./authUtils.js"; +import { pMap } from "../utils/asyncUtils.js"; +import { RELAY_BACKGROUND_CONCURRENCY } from "../nostr/relayConstants.js"; export default class AuthService { constructor({ @@ -1146,21 +1148,30 @@ export default class AuthService { true, ), ); - const backgroundPromises = backgroundRelays.map((relayUrl) => - this.fetchProfileFromRelay( - relayUrl, - filter, - normalized, - BACKGROUND_PROFILE_TIMEOUT_MS, - false, - ), - ); - const background = Promise.allSettled([ - ...fastPromises, - ...backgroundPromises, + const background = Promise.all([ + Promise.allSettled(fastPromises), + pMap( + backgroundRelays, + async (relayUrl) => { + try { + const value = await this.fetchProfileFromRelay( + relayUrl, + filter, + normalized, + BACKGROUND_PROFILE_TIMEOUT_MS, + false, + ); + return { status: "fulfilled", value }; + } catch (reason) { + return { status: "rejected", reason }; + } + }, + { concurrency: RELAY_BACKGROUND_CONCURRENCY }, + ), ]) - .then((outcomes) => { + .then(([fastOutcomes, backgroundOutcomes]) => { + const outcomes = [...fastOutcomes, ...backgroundOutcomes]; const aggregated = []; outcomes.forEach((outcome) => { if (outcome.status === "fulfilled") {