Files
bitvid/docs/KNOWN_BUGS.md
T
thePR0M3TH3ANandClaude Opus 4.8 4f7ef08935 fix(feed): render the grid after the disclaimer is dismissed (logged-out)
KNOWN_BUGS #1: logged out, the video grid stayed empty after dismissing the
disclaimer until a manual page refresh.

Reproduced deterministically with scripts/perf/disclaimer-grid.mjs (fresh
context, real relays) once it actually waits for and dismisses the
disclaimer: post-dismiss card timeline was 0,0,0,… and only a reload
yielded 132 cards.

Root cause: bootstrapInterface (js/index.js) opens the disclaimer — which
makes the background inert via staticModalAccessibility — and THEN awaits
handleHashChange(), which loads the feed view and renders into that
inert/hidden background, so the initial grid mount/render is dropped.
Dismissing the disclaimer never re-triggered a render.

Fix: disclaimer.js hide() now dispatches a `bitvid:disclaimer-dismissed`
event; index.js listens once and calls application.loadVideos(true) so the
feed re-renders into the now-interactive background. Only fires when a shown
disclaimer is dismissed, so returning visitors (who never see it) get no
redundant reload.

Harness after fix: post-dismiss timeline 132,132,… immediately. Full build
+ unit suite green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 15:40:06 -04:00

11 KiB

Known Bugs (deferred)

Running list of confirmed bugs/limitations found during the data-flow refactor (see architecture-refactor.md) that we chose to fix later rather than block the current phase. Distinct from the planned refactor work in that doc — these are defects.

Format: [severity] short title — where — what — suggested fix / phase.


Open

