Skip to content

Plan: admin-analytics-dashboard (owner-gated /admin surface + Phase 2 PostHog)

URL: https://mkdocs.justinsforge.com/memory/plans/admin-analytics-dashboard-2026-07-07/

Date: 2026-07-07 Approved spec: A new owner-gated /admin surface on app.creatortrack.ai (dev :3062/admin) showing platform-wide and per-user analytics built entirely on CreatorTrack's own Postgres. Platform view: total users/workspaces, DAU/WAU/MAU, signups over time, job-run/error health. Users list -> per-user drill-down: activity & engagement (last seen, sessions/day approximated from activity gaps, pageviews over time), app adoption (which apps they open + frequency), content volume (nodes/tasks/transactions/campaigns/CRM records created). Hard-gated by assertPersonalAppsOwner(me); cross-user aggregates run inside asUser(ownerIdentity) where RLS already grants all-row visibility. Read-only. Phase 2 (documented, NOT built this session): self-hosted PostHog at posthog.creatortrack.ai behind Cloudflare Access, with replay/funnel/dwell panels embedded back into /admin.

Worktree: ~/forge-suite-wt/feat/admin-analytics-dashboard (branch feat/admin-analytics-dashboard, DB clone ct_feat_admin_analytics_dashboard, dev on :3062)

Out of scope this round: - PostHog deployment, instrumentation, or embedding (Phase 2, documented only). - True screen-time/dwell tracking (Phase 1 approximates engagement from event timestamps; real dwell comes from PostHog). - Any write/mutation path; the entire surface is read-only. - Changing the existing per-page analytics (getPageAnalytics) behavior. - New DB roles or RLS bypass; owner all-row visibility via existing RLS only.

Task list

Task 1: Platform-overview read-model

  • Files: create ~/forge-suite-wt/feat/admin-analytics-dashboard/lib/admin-analytics.ts
  • What: Add getPlatformOverview(owner: Identity) returning { totalUsers, totalWorkspaces, totalMemberships, dau, wau, mau, signupsByDay[] }. DAU/WAU/MAU = distinct actor_user_id across app.activity + distinct user_id across app.page_views in the window. Signups from core.users.created_at. All queries wrapped in asUser(owner, tx).
  • Verification: cd ~/forge-suite-wt/feat/admin-analytics-dashboard && npx tsc --noEmit 2>&1 | head (clean) and PGDATABASE=ct_feat_admin_analytics_dashboard npx tsx -e "import {getPlatformOverview} from './lib/admin-analytics'; import {ownerIdentity} from './lib/admin-analytics'; getPlatformOverview(ownerIdentity()).then(r=>{console.log(JSON.stringify(r,null,2));process.exit(0)})" returns counts without error.
  • Commit: feat(admin): platform-overview read-model (getPlatformOverview)

Task 2: Per-user read-models

  • Files: edit lib/admin-analytics.ts
  • What: Add listUsers(owner) -> [{ userId, email, name, createdAt, lastSeen, events30d, pageviews30d }] (lastSeen = max activity/pageview ts); and getUserDetail(owner, userId) -> { profile, activityByDay[], pageviewsByDay[], appAdoption[], contentVolume }. appAdoption derived from app.nodes.type + app.activity grouped by app; contentVolume = counts from app.nodes, finance.transactions, campaigns.*, crm.* for that user's workspaces. Sessions/day approximated by grouping activity timestamps with a 30-min gap threshold.
  • Verification: npx tsc --noEmit clean; PGDATABASE=ct_feat_admin_analytics_dashboard npx tsx -e "import {listUsers,getUserDetail,ownerIdentity} from './lib/admin-analytics'; (async()=>{const u=await listUsers(ownerIdentity());console.log(u.length,'users');if(u[0])console.log(JSON.stringify(await getUserDetail(ownerIdentity(),u[0].userId),null,2));process.exit(0)})()" runs clean.
  • Commit: feat(admin): per-user read-models (listUsers, getUserDetail)

Task 3: Owner-gated JSON API routes

  • Files: create app/api/admin/overview/route.ts, app/api/admin/users/route.ts, app/api/admin/users/[id]/route.ts
  • What: Each route: const me = await currentUser(); assertPersonalAppsOwner(me); (404/notFound semantics via 403 JSON for non-owner), then return the matching read-model as JSON. Accept ?days= window param (default 30).
  • Verification: With dev owner session active on :3062: curl -fsS http://127.0.0.1:3062/api/admin/overview | jq '{totalUsers,dau}' returns numbers; curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3062/api/admin/overview without owner cookie returns 401/403.
  • Commit: feat(admin): owner-gated /api/admin overview + users routes

