Skip to content

feat: complete frontend analytics attribution - #2522

Merged
jwalkingjew merged 10 commits into
masterfrom
codex/frontend-analytics-audit
Sep 24, 2026
Merged

jwalkingjew merged 10 commits into
masterfrom
codex/frontend-analytics-audit

Conversation

@jwalkingjew

@jwalkingjew jwalkingjew commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • add success-level analytics for newsletter signup, profile publication, and comment edits
  • preserve the existing centralized comment-create, entity-vote, claim-response, and debate-winner vote paths while adding entity and surface context
  • attribute person-profile navigation from claim response lists, debate media, claim provenance, comment authors, and the debate people hub
  • give email and account forms, profile editing, debate hub controls, and debate side-panel actions stable analytics types, labels, and intents
  • retain claim and debate context when comments open through the shared global panel
  • keep shared controls reusable without leaking hub attribution into the standalone rematch flow or putting live counts into analytics labels
  • keep payloads privacy-safe: no newsletter email, OTP, comment text, or edited profile values are recorded

Audit results

User action Coverage
Newsletter popup Form started and submitted plus signup_completed only after the API succeeds
Account creation from popup Typed account form plus existing Privy signed_up / signed_in, attributed to explore_email_capture
Comment create/reply Centralized comment_created after successful publish, with entity type, surface, space, and parent ID
Comment edit Centralized content_edited after successful publish
Shared comments panel Claim and debate types survive the panel handoff, including Explore cards
Entity and claim voting Existing shared vote_cast path covers curation, stance, veracity, clearing and switching, and indexed outcomes
Debate winner voting Existing vote_cast operation path covers submitted and indexed outcomes
Profile editing published_edit only after a real successful profile publication; no-op saves are excluded
Person navigation graph_relationship_followed with personal-space or person IDs and originating surface
Debate hub and panels Stable auto-capture labels and intents for entry points, tabs, availability, hub actions, comments, claims, share, and closes

Validation

  • bun run test before the Copilot-review fixes: 572 files, 6,840 tests passed
  • full suite after the fixes: 6,839/6,842 tests passed; the three unrelated high-load failures each passed immediately in isolation
  • all affected test files after rebasing onto latest master: 10 files, 252 tests passed
  • both new regression tests were mutation-checked by removing their fix and confirming they failed
  • bun run typecheck in apps/web
  • ESLint on changed analytics files
  • git diff --check

@vercel

vercel Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
geogenesis Ready Ready Preview Sep 24, 2026 10:29pm UTC

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Analytics can block debate profile navigation and misclassify personal-space-only profiles.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Completes frontend analytics attribution across signup, comments, profiles, and debate workflows.

Changes:

  • Adds success-only analytics for newsletter signup, profile publication, and comment edits.
  • Preserves entity and surface context across comments and profile navigation.
  • Adds stable debate-control metadata and regression coverage.