0. Encrypted lists (hashtags, DMs, blocks) fail to DECRYPT on login (top priority)

  • Severity: high — the user's recurring "hashtags/DMs/watch-history don't load in the profile modal."
  • Diagnosed from real-env console (2026-06-14): the lists are fetched (e.g. block events Array(2)) but decryption fails, three different ways:
    • DMs: DM decryption helpers are unavailable (client.js listDirectMessages → buildDmDecryptContext returns 0 decryptors → hard throw, no retry). Happens when ensureExtensionPermissions returns ok:false.
    • Blocks: Decryption timed out; signerStatus: 'missing'/'unknown' (userBlocks.js applyEvents) — the signer/decrypt capability isn't resolved.
    • Hashtags: Decryption timed out after 6s (hashtagPreferencesService.js).
  • Root cause (strong): a relay-connection STORM. On cold load the app opens WebSockets to the user's full ~20-relay NIP-65 list (most dead) because subsystems build relay lists from relayManager.getReadRelayUrls() + getWriteRelayUrls() (uncapped) — e.g. userBlocks.loadBlocksInternal. The dozens of failing connections churn the main thread and starve the nip-07 extension's postMessage round-trips → permission/decrypt calls fail or time out.
  • Partial mitigation shipped: hashtag + subscription decrypt timeout 6s→15s.
  • Relay-storm fix SHIPPED (2026-06-14): capReadRelays (in js/nostr/toolkit.js) bounds every read/subscribe path to MAX_SUBSCRIBE_RELAYS = 8, applied centrally at the chokepoints — client.js applyRelayPreferences (this.relays/readRelays) and relayBatchFetcher.js fetchListIncrementally (used by blocks, subs, hashtags). Writes stay uncapped. The helper is user-relays-first (a user's own relays are where their data authoritatively lives, and small fast-path limits would otherwise crowd them out — the bug that broke the first attempt) while RESERVING RESERVED_DEFAULT_RELAY_SLOTS = 2 for the reliable default aggregators so the "~20 personal relays, all dead" case still reaches a live relay. Test-aware: under test the reserved core is the test-relay override, so harnesses stay isolated. Regression coverage: tests/relay-subscribe-cap.test.mjs (bounded + reserved-defaults + writes-uncapped), and the spec-corrected user-blocks, subscriptions-manager, and nostr/client fetch tests.
  • Real-env follow-up (2026-06-15 console): the relay cap WORKED (every sub now fans out to exactly 8 relays) but exposed the true blocker: under the post-login burst the nip-07 extension's message port drops ("message channel closed"), after which EVERY decrypt hangs to its ~15s timeout, each list service retries forever, and the app never recovers without a page refresh (CPU pinned — "fans at max"). The signer-readiness gate also took ~67s. Two-part fix in progress ("cut the burst, then add a safety net"):
    • Burst reduction (SHIPPED, increment 1): stop eagerly loading DM history at login (authSessionCoordinator dmStatePromise now defers; DMs load lazily when the Messages tab opens). Removes a 50-message decrypt burst that competed for the channel. Test: tests/app/login-defers-dms.test.mjs.
    • Channel circuit breaker (SHIPPED, increment 2): js/nostr/nip07Permissions.js now opens a circuit after CIRCUIT_TIMEOUT_THRESHOLD consecutive call timeouts — subsequent calls fail FAST (code: "nip07-channel-unresponsive") instead of each hanging ~15s, so the per-list retry loops stop pinning the CPU. A single periodic probe detects recovery; one success closes the circuit (no refresh). Responsive errors don't open it; interactive permission prompts bypass it. Test: tests/nostr/nip07-channel-breaker.test.mjs.
  • Resilience shipped (increments 3-6): user-relays-first relay cap with reserved defaults; transient-decrypt → retry (channel-death/timeout no longer swallowed as "empty"); fetch/decrypt decouple + ProfileModalController ensureLoaded (cut the login REQ burst); subscription refresh-loop guard; generous decrypt budget (30s) + 60s backoff cap so a slow-but-responsive signer completes; permission-handshake variant timeout 3s→20s so slow signers aren't killed mid-grant.
  • DEFINITIVE root cause for "lists never load" (2026-06-16): confirmed with a raw window.nostr probe in the user's browser — a single clean getPublicKey → nip04.encrypt → nip04.decrypt that bypasses ALL of bitvid hung forever, and the extension's own console showed ObjectMultiplex - orphaned data for stream "background-liveness" + malformed chunk + MaxListenersExceededWarning. i.e. the NIP-07 extension's content-script ↔ background-service-worker channel was dead/orphaned (classic MV3 worker death). This is an EXTENSION/environment failure, not a bitvid bug — no client change can force an unresponsive signer to answer. Fix is user-side: reload/unlock the extension + refresh.
  • Client mitigation (SHIPPED): js/utils/signerHealthNotice.js — after a few consecutive list-decrypt timeouts with signerStatus: "present", emit ONE rate-limited, actionable user notice ("signer isn't responding to decryption — unlock/reload your extension and refresh") instead of a silent forever-retry. Wired into blocks/hashtags/subscriptions; reset on any decrypt success. Test: tests/signer-health-notice.test.mjs.
  • Harness (scripts/perf/nip07-channel-sim.mjs): with a LATENCY override it proves the client now loads all lists for signers answering up to the decrypt budget; lists only fail once per-call latency exceeds the budget (i.e. the signer is the wall, not the client).
  • Still open: liveness-ranked relay health (see #4); DM decrypt resilience (don't hard-fail DMs on a transient permission miss — retry); optional nip-07 queue concurrency bump for slow signers (risks channel drops on some providers — opt-in).

1. Logged-out: video grid stays empty until a manual refresh — FIXED (2026-06-16)

  • Severity: medium (first-load UX, logged-out).
  • Symptom: logged out, after dismissing the disclaimer the grid doesn't populate until the page is refreshed.
  • Root cause (CONFIRMED via repro): boot order in bootstrapInterface (js/index.js) opens the disclaimer (which makes the background inert via staticModalAccessibility), THEN await handleHashChange() loads the feed view and renders into that inert/hidden background — so the mount/render is dropped — and dismissing the disclaimer never re-triggered a render.
  • Fix (SHIPPED): disclaimer.js hide() dispatches a bitvid:disclaimer-dismissed event; index.js listens once and calls application.loadVideos(true) so the feed re-renders into the now-interactive background. Fires only when a shown disclaimer is dismissed (returning visitors are unaffected).
  • Repro/regression (scripts/perf/disclaimer-grid.mjs): fresh context, real relays; waits for #acceptDisclaimer, dismisses, then polls the grid for 15s. Before: post-dismiss card timeline 0,0,0,…, only a reload showed 132. After: 132,132,… immediately on dismiss. (The earlier "couldn't reproduce headlessly" was because the harness didn't actually wait for/raise the disclaimer; once it does, the bug is deterministic.)

2. Residual full-feed reload loop on list-loaded signals — FIXED (P5)

  • Status: RESOLVED 2026-06-14. handleBlocksLoaded now reloads the feed only when the block-set signature actually changes (emit-on-change guard); handleRelaysLoaded only refreshes the relay UI (never the feed), and handleHashtagPreferencesChange already deduped by signature. Regression test: tests/app/blocks-loaded-reload-guard.test.mjs (cheat-resistant).

3. Direct-pool-access lint still misses MULTI-LINE access

  • Severity: low (tooling gap — weakens the L1 chokepoint guarantee).
  • Status: partially fixed — the regex now catches bare pool.sub( / pool.list( (re-baselined to 22 files). Still MISSES access split across lines, e.g. js/services/profileMetadataService.js writes nostr.pool newline .list( — so profiles is an unflagged fetch source.
  • Fix: normalize whitespace before matching (collapse \.pool\s*\n\s*\.list) or move to an AST/grep -A1 check. Add profileMetadataService (and any other multi-line offenders) to the allowlist once detected.

4. Interim relay cap is static, not liveness-ranked

  • Severity: low (acceptable interim).
  • Where: js/nostr/toolkit.js capReadRelays — bounds the read/subscribe set to 8 (user relays first, 2 slots reserved for reliable defaults) but does NOT re-rank by live health. If a user's first 6 relays are dead, reads lean on the 2 reserved defaults until other paths recover; the dead ones still get a (bounded) connection attempt each cold load.
  • Fix: P1's deferred RelayHealth — rank the bounded set by rolling liveness and re-resolve on health changes, so dead relays drop out of the read set entirely instead of consuming slots.

Pre-existing test failures (predate this refactor)

Confirmed failing at the pre-session baseline (4bd9503c) — not caused by the refactor, but should be triaged. Not in KNOWN_ISSUES.md.

5. tests/nostr-delete-flow.test.mjs

  • "deleted events should not trigger subscription callbacks" — a deleted event reaches the feed callback (buffer delete-filter or dedupe). Investigate the VideoEventBuffer delete handling.

6. tests/view-counter.test.mjs

  • "view event should append the session tag when requested" — view-event publishing tag composition (unrelated to the P3 counting refactor).

7. tests/nostr-count-fallback.test.mjs

  • Fails at baseline; NIP-45 COUNT fallback path. Triage.

8. tests/nostr/nip07Permissions.test.js

  • writeStoredNip07Permissions / clearStoredNip07Permissions fail under Node (no real localStorage). Likely environmental — provide a localStorage shim (see tests/test-helpers/setup-localstorage.mjs) or guard the test.

9. tests/app/hydrate-sidebar-navigation.test.mjs

  • Fails at the pre-session baseline (4bd9503c). Triage.

10. tests/app/is-user-logged-in.test.mjs

  • Fails at the pre-session baseline (4bd9503c). Triage.

Perf follow-ups (not bugs, but tracked)

  • One-time login enrichment burst (~75 REQ frames): batched and O(relays), settles to idle, but could be trimmed by hydrating only on-screen cards.
  • Remove js/devReqMonitor.js import + scripts/perf/* diagnostics in P6 cleanup once the refactor lands.