From 4f7ef08935c8c59fa2743b82d0e33345d7a9fb3c Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 16 Jun 2026 15:40:06 -0400 Subject: [PATCH] fix(feed): render the grid after the disclaimer is dismissed (logged-out) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/KNOWN_BUGS.md | 27 +++++++++++++++++---------- js/disclaimer.js | 9 +++++++++ js/index.js | 21 +++++++++++++++++++++ scripts/perf/disclaimer-grid.mjs | 30 +++++++++++++++++++++++------- 4 files changed, 70 insertions(+), 17 deletions(-) diff --git a/docs/KNOWN_BUGS.md b/docs/KNOWN_BUGS.md index 844e0ca3..0fd252ba 100644 --- a/docs/KNOWN_BUGS.md +++ b/docs/KNOWN_BUGS.md @@ -92,19 +92,26 @@ refactor work in that doc — these are defects. 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 +### 1. Logged-out: video grid stays empty until a manual refresh — FIXED (2026-06-16) - **Severity:** medium (first-load UX, logged-out). -- **Where:** initial feed render path (`js/index.js` disclaimer flow → - `viewManager` → `feedCoordinator`); rendering is a side-effect of event - ordering. - **Symptom:** logged out, after dismissing the disclaimer the grid doesn't populate until the page is refreshed. -- **Root cause (suspected):** the grid paints as a side-effect of a data event - that can fire before the grid container is mounted (render coupled to event - ordering, not to state). -- **Fix:** P5 — unidirectional flow: render from AppState whenever it changes, - decoupled from event ordering. Could not reproduce headlessly (real-relay - feed returned nothing in the sandbox); reproduce in a real browser first. +- **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 diff --git a/js/disclaimer.js b/js/disclaimer.js index 8fa7df76..3e0b747f 100644 --- a/js/disclaimer.js +++ b/js/disclaimer.js @@ -42,6 +42,15 @@ class DisclaimerModal { this.modal.classList.add("hidden"); } localStorage.setItem("hasSeenDisclaimer", "true"); + // The feed view mounts/renders DURING boot while this disclaimer is open and + // the background is inert/hidden, so the initial grid render is dropped and + // never recovers without a manual refresh (KNOWN_BUGS #1). Now that the + // background is interactive again, tell the app to (re)render the feed. + try { + document.dispatchEvent(new CustomEvent("bitvid:disclaimer-dismissed")); + } catch (_) { + // CustomEvent unavailable (non-browser) — nothing to notify. + } } show() { diff --git a/js/index.js b/js/index.js index c0069f4c..a7caf6ae 100644 --- a/js/index.js +++ b/js/index.js @@ -1274,6 +1274,27 @@ async function bootstrapInterface() { window.addEventListener("hashchange", handleHashChange); + // The feed view loads here while the disclaimer (opened above for first-time + // visitors) holds the background inert, so its initial grid render is dropped. + // When the user dismisses the disclaimer the background becomes interactive + // again — re-load the feed so the grid populates without a manual refresh + // (KNOWN_BUGS #1). Fires only when a shown disclaimer is dismissed; returning + // visitors never see it, so there's no redundant reload for them. + document.addEventListener( + "bitvid:disclaimer-dismissed", + () => { + if (application && typeof application.loadVideos === "function") { + Promise.resolve(application.loadVideos(true)).catch((error) => { + devLogger.warn( + "[Interface] Failed to reload feed after disclaimer dismissed:", + error, + ); + }); + } + }, + { once: true }, + ); + await handleHashChange(); disclaimerModal.show(); diff --git a/scripts/perf/disclaimer-grid.mjs b/scripts/perf/disclaimer-grid.mjs index c40725d6..76e7bef8 100644 --- a/scripts/perf/disclaimer-grid.mjs +++ b/scripts/perf/disclaimer-grid.mjs @@ -19,23 +19,39 @@ page.on("pageerror", (e) => logs.push(`[pageerror] ${String(e).split("\n")[0]}`) await page.goto(APP, { waitUntil: "domcontentloaded", timeout: 30000 }); -// Wait for disclaimer modal to appear. -await page.waitForTimeout(3000); +// Wait for the disclaimer accept button to actually exist (loadDisclaimer is async). +await page.waitForSelector("#acceptDisclaimer", { timeout: 15000 }).catch(() => {}); +await page.waitForTimeout(1500); const hadDisclaimer = await page.evaluate(() => { const m = document.getElementById("disclaimerModal"); - return !!m && getComputedStyle(m).display !== "none" && m.offsetParent !== null; + if (!m) return false; + const cs = getComputedStyle(m); + // Detect via several common "shown" mechanisms, not just display. + return ( + m.offsetParent !== null || + cs.display !== "none" || + m.classList.contains("is-open") || + m.getAttribute("aria-hidden") === "false" || + !m.hasAttribute("hidden") + ); }); const beforeDismiss = await cardCount(page); -// Dismiss the disclaimer (click its primary/accept button). +// Dismiss the disclaimer via its real accept button. let clicked = false; try { - const btn = page.locator('#disclaimerModal button, [data-testid="disclaimer-accept"]').first(); + const btn = page.locator("#acceptDisclaimer").first(); if (await btn.count()) { await btn.click({ timeout: 4000 }); clicked = true; } } catch (_) {} -await page.waitForTimeout(6000); -const afterDismiss = await cardCount(page); +// Poll the grid for up to ~15s AFTER dismiss — captures "never renders". +const timeline = []; +for (let i = 0; i < 15; i++) { + await page.waitForTimeout(1000); + timeline.push(await cardCount(page)); +} +const afterDismiss = timeline[timeline.length - 1]; +console.log("post-dismiss card timeline (1s steps):", timeline.join(",")); // Now reload and re-check (the "refresh fixes it" path). await page.reload({ waitUntil: "domcontentloaded" });