feed: add public popularity (views-over-time) chart in the ⋯ menu (#26)

A "Popularity" item in the video ⋯ menu opens a public views-over-time chart
(view data is public). Builds on the now-trustworthy view counts (#4).

- buildViewCountTimeSeries buckets raw kind-30079 view events by the counter's
  dedupe window (one view per viewer per day) into a cumulative series.
- buildViewCountChartSvg renders a token-colored (currentColor / text-accent)
  SVG area+line chart — no charting library, no raw colors.
- The modal fetches via listVideoViewEventsWithDefaultClient, shows the total,
  and updates LIVE by subscribing to view events (debounced re-render); cleans
  up on close (Esc / backdrop / ✕). Lazy-loaded from the menu handler so it
  only ships when opened.

New size tokens for the modal; reuses .ds-overlay-backdrop for the scrim.

Tests: tests/view-count-chart.test.mjs (cumulative dedupe, junk filtering,
SVG line/area + empty/single-point).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-24 13:51:41 -04:00
1 parent 0a37e625bb
commit 575befb83c
7 files changed
+445 -5

No files matched your search

+22
View File
@@ -1162,6 +1162,28 @@ export default class MoreMenuController {
.catch(() => this.callbacks.showError("Failed to copy the link."));
break;
}
case "view-stats": {
const targetVideo =
video || (context === "modal" ? currentVideo : null) || currentVideo;
if (!targetVideo) {
this.callbacks.showError("No video selected.");
break;
}
// Dismiss the menu, then open the public popularity chart (lazy-loaded).
try {
this.activePopover?.close?.();
} catch (error) {
// ignore
}
try {
const { openPopularityModal } = await import("../../viewCountChart.js");
openPopularityModal({ video: targetVideo });
} catch (error) {
userLogger.warn("[MoreMenu] Failed to open popularity modal:", error);
this.callbacks.showError("Couldn’t open popularity.");
}
break;
}
case "remove-history": {
await this.callbacks.handleRemoveHistoryAction(dataset);
break;