Skip to content

Replace the lesson showcase with the Schedule Rush onboarding game + activation-funnel fixes - #3036

Merged
tyler-dane merged 8 commits into
mainfrom
claude/onboarding-game-redesign-bhkzva
Sep 1, 2026
Merged

Replace the lesson showcase with the Schedule Rush onboarding game + activation-funnel fixes#3036
tyler-dane merged 8 commits into
mainfrom
claude/onboarding-game-redesign-bhkzva

Conversation

@tyler-dane

Copy link
Copy Markdown
Contributor

Summary

Onboarding wasn't landing: analytics showed zero onboarding completions even from newly activated users, and session recordings surfaced four distinct causes. This PR addresses all four:

  1. The 12-step lesson showcase is now "Schedule Rush" — one 90-second run against a fixed queue of ten scheduling tasks (create, typed quick-times like 1230, Shift+Arrow nudges, Tab edge resize, Delete, Mod+Z undo), played with the real KEYMAP verbs on the same sandbox grid. Tetris analogues: a piece queue with next-piece preview, a dashed ghost target slot, lock-in flashes, score/speed-bonus/streak multipliers, and a soft time-up landing (score stands, replay offered).
  2. The demo→signup handoff was inverted — the old graduation step deliberately stripped every signup affordance (three separate guards), so finishing the demo could never produce a showcase-sourced signup. The game's end screen makes signup the primary CTA (Enter) for anonymous players; signup_started {source:"shortcut_showcase"} now fires from the completion path, not just early bail-outs.
  3. Auth routing bug: SuperTokens websiteBasePath pointed at /login, a route that does not exist (auth is modal-only via ?auth=), so SuperTokens-generated links 404'd into the pirate screen. It now points at /day, which resolves and preserves search params. The half-wired email-verification recipe (web-only init, dead ?auth=verify branch, no backend recipe) is removed so both ends agree.
  4. Mobile wall: nothing checked isMobileOS() before mounting onboarding, so phone users completed the whole walkthrough underneath which sat MobileGate telling them to come back on a desktop. The onboarding trio no longer mounts on mobile — the gate (waitlist + copy-link) shows first, and no seen-flags get burned on a phone.

Plus google_oauth_redirect_started {intent} fires right before the Google redirect, so abandoned OAuth round trips are distinguishable from silent failures on return.

Analytics notes for dashboard owners:

  • shortcut_showcase_step_completed is retired; the game emits richer shortcut_game_run_started, shortcut_game_task_shown, shortcut_game_task_completed, and shortcut_game_replayed, and shortcut_showcase_finished/skipped gained outcome/score/phase payloads.
  • The event names in the recent analytics report (onboarding_game_started, checklist_shown, onboarding_task_completed) never existed in this codebase — dashboards referencing them are querying nothing.

Simplicity

The showcase package keeps its public store API byte-compatible (all 8 external call sites — RootShell, WelcomeModal, CommandPalette, auth callbacks, FirstEventPrompt — compile untouched), its storage keys, entry points, app-lock/focus-trap takeover shell, and the FirstEventPrompt handoff. The 12-branch lesson ladder, step metadata, phase-swap copy helpers, practice palette, and practice legend are deleted; the game reducer (game.state.ts) is pure with every timestamp passed as an argument (no Date.now() inside), and the 10-task queue is deterministic and authored in one file (game.tasks.ts). Mid-run resume was simplified away: any stored progress value (legacy lesson ids included) just re-offers a fresh run from the one-screen how-to card.

Automated validation

  • Playwright drove the full game in a real browser: how-to card → 90s clock → all ten tasks cleared by keyboard → end screen with score, "Moves you learned", and signup CTA → graduation into the first-event prompt. This caught a bug unit tests could not: focus parked on the Skip button made the lock-in Enter activate "Skip to sign up" mid-run; mid-run focus now sits on the takeover region.
  • Escape double-tap exit, skip-to-signup (U), Enter→signup from the end screen (?auth=signup URL asserted), reminders opt-in (N, compass.notifications.enabled polled to true), and reload-mid-game re-offer were all exercised end to end.

Independent review

Adversarial re-read of the diff during development; findings fixed before push: the quick-time buffer helper rejected valid 4-digit times (caught by the scripted-run unit test), end-screen Enter hint contradicted the auto-focused button ("hints never lie"), and the two focus/keyboard bugs caught by Playwright above.

Test plan

  • bun run lint — clean (19 pre-existing warnings, 0 errors)
  • bun run type-check — clean (app + web tests configs)
  • bun run test:web — 2962/2963 pass; the one failure (PublicBookingPage slot-pane skeleton race) fails identically on an unmodified checkout of main and is unrelated (filed as a follow-up task)
  • bun packages/scripts/src/testing/test-parallel.ts backend-fast -- packages/backend/src/common/middleware/supertokens.middleware.test.ts and ...util.test.ts — pass; the full test:backend:fast suite has environment-caused failures (SSE/config 60s timeouts) reproduced identically on origin/main in this container
  • bunx playwright test e2e/onboarding/ --project=chromium-desktop — 8/8 pass (7 rewritten Schedule Rush specs + first-run.spec.ts unmodified)
  • New unit coverage: game.state.test.ts (scripted winning run, time-up, streak math, per-validator cases incl. resize rejecting whole-block moves and undo restoring the scripted delete), extended practice.state.test.ts, rewritten store/storage/component suites