File Description
apps/​web/​partials/​space-page/​space-member-row.tsx Tracks member profile navigation.
apps/​web/​partials/​profile/​edit-profile-dialog.tsx Labels profile editing analytics.
apps/​web/​partials/​explore/​explore-welcome-banner.tsx Attributes debate hub entry.
apps/​web/​partials/​explore/​email-capture-popup.tsx Tracks successful newsletter signup.
apps/​web/​partials/​explore/​email-capture-popup.test.tsx Tests signup attribution.
apps/​web/​partials/​explore/​email-capture-account-step.tsx Attributes account signup flow.
apps/​web/​partials/​explore/​debate-explore-feed-card.tsx Preserves debate comment context.
apps/​web/​partials/​explore/​debate-explore-feed-card.test.tsx Tests debate panel attribution.
apps/​web/​partials/​explore/​claim-explore-feed-card.tsx Preserves claim comment context.
apps/​web/​partials/​entity-page/​entity-vote-buttons.tsx Tracks voter profile navigation.
apps/​web/​partials/​comments/​entity-comments-panel.tsx Propagates comment target context.
apps/​web/​partials/​comments/​entity-comments-panel-host.tsx Forwards panel target type.
apps/​web/​partials/​comments/​entity-comments-panel-host.test.tsx Tests panel context retention.
apps/​web/​partials/​comments/​entity-comments-button.tsx Labels and contextualizes panel opens.
apps/​web/​partials/​comments/​entity-comments-button.test.tsx Tests comment-button attribution.
apps/​web/​partials/​comments/​comments-section.tsx Adds comment and author attribution.
apps/​web/​partials/​active-proposal/​proposal-comments-panel.tsx Identifies proposal comments.
apps/​web/​core/​topics/​browse/​topic-page-view.tsx Identifies topic comments.
apps/​web/​core/​hooks/​use-publish-comment.ts Tracks successful creates and edits.
apps/​web/​core/​hooks/​use-publish-comment.test.ts Tests comment analytics timing.
apps/​web/​core/​hooks/​use-entity-comments-panel.ts Stores comment target type.
apps/​web/​core/​hooks/​use-edit-profile.ts Tracks successful profile publication.
apps/​web/​core/​hooks/​use-edit-profile.test.tsx Tests profile publication analytics.
apps/​web/​core/​debates/​matchmaking/​people-tab.tsx Tracks debate-person navigation.
apps/​web/​core/​debates/​matchmaking/​people-tab.test.tsx Tests person-link attribution.
apps/​web/​core/​debates/​matchmaking/​hub-pill-button.tsx Adds reusable hub analytics metadata.
apps/​web/​core/​debates/​matchmaking/​hub-pill-button.test.tsx Tests hub metadata overrides.
apps/​web/​core/​debates/​matchmaking/​debates-hub-panel.tsx Labels hub navigation controls.
apps/​web/​core/​debates/​matchmaking/​debates-hub-button.tsx Labels the navbar hub toggle.
apps/​web/​core/​debates/​browse/​use-open-debater-profile.ts Tracks debate-media profile opens.
apps/​web/​core/​debates/​browse/​debate-interaction-bar.tsx Labels debate actions.
apps/​web/​core/​debates/​browse/​debate-feed.tsx Attributes debate comments and hub entry.
apps/​web/​core/​debates/​browse/​debate-claims-panel.tsx Labels claims-panel closure.
apps/​web/​core/​debates/​browse/​debate-claim-ticker.test.tsx Tests media profile attribution.
apps/​web/​core/​claims/​browse/​claim-side-responders.tsx Attributes responder navigation.
apps/​web/​core/​claims/​browse/​claim-provenance.tsx Attributes provenance navigation.
apps/​web/​core/​claims/​browse/​claim-position-comment.tsx Contextualizes position comments.
apps/​web/​core/​claims/​browse/​claim-page-view.tsx Identifies claim comments.
apps/​web/​core/​analytics.ts Adds shared analytics helpers.
apps/​web/​core/​analytics.test.ts Tests new event payloads.
apps/​web/​atoms/​index.ts Retains comment target type.
apps/​web/​app/​space/​[id]/​(space)/​debates/​rematches/​[sessionId]/​rematch-page-client.tsx Separates rematch attribution.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/web/core/debates/browse/use-open-debater-profile.ts Outdated
Comment thread apps/web/core/analytics.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Several authentication, profile-ID, and responder-surface paths still produce missing or incorrect analytics attribution.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 Medium severity · 1 Low severity

Open (4)
Resolved since last review (2)

Comment thread apps/web/core/analytics.ts Outdated
Comment thread apps/web/partials/entity-page/entity-vote-buttons.tsx Outdated
Comment thread apps/web/partials/explore/email-capture-account-step.tsx
Comment thread apps/web/core/debates/matchmaking/hub-pill-button.tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Shared controls still misattribute rematch activity, and auth errors can affect unrelated signup attempts.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 4 Medium severity

Open (4)
Resolved since last review (4)

Comment thread apps/web/core/debates/matchmaking/claims-tab.tsx Outdated
Comment thread apps/web/core/debates/matchmaking/filter-switch.tsx Outdated
Comment thread apps/web/core/debates/matchmaking/hub-pill-button.tsx Outdated
Comment thread apps/web/core/hooks/use-privy-sign-in.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The broad cross-cutting analytics and authentication lifecycle changes warrant final human validation despite strong regression coverage.

Review effort: Balanced
Findings: None

Resolved since last review (4)
Previously missed (1)

In code that hasn't changed since last review

Low severity Update comment to reflect the new observational click handler

apps/​web/​core/​debates/​matchmaking/​people-tab.tsx:422

The preceding source comment still says this link has “no click handler at all,” but this new analytics handler makes that documentation false. Update it to explain that the handler is observational and does not prevent default, matching the regression test below.

@jwalkingjew

Copy link
Copy Markdown
Collaborator Author

