Skip to content

feat(analytics): link browser acquisition to product usage - #532

Open
Rinat S (rsolmano) wants to merge 1 commit into
feat/attribution-app-lifecyclefrom
feat/attribution-app-claim
Open

Rinat S (rsolmano) wants to merge 1 commit into
feat/attribution-app-lifecyclefrom
feat/attribution-app-claim

Conversation

@rsolmano

Copy link
Copy Markdown
Collaborator

Problem

The website can now create and bind claims, but packaged ThinkRail hosts cannot open, redeem, persist, or apply them to product usage. Marketing context therefore stops at the download.

Approach

Mirror the closed claim schema inside the server analytics boundary and run one verifier-backed, readiness-controlled system-browser flow per packaged installation. Persist only normalized campaign context, keep the one-time journey/bridge IDs on acquisition_linked, and gate all later campaign enrichment with the existing additional-analytics preference.

Changes

  • Add strict create/status/redeem client validation with per-request and overall abort deadlines.
  • Persist an exclusive terminal attempt marker and atomically replace it with campaign-only acquisition data after redeem.
  • Emit consent-gated acquisition_linked once; enrich later basic/additional events with first/last campaign fields while sharing remains on.
  • Expire persisted and active attribution after 30 days; disabling sharing aborts work and removes future enrichment.
  • Start claims only after confirmed settings and explicit launcher readiness.
  • Reuse CLI's existing browser opener after the local UI opens; --no-open never consumes an attempt.
  • Pass Electrobun's system opener through the generated runtime and signal readiness on first desktop DOM-ready; no deep links or new RPC.
  • Add compact Settings-only attribution details without changing the first-run dialog copy.

Screenshots

Not applicable — no layout or control changes; the only visible change is compact explanatory text in the existing Privacy section.

Testing

  • Server suite — 1,182 passed, 2 platform skips.
  • CLI suite — 68 passed; desktop suite — 52 passed.
  • Affected server/CLI/web/website typechecks — passed; direct desktop TypeScript check passed. The scripted desktop/root typecheck could not acquire an Electrobun build lock held by an existing dev process.
  • Binary build and smoke — passed.
  • Consent/privacy E2E — 16 passed.
  • Complete bun run e2e — 413/414 passed; one unrelated session.dispose cleanup timeout. bun run e2e -- --last-failed rerun passed the failed test.
  • Root bun run test — 15/15 package tasks passed.
  • Lint, dependency, boundary, seam, and spec-surface gates — passed with five pre-existing unrelated suppression warnings.
  • Independent reviewer verdict — Approve.

Checklist

  • Fast gates pass: bun run lint, bun run typecheck, bun run test — lint/test and affected typechecks pass; root typecheck is blocked by the existing Electrobun dev lock
  • E2E suite passes for app-affecting changes (bun run e2e, or bun run e2e:full when touching agent behavior)
  • Before/after screenshots are included for frontend changes, or marked not applicable
  • Relevant SPEC.md / top-level specs updated to reflect any boundary, contract, or behavior change
  • I have read the Contributing guide and agree to the Code of Conduct

@github-actions

Copy link
Copy Markdown

🌐 Website preview

Preview https://pr-532.thinkrail-website.pages.dev
Routes Landing · Blog · Vibecoding · Agentic development
Commit 6ef688b

This comment updates in place on every push that touches the website or shared build inputs.

@jetbrains-air jetbrains-air Bot 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.

Requesting changes — please address the blocking inline finding.

const outgoing: OutgoingEvent = {
name: event.name,
params: { ...s.env, ...("params" in event ? event.params : {}) },
params: { ...s.env, ...campaign, ...("params" in event ? event.params : {}) },

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.

Blocking:
Problem: Campaign fields are added to basic events on the permanent basic sink, while revocation only retires the additional sink. Queued or retried basic requests therefore retain consent-scoped attribution after sharing is disabled, violating the analytics revocation contract.
Failure scenario: A campaign-linked message event is queued or receives a retryable failure, the user turns additional sharing off, and the basic sink later sends or retries that event with first/last-touch campaign fields even though consent has been revoked.
Suggested fix: Route campaign-enriched basic deliveries through a consent-generation-owned sink (or rotate/gate the enriched basic queue on revocation) while keeping future non-enriched basics on an active basic sink.

@rsolmano
Rinat S (rsolmano) added this pull request to stack #533 September 19, 2026 12:04
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