Skip to content

Usage analytics for the funnel: server events after the account's own yes - #130

Merged
NiallJoeMaher merged 2 commits into
mainfrom
analytics-funnel
Nov 3, 2026
Merged

NiallJoeMaher merged 2 commits into
mainfrom
analytics-funnel

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

What

Sees who converts, across every app, without breaking the cookie promise.

  • Consent on the account. settings.analytics (yes | no | null). The web banner, the AI tool consent screen (where MCP-first people sign up) and the iPhone prompt all read and write it; a device with no answer takes the account's, the latest answer wins.
  • One track() in apps/web/lib/telemetry.ts. Sends only when the account said yes. Each event's properties are on an allow-list (enums, counts, timings); anything else is dropped before it leaves.
  • Events: signed_up (with via: try / mcp / public-page / shared-list / ios / extension / direct), plucked (every surface, incl. email-in and the share sheet), ai_tool_connected / ai_tool_disconnected, mcp_tool_called (tool, client name, ok, error code, result count, duration), mcp_prompt_used, Lists, imports, highlights, opens, search counts, API keys, handle, export. Person properties for cohorts: plucked_from_*, ai_tools, items_bucket, has_public_list, digest_on, locale.
  • signed_up is sent at the first yes, dated when the account began, with a uuid fixed by the account, so PostHog keeps one.
  • pnpm numbers: MongoDB counts for everyone (sign-ups by via, Items by source, accounts by number of surfaces, AI tools), for the people PostHog never sees.
  • Docs: docs/analytics.md (the event list; telemetry.test.ts fails if it drifts from the code), ADR 0001, privacy page in all ten languages.

packages/mcp stays free of PostHog: it gets onCall / onPrompt callbacks, and the route sends them on.

Checks

  • pnpm typecheck, pnpm lint, pnpm test (508) pass.
  • e2e mcp, signin, site, extension, account: 23/24 pass. The failure is the account import, which already fails locally from the clogged digest queue; CI starts from a fresh database.
  • pnpm design not run: no visual change.

Before merging

Read the new privacy sentence (privacy.sections.cookies.body) in each language. I translated it with the glossaries in docs/i18n/; a native read would help.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Chg7BFTY54anasBWfWnSm8

… yes

The cookie answer now belongs to the account (settings.analytics). The web
banner, the AI tool consent screen and the iPhone prompt read and write it,
and the server sends usage only when it is yes. One track() in
lib/telemetry.ts sends plucked (every surface), signed_up (with via),
AI tool connected and disconnected, every MCP tool call and prompt, Lists,
imports, search counts and more. Each event may carry only the properties
on its allow-list; docs/analytics.md lists the same, and a test holds the
two together. pnpm numbers prints MongoDB counts for everyone. The privacy
page says so in all ten languages. ADR 0001 records why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Chg7BFTY54anasBWfWnSm8
Signed-off-by: NiallJoeMaher <nialljoemaher@gmail.com>
@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pluck Ready Ready Preview Nov 3, 2026 5:55pm UTC

Request Review

A "Pluck off" given anywhere but a signed-in page (the marketing site, /signin,
or a save that failed offline) was overwritten by the account's older "yes" at
the next signed-in page, which turned tracking back on.

Each answer now carries when it was given (settings.analyticsAt). A device takes
the account's answer only when it is newer, and gives the account its own when
that is; the server ignores a save older than what it has, and never dates one
ahead of now. The banner saves to the account from any page. An answer lapses
after a year everywhere, so a signed-in browser is asked again as the banner
promises, and the server counts nothing meanwhile.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Per4f8yL5A7nL9frZVDJj7
Signed-off-by: NiallJoeMaher <nialljoemaher@gmail.com>
@NiallJoeMaher
NiallJoeMaher merged commit 815385e into main Nov 3, 2026
5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the analytics-funnel branch November 3, 2026 18:08
NiallJoeMaher added a commit that referenced this pull request Nov 3, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Per4f8yL5A7nL9frZVDJj7
Signed-off-by: NiallJoeMaher <nialljoemaher@gmail.com>
NiallJoeMaher added a commit that referenced this pull request Nov 3, 2026
)

Updating the app asked "Help make Pluck better?" again. 1.4.0 keeps the
answer under a new key ("answer", with when) and nothing carried the old
one over; before #130 the account held no answer to fall back on.

- iOS carries a "consent" answer from an older build over, dated now.
- An account's answer never lapses (freshAnswer); it changes only in
  Settings or Cookies. A browser with no account still asks after a year.
- The app waits for the account's answer before asking, so a phone new
  to the account never asks what the account already said.
- Privacy says the iPhone and iPad app asks once, in all ten languages.


Claude-Session: https://claude.ai/code/session_01Chg7BBfwaoFjYv8SEWTjMY

Signed-off-by: NiallJoeMaher <nialljoemaher@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant