Tutorial System Developer Guide¶
This guide explains the per-page tutorial system's architecture, key components, and how to add new tutorials. It targets developers working on user onboarding, feature discovery, and help systems in the WebUI.
Overview¶
The Tutorial System provides contextual, page-specific guided tours using React Joyride. Key features include:
- Per-page tutorials: Each page can have multiple tutorials covering different aspects
- First-visit prompts: Automatic notification for new users on pages with tutorials
- Help modal: Unified modal (triggered by
?key) showing available tutorials and keyboard shortcuts - Quick Chat integration:
Browse GuidesincludesTutorials for this pageabove curated workflow cards - Completion tracking: Persistent storage of tutorial completion status
- i18n support: All tutorial content supports internationalization
Architecture¶
flowchart TB
subgraph Layout["Layout.tsx"]
PageHelpModal["PageHelpModal<br/>(? key trigger)"]
TutorialRunner["TutorialRunner<br/>(Joyride wrapper)"]
TutorialPrompt["TutorialPrompt<br/>(First-visit notification)"]
QuickChatGuidesPanel["QuickChatGuidesPanel<br/>(Browse Guides: per-page tutorials)"]
end
subgraph Registry["tutorials/"]
TutorialRegistry["registry.ts<br/>(Central definitions)"]
Definitions["definitions/<br/>(Per-page tutorials)"]
end
subgraph Store["store/"]
TutorialStore["tutorials.ts<br/>(Zustand store)"]
end
subgraph Persistence["Persistence"]
LocalStorage["localStorage<br/>(tldw-tutorials)"]
end
subgraph i18n["i18n"]
TutorialStrings["locale/en/tutorials.json"]
end
PageHelpModal -->|"getTutorialsForRoute()"| TutorialRegistry
QuickChatGuidesPanel -->|"getTutorialsForRoute()"| TutorialRegistry
PageHelpModal -->|"startTutorial()"| TutorialStore
QuickChatGuidesPanel -->|"startTutorial()"| TutorialStore
TutorialRunner -->|"getTutorialById()"| TutorialRegistry
TutorialRunner -->|"read active state"| TutorialStore
TutorialPrompt -->|"getPrimaryTutorialForRoute()"| TutorialRegistry
TutorialPrompt -->|"hasSeenPromptForPage()"| TutorialStore
TutorialRegistry --> Definitions
TutorialStore <-->|"persist"| LocalStorage
PageHelpModal -->|"t()"| TutorialStrings
TutorialRunner -->|"t()"| TutorialStrings
Directory Map¶
| Path | Purpose |
|---|---|
src/tutorials/ |
Tutorial definitions and registry |
src/tutorials/registry.ts |
Central registry, types, and helper functions |
src/tutorials/definitions/ |
Per-page tutorial definition files |
src/tutorials/index.ts |
Public exports for the tutorials module |
src/store/tutorials.ts |
Zustand store for state management |
src/components/Common/PageHelpModal.tsx |
Help modal UI (tutorials + shortcuts) |
src/components/Common/QuickChatHelper/QuickChatGuidesPanel.tsx |
Browse Guides UI with per-page Tutorials section |
src/components/Common/TutorialRunner.tsx |
React Joyride wrapper |
src/components/Common/TutorialPrompt.tsx |
First-visit notification toast |
src/assets/locale/en/tutorials.json |
i18n strings for tutorials |
src/public/_locales/en/tutorials.json |
Extension locale mirror for tutorial strings |
Core Components¶
1. Tutorial Registry (tutorials/registry.ts)¶
The registry is the single source of truth for all tutorial definitions. It provides:
- Type definitions:
TutorialStepandTutorialDefinitioninterfaces - Central registry:
TUTORIAL_REGISTRYarray of all tutorials - Helper functions: Route matching, route normalization, and tutorial lookup
// Types
interface TutorialStep {
target: string // CSS selector or data-testid
titleKey: string // i18n key for title
titleFallback: string // Fallback if i18n key not found
contentKey: string // i18n key for content
contentFallback: string // Fallback content
placement?: Placement // Tooltip position (auto, top, bottom, etc.)
disableBeacon?: boolean // Hide pulsing dot
spotlightClicks?: boolean // Allow clicks on spotlight
isFixed?: boolean // Target is fixed positioned
}
interface TutorialDefinition {
id: string // Unique identifier
routePattern: string // Route pattern to match
labelKey: string // i18n key for tutorial name
labelFallback: string // Fallback label
descriptionKey: string // i18n key for description
descriptionFallback: string
icon?: LucideIcon // Optional icon
steps: TutorialStep[] // Tutorial steps
prerequisites?: string[] // Required tutorial IDs
priority?: number // Sort order (lower = higher)
}
2. Zustand Store (store/tutorials.ts)¶
Manages all tutorial state with localStorage persistence for completion tracking:
Persisted State (survives page refresh):
- completedTutorials: string[] - IDs of completed tutorials
- seenPromptPages: string[] - Pages where user saw first-visit prompt
Runtime State (resets on refresh):
- activeTutorialId: string | null - Currently running tutorial
- activeStepIndex: number - Current step in active tutorial
- isHelpModalOpen: boolean - Help modal visibility
Convenience Hooks:
- useActiveTutorial() - Active tutorial state and controls
- useHelpModal() - Help modal open/close controls
- useTutorialCompletion() - Completion tracking
3. PageHelpModal (components/Common/PageHelpModal.tsx)¶
Unified help modal triggered by pressing ?. Features:
- Two tabs: Tutorials and Keyboard Shortcuts
- Route-aware: Shows only tutorials for current page
- Completion status: Visual indicators for completed tutorials
- Start/replay: Button to launch or replay tutorials
- Accessible: Full keyboard navigation and ARIA labels
4. TutorialRunner (components/Common/TutorialRunner.tsx)¶
Wrapper around React Joyride that:
- Converts
TutorialDefinitionto JoyrideStep[]format - Applies i18n translations to step content
- Handles step progression and completion callbacks
- Matches app design system styling
- Gracefully handles missing targets
5. TutorialPrompt (components/Common/TutorialPrompt.tsx)¶
Shows a toast notification on first visit to pages with tutorials:
- Appears after 2-second delay
- Offers quick start of primary tutorial
- Persists "seen" state to avoid repeated prompts
- Uses Ant Design notification API
6. QuickChatGuidesPanel (components/Common/QuickChatHelper/QuickChatGuidesPanel.tsx)¶
Quick Chat Browse Guides now surfaces tutorials on the current route:
Tutorials for this pageis always rendered above workflow cards- entries show step count, completion state, and locked status (prerequisites unmet)
- start/replay actions call tutorial store (
startTutorial) and launch Joyride
Current P0/P1 Tutorial Coverage¶
Design rationale and release scope reference:
../Design/Tutorial_Per_Page_Coverage.md
(/chat → playground-basics, /research-studio → workspace-playground-basics, /media → media-basics, and full P0/P1 map).
| Route | Basics Tutorial ID |
|---|---|
/chat |
playground-basics |
/research-studio |
workspace-playground-basics |
/media |
media-basics |
/knowledge |
knowledge-basics |
/characters |
characters-basics |
/prompts |
prompts-basics |
/evaluations |
evaluations-basics |
/notes |
notes-basics |
/flashcards |
flashcards-basics |
/world-books |
world-books-basics |
Adding a New Tutorial¶
Follow this checklist to add a tutorial for a new page:
Step 1: Create Definition File¶
Create a new file in src/tutorials/definitions/:
// src/tutorials/definitions/my-feature.ts
import { BookOpen, Settings } from "lucide-react"
import type { TutorialDefinition } from "../registry"
const myFeatureBasics: TutorialDefinition = {
id: "my-feature-basics",
routePattern: "/my-feature",
labelKey: "tutorials:myFeature.basics.label",
labelFallback: "Getting Started",
descriptionKey: "tutorials:myFeature.basics.description",
descriptionFallback: "Learn the basics of My Feature",
icon: BookOpen,
priority: 1,
steps: [
{
target: '[data-testid="main-action-button"]',
titleKey: "tutorials:myFeature.basics.step1Title",
titleFallback: "Main Action",
contentKey: "tutorials:myFeature.basics.step1Content",
contentFallback: "Click here to perform the main action.",
placement: "bottom",
disableBeacon: true // First step typically disables beacon
},
{
target: '[data-testid="settings-panel"]',
titleKey: "tutorials:myFeature.basics.step2Title",
titleFallback: "Settings",
contentKey: "tutorials:myFeature.basics.step2Content",
contentFallback: "Customize your experience here.",
placement: "left"
}
]
}
export const myFeatureTutorials: TutorialDefinition[] = [
myFeatureBasics
]
Step 2: Register in Registry¶
Add your tutorials to src/tutorials/registry.ts:
import { myFeatureTutorials } from "./definitions/my-feature"
export const TUTORIAL_REGISTRY: TutorialDefinition[] = [
...playgroundTutorials,
...workspacePlaygroundTutorials,
...mediaTutorials,
...knowledgeTutorials,
...charactersTutorials,
...myFeatureTutorials // Add here
]
Step 3: Add i18n Strings¶
Add translations to both locale files:
src/assets/locale/en/tutorials.jsonsrc/public/_locales/en/tutorials.json
Example shape:
{
"myFeature": {
"basics": {
"label": "Getting Started",
"description": "Learn the basics of My Feature",
"step1Title": "Main Action",
"step1Content": "Click here to perform the main action.",
"step2Title": "Settings",
"step2Content": "Customize your experience here."
}
}
}
Step 4: Add data-testid Attributes¶
Ensure target elements have data-testid attributes:
// In your component
<button data-testid="main-action-button">
Do Something
</button>
<div data-testid="settings-panel">
{/* Settings content */}
</div>
Step 5: Export (Optional)¶
If you want direct access to your tutorials, export from src/tutorials/index.ts:
export { myFeatureTutorials } from "./definitions/my-feature"
Verification Checklist¶
- Tutorial appears in Help Modal (
?key) when on the target page - Tutorial appears in Quick Chat Helper -> Browse Guides -> Tutorials for this page
- All steps have valid targets (check console for warnings)
- i18n keys resolve correctly (no fallback text showing)
- Tutorial completes and marks as completed
- First-visit prompt appears for new users
- Replay button works for completed tutorials
- Manual QA checklist passes (see the quick-chat tutorial checklist artifacts under
Docs/Plans/)
Route Pattern Matching¶
The registry matches canonical routes and normalizes legacy route aliases:
| Pattern | Matches |
|---|---|
/chat |
Exact canonical chat route |
/media |
Exact canonical media route |
/settings/* |
Wildcard routes under /settings/ |
/options/playground (input route) |
Normalized to /chat before matching |
/options/evaluations (input route) |
Normalized to /evaluations before matching |
chrome-extension://.../options.html#/knowledge?tab=x (input route) |
Normalized to /knowledge before matching |
// Examples
routePattern: "/chat" // Canonical route in definitions
routePattern: "/settings/*" // Wildcard route
normalizeTutorialRoute("/options/playground") // -> "/chat"
i18n Integration¶
Namespace¶
All tutorial strings use the tutorials namespace:
// In components
const { t } = useTranslation(["tutorials", "common"])
// Key format
t("tutorials:playground.basics.label")
Key Conventions¶
| Key Type | Pattern | Example |
|---|---|---|
| Label | {page}.{tutorial}.label |
playground.basics.label |
| Description | {page}.{tutorial}.description |
playground.basics.description |
| Step Title | {page}.{tutorial}.{stepName}Title |
playground.basics.modelTitle |
| Step Content | {page}.{tutorial}.{stepName}Content |
playground.basics.modelContent |
Pluralization¶
Step counts use ICU message format:
{
"steps": "{count, plural, one {# step} other {# steps}}"
}
Styling¶
The TutorialRunner applies consistent styling that matches the app's design system:
const tutorialStyles = {
options: {
primaryColor: "var(--color-primary, #6366f1)",
textColor: "var(--color-text, #1f2937)",
backgroundColor: "var(--color-surface, #ffffff)",
// ...
}
}
Customize individual step appearance using CSS classes on target elements if needed.
Troubleshooting¶
| Problem | Cause | Solution |
|---|---|---|
| Tutorial not showing | Route pattern doesn't match | Use canonical route in definitions and verify normalization in normalizeTutorialRoute() |
| Step skipped | Target not found | Verify data-testid exists and is visible |
| Wrong position | Element layout | Try different placement value |
| Prompt not appearing | Already seen | Clear localStorage key tldw-tutorials |
| i18n not working | Missing namespace | Ensure tutorials namespace is loaded |
| Beacon visible on first step | Default behavior | Set disableBeacon: true |
Debugging¶
Access the store in browser console:
// Get current state
window.__tldw_useTutorialStore.getState()
// Reset all progress
window.__tldw_useTutorialStore.getState().resetProgress()
// Manually start a tutorial
window.__tldw_useTutorialStore.getState().startTutorial('playground-basics')
Best Practices¶
- Keep tutorials focused: 3-6 steps per tutorial, covering one aspect
- Split complex features: Multiple small tutorials > one long tutorial
- Use prerequisites: Chain tutorials that build on each other
- Disable beacon on first step: Reduces confusion
- Provide fallback text: Always include fallbacks for i18n keys
- Test on different screen sizes: Tooltip placement may vary
- Use semantic data-testid: Descriptive names aid maintenance
- Consider fixed elements: Set
isFixed: truefor sticky/fixed targets
For design changes or new features, coordinate with UX and update this guide accordingly.