Task 4: /admin route scaffold + hard gate + Platform view

  • Files: create app/(suite)/admin/layout.tsx (calls assertPersonalAppsOwner(me), else notFound()), app/(suite)/admin/page.tsx (Platform overview: stat tiles for users/workspaces/DAU/WAU/MAU + signups-over-time chart), app/(suite)/admin/_components/StatTile.tsx
  • What: Server components reading getPlatformOverview directly (not via HTTP). Reuse existing chart primitives from the finance/analytics UI where present; otherwise a minimal inline bar/line.
  • Verification: Browser (dev owner) http://100.97.43.104:3062/admin renders platform tiles + chart; non-owner request curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3062/admin returns 404.
  • Commit: feat(admin): /admin route with owner gate + platform overview

Task 5: Users list + per-user drill-down UI

  • Files: create app/(suite)/admin/users/page.tsx (sortable list: email, last seen, 30d events, 30d pageviews), app/(suite)/admin/users/[id]/page.tsx (drill-down: engagement charts, app-adoption breakdown, content-volume tiles, recent activity timeline), app/(suite)/admin/_components/UserRow.tsx, app/(suite)/admin/_components/AdoptionBars.tsx
  • What: Server components using listUsers / getUserDetail. Rows link to /admin/users/[id].
  • Verification: Browser: /admin/users lists users; clicking a row loads /admin/users/<id> with populated charts and content-volume tiles; no console errors in the dev log tail /tmp/ct-agent-feat_admin_analytics_dashboard.log.
  • Commit: feat(admin): users list + per-user analytics drill-down

Task 6: Owner-only nav entry

  • Files: edit the suite sidebar/nav component (identify exact file in app/(suite)/layout.tsx render tree; likely components/workspace/*Sidebar* or the layout itself)
  • What: Add an "Admin" nav link visible only when isPersonalAppsOwner(me), routing to /admin.
  • Verification: Browser (dev owner): Admin link visible in nav and navigates to /admin. Confirm gate: link absent in the owner-filtered branch when isPersonalAppsOwner is false (grep the conditional).
  • Commit: feat(admin): owner-only Admin nav entry

Task 7: Resilience pass (per-panel error isolation)

  • Files: edit app/(suite)/admin/page.tsx, app/(suite)/admin/users/[id]/page.tsx
  • What: Wrap each data panel so a failing query renders an inline error card instead of crashing the route (React error boundary or try/catch around each read-model call returning a typed error state). Honors doctrine "fail loud" (surface the error visibly, don't swallow).
  • Verification: Temporarily point one read-model at a bad table name, reload /admin, confirm only that panel shows an error card and the rest render; revert.
  • Commit: feat(admin): per-panel error isolation on /admin

Task 8: Phase-2 PostHog deployment spec (documentation only, no infra)

  • Files: create ~/forge-suite-wt/feat/admin-analytics-dashboard/docs/admin-posthog-phase2.md
  • What: Write the Phase-2 blueprint: self-hosted PostHog LXC on the fleet, hostname posthog.creatortrack.ai via Cloudflare tunnel + Cloudflare Access (owner-only), Next.js posthog-js App-Router instrumentation plan, which events to capture, and which panels (session replay, activation funnel, retention) to iframe-embed into /admin. Explicitly a plan, not executed this session.
  • Verification: test -f docs/admin-posthog-phase2.md && head -5 docs/admin-posthog-phase2.md
  • Commit: docs(admin): Phase-2 PostHog deployment blueprint

Task 9: Register and document

  • Files: edit /home/justinwieb/forge/MEMORY.md (index line under CreatorTrack section), create /home/justinwieb/forge/memory/general/reference_creatortrack_admin_analytics.md
  • What: Topic file documenting the /admin surface (route, owner gate, read-model lib, data sources, Phase-2 PostHog pointer) with the mkdocs URL: header; add MEMORY.md index entry. Run the CreatorTrack production build as the end-to-end check.
  • Verification: cd ~/forge-suite-wt/feat/admin-analytics-dashboard && npx next build 2>&1 | tail -20 succeeds (route /admin in the build output); grep admin_analytics /home/justinwieb/forge/MEMORY.md.
  • Commit: docs(admin): register admin-analytics in forge memory