🤖 Generated with Claude Code

https://claude.ai/code/session_018r9yps9Wc8MPByQs1qonrY


Generated by Claude Code

claude added 7 commits August 31, 2026 17:49
… dead verify flow

The web app has no /login route (auth is modal-only via ?auth=), so any
SuperTokens-generated website link 404'd. /day resolves and preserves
search params through its redirect. Email verification was only half
wired (web recipe with no backend recipe, and no ?auth=verify modal
view), so the web-side init and dead verify branches are removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018r9yps9Wc8MPByQs1qonrY
…Auth redirects

Mobile users could complete the whole keyboard walkthrough (it paints
over MobileGate) only to be told to open Compass on a desktop; the
overlays now never mount on a mobile OS so the gate shows first.
google_oauth_redirect_started marks 'left for Google' so abandoned OAuth
round trips are distinguishable from silent failures on return.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018r9yps9Wc8MPByQs1qonrY
…game

The 12-step lesson takeover taught shortcuts one lecture at a time and
users bailed before finishing. Schedule Rush keeps the same sandbox,
takeover shell, entry points, and storage contract, but turns practice
into one 90-second run: a fixed queue of ten scheduling tasks (create,
typed quick-times, nudge, edge resize, delete, undo) placed against a
dashed ghost slot, with scoring, speed bonuses, and streak multipliers.
Time-up is a soft landing onto the same end screen as a clear.

The end screen fixes the inverted signup funnel: the old graduation step
stripped every signup affordance, so finishing the demo could never
produce a showcase-sourced signup. Anonymous players now get signup as
the primary CTA (Enter), with the calendar, replay, and the reminders
opt-in beside it.

The pure game reducer takes every timestamp as an argument, so a full
winning run scripts in unit tests without fake timers. Analytics gain
per-task shown/completed events plus run started/finished payloads;
shortcut_showcase_step_completed is retired. The mid-run resume-step key
now stores an in-progress sentinel: any stored value (legacy lesson ids
included) re-offers the game from its one-screen how-to card.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018r9yps9Wc8MPByQs1qonrY
The Playwright run also caught a real-browser bug the unit tests could
not: seatFocus parked focus on the Skip button during a run, so the
lock-in Enter activated Skip to sign up. Mid-run focus now sits on the
takeover region itself, keeping Enter and Tab as game verbs. The
end screen's N reminders shortcut no longer requires focus off a button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018r9yps9Wc8MPByQs1qonrY
The HUD timer refactor left remainingMs with no callers; knip fails CI
on unused exports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018r9yps9Wc8MPByQs1qonrY
…n Sep 1

The recurrence Ends on picker opened at today's month when no end date
was chosen (react-datepicker falls back to today with a null selection),
which put it months away from the event being extended and made the
past-midnight regression test time out the moment the real-world month
rolled past its August fixture. It now opens at the event's own month.

The booking prefetch e2e assumed arriving on the next month always
prefetches the month after it, but with the 60-day horizon that month is
unreachable early in a calendar month (Sep 1 + 60d lands before Nov 1).
The spec now lets any arrival prefetch settle and asserts the horizon
rule instead of a fixed fetch count.

Both failures reproduce identically on origin/main today; they are
ported here to get this PR's CI green and will no-op once main carries
an equivalent fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018r9yps9Wc8MPByQs1qonrY

Copy link
Copy Markdown
Contributor Author

CI status note: the unit (web) and e2e failures on this PR were two date-rollover breakages that exist on main — both reproduce identically on a clean checkout of origin/main as of Sep 1 UTC, and both started failing the moment the calendar month rolled over (main's own last green run was Aug 31):

  1. RecurrenceSection > keeps the event's own date selectable when the event ends after midnight times out because the "Ends on" picker opens at today's month when no end date is chosen (react-datepicker falls back to today on a null selection), so the test's fixed August 2026 fixture is no longer in view. Fixed in EndsOnDate.tsx by opening at the event's own month — which is also the month the user is actually extending.
  2. public-booking.spec.ts › renders a prefetched month without issuing a new slots request expects a third slot fetch on arriving at the next month, but with the schema-capped 60-day horizon the month-after-next is unreachable early in a calendar month (Sep 1 + 60d lands before Nov 1), so no third fetch can happen. The spec now settles any in-flight arrival prefetch and asserts the horizon rule instead of a fixed count.

Both fixes are ported into this branch (commit 9626b38) to get CI green and will no-op once main carries an equivalent change — you may want to cherry-pick them to main since every other PR opened this week will hit the same two failures. The remaining intermittent PublicBookingPage unit timeouts seen on the first run cleared on re-run and match the known load-sensitivity of that suite.


Generated by Claude Code

…e-redesign-bhkzva

# Conflicts:
#	e2e/booking/public-booking.spec.ts
@tyler-dane
tyler-dane merged commit 70f2f56 into main Sep 1, 2026
27 checks passed
@tyler-dane
tyler-dane deleted the claude/onboarding-game-redesign-bhkzva branch September 1, 2026 01:37
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.

2 participants