mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
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]>
This commit is contained in:
1 parent
bf30233649
commit
4f7ef08935
4 files changed
+70
-17
No files matched your search
+17
-10
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+21
@@ -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();
|
||||
|
||||
@@ -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" });
|
||||
|
||||
Reference in new issue
Block a user