Add a page-wide Humans / Bots / All toggle to the project stats page - #233
Merged
Merged
Conversation
Every number on /dashboard/projects/:id/stats now follows one ?who= value (humans by default): the headline tiles, the Traffic pulse series, each breakdown card and the live panel. It maps onto a trailing `p_kind text default null` on every tracker panel RPC (singles, _multi and the tracker_recent_* raw twins), and onto a bucket filter on the live-events route. The bucket-less rollups (event / device / geo / exit) gain a `kind` column in their primary key so a human and a bot hit on the same path are two rows; ingest derives it from the bucket. Rows from before the migration are `unknown` and appear only under All, which the toggle says. The three portfolio RPCs that spill keep `set work_mem = '16MB'`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC
ThreatCrush Security Scan39 finding(s) HIGH/CRITICAL: 2 | MEDIUM: 28 | LOW: 9
Snippets are redacted; ThreatCrush never prints matched credential material. |
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.
What the toggle does
/dashboard/projects/:id/statsgets a page-wide Humans · Bots · All segmented control (same visual language as the per-card timeframe tabs), persisted in the URL as?who=humans|bots|all, defaulthumans. One value drives everything on the page:Metrictiles (Humans: Human visits + AI referrals; Bots: Bot crawls; All: the three tiles as before)TrafficPulseseries (Humans: the human band with AI referrals inside it; Bots: the bot band alone; All: humans + bots stacked), and its frame totalBreakdownPanel/RankedPanel(event mix, sources, pages, exit pages, referrers, actions, countries, cities, devices, browsers, OS) including their timeframe-tab refetches (use-panel-rangesendswho, caches perwho:range)/api/projects/:id/live-events?who=), filtered on the bucket prefixUnder Humans or Bots a caption reads: "Split recorded from 5 Sep 2026; earlier traffic appears under All." Every label keeps its definition tooltip. The mapping lives once in
lib/tracker/who.ts; the tracker-stats and live-events routes reject a junkwhowith 400 and default to humans when absent.The
kindcolumn, and the "unknown before 5 Sep" caveatMigration
supabase/migrations/20260905190000_tracker_kind_split.sql(idempotent):kind text not null default 'unknown' check (kind in ('human','bot','unknown'))totracker_event_daily_stats,tracker_device_daily_stats,tracker_geo_daily_stats,tracker_exit_daily_stats, plustracker_exit_sessions(so the exit marker is decremented off the row it actually sits on)kindas the last column (the ingest route reads/writes by the full key, so the PK is the conflict target); guarded on whether the current PK already nameskindincludelists withkind(page_pathstays); new names created first, old ones dropped after, so coverage never lapsesapp/api/track/route.ts) deriveskindfrom the bucket once (kindFromBucket: bot iff bucket starts withbot:, AI referrals are human) and writes it into all four rollup upserts and the exit sessionsRows written before this migration are
kind = 'unknown'. Nothing in the old rollups can say what they were, so they are not guessed at: they appear only under All. Under Humans/Bots the pages / referrers / actions / exit pages / countries / cities / devices panels start on the day the migration is applied. The bucket-based figures (tiles, series, Top sources) and the rawtracker_eventstable have always carried the bucket, so those are split across all history.RPC signature changes
Every panel RPC gains a trailing
p_kind text default null(null = all rows;'human'/'bot'filter onkindfor the rollups, on the bucket prefix fortracker_daily_stats/tracker_events). Each isdrop function if exists <exact old signature>thencreate functionwith the old arguments first, so existing callers (the portfolio analytics page, MCP) keep working; grants re-issued forauthenticated, service_role;security invokerthroughout.tracker_daily_series,tracker_bucket_totals,tracker_event_mix,tracker_top_pages,tracker_top_referrers,tracker_top_actions,tracker_top_exit_pages,tracker_top_countries,tracker_top_cities,tracker_device_totals_multi: the same tentracker_recent_series,tracker_recent_bucket_totals,tracker_recent_event_mix,tracker_recent_top_pages,tracker_recent_top_referrers,tracker_recent_top_actions,tracker_recent_top_countries,tracker_recent_top_citiesset work_mem = '16MB'is preserved ontracker_top_pages_multi,tracker_top_actions_multiandtracker_top_exit_pages_multi(the drifted live definitions fromtracker_reporting_indexes); a contract test pins that it is on exactly those three. Not touched:tracker_first_day,tracker_project_totals,tracker_project_daily_series,dashboard_project_traffic.lib/tracker/series.ts's legacyevents = pageviews + interactionsbackfill is now only applied to an unfiltered series, so a filtered zero day stays zero instead of borrowing the other side's rows.Apply the migration via MCP BEFORE merge
The code passes
p_kindon every RPC call, so without the migration every stats panel 404s on the missing signature. Apply20260905190000_tracker_kind_split.sqlby hand via the Supabase MCP first, then merge. Note the PK rebuild ontracker_event_daily_stats(~1.2M rows) takes an ACCESS EXCLUSIVE lock for the seconds the index build takes; ingest writes queue for that window. The check constraints are addednot valid+validateso they never block writes.Tests
tests/tracker-who.test.ts(new): who parsing / validation, who -> p_kind,kindFromBucket, tile and series-layer selection,panelUrl,p_kindon every panel at rollup and raw ranges, backfill guardtests/contract/tracker-kind-split.test.ts(new): migration contract (kind columns + PKs, index includes, every RPC dropped by exact old signature and re-created withp_kindlast, work_mem on exactly the three, never definer)tests/contract/tracker-stats-who.test.ts(new): both API routes default / map / 400tests/contract/tracker-exit.test.ts,tests/tracker-rollup-ranges.test.tsextended🤖 Generated with Claude Code
https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC