chore(watch-history): production-visible opt-in diagnostics for removal/read

Watch-history delete still misbehaves for the user; add targeted diagnostics to
pinpoint whether it's the publish, relay propagation, or the local cache/read.

- logWatchHistoryDebug now routes to console (not the prod-suppressed devLogger),
  so enabling the existing `bitvid:debug:watch-history` flag surfaces logs on the
  hosted site too.
- Instrument the removal path (watchHistoryController): the computed remaining
  list (count + values + source), snapshot result, list-update result/finalCount,
  and publish failures.
- Instrument the read path (historyView.loadHistory): what a (force) refresh
  actually returns (count + values) — directly tests the "is the cache returning
  the stale full list?" hypothesis.

Flag-gated and off by default; no behavior change (watch-history tests green).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-20 18:16:10 -04:00
1 parent 5dd5451dc7
commit 4a2ba2cc2f
3 files changed
+57 -12

No files matched your search

+8
View File
@@ -16,6 +16,7 @@ import {
} from "./config.js";
import { getApplication } from "./applicationContext.js";
import { devLogger, userLogger } from "./utils/logger.js";
import { logWatchHistoryDebug } from "./watchHistoryDebug.js";
import {
normalizeVideoModerationContext,
applyModerationContextDatasets,
@@ -2321,6 +2322,13 @@ export function createWatchHistoryRenderer(config = {}) {
});
}
const feedItems = Array.isArray(result?.items) ? result.items : [];
logWatchHistoryDebug("watch-history:read", "info", "loadHistory result", {
force: force === true,
itemCount: feedItems.length,
values: feedItems
.map((entry) => entry?.pointer?.value || entry?.value)
.slice(0, 20),
});
const normalized = [];
for (const entry of feedItems) {
const pointer = normalizePointerInput(entry?.pointer || entry);
+34 -4
View File
@@ -5,6 +5,7 @@ import {
} from "../nostr/watchHistory.js";
import { normalizeDesignSystemContext } from "../designSystem.js";
import { devLogger } from "../utils/logger.js";
import { logWatchHistoryDebug } from "../watchHistoryDebug.js";
const noop = () => {};
@@ -307,6 +308,15 @@ export default class WatchHistoryController {
});
}
logWatchHistoryDebug("watch-history:remove", "info", "removal requested", {
removedPointerKey,
itemsSource: Array.isArray(payload.items) && payload.items.length
? "renderer"
: "loadLatest",
remainingCount: normalizedItems.length,
remainingValues: normalizedItems.map((p) => p?.value).slice(0, 20),
});
this.showSuccess("Removing from history…");
try {
@@ -316,18 +326,33 @@ export default class WatchHistoryController {
// Removal must replace the list, not merge the removed item back in.
replace: true,
});
logWatchHistoryDebug("watch-history:remove", "info", "snapshot published", {
ok: snapshotResult?.ok,
empty: snapshotResult?.empty,
skipped: snapshotResult?.skipped,
});
try {
await this.nostrClient?.updateWatchHistoryList?.(normalizedItems, {
actorPubkey: actorCandidate || undefined,
replace: true,
source: reason,
const updateResult = await this.nostrClient?.updateWatchHistoryList?.(
normalizedItems,
{
actorPubkey: actorCandidate || undefined,
replace: true,
source: reason,
},
);
logWatchHistoryDebug("watch-history:remove", "info", "list update published", {
ok: updateResult?.ok,
finalItemCount: updateResult?.items?.length,
});
} catch (updateError) {
devLogger.warn(
"[watchHistoryController] Failed to update local watch history list:",
updateError,
);
logWatchHistoryDebug("watch-history:remove", "warn", "list update failed", {
error: updateError?.message || String(updateError),
});
}
this.showSuccess(
@@ -336,6 +361,11 @@ export default class WatchHistoryController {
return { handledToasts: true, snapshot: snapshotResult };
} catch (error) {
logWatchHistoryDebug("watch-history:remove", "warn", "removal publish failed", {
error: error?.message || String(error),
retryable: error?.result?.retryable,
result: error?.result || null,
});
let message = "Failed to remove from history. Please try again.";
if (error?.result?.retryable) {
message =
+15 -8
View File
@@ -1,7 +1,5 @@
// js/watchHistoryDebug.js
import { devLogger } from "./utils/logger.js";
const WATCH_HISTORY_DEBUG_STORAGE_KEY =
"bitvid:debug:watch-history";
@@ -170,10 +168,19 @@ export function logWatchHistoryDebug(namespace, level, message, details) {
return;
}
// Route to console directly (not devLogger) so these opt-in diagnostics are
// visible in PRODUCTION builds too — the whole point of the flag is to debug
// issues on the hosted site, where devLogger is suppressed.
const consoleScope =
globalScope && globalScope.console ? globalScope.console : null;
const sink = consoleScope || (typeof console !== "undefined" ? console : null);
if (!sink) {
return;
}
const methodName =
typeof level === "string" && level && typeof devLogger[level] === "function"
typeof level === "string" && level && typeof sink[level] === "function"
? level
: typeof devLogger.info === "function"
: typeof sink.info === "function"
? "info"
: "log";
@@ -181,13 +188,13 @@ export function logWatchHistoryDebug(namespace, level, message, details) {
try {
if (details && typeof details === "object") {
devLogger[methodName](prefix, details);
sink[methodName](prefix, details);
} else {
devLogger[methodName](prefix);
sink[methodName](prefix);
}
} catch (error) {
if (typeof devLogger.log === "function") {
devLogger.log(prefix);
if (typeof sink.log === "function") {
sink.log(prefix);
}
}
}