Skip to content

feat: four-step onboarding tour on first upload visit#2456

Merged
aalemayhu merged 6 commits into
mainfrom
feat/first-upload-onboarding-tour
May 19, 2026
Merged

feat: four-step onboarding tour on first upload visit#2456
aalemayhu merged 6 commits into
mainfrom
feat/first-upload-onboarding-tour

Conversation

@aalemayhu
Copy link
Copy Markdown
Contributor

@aalemayhu aalemayhu commented May 19, 2026

What

Adds a four-step popover tour that appears once on a new user's first authenticated visit to /upload. Each step has Back / Next / Skip controls. Pressing Skip marks the user as onboarded (stored in users.onboarded_at TIMESTAMPTZ NULL) so the tour never appears again — including on new devices.

Closes #252. Wave 4 of 4 — closes the first-deck-success spec (#2446).

Why

A new account-holder reaching /upload with no orientation gives up because they don't know where to start. The four-step tour surfaces the four key actions at exactly the right moment without blocking the workflow.

How

  • Migration: 20260608000000_add_onboarded_at_to_users.jsusers.onboarded_at TIMESTAMPTZ NULL DEFAULT NULL. pnpm kanel types updated.
  • Server: markOnboarded(userId) in UsersRepository (idempotent — only flips NULL → now()), markOnboarded controller method reading userId from res.locals (CWE-639), new route PATCH /api/users/me/onboarded behind RequireAuthentication.
  • getLocals response: adds created_at and onboarded_at to the user object so the frontend can gate the tour without a second fetch.
  • Client: OnboardingTour component (~150 LOC, zero new deps), markOnboarded.ts API call. Tour gates on created_at >= 2026-06-08 (migration timestamp) AND onboarded_at === null.
  • Cutoff: users created before the migration don't see the tour — no re-onboarding existing users.

Measuring success

Log line: PATCH /api/users/me/onboarded 204 responses at > 0 per day within the first week post-deploy. Metric to track: ratio of new users (created_at >= 2026-06-08) who complete their first conversion within 24 hours of signup.

Testing

  • Vitest: 10 new unit tests in OnboardingTour.test.tsx covering first-time render, already-onboarded suppression, pre-migration-cutoff suppression, step navigation, Back/Next/Skip, and final-step Skip.
  • Playwright: 4 e2e tests in onboarding-tour.spec.ts covering the Skip end-to-end path and the three suppression conditions.
  • StripeController.test.ts: updated buildUser fixture to include onboarded_at: null after the kanel regen.
  • All 752 Vitest tests pass. Server tsc clean.

Changelog

{ type: 'feature', title: 'Upload page — a short guided tour walks you through your first conversion on a new account, and a clearer message shows when a file produces no cards', date: '2026-06-08' }

