Tasks: Guest Tutorial Mode & Feature Spotlight
Input: Design documents from specs/015-guest-tutorial-mode/
Prerequisites: plan.md, spec.md, data-model.md, contracts/ui-contract.md
Tests: Included per user story (project constitution §III requires test coverage). Tasks are implementation-first, tests-after within each story (no TDD required — implement directly, tests validate afterward).
Organization: Tasks are grouped by user story (from spec.md) to enable independent implementation and testing of each story.
Note: This supersedes the previous tasks.md (centered-modal design). The centered-modal implementation (tutorial-modal.tsx, tutorial-steps.ts, tutorial fields on GuestModeProvider) is being replaced, not extended — several tasks below start by removing files/fields from that implementation.
Format: [ID] [P?] [Story] Description
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: Maps task to US1/US2/US3/US4/US5 from spec.md
- Every task includes an exact file path
Phase 1: Setup
Purpose: Remove the superseded centered-modal implementation and confirm mount points before rebuilding
- [ ] T001 Delete
frontend/components/features/tutorial/tutorial-modal.tsxandfrontend/components/features/tutorial/tutorial-steps.ts(superseded bytutorial-overlay.tsx/tutorial-registry.tsbuilt in later phases) - [ ] T002 Delete
frontend/tests/unit/tutorial-modal.test.tsx(superseded bytutorial-overlay.test.tsx) - [ ] T003 [P] Confirm
TutorialProvidermount point: inspectfrontend/lib/providers/index.tsxto confirm current provider order (GuestModeProvideris innermost) before nestingTutorialProviderinside it - [ ] T004 [P] Confirm
PopoverAnchorforwardsvirtualRefto@radix-ui/react-popper'sAnchorby re-checkingfrontend/components/ui/popover.tsxand the installed@radix-ui/react-popovertype defs (already verified during design; re-confirm versions haven't drifted)
Phase 2: Foundational (Blocking Prerequisites)
Purpose: Provider split, registry data, and the generic positioning hook that every user story depends on
⚠️ CRITICAL: No user story work can begin until this phase is complete
- [ ] T005 Strip tutorial state out of
frontend/lib/providers/guest-mode-provider.tsx: removeisTutorialOpen,tutorialStep,openTutorial,closeTutorial,nextTutorialStep,prevTutorialStepand theTUTORIAL_STEPSimport;enterGuestMode()/exitGuestMode()no longer touch tutorial state; context type back to{ isGuestMode, enterGuestMode, exitGuestMode } - [ ] T006 [P] Create
frontend/components/features/tutorial/tutorial-registry.ts:TutorialStepinterface (id,titleKey,descriptionKey,icon?,targetId?,route),TutorialTourinterface (id,kind: "onboarding" | "spotlight",steps), andTUTORIAL_TOURSarray with the single"guest-onboarding"tour (welcome/articles/graph/cta,Sparkles/Newspaper/GitBranch/LogInicons fromlucide-react, perdata-model.md) - [ ] T007 Create
frontend/lib/providers/tutorial-provider.tsx:TutorialProvider+useTutorial()hook; stateisTutorialOpen,activeTourId,tutorialStep; actionsopenTutorial(tourId?)(defaults to"guest-onboarding", guarded no-op whenstatus === 'unauthenticated' && !isGuestMode),closeTutorial()(writes active tour id tolocalStorage['tutorial_seen_tours']only when active tour'skind === "spotlight"),nextTutorialStep()/prevTutorialStep()(bounded by active tour'ssteps.length); internaluseEffectonisGuestMode→openTutorial("guest-onboarding")on false→true transition; internaluseEffecton[pathname, isGuestMode, status]usingusePathname()→ auto-open the first unseenkind:"spotlight"tour whosesteps[0].route === pathname, gated by(isGuestMode || status === 'authenticated') && !isTutorialOpen(depends on T006 forTUTORIAL_TOURS) - [ ] T008 Mount
TutorialProviderinsideGuestModeProviderinfrontend/lib/providers/index.tsx; exportuseTutorialalongside the existinguseGuestModeexport (depends on T005, T007) - [ ] T009 [P] Create
frontend/components/features/tutorial/use-tutorial-target.ts:useTutorialTarget(targetId?: string): DOMRect | null—requestAnimationFramepolling for up to 3s if the element isn't immediately present, then recalculates onwindow resize,scroll(capture phase), and viaResizeObserveron the found element; returnsnullwhentargetIdisundefinedor the timeout elapses - [ ] T010 [P] Create
frontend/components/features/tutorial/use-is-mobile.ts:useIsMobile(): boolean,window.innerWidth < 768, updates onresize - [ ] T011 [P] Add a minimal
ResizeObserverpolyfill/mock tofrontend/vitest.setup.ts(jsdom has no native implementation; required by T009's tests and any component test that rendersTutorialOverlay)
Checkpoint: Foundation ready — user story implementation can now begin
Phase 3: User Story 1 - 首次進入 Guest Mode 自動顯示 Spotlight 導覽 (Priority: P1) 🎯 MVP
Goal: Entering guest mode automatically starts the Guest Onboarding Tour: dimmed overlay, highlight cutout over the target NavBar element, anchored description card, and automatic page navigation per step
Independent Test: Go to /login → click "Continue as Guest" → overlay appears showing step 1 (Welcome, centered card, no highlight); clicking "Next" navigates to /articles and highlights the Articles NavBar link
Implementation for User Story 1
- [ ] T012 [US1] Build
frontend/components/features/tutorial/tutorial-overlay.tsx: resolve activeTutorialTour/TutorialStepfromuseTutorial()+TUTORIAL_TOURS; rendernullwhen!isTutorialOpen;useEffecton[tutorialStep, activeTourId]torouter.push(step.route)when it differs fromusePathname(); calluseTutorialTarget(useIsMobile() ? undefined : step.targetId)(depends on T006, T007, T009, T010) - [ ] T013 [US1] In
tutorial-overlay.tsx, implement spotlight mode (rect !== null): full-screenfixed inset-0 pointer-events-autotransparent click-blocking div; a positioned div matchingrectwithbox-shadow: 0 0 0 9999px rgba(0,0,0,0.6)and rounded corners for the dimmed-with-cutout effect;PopoverAnchorwithvirtualRefpointing atrect+PopoverContentrendering title/description/step dots/nav buttons; manualkeydownlistener for Escape-to-close (depends on T012) - [ ] T014 [US1] In
tutorial-overlay.tsx, implement centered-card mode (rect === null): reuseDialog/DialogContentfromcomponents/ui/dialogfor Welcome step, mobile viewport, or 3s target-not-found timeout (depends on T012) - [ ] T015 [US1] Add
id="tutorial-target-articles"to the ArticlesLinkandid="tutorial-target-graph"to the GraphLinkinfrontend/components/features/navigation/nav-bar.tsx - [ ] T016 [US1] Add
id="tutorial-target-login"to the loginButton/Linkin the unauthenticated branch offrontend/components/features/navigation/nav-bar.tsx(the CTA step's highlight target) - [ ] T017 [US1] Add last-step CTA buttons in
tutorial-overlay.tsx: "Sign In" (router.push('/login')) and "Register" (router.push('/register')), both callingcloseTutorial()(depends on T013, T014) - [ ] T018 [US1] Mount
<TutorialOverlay />insidefrontend/app/layout-shell.tsx, replacing the removed<TutorialModal />(depends on T013, T014)
Tests for User Story 1
- [ ] T019 [P] [US1] Create
frontend/tests/unit/tutorial-provider.test.tsx:enterGuestMode()(viauseGuestMode) triggersopenTutorial("guest-onboarding")inTutorialProvider;closeTutorial()does NOT write totutorial_seen_toursfor the onboarding tour;nextTutorialStep()/prevTutorialStep()respect bounds of the active tour - [ ] T020 [P] [US1] Create
frontend/tests/unit/use-tutorial-target.test.ts: returns the element's rect once mounted; polls and eventually returnsnullif the target never appears; recalculates onresize - [ ] T021 [P] [US1] Create
frontend/tests/unit/tutorial-overlay.test.tsx: rendersnullwhen closed; renders centered card for the Welcome step; renders spotlight mode (mockedgetBoundingClientRect) for the Articles step; Next/Back transitions update the visible step and callrouter.push - [ ] T022 [US1] Create
frontend/tests/integration/guest-tutorial.spec.ts: overlay auto-appears after clicking "Continue as Guest"; "Next" navigates to/articlesthen/graphwith the highlight box following; "Skip" closes the overlay; last step highlights the login button with working "Sign In"/"Register" navigation
Checkpoint: User Story 1 is fully functional and independently testable (MVP)
Phase 4: User Story 2 - 使用者可手動重新開啟 Guest Onboarding Tour (Priority: P2)
Goal: Guests and members can reopen the Guest Onboarding Tour at any time via the NavBar HelpCircle entry point
Independent Test: In guest mode, close the tour → click the NavBar "?" icon → tour reopens from step 1, navigating back to /
Implementation for User Story 2
- [ ] T023 [US2] Update the
HelpCirclebutton infrontend/components/features/navigation/nav-bar.tsxto callopenTutorial()fromuseTutorial()(wasuseGuestMode()); keep the existing(isGuestMode || !!session)visibility guard andTooltip/t('tutorial.reopenLabel')wiring (depends on T008)
Tests for User Story 2
- [ ] T024 [P] [US2] Extend
frontend/tests/unit/tutorial-provider.test.tsx:openTutorial()with no argument opens"guest-onboarding"at step 0; is a no-op when not in guest mode and unauthenticated - [ ] T025 [P] [US2] Extend
frontend/tests/unit/nav-bar.test.tsx: HelpCircle callsuseTutorial().openTutorial; hidden for pure unauthenticated (paywall) users; visible for authenticated members - [ ] T026 [US2] Extend
frontend/tests/integration/guest-tutorial.spec.ts: clickingHelpCirclereopens the tour from step 1 (navigating back to/if elsewhere); icon hidden for paywall users, visible for authenticated members
Checkpoint: User Stories 1 AND 2 both work independently
Phase 5: User Story 3 - Tutorial 步驟精準 Highlight 核心功能頁 (Priority: P2)
Goal: The 4 Guest Onboarding steps clearly cover Welcome, Articles, Graph, and the Sign Up/Login CTA, each highlighting the correct concrete UI element, with graceful fallback when a target can't be found
Independent Test: Open the tour → step through all 4 steps → confirm each highlights the correct NavBar element and navigates to the correct route; simulate a missing target and confirm fallback to centered card after 3s
Implementation for User Story 3
- [ ] T027 [US3] Add "Step {current} of {total}" progress text (
tutorial.stepOf) alongside the dot indicator in both spotlight and centered-card branches oftutorial-overlay.tsx(single-brace{current}/{total}placeholders, matching the actualt()implementation) - [ ] T028 [US3] Verify/adjust the 3s timeout fallback in
use-tutorial-target.tsis wired sotutorial-overlay.tsxrenders centered-card mode once the hook settles onnull(no spinner/blocking state in between)
Tests for User Story 3
- [ ] T029 [P] [US3] Extend
frontend/tests/unit/tutorial-overlay.test.tsx: all 4 steps render in order (welcome → articles → graph → cta) with correcttargetId/routeper step; "Back" hidden on step 0; "Step X of 4" text correct per step; a step with a permanently-missing target falls back to centered card - [ ] T030 [US3] Extend
frontend/tests/integration/guest-tutorial.spec.ts: all 4 steps appear in the correct order with matching titles/highlight targets; final step shows both CTA buttons
Checkpoint: User Stories 1, 2, and 3 all work independently
Phase 6: User Story 4 - 多語系支援 (Priority: P3)
Goal: Tutorial content is fully translated and switches live with the app's locale setting
Independent Test: Switch language to zh-TW → enter guest mode → tour displays all steps in Traditional Chinese
Implementation for User Story 4
- [ ] T031 [P] [US4] Verify all
tutorial.*keys survive thetutorial-steps.ts→tutorial-registry.tsmigration infrontend/lib/providers/locales/en.json(no content changes expected — same keys as the original 015 implementation) - [ ] T032 [P] [US4] Verify all
tutorial.*keys survive the migration infrontend/lib/providers/locales/zh-TW.json(no content changes expected)
Tests for User Story 4
- [ ] T033 [P] [US4] Extend
frontend/tests/unit/tutorial-overlay.test.tsx: renders zh-TW copy whenlocale='zh-TW'and English copy whenlocale='en', in both spotlight and centered-card modes - [ ] T034 [US4] Extend
frontend/tests/integration/guest-tutorial.spec.ts: tutorial content displays correctly in zh-TW when the app locale is zh-TW
Checkpoint: All 4 original user stories are independently functional
Phase 7: User Story 5 - Feature Spotlight Tour 機制(新功能對所有使用者自動導覽) (Priority: P2)
Goal: A generic, registry-driven mechanism so that a kind: "spotlight" tour auto-opens for guest/member users the first time they visit its target page, and is never shown again once dismissed — without forcing navigation away from the user's current page
Independent Test: Register a test-only kind: "spotlight" tour targeting /articles in the registry → visit /articles as a user who hasn't seen it → overlay auto-opens → close it → reload /articles → overlay does not reopen
Implementation for User Story 5
- [ ] T035 [US5] Verify the spotlight auto-trigger effect in
frontend/lib/providers/tutorial-provider.tsx(built in T007) correctly readslocalStorage['tutorial_seen_tours'], filtersTUTORIAL_TOURSforkind === "spotlight", and only opens whensteps[0].route === pathname(no cross-page forced navigation for this kind) - [ ] T036 [US5] Verify
closeTutorial()intutorial-provider.tsxappendsactiveTourIdtolocalStorage['tutorial_seen_tours']only when the active tour'skind === "spotlight"(onboarding tours must remain unaffected, per FR-001) - [ ] T037 [US5] Verify mutual exclusion: the spotlight auto-trigger effect checks
!isTutorialOpenso it cannot interrupt an in-progress Guest Onboarding Tour (FR-019)
Tests for User Story 5
- [ ] T038 [P] [US5] Extend
frontend/tests/unit/tutorial-provider.test.tsxwith a fixture spotlight tour (test-only entry, not added to the realTUTORIAL_TOURS, injected via module mock): auto-opens on matching route + unseen + guest/member role; does not auto-open for paywall users; does not auto-open whenisTutorialOpenis alreadytrue; writes totutorial_seen_tourson close; does not reopen once seen - [ ] T039 [US5] Extend
frontend/tests/integration/guest-tutorial.spec.ts(or a newfeature-spotlight.spec.ts) with a fixture spotlight tour: auto-opens on first visit to its route without navigating away from that route; does not reopen after the tour is closed and the page is reloaded; not shown to paywall (pure unauthenticated) users
Checkpoint: All 5 user stories are independently functional; the registry is ready for a real Feature Spotlight tour (e.g. chat) to be added later as a pure data change
Phase 8: Polish & Cross-Cutting Concerns
Purpose: Regression safety and non-functional requirements (SC-003, SC-004, SC-006, mobile edge case)
- [ ] T040 [P] Run
docker compose exec frontend npm run test— confirm all unit tests pass, including the newly rewritten tutorial suite, with no regressions in unrelated files (per project convention, tests run inside Docker, not on host) - [ ] T041 [P] Run
docker compose exec frontend npm run test:e2e— confirmguest-tutorial.spec.tsand all other existing E2E specs still pass (no regressions) - [ ] T042 [P] Run lint/format only on the new/modified tutorial files (not the whole repo) — e.g.
docker compose exec frontend npx eslint components/features/tutorial lib/providers/tutorial-provider.tsx lib/providers/guest-mode-provider.tsx components/features/navigation/nav-bar.tsx app/layout-shell.tsx tests/unit/tutorial-*.test.tsx tests/unit/use-tutorial-target.test.ts tests/integration/guest-tutorial.spec.tsand the equivalent scopedprettier --write— do NOT run an unscopednpm run format(reformats the entire repo) - [ ] T043 Manually verify keyboard accessibility (Tab order, Enter triggers buttons, Escape closes) on
TutorialOverlayin both spotlight and centered-card modes per SC-003 - [ ] T044 Manually verify
TutorialOverlayat narrow mobile viewport widths (< 768px) falls back to centered card for every step, with no overflow, per FR-016 - [ ] T045 Manually verify highlight box position tracks the target element within ~2px after a window resize and after scrolling a page with a non-fixed target, per SC-006
Dependencies & Execution Order
Phase Dependencies
- Setup (Phase 1): No dependencies — can start immediately
- Foundational (Phase 2): Depends on Setup — BLOCKS all user stories
- User Story 1 (Phase 3): Depends on Foundational only — MVP
- User Story 2 (Phase 4): Depends on Foundational only (independent of US1, but naturally follows since HelpCircle reuses the overlay built in US1)
- User Story 3 (Phase 5): Depends on Foundational + US1's
tutorial-overlay.tsxexisting (adds progress label + fallback polish to it) - User Story 4 (Phase 6): Depends on Foundational only (verifies i18n; works once US1 renders any content)
- User Story 5 (Phase 7): Depends on Foundational (the spotlight trigger logic was built as part of T007) + US1's
tutorial-overlay.tsxfor rendering — mostly verification + tests for logic already implemented in Phase 2 - Polish (Phase 8): Depends on all desired user stories being complete
User Story Dependencies
- US1 (P1): No dependencies on other stories — deliverable as MVP alone
- US2 (P2): Reuses the
TutorialOverlay/TutorialProviderfrom Foundational/US1 but is independently testable (icon click → reopen) - US3 (P2): Adds to the same
tutorial-overlay.tsxfile as US1; independently testable via step-order/content/fallback assertions - US4 (P3): Additive i18n verification; independently testable via locale switch
- US5 (P2): Exercises the spotlight trigger logic already present in
TutorialProvider(Phase 2); independently testable with a fixture tour, no dependency on the real onboarding tour content
Parallel Opportunities
- T003, T004 (Setup) in parallel
- T006, T009, T010, T011 (Foundational) in parallel; T007 depends on T006, T008 depends on T005+T007
- T019, T020, T021 (US1 tests) in parallel with each other, after T012-T018
- T024, T025 (US2 tests) in parallel
- T029 (US3 test) in parallel
- T031, T032 (US4 i18n verification) in parallel; T033 after both
- T038 (US5 test) in parallel with US5 verification tasks
- T040, T041, T042 (Polish) in parallel
Implementation Strategy
MVP First (User Story 1 Only)
- Complete Phase 1: Setup (remove superseded files)
- Complete Phase 2: Foundational (provider split, registry, positioning hooks — blocks everything)
- Complete Phase 3: User Story 1
- STOP and VALIDATE: Manually enter guest mode and confirm the spotlight tour appears, navigates pages, and highlights the correct elements
- Demo if ready — this alone satisfies FR-001 through FR-007, FR-013 through FR-016
Incremental Delivery
- Setup + Foundational → foundation ready (provider split + registry + hooks)
- US1 → test independently → MVP demo (spotlight tour working end-to-end)
- US2 → test independently → manual reopen entry point live
- US3 → test independently → step content/progress/fallback polish
- US4 → test independently → zh-TW support live
- US5 → test independently → generic Feature Spotlight mechanism live (ready for a real spotlight tour's content to be added later as pure data)
- Polish → full regression pass + accessibility/mobile/positioning-accuracy checks
Notes
- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability
- Zero new npm packages — reuses
components/ui/dialog.tsx,components/ui/popover.tsx(+ itsvirtualRefsupport from@radix-ui/react-popper),components/ui/tooltip.tsx,lucide-react(already dependencies) - No backend changes; no database migrations; all "seen" state is
localStorage-only (see plan.md "Why localStorage, not DB") - Commit after each task or logical group
- Stop at any checkpoint to validate a story independently
- Do not run an unscoped
npm run format/npm run lint --fixacross the whole repo — a prior session run ofnpm run formaton the whole project produced ~230 unrelated file diffs that had to be reverted; always scope format/lint commands to the files touched by this feature (see T042)