Skip to content

feat(web): mission-based guided tour redesign - #2756

Merged
tyler-dane merged 6 commits into
mainfrom
claude/better-onboarding-plan-review-cc1e15
Aug 11, 2026
Merged

feat(web): mission-based guided tour redesign#2756
tyler-dane merged 6 commits into
mainfrom
claude/better-onboarding-plan-review-cc1e15

Conversation

@tyler-dane

Copy link
Copy Markdown
Contributor

Summary

Redesigns the onboarding guided tour from keydown-detected steps to mission-based verification against real app state, per 04-guided-tour-spec.md. This branch also carries the prior "onboarding v1" commit (always-on tour entry, resumable tour, browser trial gate), which was already built/verified in an earlier session and is unchanged here.

  • Seeds a full demo week (today unchanged: 6 timed + 1 all-day; ±1/±2 days get 2-3 events each) with a deliberate Team sync/Dentist overlap tomorrow as the tour's capstone mission target.
  • Deletes the entire ephemeral sandbox-events system (onboarding.sandbox-events.ts and friends) and all its plumbing through the event query/view-model pipeline. Missions now target real, persistent demo events by stable id.
  • New 11-step list across three acts: Act 1 (create, save, moveFocus, editSequence) stays close to before but with deeper verification (moveFocus now confirms focus actually landed on a different event, not just any arrow keydown). Act 2 drops palette/? in favor of two newly-taught skills (resizeEdge: Tab-cycle-to-edge + Shift+Arrow resize; placeDraft: Shift+Arrow grid placement), plus mission-phrased jump/move/undo steps verified against real event state. Act 3 makes Hardcore Mode the graduation finale.
  • New stuck-fallback (useOnboardingStepAssist): reveals a "Show me" button after 2 failed non-typing attempts or 15s idle, since verified steps no longer expose an always-on bypass "Next" button.

Simplicity

  • Gated useEventById's query-cache scan to only the 3 steps that read it, instead of scanning on every render of the (always-mounted) tour hook.
  • Deduped a repeated focus+snapshot pair into enterDentistMission; derived todayAt from dayAt instead of a second near-identical dayjs chain.
  • Removed a step-ids export (ONBOARDING_VERIFIED_STEP_IDS) that ended up unused once the button-gating landed on a simpler isAssistVisible check.

Automated validation

Drove the tour live at localhost:9089 (anonymous session, fresh IndexedDB/localStorage):

  • Seeded week and the Team sync/Dentist overlap render correctly on the grid.
  • createsavemoveFocus (auto-focused Morning standup, arrow key moved focus to a genuinely different event, verified via real focus events) → editSequence (E, T opened the form and landed in the title field) → fork all completed via real interaction, not the assist fallback.
  • Resume card correctly reported "1 of 11 done" after a reload mid-tour and resumed at the right step.
  • targetEventmoveresizeEdgeplaceDraftundo verified via the "Show me" stuck-fallback (this session's browser-automation tool could not dispatch synthetic Shift+Arrow held-modifier chords in a way the app's shortcut handlers picked up - confirmed as a tooling limitation, not an app or verification bug, by reproducing the same non-response on a plain non-tour event outside any mission gating; single keys, Shift taps, and the real Shift-Shift double-tap gesture all worked live).
  • hardcore: a real Shift-Shift double-tap toggled Hardcore Mode and correctly finished the tour - compass.onboarding.has-seen-onboarding-tour was set to true, no leftover tour card, no crash.

Independent review

Two independent review passes (feature-dev:code-reviewer, no access to implementation notes):

  1. Found and fixed: resizeEdge's copy/verification promised a single Tab reaches the end edge, but edge focus starts unset and the cycle order is [null, startDate, endDate], so a single real Tab landed on startDate, not endDate - the mission would never auto-complete for anyone following the on-screen instruction. Fixed by seeding edge focus to startDate on step entry.
  2. Found and fixed (final gate, after the above fix was committed): the undo step's redo-phase check only verified the schedule had changed again after the revert, not that it matched the pre-undo schedule exactly - a manual re-edit of Dentist (not an actual redo) would false-positive the mission. Fixed to compare against the original step-entry snapshot, and added a regression test covering the false-positive case. Also surfaced the real redo binding (Mod+Shift+Z) in the card copy, which previously only showed Mod+Z.

Both fixes are on HEAD with regression tests. No further findings from either pass.

