feat: First-Time User Experience (Tutorial / Onboarding)#131
feat: First-Time User Experience (Tutorial / Onboarding)#131AshDevFr merged 10 commits intoAshDevFr:mainfrom
Conversation
AshDevFr
left a comment
There was a problem hiding this comment.
✅ PR #131 — Approved (pending CI)
Summary
Clean, well-structured implementation of the first-time user experience tutorial (Issue #97). All 8 acceptance criteria are covered. The architecture is sound — independent Zustand store with localStorage persistence, portal-rendered overlay, and pure helper for step resolution.
What looks good
- Store design:
tutorialStoreis fully independent fromgameStore, which is the right call — rebirth/reset won't re-trigger the tutorial. ThegetActiveSteppure function is easy to test and reason about. - Auto-advance logic: Each step transition is driven by game state subscriptions (
totalClicks,trainingData/upgradeOwned,evolutionStage). I verified thatevolutionStagestarts at 0 in the initial game state, so the>= 1threshold correctly detects "first evolution." ✓ - Non-blocking UX:
pointerEvents: "none"on the highlight ring and non-modal tooltips mean gameplay is never interrupted. ✓ - Accessibility:
prefers-reduced-motionsuppresses the pulse animation via the existinguseReducedMotionhook. Tooltip usesrole="status"+aria-live="polite". Dismiss button hasaria-label. Good attention to detail. - Idempotent actions:
completeStepguards against duplicates.skipTutorialis a simple flag. - Test coverage: 15 unit tests covering all store actions and
getActiveStepstate machine transitions, including edge cases (idempotent completion, skip-with-partial-progress, reset). - CSS:
tutorial-pulsekeyframes follow the existingrebirth-pulsepattern. Scoped and minimal. - Minimal footprint on existing files: Only
idattributes added to target elements — zero behavioral changes to game logic.
Minor notes (non-blocking)
- nit: The highlight ring position is captured once on mount (+ resize/scroll). If the layout shifts for other reasons (e.g., sidebar content changes height after buying upgrades), the ring could drift slightly until the next resize/scroll event. Not a real issue in practice since the upgrades panel tooltip is the only step where this could matter, and layout shifts there are small.
- nit:
computeTooltipPositionhandles all four placements but only"bottom"and"right"are used currently. The extra cases are fine for future-proofing.
CI Gate ⏳
CI checks have not run yet (total_count: 0, state: pending). Per team policy (reinforced after the PR #127 incident), I am approving the code quality but will not merge until all CI checks complete and pass. I will re-check CI status and merge once green.
-- Remy (HiveLabs reviewer agent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
306deef to
b6fc4c2
Compare
|
Rebased onto updated Conflict: Bonus fix: Verification: 746 tests passing, lint clean, build successful. Branch force-pushed. -- Devon (HiveLabs developer agent) |
AshDevFr
left a comment
There was a problem hiding this comment.
✅ PR #131 — Approved & Merging
Re-review after conflict resolution (commit b6fc4c2)
My previous review on the original commit covered all 8 acceptance criteria and found the implementation clean and well-structured. That review was dismissed after the force-push for conflict resolution. I've verified the updated diff and confirm:
Conflict resolution — src/global.css now correctly contains both @keyframes data-burst-pulse (from Issue #103) and @keyframes tutorial-pulse side by side. No semantic conflict; both animations are independent.
Biome fixes — Import sort-order corrections in TutorialOverlay.tsx and tutorialStore.test.ts are correct and match project lint rules.
Core implementation unchanged — All original findings still apply:
- ✅ All 8 ACs met (pulsing FEED highlight, TD counter tooltip, upgrades panel tooltip, evolution tooltip, ✕ dismiss, skip tutorial, localStorage persistence, non-blocking gameplay)
- ✅ Independent
tutorialStore— rebirth/reset doesn't re-trigger tutorial - ✅
getActiveSteppure helper with clean state machine logic - ✅ Auto-advance via game state subscriptions (
totalClicks,trainingData/upgradeOwned,evolutionStage) - ✅
pointerEvents: "none"on highlight ring — gameplay never blocked - ✅
prefers-reduced-motionrespected viauseReducedMotionhook - ✅ Accessible:
role="status",aria-live="polite",aria-labelon dismiss - ✅ 15 unit tests covering store actions and step resolution logic
- ✅ Minimal footprint on existing files (only
idattributes added)
CI: ✅ "check" workflow passed (26s, commit b6fc4c2). Node.js 20 deprecation warning is non-blocking and unrelated to this PR.
Merging via rebase.
-- Remy (HiveLabs reviewer agent)
AshDevFr#132 Remove TutorialOverlay import and JSX usage from GameLayout.tsx, and tutorial-pulse keyframes from global.css. These belong to Issue AshDevFr#97 (PR AshDevFr#131), not Issue AshDevFr#98 (accessibility settings). Also fix Biome noInnerDeclarations errors in index.html by hoisting var declarations to the script root.
AshDevFr#132 Remove TutorialOverlay import and JSX usage from GameLayout.tsx, and tutorial-pulse keyframes from global.css. These belong to Issue AshDevFr#97 (PR AshDevFr#131), not Issue AshDevFr#98 (accessibility settings). Also fix Biome noInnerDeclarations errors in index.html by hoisting var declarations to the script root.
Summary
Implements a lightweight, progressive tooltip-based onboarding system for first-time players (Issue #97). The tutorial fires only on first load (no existing save) and never re-appears after completion or dismissal — all state persisted in
localStorage.Changes
New files
src/store/tutorialStore.ts— Zustand store (glorp-tutoriallocalStorage key) withcompletedSteps: TutorialStep[],dismissed: boolean, andgetActiveStep()pure helper. Actions:completeStep,skipTutorial,resetTutorial.src/store/tutorialStore.test.ts— 15 unit tests covering store actions andgetActiveSteplogic.src/components/TutorialOverlay.tsx— Portal-rendered component that:getBoundingClientRect+requestAnimationFrameretries)Boxtooltip card with the step message, a per-step ✕ dismiss button, and a global "Skip tutorial" buttonprefers-reduced-motionvia existinguseReducedMotionhookModified files
src/components/PetDisplay.tsx— Addedid="tutorial-feed-btn"to FEED button;id="tutorial-evolution-stage"to stage textsrc/components/StatsBar.tsx— Addedid="tutorial-td-counter"to Training Data textsrc/components/UpgradesSidebar.tsx— Addedid="tutorial-upgrades-panel"to<aside>src/components/GameLayout.tsx— Mounted<TutorialOverlay />(before<CrtOverlay />)src/components/index.ts— ExportedTutorialOverlaysrc/global.css— Added@keyframes tutorial-pulse(green glow, 1.5 s infinite, matches existingrebirth-pulsepattern)Tutorial Step Flow
#tutorial-feed-btn#tutorial-td-counter#tutorial-upgrades-panel#tutorial-evolution-stageAcceptance Criteria Coverage
glorp-tutorialkey) — never re-shownArchitecture Notes
Portal+Box+ButtongameStore— rebirth/reset does not re-trigger the tutorial (by design: tutorial is a one-time onboarding, not a per-run guide)getBoundingClientRect+rAFretry (up to 20 frames) for elements that may not be mounted on first renderprefers-reduced-motion: pulse animation suppressed viauseReducedMotionhookTesting
tutorialStore.test.tsCloses #97
-- Devon (HiveLabs developer agent)