fix(analytics): use server-aggregated creator analytics endpoint#375
Open
rabble wants to merge 20 commits into
Open
fix(analytics): use server-aggregated creator analytics endpoint#375rabble wants to merge 20 commits into
rabble wants to merge 20 commits into
Conversation
Adds Filipino as a supported locale (code `fil`, label "Filipino")
with brand-voice translations across all 11 namespaces.
- `tl`, `tl-PH`, and `fil-PH` browser locales all alias to `fil`
so users on older or newer OS settings get matched.
- Voice tuned for PH internet-native users: Taglish where natural
("Loops ng mga sinusundan mo.", "Tara, sumama sa community"),
English kept for nav/category labels (matches TikTok/IG/FB-PH
convention), formal register only on legal/policy section
titles where it belongs.
- Existing locale-parity test covers key completeness; alias
resolution test added in config.test.ts.
A native PH reviewer should sanity-check `common.json` brand
strings before this ships ("Uwi na tayo.", "Sagot ka namin.",
the NIP-44 signer fallback paragraph).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nto i18n Two issues surfaced when QAing the Filipino locale on a deploy preview: many UI strings still rendered in English even with Filipino selected. Cause #1: my initial Taglish strategy left nav/categories/discovery labels in English (Music/Sports/Discover/Categories/Hot/Tags etc.). Other locales follow a full-translation pattern (Spanish: Inicio/Descubrir/Categorias/ Musica), so Filipino now matches: Tahanan/Tuklasin/Mga Kategorya/Musika/ Sayaw/Komedya/Paglalakbay/Pamilya/Pagkain/Teknolohiya/etc. Sports, Fashion, and Profile/Analytics stay as loanwords where Tagalog has no commonly-used native term. Cause #2: four prominent strings were hardcoded in components and never went through i18n at all. Lifted them into the catalog and wired t(): - "Log in" (LoginArea) → auth.logIn - "Classic Viners" (ClassicVinersRow header) → discovery.classicViners - "Play all" (VideoFeed + SearchPage compilation buttons) → common.playAll - "Archived" + tooltip (VineBadge) → vineBadge.archived / vineBadge.tooltip New keys added to all 16 locales (English placeholders for the 14 non-fil non-en locales — they can be properly translated in follow-up PRs). Also addressed PR review feedback on the alias logic: - Removed redundant `fil: 'fil'` entry from LOCALE_ALIASES (never reached because SUPPORTED_LOCALES includes 'fil' and is checked first). - Split the alias test into two: one for legacy `tl`/`tl-PH` aliases (LOCALE_ALIASES path), one for `fil-PH` regional variants (standard split-on-dash fallback). Was misleading to lump them together. Test setup: VideoFeed/SearchPage/LoginArea tests now init i18n and stub localStorage in beforeEach (matching the AppSidebar.test.tsx pattern). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts every user-facing string in VideoCard into the i18n catalog
under the new `videoCard` namespace in common.json:
- 12 aria-labels (subtitles, mute, fullscreen, like/unlike, repost,
view-likes/reposts, comment, share, download, lists, more options)
- 7 toast title/description pairs (mute success/fail, pin/unpin
success/fail, download error/start)
- 8 dropdown menu items (pin/unpin, delete, report video/user, send
via DM, view source, mute user)
- Repost-attribution strings with i18n plural support
(repostedBySingle / repostedByMultiple_one / _other)
- Failed-load + retry copy, age-gate action labels, loading-profile
fallback, view-source dialog title
Filipino translations follow the brand-aligned full-translation
pattern (e.g., "I-mute si {name}", "Burahin ang video", "Wala na
sa feed mo si {name}.").
The 14 non-en/non-fil locales got English placeholders for now —
quality translations should follow in per-locale review PRs.
Test setup: VideoCard.test.tsx now inits i18n + stubs localStorage
in beforeEach (matching the LoginArea/VideoFeed/SearchPage pattern
established earlier in this branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…llel agents Second pass of i18n rollout: dispatched 5 parallel subagents to wire high-impact dialog and profile surfaces. Each agent edited its component(s) directly and emitted a JSON patch to /tmp/i18n-patches/; patches were aggregated and merged into all 16 locales centrally to avoid race conditions on common.json. Components wired (with new namespace per component): - addToListDialog (27 keys) - createListDialog (31 keys) - editListDialog (29 keys) - zapDialog (20 keys) - walletModal (31 keys) - profileHeader (26 keys) - loginDialog (41 keys) - accountSwitcher (7 keys) - reportContentDialog (26 keys) - deleteVideoDialog (10 keys) - viewSourceDialog (13 keys) - ageVerificationOverlay (8 keys) - badgeDetailModal (4 keys) Total: 273 keys × 16 locales. Filipino translations follow the established pattern (Spanish-style full translation, Tagalog `I-` prefix on English verbs where natural, brand-voice casual-direct tone). The 14 non-en/non-fil locales got English placeholder values for the new keys — translation-quality follow-up needed per locale. Test setup: 5 component test files that touched the new t() calls got the standard localStorage stub + initializeI18n boilerplate added to beforeEach. Verification: - 727/727 vitest tests pass - tsc --noEmit clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… agents Batch 2 of i18n rollout. Five parallel subagents wired: - Settings: ModerationSettingsPage, LinkedAccountsSettingsPage, LinkedAccounts (100 keys) - Analytics: AnalyticsPage, LeaderboardPage, DebugVideoPage (57 keys) - Forms: EditProfileForm, VideoMetadataForm, UploadPage (55 keys) - Content pages: VideoPage, ProfilePage, CategoryPage, HashtagPage, ListDetailPage, ConversationPage (160 keys, with plural forms) - Feed components: VideoCommentsModal, VideoPlayer, ThumbnailPlayer, HashtagExplorer, ContentOriginBadges, ProofModeBadge, PinnedVideosSection (46 keys) New namespaces (22 total): moderationSettings, linkedAccountsSettings, linkedAccounts, analyticsPage, leaderboardPage, debugVideoPage, editProfileForm, videoMetadataForm, uploadPage, videoPage, profilePage, categoryPage, hashtagPage, listDetailPage, conversationPage, videoCommentsModal, videoPlayer, thumbnailPlayer, hashtagExplorer, contentOriginBadges, proofModeBadge, pinnedVideosSection. Filipino translations follow the established Spanish-style full- translation pattern (Tagalog `I-` prefix on English verbs, brand-voice casual-direct). The 14 non-en/non-fil locales got English placeholders for these new keys — translation pass coming next in this PR. Test setup: ThumbnailPlayer + VideoPlayer test files (which still contained hardcoded text assertions) got the standard localStorage stub + initializeI18n boilerplate added to beforeEach. Verification: - 727/727 vitest tests pass - tsc --noEmit clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous two batches lifted ~1,200 strings into the i18n catalog
and translated them to en + fil. The 14 other locales (ar, de, es, fr,
id, it, ja, ko, nl, pl, pt, ro, sv, tr) had been carrying English
placeholder values for the new keys, which produced a degraded mixed-
language UX (existing nav/menu translated; new dialogs/profile/settings
in English).
This commit dispatches 14 parallel translation agents — one per locale
— each translating the full set of new keys from the en source. Every
agent reported 766/766 leaves translated, all interpolation placeholders
({{name}}, {{count}}, etc.) preserved verbatim, all i18next plural
suffix keys (_one/_other) intact, and brand/protocol terms (Divine,
Vine, Nostr, NIP-*, ProofMode, sats, npub, etc.) left untouched.
14 × 766 = 10,724 translation entries applied across these namespaces:
common.playAll, auth.logIn, discovery.classicViners, vineBadge,
videoCard, addToListDialog, createListDialog, editListDialog,
zapDialog, walletModal, profileHeader, loginDialog, accountSwitcher,
reportContentDialog, deleteVideoDialog, viewSourceDialog,
ageVerificationOverlay, badgeDetailModal, moderationSettings,
linkedAccountsSettings, linkedAccounts, analyticsPage, leaderboardPage,
debugVideoPage, editProfileForm, videoMetadataForm, uploadPage,
videoPage, profilePage, categoryPage, hashtagPage, listDetailPage,
conversationPage, videoCommentsModal, videoPlayer, thumbnailPlayer,
hashtagExplorer, contentOriginBadges, proofModeBadge,
pinnedVideosSection.
Tone targets per locale (from agent prompts): casual-direct in all
languages, informal address (tu/du/sen/kamu/ty/etc., not formal),
brand voice "never corporate". Existing pre-PR strings are untouched.
⚠️ NATIVE-SPEAKER REVIEW STILL RECOMMENDED before broad campaigns in
each market. AI translations are high-quality on average but may
include awkward phrasing, register mismatches, or incorrect idioms
that only a native reviewer will catch. File follow-up issues per
locale for native review.
Verification:
- 727/727 vitest tests pass
- tsc --noEmit clean
- Locale-parity test passes (every locale has every English key)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Batch 3 of the i18n rollout (5 parallel agents) wired: - Pages K (4): Index, NotificationsPage, GetEmbedPage, TrendingPage (~56 strings) - Pages L (6): MerchPage, EventPage, UniversalUserPage, AtUsernamePage, AppCallbackPage, AuthCallbackPage (~58 strings) - Landing/badges M (5): AuthenticDemo, VerifiedDemo, DecentralizedDemo, OriginalContentBadge, VideoVerificationBadgeRow (~15 strings) - Input/recording N (5): CameraRecorder, comments/Comment, PWAInstallPrompt, RelaySelector, SocialLinks (~50 strings) - Misc O (6): FullscreenVideoItem, PrivacyPage chrome, TermsPage chrome, _BrandPreview (no-op, dev-only), ui/sidebar, ui/pagination (~24 strings) 26 new namespaces, ~216 keys × 16 locales (en + fil + 14 AI translations). Translation pass: 14 parallel agents (one per locale: ar, de, es, fr, id, it, ja, ko, nl, pl, pt, ro, sv, tr) translated the new 216 keys. All reported 216/216 leaves translated, all interpolation placeholders preserved, all `_one`/`_other` plural suffixes intact, all brand names (Divine, Vine, Nostr, NIP-*, ProofMode, App Store, Google Play, Bonfire, npub, sats, etc.) untouched. Test setup: - PWAInstallPrompt.test.tsx now inits i18n in beforeEach - static-pages-i18n.test.tsx updated: the previous "keeps privacy copy in english" / "keeps terms copy in english" tests asserted the pre-i18n behavior of those pages. Page titles and "last updated" labels are now translated (chrome-only); legal body paragraphs remain hardcoded English. Tests now verify the translated chrome with a comment explaining the chrome-only scope. Verification: - 727/727 vitest tests pass - tsc --noEmit clean - Locale-parity test passes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…components CI build was failing because the main JS bundle hit 3.87 MB after this PR's i18n catalog growth (16 locales × ~1500 keys, eager-loaded via import.meta.glob). The VitePWA workbox precache limit was 3 MB, so the service-worker generation step refused to precache the asset, failing the build. - Bumped maximumFileSizeToCacheInBytes to 5 MB (with a comment flagging the longer-term fix: lazy-load locale JSON instead of eager glob). Also lands batch 4 of the i18n component-wiring rollout — 9 small remaining components/UI primitives: - FullscreenFeed (6 toast strings) - DeleteCommentDialog (10 strings) - InviteCodeForm (5 strings) - ApplePodcastEmbed (7 strings) - VideoCardWithMetrics (2 toast strings) - ui/sheet, ui/dialog, ui/breadcrumb, ui/carousel (sr-only labels) 35 keys × 16 locales added. The 14 non-en/non-fil locales got English placeholders for these new keys; a follow-up translation pass will fill those in (consistent with how earlier batches in this PR were handled). Test setup: FullscreenFeed.test.tsx now inits i18n in beforeEach. Verification: - 727/727 vitest pass - npm run build succeeds locally (3.9 MB main bundle, under new 5 MB limit) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final translation pass for this branch. 14 parallel translation agents
translated the 36 batch-4 keys (FullscreenFeed toasts, DeleteCommentDialog,
InviteCodeForm, ApplePodcastEmbed, VideoCardWithMetrics, plus shadcn
ui/sheet/dialog/breadcrumb/carousel sr-only labels) into ar, de, es, fr,
id, it, ja, ko, nl, pl, pt, ro, sv, tr.
All agents reported 36/36 leaves translated, all interpolation
placeholders ({{showName}}) and brand names (Divine, Vine, Apple
Podcasts, podcast episode titles) preserved.
⚠️ Native-speaker review still recommended for all 14 AI-translated
locales before broad market campaigns.
Verification:
- 727/727 vitest tests pass
- tsc --noEmit clean
- npm run build succeeds (3.9 MB main bundle, under 5 MB workbox limit)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds api.divine.video's new period parameter (now/today/week/month/all) as a new "Popular" tab on /trending, deep-linkable via ?sort=&period=. Drops Controversial from visible tabs (off-brand engagement-bait). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12-task TDD plan covering type changes, client/hook plumbing, URL-driven page state, controversial coercion, empty state, i18n, a11y, and verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Task 2: switch funnelcakeClient tests to existing dynamic-import + global.fetch pattern (vi.spyOn would conflict with module mocks) - Task 3: add mockFetchVideosV2 — current test mock omits it, so any trending-feed test silently calls undefined() - Task 6: drop unused period icon imports (pills are text-only) - Task 8: encode New tab as ?sort=new (fixes round-trip bug where clicking New silently reverted to Hot); drop the brittle URL-rewrite useEffect; add LocationProbe + tests for URL behavior - Task 9: extend the existing empty-state branch instead of duplicating the wrapper; concrete insertion point and test guidance - New "Risks & gotchas" section covering DiscoveryPage spillover, edge-feed cache, mock-coverage gap, and brand guardrails Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Task 2: fix step numbering (Step 3 was missing — went from 2→4)
- Task 8: switch period-pill tests to data-testid selectors so they
don't depend on the i18n mock returning real translations
(the mock returns t(key)=>key, which broke /this week/i regexes).
Add data-testid attributes to the page implementation accordingly.
Strengthen ?sort=controversial test to also assert period row hidden.
- Task 9: replace the i18n-key-alternation selector with the actual
EN translation ("Quiet hour"), since VideoFeed.test.tsx initializes
real i18n via initializeI18n. Add a negative test (popular+week
should NOT show quiet-hour empty state).
- Risks: explicit list of files that still reference 'controversial'
in code (useInfiniteSearchVideos, useInfiniteVideos, useVideoProvider,
useVideoByIdFunnelcake, relayCapabilities) — none need editing
because the SortMode type literal is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the unnecessary "add mockFetchVideosV2.mockReset()" instruction — the existing vi.clearAllMocks() in beforeEach (line 56 of useInfiniteVideosFunnelcake.test.ts) already clears all vi.fn() call history. - Fix the manual-QA Step 6 wording: it claimed the URL would rewrite from ?sort=controversial to ?sort=hot, but the actual design (Task 8) is that coercion is render-only. Updated to match the implementation. - Same fix in the PR body's test plan checklist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Conventions: add the project's mandated Co-Authored-By footer (per CLAUDE.md: "Claude Opus 4.5 <noreply@anthropic.com>"). The prior conventions block only described the subject-line format and would have produced commits missing the AI-assist trailer. - File-structure table: drop the dangling useVideoProvider.test.ts row (the file exists, no task touches it, and the new 'popular' branch in mapToFunnelcakeSortMode is exercised end-to-end via Task 8's URL-state tests). Added VideoFeed.test.tsx to the table to reflect the empty-state work in Task 9. - Task 7: drop dead trendingPage.popular.tabLabel and .tabDescription i18n keys — the page renders mode.label / mode.description from the EXTENDED_SORT_MODES constant (hardcoded EN), matching the existing pattern. Including these keys would have wasted a translation round in Task 10 across 14 locales for strings nothing reads. - Task 10 German example: replace the dropped tab-* keys with period.* translations, which are the strings actually used. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- File-structure table: change funnelcakeClient.test.ts row from "(new or extended)" to "(extend — file already exists)" to match what Task 2 actually does. The existing wording was a leftover from the first draft and could have led the executor to briefly consider creating a duplicate test file. The plan has now converged — no other substantive issues found on this pass. Verified: - jest-dom matchers (.toBeInTheDocument, .toHaveAttribute) are set up in src/test/setup.ts, so the new tests' DOM assertions will work - vi.clearAllMocks() in the existing useInfiniteVideosFunnelcake test beforeEach clears call history but not mockReturnValue; the existing pattern re-sets defaults each beforeEach, and our new tests follow that pattern - queryKey changes (period added) trigger fresh TanStack Query state per period — desired UX for switching windows - Recommendations test path doesn't exercise fetchVideosV2, so adding the mock is safe and won't change existing assertions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Real bug found that prior passes missed: useInfiniteVideosFunnelcake.ts:210-234 unconditionally consumes window.__DIVINE_FEED__ on the first trending page, regardless of sortMode or period. The Fastly edge worker injects this for the default trending feed (sort=watching, no period). Result: a user who deep-links to /trending?sort=popular&period=today would see the wrong sort on page 1 (edge default) and only get Popular Today starting on page 2. Confusing, broken UX for the most common entry point (shared/bookmarked URLs). My earlier "Risks & gotchas" note had claimed the queryKey change made this safe. That was wrong — the edge consumption is inside queryFn before any queryKey-driven cache lookup. Fix: Task 3 gains a new Step 7 that guards edge consumption with `!period`, plus a regression test asserting fetchVideosV2 is called and the edge cache is left intact when period is set. Existing Hot/Top/Rising sorts keep their edge behavior — they already accept the same kind of mismatch in production and this plan deliberately doesn't expand the scope. Renumbered Steps 7-9 → 8-10. Updated Risks section to reflect that the bug is fixed by the plan, not just documented. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Task 3 Step 7 fix added in the previous pass needs a manual QA step in Task 12 that actually exercises the bug. The edge worker only injects __DIVINE_FEED__ on initial HTML responses, not on client-side SPA navigations — so testing the fix requires opening a fresh tab to the deep link, not clicking around. The new Step 8 in Task 12's manual verification: 1. Open a fresh tab to /trending?sort=popular&period=week 2. Verify the first API call includes sort=popular&period=week 3. Verify window.__DIVINE_FEED__ is still defined (untouched because the hook skipped it) Without this, a regression in the edge guard would slip past unit tests (which mock window) and only surface in production when the first user reports "the popular page shows the wrong sort on first load." Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…vior
Verified against compute-js/src/index.js:204-207: the Fastly edge
worker injects BOTH window.__DIVINE_FEED__ and __DIVINE_FEED_TYPE__
together, with the latter set to a string ("trending" by default,
or one of the discovery-feed values). The previous version of the
regression test set __DIVINE_FEED__ but explicitly deleted
__DIVINE_FEED_TYPE__ — that exercises the hook's fallback branch
(edgeFeedType === undefined && feedType === 'trending') instead of
the realistic branch (edgeFeedType === feedType).
Updated the test fixture to:
1. Set __DIVINE_FEED_TYPE__='trending' to match the production
injection shape, exercising the realistic code path
2. Tighten the cleanup to delete both globals (matches what the
hook would have done if it had consumed the cache)
3. Use a typed EdgeWindow alias so the casts are less noisy
4. Add a comment pointing to the edge worker source line so a
future reader can verify the assumption
A regression that broke the realistic branch of the guard would
have slipped past the previous test version. Now caught.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The dashboard was paginating up to 4×50=200 videos and POSTing every ID
to /api/videos/stats/bulk, which (a) silently truncated totals for any
creator with more than 200 uploads and (b) failed outright with HTTP 400
"Maximum 100 event_ids allowed" once a creator crossed 100 uploads -
the symptom rendered as "Funnelcake bulk stats error: 400" on the page.
Switch to the existing NIP-98 endpoint GET /api/users/{pubkey}/analytics,
which returns server-aggregated totals, timeseries, and top-N IDs over
the whole catalogue. Hydrate just the top-N IDs via /api/videos/bulk for
title/thumbnail rendering. Net: 3 requests instead of 5, no per-creator
size cliff, and the totals are correct regardless of catalogue size.
- config: add userAnalytics + bulkVideos endpoint paths
- types: mirror CreatorAnalyticsResponse / TopPost / summary / timeseries
- funnelcakeClient: rename private auth helper to be generic across
Funnelcake endpoints, add fetchCreatorAnalytics + fetchBulkVideos
- analyticsTransform: replace the merge/compute path with
kpisFromAnalytics + topPostToPerformance reading the aggregate
- useCreatorAnalytics: drop the paginator; require signer from
useCurrentUser; queryKey now carries the window
- tests: rewrite for the new transform surface (30 passing)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deploying divine-web with
|
| Latest commit: |
e4cb631
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://e9239eed.divine-web.pages.dev |
| Branch Preview URL: | https://fix-creator-analytics-aggreg.divine-web.pages.dev |
🚀 Preview Deployment
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The creator analytics dashboard was failing with "Funnelcake bulk stats error: 400" for active creators. Root cause:
useCreatorAnalyticspaginated up to 4×50=200 of the creator's videos and POSTed all those IDs to/api/videos/stats/bulk, which (a) silently truncated totals for anyone with more than 200 uploads and (b) returnedHTTP 400 "Maximum 100 event_ids allowed"once a creator crossed 100 uploads.Switch to the existing NIP-98 endpoint
GET /api/users/{pubkey}/analytics(handler lives in funnelcake atcrates/api/src/handlers.rs:6227, registered atrouter.rs:734). It returns server-aggregated totals, timeseries, and a top-N posts list over the full catalogue. Hydrate the top-N IDs (≤10) viaPOST /api/videos/bulkfor title/thumbnail rendering.What changes
signer(the analytics endpoint is NIP-98); dashboard page already gates onuser?.pubkeyso this is a sub-second waitFiles
src/config/api.ts— +userAnalytics, +bulkVideosendpoint pathssrc/types/funnelcake.ts— types mirroringCreatorAnalyticsResponse/CreatorAnalyticsSummary/CreatorAnalyticsTimeseries/CreatorTopPostsrc/types/creatorAnalytics.ts— +CreatorAnalyticsWindowtype, +windowfield onCreatorAnalyticsDatasrc/lib/funnelcakeClient.ts— renamed private helperauthenticatedNotificationRequest→authenticatedFunnelcakeRequest(it's no longer notifications-only; 3 internal call sites updated); addedfetchCreatorAnalytics(NIP-98) andfetchBulkVideossrc/lib/analyticsTransform.ts— replaced merge/compute path withkpisFromAnalytics+topPostToPerformance+ newbuildAnalyticsData(analytics, topMeta, profile)signaturesrc/hooks/useCreatorAnalytics.ts— dropped the paginator; pulls signer fromuseCurrentUser; queryKey now carrieswindow;enabledrequires bothpubkeyandsignersrc/lib/analyticsTransform.test.ts— rewritten for the new transform surface (8 tests)AnalyticsPage.tsxdid not need changes — the hook's public signature is backward-compatible (window param defaults to30d).Test plan
npx tsc --noEmit— cleannpx vitest run src/lib/analyticsTransform.test.ts src/lib/funnelcakeClient.test.ts— 30/30 passnpx eslinton all 7 changed files — 0 issues/analytics, confirm totals render and reflect the full catalogue/analytics, confirm totals still render and match the prior dashboard🤖 Generated with Claude Code