Test plan

  • TZ=UTC NODE_ENV=test PORT=0 bun test ./src/components/OnboardingTour/ - 34/34 pass
  • TZ=UTC NODE_ENV=test PORT=0 bun test ./src/events/ ./src/grid/ ./src/views/Day/ ./src/views/Week/ ./src/calendars/ ./src/shortcuts/ ./src/common/storage/ - 962/964 pass; the 2 failures (day.event.query.test.ts Sync-skew tests) are pre-existing on main, unrelated to this change (confirmed by git diff - the file is untouched here).
  • bunx typescript@7.0.2 -p packages/web/tsconfig.app.json --noEmit - clean
  • bunx typescript@7.0.2 -p packages/web/tsconfig.test.json --noEmit - clean
  • ./node_modules/.bin/biome check across all changed files - clean
  • Full bun run type-check has a pre-existing, unrelated failure (express-rate-limit module not installed in this worktree's node_modules, packages/backend/src/billing/billing.routes.config.ts) - not caused by this change.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

tyler-dane and others added 6 commits August 11, 2026 13:54
…ser trial gate

Welcome modal: shortcut hints reveal on hover/focus instead of always-on,
FAQ trimmed and reworded, and Escape/backdrop now starts the guided tour
instead of permanently skipping it (no dismiss-to-blank-calendar path).

Tour: Escape shows an inline skip confirm rather than bailing instantly,
step progress persists so an abandoned tour offers to resume, and the
nudge lesson is reworded to 'move' to match the shortcut legend.

Trial: anonymous visitors get an auto-started 7-day browser-local trial
with a sidebar countdown and a hard gate at expiry (sign up / log in /
export). Signed-in users are governed by the existing server billing
status, whose trial length is aligned to 7 days. The opt-in TrialCTA is
removed since the trial now starts on its own, and the anonymous save
toast is suppressed (not consumed) while onboarding is on screen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace keydown-based step advancement with real-state verification
(focus, event schedule diffs, edge focus, draft activity, keyboard-only
mode). Seed a full demo week with a deliberate Team sync/Dentist overlap
as the tour's capstone target, and delete the ephemeral sandbox-events
system in favor of missions against real, persistent demo events. Add
resizeEdge and placeDraft as newly-taught skills, cut palette/shortcuts
from the required path, and add a stuck-fallback ("Show me") that
reveals after 2 failed attempts or 15s idle so verification can't
become a hard wall.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…match

Gate useEventById's query-cache scan to only the move/resizeEdge/undo
steps that actually read it, dedupe the repeated focus+snapshot pair
into enterDentistMission, and derive todayAt from dayAt instead of
duplicating the dayjs chain.

Also fixes a real bug found by review: resizeEdge's copy and
verification promised a single Tab reaches the end edge, but edge
focus starts unset and EDGE_CYCLE is [null, startDate, endDate], so a
single Tab actually landed on startDate. The step now seeds edge focus
to startDate on entry so one real Tab reaches endDate as advertised.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…dule

Phase 2 previously advanced on any schedule change away from the
post-undo snapshot, so a manual re-edit of Dentist (not an actual
redo) would false-positive the mission complete. Compare against the
original step-entry snapshot instead - a genuine redo always restores
it exactly, while a manual re-edit essentially never reproduces the
same timestamps. Also spells out the real redo binding (Mod+Shift+Z)
in the card copy, which previously only showed Mod+Z.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pre-existing dead code from the onboarding v1 commit, surfaced by
knip in CI on this PR. billing.api.ts had no importers; isTrialExpired
duplicated logic useTrialStatus already computes inline via
getTrialDaysLeft.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…gs, e2e rewrite)

- useTrialStatus.test.ts: spy hasUserEverAuthenticated directly instead of
  relying on real cross-file module state. Several other test files
  mock.module the whole auth.state.util module with a bare stub and no
  restoration, which leaks process-wide across bun test files depending on
  execution order - this test was the first to assert on the real return
  value in an order-sensitive way.

- useUndoRedo.ts: normalize `color` the same way `location` already is in
  normalizedDetailsContent. A demo-seeded event (or any Event built outside
  the normal create mutation) has no `color` key at all, while the cache's
  live copy picks one up as an explicit `color: null` once it passes through
  a mutation's optimistic merge - the staleness guard was reading that as
  "the event changed since this entry was recorded" and silently declining
  otherwise-valid redos.

- useOnboardingTourProgress.ts: reset the shared Shift-tap gesture on
  entering the undo step. Act 2's several prior Shift+Arrow missions can
  leave shift-hold jump-mode armed; the very next non-arrow keydown is then
  swallowed as a (failed) day-jump letter, which is exactly what Mod+Shift+Z's
  "Z" looks like to that listener.

- e2e/onboarding/interactive-tour.spec.ts: rewritten for the new step list
  (was still asserting the old palette/shortcuts copy) and fixed to close the
  editSequence practice form before Act 2 - a form left open silently blocks
  every grid shortcut (move/resizeEdge/placeDraft) after it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tyler-dane
tyler-dane merged commit e738502 into main Aug 11, 2026
20 checks passed
@tyler-dane
tyler-dane deleted the claude/better-onboarding-plan-review-cc1e15 branch August 11, 2026 21:49
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