Addressed Copilot’s previously missed summary finding from the latest review. The finding was valid: the People-tab source comment still said the person link had no click handler after this PR added an observational analytics handler. I enumerated the same stale statement in the PR code and corrected both the component comment and the corresponding test-suite comment in 8a72c16. I did not change the handler or navigation behavior: the existing test continues to verify that the handler records analytics while Next owns navigation. I also did not add a source-text or mutation test for a comment-only correction, because that would test prose rather than product behavior and would not follow this repository’s testing patterns. There was no inline thread attached to this summary-only finding, so this PR comment records the decision and resolution.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Shared controls still misattribute some popup and rematch events, while pending comment authors can emit invalid profile identifiers.

Review effort: Balanced
Findings: None

Previously missed (3)

In code that hasn't changed since last review

Medium severity Use shared rematch analytics surface for Keep looking

apps/​web/​app/​space/​[id]/​(space)/​debates/​rematches/​[sessionId]/​rematch-page-client.tsx:2241

This is the same keepLooking action that HubQueryState renders at lines 2168-2172. That path uses analyticsSurface="rematch" and emits Debate rematch Keep looking, while this non-empty path emits Rematch keep looking, splitting one action into two labels. Use the shared rematch surface here too so attribution stays stable across empty and non-empty states.

Medium severity Use popup-specific attribution for shared request overflow menu

apps/​web/​core/​debates/​matchmaking/​request-overflow-menu.tsx:8

RequestOverflowMenu is also rendered by the app-wide IncomingRequestPopup (core/debates/debate-coordinator.tsx:323), not only by hub cards. Using the hub-only helper here makes the popup trigger and actions emit Debate hub ... / hub intents even when the hub is closed. Thread an owning surface through this shared menu and give the popup its own non-hub attribution.

Medium severity Skip attribution for pending personal-space author IDs

apps/​web/​partials/​comments/​comments-section.tsx:1112

Optimistic comments created before a personal space exists use pending:${walletAddr} as author.spaceId (use-create-comment.tsx:171-185). Clicking that author now records the synthetic pending value as a personal_space entity/profile ID, so graph_relationship_followed contains neither a Person nor personal-space ID. Skip profile attribution until the pending author is replaced with a real space ID.

@jwalkingjew

Copy link
Copy Markdown
Collaborator Author

Addressed all three previously missed findings from Copilot’s latest summary in c731224. Copilot did not create inline threads for these items, so this comment records each decision and resolution.

  1. Keep looking attribution: valid. The empty and non-empty controls invoke the same rematch action but had different labels. The non-empty button now uses the shared rematch surface, producing Debate rematch Keep looking and debate_rematch_action just like the empty-state path. I enumerated production rematch action overrides; no other one-off production label remains. Forcing this call back to the hub surface makes the new regression fail.

  2. Request overflow surface: valid misattribution, but the suggested implication that the intents were hub-specific was not correct. open_debate_request_options and manage_debate_request describe the actions without naming a surface, so renaming them would split an established event family without benefit. I kept those intents and added a shared debate-action surface helper. The hub card now explicitly uses the hub surface and the app-wide popup explicitly uses the request-popup surface, so their stable labels are distinct. Forcing the popup back to hub makes the new trigger/action regression fail.

  3. Pending comment author ID: valid. Rather than patching only one comment link, personProfileOpened now rejects the repository’s canonical pending personal-space sentinel at the shared analytics boundary. This covers all three comment-author links and any other current or future profile-open caller without changing navigation to the intentionally navigable pending page. Removing the guard makes the new analytics regression emit graph_relationship_followed with pending:0x123 and fail.

No visual, navigation, or mutation behavior changed, and no out-of-scope issues were found. Final focused result: 235 tests pass; type-check, lint, formatting, and diff checks pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Two new event sources fall outside the bundled analytics source taxonomy and can undermine the intended attribution.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread apps/web/core/analytics.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The cross-cutting analytics changes span authentication, publishing, comments, navigation, and shared debate controls.

Review effort: Balanced
Findings: None

Resolved since last review (1)

