Product Analytics
MasterSelects has a first-party product-analytics pipeline for understanding whether users reach meaningful editor outcomes. It records a fixed catalog of semantic product actions, sends them to a same-origin Cloudflare Pages Function, stores them in the existing D1 database, and exposes funnels, feature engagement, sessions, and recent activity in the protected admin dashboard.
The system is deliberately separate from runtime debugging telemetry. Product analytics answers questions such as “did an import finish?” or “did an export succeed?”; playback traces, filenames, project data, media, prompts, and diagnostic logs are not product-analytics properties.
Privacy contract
Section titled “Privacy contract”- Collection can be disabled under Settings > General > Privacy > Share product usage.
- Browser
Do Not Trackand Global Privacy Control signals disable collection. - The analytics session identifier exists only in memory. The system creates no analytics cookie and does not persist a cross-browser device identifier.
- Signed-in events may be linked to the internal account ID. Anonymous events contain only the ephemeral session identifier.
- Every event and property is defined in a compile-time allowlist. Unknown events and properties are dropped again on the server.
- Filenames, paths, project names, timeline content, media bytes, prompts, chat text, transcript text, provider errors, and raw exception messages are never accepted.
- Durations, resolutions, frame rates, sizes, and error causes are reduced to bounded categories.
- Product events are retained for 180 days. An hourly guarded cleanup deletes older rows.
The user-facing disclosure lives in the privacy pages. The preference itself is stored locally as ms.productAnalytics.enabled.
Event catalog
Section titled “Event catalog”The allowlist in src/services/productAnalytics/catalog.ts currently covers:
| Area | Events |
|---|---|
| Entry | landing viewed, app opened |
| Privacy | analytics preference enabled |
| Setup | setup started, background selected, completed, cancelled |
| Tutorial | started, step viewed, completed, skipped, cancelled |
| Projects | created, opened, closed, action failed |
| Media | import started, completed, failed, cancelled |
| Editing | semantic edit committed, control committed, properties surface viewed, undo, redo, history restore |
| Playback | started, paused/stopped/ended |
| Workspace | panel opened |
| Billing | pricing viewed, checkout started, redirected, failed, returned |
| Export | started, completed, failed, cancelled |
Committed timeline edits carry fixed action, operation, origin, and target dimensions. This distinguishes, for example, a user moving a clip from an agent trimming a clip, without retaining the free-form history label.
Control events describe a completed interaction through allowlisted identifiers only: editor area, control ID, control kind, input method, interaction, and optional effect/property type. Effects and audio effects record their registry type and parameter ID; transform, color, volume, and speed controls record stable property IDs. Parameter values, clip/effect instance IDs, labels, and project content are not collected.
Properties-tab views provide the exposure side of the feature funnel. The admin dashboard can therefore compare users who opened a surface with users who completed an interaction there. Slider and drag gestures emit once when committed, not once per pointer movement. The history facade similarly reports one semantic edit for a committed batch.
Client delivery
Section titled “Client delivery”ProductAnalyticsService owns one ephemeral session, sanitizes properties before queueing, and batches up to 20 events. It flushes after four seconds, at the batch limit, and on pagehide or hidden-page transitions using Fetch keepalive. Failed network and server requests are retried twice from a bounded 100-event in-memory queue.
The service is retained across Vite HMR updates. Turning analytics off clears queued events immediately and removes the pending flush timer.
Cloudflare and D1
Section titled “Cloudflare and D1”Migration 0020_product_analytics.sql creates product_analytics_events in the existing masterselects D1 database. Important fields are:
- event ID for idempotent
INSERT OR IGNOREdelivery; - authenticated user ID when available;
- ephemeral session ID;
- allowlisted event name and schema version;
- app version;
- sanitized JSON properties;
- client occurrence and server receipt timestamps.
POST /api/analytics/events accepts at most 25 events and 48 KB per request. It enforces same-origin requests, validates identifiers and timestamp skew, re-sanitizes every property, attaches the authenticated account server-side, and returns 202 with accepted/discarded counts.
Apply the migration before deploying Pages Functions that write or query the table:
npm run cf:migrate:remotenpm run buildnpx wrangler pages deploy dist --project-name=masterselects --branch=masterAdmin dashboard
Section titled “Admin dashboard”The protected admin dashboard includes:
- active identities, sessions, signed-in users, and events per session;
- returning identities over 30 days;
- a sequential 30-day activation funnel from app open through import, edit, export start, and export completion;
- export success rate and tutorial completions;
- 14-day daily-active chart;
- semantic edit actions and categories;
- opened properties tabs, used effects/items, and committed slider/parameter controls;
- a seven-day surface-opened to control-used engagement rate per editor area;
- the latest 30 sessions with device, environment, milestones, event counts, edit counts, and observed duration;
- the latest 100 sanitized product actions with the signed-in account or an ephemeral anonymous session label;
- opened panels, export kinds, and tutorial completion rates.
Signed-in activity is attributed through the server-side account ID and joined to the protected user record. Anonymous users remain pseudonymous and can only be followed inside their ephemeral in-memory session. No fingerprint or durable anonymous identifier is created. If the migration is missing, the dashboard remains available and shows an explicit analytics-unavailable state.
Verification
Section titled “Verification”Targeted coverage is in tests/unit/productAnalytics.test.ts, tests/unit/historyAnalytics.test.ts, tests/unit/ParamSlider.test.tsx, and tests/unit/EditableDraggableNumber.test.tsx. It verifies property minimization, semantic edit classification, control metadata, authenticated attribution, server-side re-sanitization, foreign-origin rejection, one-event-per-gesture history boundaries, and numeric-control commit semantics. A local end-to-end check should additionally POST through the Vite proxy and read the resulting D1 row to prove that content-bearing fields were removed.