feat(web): mission-based guided tour redesign - #2756
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.onboarding.sandbox-events.tsand friends) and all its plumbing through the event query/view-model pipeline. Missions now target real, persistent demo events by stable id.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.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
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.enterDentistMission; derivedtodayAtfromdayAtinstead of a second near-identical dayjs chain.ONBOARDING_VERIFIED_STEP_IDS) that ended up unused once the button-gating landed on a simplerisAssistVisiblecheck.Automated validation
Drove the tour live at
localhost:9089(anonymous session, fresh IndexedDB/localStorage):create→save→moveFocus(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) →forkall completed via real interaction, not the assist fallback.targetEvent→move→resizeEdge→placeDraft→undoverified 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-tourwas set totrue, no leftover tour card, no crash.Independent review
Two independent review passes (feature-dev:code-reviewer, no access to implementation notes):
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 onstartDate, notendDate- the mission would never auto-complete for anyone following the on-screen instruction. Fixed by seeding edge focus tostartDateon step entry.undostep'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
HEADwith regression tests. No further findings from either pass.Test plan
TZ=UTC NODE_ENV=test PORT=0 bun test ./src/components/OnboardingTour/- 34/34 passTZ=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.tsSync-skew tests) are pre-existing onmain, unrelated to this change (confirmed bygit diff- the file is untouched here).bunx typescript@7.0.2 -p packages/web/tsconfig.app.json --noEmit- cleanbunx typescript@7.0.2 -p packages/web/tsconfig.test.json --noEmit- clean./node_modules/.bin/biome checkacross all changed files - cleanbun run type-checkhas a pre-existing, unrelated failure (express-rate-limitmodule not installed in this worktree'snode_modules,packages/backend/src/billing/billing.routes.config.ts) - not caused by this change.Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com