@ohohoreilly ohohoreilly left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read every non-test hunk across the 66 files. No correctness issues found. The riskier spots all hold up:

  • use-edit-profile.ts: settleSuccess(recordPublishedEdit = true) has only two callers. The no-op path passes false and the makeProposal onSuccess calls it bare, so no callback leaks an unexpected argument into it. The only other published_edit sender is review-changes.tsx, so a profile publish isn't counted twice.
  • Email-capture sign-in fallback: the card stays mounted behind Privy's modal (hidden, because isModalOpen counts as an overlay while wantsAccount is true). Dismissing the modal reaches giveUpRef.current() through the new guarded onError in usePrivySignIn. The navbar's PrivyConnectButton now only tracks logins it started, so there's no double-counted sign-in.
  • usePrivySignIn onError: the early return behaves the same as the old unconditional clear, and the caller's callback only fires for a sign-in this hook started.
  • editComment wrapper: keeps the underlying Promise<boolean> and records the edit only when the save succeeds.
  • Wiring:
    • content_edited, signup_completed and graph_relationship_followed are registered in public/geo-analytics-*.js.
    • SmallButton, Button and PrefetchLink pass data-* and onClick through.
    • The rematch page passes analyticsSurface="rematch" to every shared control, so no hub labels leak into it.

One optional note, not a bug: the generic entity-page-body.tsx:121 still renders <CommentSection> without targetEntityType, so comments posted on a debate's full entity page get tagged entity, not debate. The PR only promises debate and claim context through the shared panel, so this may be fine as is. Worth a one-line prop if you want debate comments attributed wherever they're posted.

@jwalkingjew
jwalkingjew force-pushed the codex/frontend-analytics-audit branch from 687c4cb to a02dcfc Compare September 24, 2026 21:48
@jwalkingjew

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review. I confirmed the main conclusion: I did not find a correctness issue in the paths you called out, and there were no inline threads to address.

I did take the optional comment-attribution note. The normal debate browse view already passes targetEntityType="debate", but the generic footer is also the edit/fallback and side-panel path, where a Debate was still falling back to entity. In 68dce2bc9, the footer now derives the logical comment target from the entity data it already reads. I checked the same class rather than patching only Debate: Claim and Topic custom comment views retain their explicit types, and the shared derivation recognizes Claim, Topic, and Debate whenever one reaches the generic footer.

The regression was written first and failed against the previous behavior. I also deliberately replaced the derivation with the old entity fallback and confirmed the test failed before restoring the fix. This changes only analytics payload context; it does not change rendering, navigation, or publishing.

Rebasing across current master exposed one related new caller that landed after this PR's prior base: the profile Debates tab now uses the shared FilterSwitch, whose owning analytics surface this PR intentionally made required. In a02dcfcfe, that caller now uses a distinct profile-debates surface (Profile debates Show hidden / filter_profile_debates) rather than being mislabeled as hub or rematch activity. Its regression also failed before the new surface existed.

Validation after the rebase: 601 broad focused tests and 25 post-fix tests pass; typecheck, lint, formatting, and diff checks pass. No device-visible behavior changed.

@jwalkingjew
jwalkingjew force-pushed the codex/frontend-analytics-audit branch from 8cfc102 to 5d2c596 Compare September 24, 2026 22:25
@jwalkingjew
jwalkingjew merged commit d378877 into master Sep 24, 2026
5 checks passed
@jwalkingjew
jwalkingjew deleted the codex/frontend-analytics-audit branch September 24, 2026 22:42
jwalkingjew added a commit that referenced this pull request Sep 25, 2026
Three conflicts in `rematch-page-client.tsx`, all against the debate-rooms
work (#2543) and the analytics attribution (#2522):

- `sessionEnded` and master's `requestSpaceRef` landed in the same place and are
  independent. Both kept.
- `HideMyPositionsSwitch` keeps master's `analyticsSurface`, on this branch's
  condition: Positions is a tab now, so `tab === 'explore'` already excludes it
  and master's `source !== 'mine'` is dead.
- The source menu keeps master's `analytics` prop and this branch's label
  narrowing, which has to agree with the `value` beside it.

One thing the merge broke that neither side got wrong on its own: in a debate
room an expired session is rejoined in place rather than redirected away, so
`sessionEnded` stopped meaning "about to navigate" — and the pair header would
have frozen, permanently, over a session that was coming back. The room case is
excepted, with tests both ways. Converted is not: that redirects out of a room
as much as into one.

Master's new analytics test needed a curated page to have a source menu to
attribute at all, for the same reason the switch lost its source check.

This branch was successfully deployed

1 active deployment
Preview — 5d2c5965 Deployed Sep 24, 2026 by vercel[bot]
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.

3 participants