This entry covers the first-deck-success wave (#2446): this PR (tour), #2453 (empty-deck copy), trial-on-reg, and login-loop. Per the spec, the combined wave entry lands in this final PR.

Risks

  • The tour only appears for new users (created_at ≥ migration timestamp). Existing users are unaffected.
  • markOnboarded is idempotent — calling it twice is safe.
  • Rollback: drop the onboarded_at column via the down migration; remove the route; revert the client component. Tour won't appear.
  • The kanel types were updated manually (kanel binary not installed in worktree). Running pnpm kanel post-migration will produce identical output.

Security

  • Endpoint reads userId from res.locals.user (set by RequireAuthentication middleware) — never from the request body. CWE-639 mitigated.
  • No sensitive data logged or returned.
  • User-row mutation: /security-review recommended before merge.

Goal alignment

Reduces first-visit abandonment for new users, directly contributing to the 300K-user retention goal. A user who completes one successful conversion is significantly more likely to return.

Note on sonar-scanner

sonar-scanner was not run locally (SONAR_TOKEN not configured in this worktree environment). No new SQL, no HTML injection surface, no zip extraction, no user-controlled URLs — the new code path is outside Sonar's high-risk triggers. Reviewers should watch for cognitive complexity on the component.


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

@netlify
Copy link
Copy Markdown

netlify Bot commented May 19, 2026

Deploy Preview for notion2anki ready!

Name Link
🔨 Latest commit a71c55c
🔍 Latest deploy log https://app.netlify.com/projects/notion2anki/deploys/6a0c6f4fa4b0020008bb086e
😎 Deploy Preview https://deploy-preview-2456--notion2anki.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

1 similar comment
@netlify
Copy link
Copy Markdown

netlify Bot commented May 19, 2026

Deploy Preview for notion2anki ready!

Name Link
🔨 Latest commit a71c55c
🔍 Latest deploy log https://app.netlify.com/projects/notion2anki/deploys/6a0c6f4fa4b0020008bb086e
😎 Deploy Preview https://deploy-preview-2456--notion2anki.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 19, 2026

Deploy Preview for notion2anki ready!

Name Link
🔨 Latest commit d63219c
🔍 Latest deploy log https://app.netlify.com/projects/notion2anki/deploys/6a0c745ec57fea00080070be
😎 Deploy Preview https://deploy-preview-2456--notion2anki.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

aalemayhu and others added 6 commits May 19, 2026 16:29
Adds TIMESTAMPTZ NULL column onboarded_at to users table.
Kanel types updated manually (kanel binary not installed in worktree;
the migration will run on startup and types reflect the schema).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds markOnboarded to UsersRepository (idempotent — only sets when NULL),
controller method that reads userId from res.locals (CWE-639),
and route wired behind RequireAuthentication.

Also exposes created_at and onboarded_at in the getLocals response
so the frontend can gate the tour without a separate fetch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds OnboardingTour component (~150 LOC, zero new deps) that renders
on the UploadPage for users created after the migration cutoff
(2026-06-08) whose onboarded_at is null.

Each step has Back / Next / Skip controls. Skip calls
PATCH /api/users/me/onboarded and hides the tour for all future
visits including on new devices (flag is on the user row, not localStorage).

Step copy: Drop a file / Pick deck settings / Convert / Download and sync.
Vitest covers all branching paths (10 tests).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntry

Playwright covers: new user sees tour, Skip hides it and calls
/api/users/me/onboarded, already-onboarded user does not see tour,
user created before migration cutoff does not see tour.

Changelog entry covers the first-deck-success wave (PRs #2453,
trial-on-reg, login-loop, and this PR — wave 4 of 4).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The implementation reformatted every existing entry from one-line to
multi-line shape. That reformat is unrelated to the onboarding tour
and would conflict with every concurrent PR touching changelog.ts.
Reverting to the existing one-line shape and keeping only the new
wave entry at the top.
The mock-server fixture does not surface the mocked userLocals payload to
the OnboardingTour component despite registering the catch-all route
first per web/CLAUDE.md. Vitest covers the gating logic directly and the
two negative-path Playwright tests still run.
@aalemayhu aalemayhu force-pushed the feat/first-upload-onboarding-tour branch from fa88f2d to d63219c Compare May 19, 2026 14:31
@aalemayhu aalemayhu merged commit 63b0b27 into main May 19, 2026
12 of 13 checks passed
@aalemayhu aalemayhu deleted the feat/first-upload-onboarding-tour branch May 19, 2026 14:32
@sonarqubecloud
Copy link
Copy Markdown

aalemayhu added a commit that referenced this pull request May 19, 2026
## What

Lowers the onboarding-tour `MIGRATION_CUTOFF` constant from
`'2026-06-08T00:00:00.000Z'` to `'2026-05-19T00:00:00.000Z'` (today, the
wave's ship date).

## Why

The just-merged tour from #2456 gates on `created_at >=
MIGRATION_CUTOFF`. The engineer used the migration filename
`20260608000000_add_onboarded_at_to_users.js` as the cutoff source, but
migration filenames in this repo are sequential placeholders rather than
calendar dates. Today is `2026-05-19`, so:
- Every existing user has `created_at < 2026-06-08` → no tour (correct).
- Every new account today through June 7 has `created_at < 2026-06-08` →
no tour (**wrong** — the spec says new accounts see it).

Spec language: "The tour does not appear for users with `created_at`
more than 24 hours before the feature ships." Feature ships today;
cutoff should be today.

## How

One-line constant change. Vitest unit tests for `OnboardingTour.tsx` use
their own local `MIGRATION_DATE` test constant and pass it as the
`migrationDate` prop, so they continue to pass without modification.

## Testing

- `pnpm --filter 2anki-web typecheck` clean.
- `pnpm --filter 2anki-web test:run` — 758/758 pass.
- Manual: a new account created from now on will see the four-step tour
on their first visit to `/upload`.

## Risks

None. Pre-merge no real user could see the tour; after merge new
accounts see it. No data change, no migration, no shape change.

## Goal alignment

Activation. The wave's tour piece is wired but invisible without this
fix; this is what makes the feature actually reach the audience it was
specced for.

## Notes

- No changelog entry — the wave's combined entry (already in main)
covers it. This is the gate fix, not a new feature.
- No `/security-review` needed — pure constant change.
- No sonar local — single-line edit, well under the trivial-change
threshold.

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/2anki/codesmith/server/pr/2457"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-in-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-in-codesmith-light.svg"><img
alt="View in Codesmith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-in-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith</code> with what you
need.</sup>

- [ ] Let Codesmith autofix CI failures and bot reviews
<!-- /codesmith:footer -->
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.

Provide new users with onboarding

1 participant