Add onboarding wizard for teaching the record key #301

Merged
talksik merged 3 commits from claude/sleepy-dijkstra-ffvu14 into main 2026-06-21 18:45:14 +00:00
talksik commented 2026-06-21 16:52:05 +00:00 (Migrated from github.com)

Summary

Implements a comprehensive first-run onboarding wizard that teaches users the core functionality of llink—the record key (backtick). The wizard is rendered as a full-screen overlay above the main app to prevent keyboard handler conflicts and guides users through five interactive lessons before they start using the app.

Key Changes

  • OnboardingOverlay: Full-screen wizard component managing step progression (welcome → hold → toggle → tap → permission → done) with Escape-to-skip functionality and progress indicators
  • Gesture detection hooks:
    • useHoldGesture: Detects press-and-hold with visual progress feedback (600ms for teaching vs. 250ms real threshold)
    • useTapGesture: Counts discrete key taps, supporting both single-tap (audio/video toggle) and multi-tap (start/stop) scenarios
  • Step components: Specialized lessons for each gesture (HoldStep, TapStep, ToggleStep, PermissionStep) with contextual feedback and auto-advance on success
  • OnboardingKeyboard: Visual keyboard diagram showing the record key in physical context (Esc above, 1 to the right, Tab/Q below) with state-based styling (idle/active/success)
  • OnboardingStep: Shared layout component maintaining consistent vertical rhythm across all steps
  • OnboardingStore: Zustand store tracking completion state in localStorage with reset capability for QA
  • App integration: Conditionally renders overlay when user hasn't completed onboarding, blocking main app until wizard completes
  • Constants: Extracted HOLD_THRESHOLD_MS (250ms) to lib/constants for consistency between onboarding and actual recording behavior

Notable Implementation Details

  • Gesture detection uses requestAnimationFrame for smooth progress visualization and respects isTypingTarget() to avoid interfering with text inputs
  • Hold gesture succeeds via timer (not keyup) to handle OS-level key interception (e.g., macOS accent popover)
  • Onboarding hold duration (600ms) is deliberately longer than production threshold (250ms) to make the gesture teachable
  • Panel focus management: gesture steps focus the dialog container for window-level key handling; static steps autofocus buttons for native Enter activation
  • Media settings changes during toggle lesson are reverted on unmount to avoid persisting tutorial state
  • Permission step gracefully handles denied state with fallback to continue anyway

https://claude.ai/code/session_01A9AHySdLb1zfZAEm6SuaQP

Summary by CodeRabbit

  • New Features
    • Added an onboarding wizard for authenticated users that guides key interactions (press-and-hold, tap, and recording mode toggling) with an on-screen keyboard visualization.
    • Included microphone/camera permission step with “allow” and “continue anyway” flows, plus an option to skip onboarding (Esc/skip hint).
  • Bug Fixes
    • Ensured onboarding auto-advances after success and properly restores any temporary recording-mode changes when the step ends.
  • Refactor
    • Centralized hold threshold and shared key-hold/tap behavior into reusable hooks and shared constants.
## Summary Implements a comprehensive first-run onboarding wizard that teaches users the core functionality of llink—the record key (backtick). The wizard is rendered as a full-screen overlay above the main app to prevent keyboard handler conflicts and guides users through five interactive lessons before they start using the app. ## Key Changes - **OnboardingOverlay**: Full-screen wizard component managing step progression (welcome → hold → toggle → tap → permission → done) with Escape-to-skip functionality and progress indicators - **Gesture detection hooks**: - `useHoldGesture`: Detects press-and-hold with visual progress feedback (600ms for teaching vs. 250ms real threshold) - `useTapGesture`: Counts discrete key taps, supporting both single-tap (audio/video toggle) and multi-tap (start/stop) scenarios - **Step components**: Specialized lessons for each gesture (HoldStep, TapStep, ToggleStep, PermissionStep) with contextual feedback and auto-advance on success - **OnboardingKeyboard**: Visual keyboard diagram showing the record key in physical context (Esc above, 1 to the right, Tab/Q below) with state-based styling (idle/active/success) - **OnboardingStep**: Shared layout component maintaining consistent vertical rhythm across all steps - **OnboardingStore**: Zustand store tracking completion state in localStorage with reset capability for QA - **App integration**: Conditionally renders overlay when user hasn't completed onboarding, blocking main app until wizard completes - **Constants**: Extracted `HOLD_THRESHOLD_MS` (250ms) to lib/constants for consistency between onboarding and actual recording behavior ## Notable Implementation Details - Gesture detection uses `requestAnimationFrame` for smooth progress visualization and respects `isTypingTarget()` to avoid interfering with text inputs - Hold gesture succeeds via timer (not keyup) to handle OS-level key interception (e.g., macOS accent popover) - Onboarding hold duration (600ms) is deliberately longer than production threshold (250ms) to make the gesture teachable - Panel focus management: gesture steps focus the dialog container for window-level key handling; static steps autofocus buttons for native Enter activation - Media settings changes during toggle lesson are reverted on unmount to avoid persisting tutorial state - Permission step gracefully handles denied state with fallback to continue anyway https://claude.ai/code/session_01A9AHySdLb1zfZAEm6SuaQP <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an onboarding wizard for authenticated users that guides key interactions (press-and-hold, tap, and recording mode toggling) with an on-screen keyboard visualization. * Included microphone/camera permission step with “allow” and “continue anyway” flows, plus an option to skip onboarding (Esc/skip hint). * **Bug Fixes** * Ensured onboarding auto-advances after success and properly restores any temporary recording-mode changes when the step ends. * **Refactor** * Centralized hold threshold and shared key-hold/tap behavior into reusable hooks and shared constants. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-06-21 16:52:12 +00:00 (Migrated from github.com)

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: 28072598-94dd-4fc2-a04b-059d7de66473

📥 Commits

Reviewing files that changed from the base of the PR and between 5801ca4098104dc236fc6618f1ea2a884257a0d2 and e6bf024729.

📒 Files selected for processing (13)
  • js/desktop/src/App.tsx
  • js/desktop/src/features/compose/compose-overlay.tsx
  • js/desktop/src/features/onboarding/hold-step.tsx
  • js/desktop/src/features/onboarding/onboarding-keyboard.tsx
  • js/desktop/src/features/onboarding/onboarding-overlay.tsx
  • js/desktop/src/features/onboarding/onboarding-step.tsx
  • js/desktop/src/features/onboarding/permission-step.tsx
  • js/desktop/src/features/onboarding/tap-step.tsx
  • js/desktop/src/features/onboarding/toggle-step.tsx
  • js/desktop/src/features/onboarding/use-hold-gesture.ts
  • js/desktop/src/features/onboarding/use-tap-gesture.ts
  • js/desktop/src/lib/constants.ts
  • js/desktop/src/stores/onboarding-store.ts
Files skipped from review due to trivial changes (1)
  • js/desktop/src/features/compose/compose-overlay.tsx
🚧 Files skipped from review as they are similar to previous changes (12)
  • js/desktop/src/features/onboarding/permission-step.tsx
  • js/desktop/src/features/onboarding/hold-step.tsx
  • js/desktop/src/lib/constants.ts
  • js/desktop/src/stores/onboarding-store.ts
  • js/desktop/src/features/onboarding/use-tap-gesture.ts
  • js/desktop/src/App.tsx
  • js/desktop/src/features/onboarding/onboarding-step.tsx
  • js/desktop/src/features/onboarding/onboarding-overlay.tsx
  • js/desktop/src/features/onboarding/tap-step.tsx
  • js/desktop/src/features/onboarding/toggle-step.tsx
  • js/desktop/src/features/onboarding/use-hold-gesture.ts
  • js/desktop/src/features/onboarding/onboarding-keyboard.tsx

📝 Walkthrough

Walkthrough

Adds a first-run onboarding wizard that gates the authenticated desktop UI behind a five-step full-screen overlay teaching keyboard shortcuts. Introduces useHoldGesture and useTapGesture hooks, shared OnboardingStep/OnboardingKeyboard UI primitives, four gesture-driven step components, a useOnboardingStore Zustand store with localStorage persistence, and wires the overlay into App.tsx.

Changes

First-run onboarding wizard

Layer / File(s) Summary
Onboarding store and shared constant
js/desktop/src/stores/onboarding-store.ts, js/desktop/src/lib/constants.ts, js/desktop/src/features/compose/compose-overlay.tsx
Creates useOnboardingStore with hasCompletedOnboarding, markComplete, and reset backed by localStorage. Adds HOLD_THRESHOLD_MS = 250 to the shared constants module and removes the duplicate inline definition from compose-overlay.tsx, updating its import accordingly.
Gesture detection hooks
js/desktop/src/features/onboarding/use-hold-gesture.ts, js/desktop/src/features/onboarding/use-tap-gesture.ts
useHoldGesture tracks idle → holding → success with requestAnimationFrame-driven progress, key-repeat/typing-target guards, and preventDefault. useTapGesture counts discrete keydowns up to a threshold and exposes { taps, satisfied } with a live onTap ref.
Shared onboarding UI primitives
js/desktop/src/features/onboarding/onboarding-step.tsx, js/desktop/src/features/onboarding/onboarding-keyboard.tsx
OnboardingStep exports SUCCESS_DWELL_MS = 2000 and renders a shared layout with aria-live="polite" status and optional slots. OnboardingKeyboard renders a small keyboard with a KeyCap subcomponent that applies state-based styling and a progress fill bar.
Individual step components
js/desktop/src/features/onboarding/hold-step.tsx, js/desktop/src/features/onboarding/tap-step.tsx, js/desktop/src/features/onboarding/toggle-step.tsx, js/desktop/src/features/onboarding/permission-step.tsx
HoldStep teaches backquote hold at 600 ms. TapStep requires two backquote taps. ToggleStep uses a 'v' tap to switch recordingMode and restores it on unmount. PermissionStep calls requestLabels(), auto-advances on grant, and offers "Continue anyway" on denial. All share the dwell-then-advance pattern.
Overlay orchestration and App wiring
js/desktop/src/features/onboarding/onboarding-overlay.tsx, js/desktop/src/App.tsx
OnboardingOverlay sequences welcome → hold → tap → toggle → permission with goNext/goBack, focus management for gesture steps, and a global keydown handler (Esc skips, Enter advances from welcome, arrow/backspace/delete go back). App.tsx renders the overlay instead of the authenticated UI when hasCompletedOnboarding is false.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant OnboardingOverlay
  participant GestureStep
  participant useOnboardingStore

  App->>useOnboardingStore: read hasCompletedOnboarding
  useOnboardingStore-->>App: false
  App->>OnboardingOverlay: render(onComplete=markComplete)
  loop welcome → hold → tap → toggle → permission
    OnboardingOverlay->>GestureStep: render current step
    GestureStep->>GestureStep: user performs gesture (hold/tap/toggle/allow)
    GestureStep-->>OnboardingOverlay: onAdvance() after SUCCESS_DWELL_MS
    OnboardingOverlay->>OnboardingOverlay: goNext() → advance step
  end
  OnboardingOverlay->>useOnboardingStore: onComplete() → markComplete()
  useOnboardingStore->>useOnboardingStore: persist to localStorage
  useOnboardingStore-->>App: hasCompletedOnboarding=true
  App->>App: render AuthenticatedApp

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop, hop! A wizard appears on first run,
Teaching the keys before the magic's begun.
Hold the backtick, tap it twice, toggle your mode,
Grant the mic its voice along the onboarding road.
The rabbit marks it done and stores it away—
No second tutorial needed, hooray! 🎉


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/flowy-live/llink/pull/301?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <details> <summary>ℹ️ Recent review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Free **Run ID**: `28072598-94dd-4fc2-a04b-059d7de66473` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 5801ca4098104dc236fc6618f1ea2a884257a0d2 and e6bf0247295ecd33650c1340a680028b2404ad79. </details> <details> <summary>📒 Files selected for processing (13)</summary> * `js/desktop/src/App.tsx` * `js/desktop/src/features/compose/compose-overlay.tsx` * `js/desktop/src/features/onboarding/hold-step.tsx` * `js/desktop/src/features/onboarding/onboarding-keyboard.tsx` * `js/desktop/src/features/onboarding/onboarding-overlay.tsx` * `js/desktop/src/features/onboarding/onboarding-step.tsx` * `js/desktop/src/features/onboarding/permission-step.tsx` * `js/desktop/src/features/onboarding/tap-step.tsx` * `js/desktop/src/features/onboarding/toggle-step.tsx` * `js/desktop/src/features/onboarding/use-hold-gesture.ts` * `js/desktop/src/features/onboarding/use-tap-gesture.ts` * `js/desktop/src/lib/constants.ts` * `js/desktop/src/stores/onboarding-store.ts` </details> <details> <summary>✅ Files skipped from review due to trivial changes (1)</summary> * js/desktop/src/features/compose/compose-overlay.tsx </details> <details> <summary>🚧 Files skipped from review as they are similar to previous changes (12)</summary> * js/desktop/src/features/onboarding/permission-step.tsx * js/desktop/src/features/onboarding/hold-step.tsx * js/desktop/src/lib/constants.ts * js/desktop/src/stores/onboarding-store.ts * js/desktop/src/features/onboarding/use-tap-gesture.ts * js/desktop/src/App.tsx * js/desktop/src/features/onboarding/onboarding-step.tsx * js/desktop/src/features/onboarding/onboarding-overlay.tsx * js/desktop/src/features/onboarding/tap-step.tsx * js/desktop/src/features/onboarding/toggle-step.tsx * js/desktop/src/features/onboarding/use-hold-gesture.ts * js/desktop/src/features/onboarding/onboarding-keyboard.tsx </details> </details> --- <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough Adds a first-run onboarding wizard that gates the authenticated desktop UI behind a five-step full-screen overlay teaching keyboard shortcuts. Introduces `useHoldGesture` and `useTapGesture` hooks, shared `OnboardingStep`/`OnboardingKeyboard` UI primitives, four gesture-driven step components, a `useOnboardingStore` Zustand store with `localStorage` persistence, and wires the overlay into `App.tsx`. ## Changes **First-run onboarding wizard** | Layer / File(s) | Summary | |---|---| | **Onboarding store and shared constant** <br> `js/desktop/src/stores/onboarding-store.ts`, `js/desktop/src/lib/constants.ts`, `js/desktop/src/features/compose/compose-overlay.tsx` | Creates `useOnboardingStore` with `hasCompletedOnboarding`, `markComplete`, and `reset` backed by `localStorage`. Adds `HOLD_THRESHOLD_MS = 250` to the shared constants module and removes the duplicate inline definition from `compose-overlay.tsx`, updating its import accordingly. | | **Gesture detection hooks** <br> `js/desktop/src/features/onboarding/use-hold-gesture.ts`, `js/desktop/src/features/onboarding/use-tap-gesture.ts` | `useHoldGesture` tracks `idle → holding → success` with `requestAnimationFrame`-driven progress, key-repeat/typing-target guards, and `preventDefault`. `useTapGesture` counts discrete keydowns up to a threshold and exposes `{ taps, satisfied }` with a live `onTap` ref. | | **Shared onboarding UI primitives** <br> `js/desktop/src/features/onboarding/onboarding-step.tsx`, `js/desktop/src/features/onboarding/onboarding-keyboard.tsx` | `OnboardingStep` exports `SUCCESS_DWELL_MS = 2000` and renders a shared layout with `aria-live="polite"` status and optional slots. `OnboardingKeyboard` renders a small keyboard with a `KeyCap` subcomponent that applies state-based styling and a progress fill bar. | | **Individual step components** <br> `js/desktop/src/features/onboarding/hold-step.tsx`, `js/desktop/src/features/onboarding/tap-step.tsx`, `js/desktop/src/features/onboarding/toggle-step.tsx`, `js/desktop/src/features/onboarding/permission-step.tsx` | `HoldStep` teaches backquote hold at 600 ms. `TapStep` requires two backquote taps. `ToggleStep` uses a `'v'` tap to switch `recordingMode` and restores it on unmount. `PermissionStep` calls `requestLabels()`, auto-advances on grant, and offers "Continue anyway" on denial. All share the dwell-then-advance pattern. | | **Overlay orchestration and App wiring** <br> `js/desktop/src/features/onboarding/onboarding-overlay.tsx`, `js/desktop/src/App.tsx` | `OnboardingOverlay` sequences `welcome → hold → tap → toggle → permission` with `goNext`/`goBack`, focus management for gesture steps, and a global `keydown` handler (`Esc` skips, `Enter` advances from welcome, arrow/backspace/delete go back). `App.tsx` renders the overlay instead of the authenticated UI when `hasCompletedOnboarding` is false. | ## Sequence Diagram(s) ```mermaid sequenceDiagram participant App participant OnboardingOverlay participant GestureStep participant useOnboardingStore App->>useOnboardingStore: read hasCompletedOnboarding useOnboardingStore-->>App: false App->>OnboardingOverlay: render(onComplete=markComplete) loop welcome → hold → tap → toggle → permission OnboardingOverlay->>GestureStep: render current step GestureStep->>GestureStep: user performs gesture (hold/tap/toggle/allow) GestureStep-->>OnboardingOverlay: onAdvance() after SUCCESS_DWELL_MS OnboardingOverlay->>OnboardingOverlay: goNext() → advance step end OnboardingOverlay->>useOnboardingStore: onComplete() → markComplete() useOnboardingStore->>useOnboardingStore: persist to localStorage useOnboardingStore-->>App: hasCompletedOnboarding=true App->>App: render AuthenticatedApp ``` ## Estimated code review effort 🎯 3 (Moderate) | ⏱️ ~25 minutes ## Poem > 🐇 Hop, hop! A wizard appears on first run, > Teaching the keys before the magic's begun. > Hold the backtick, tap it twice, toggle your mode, > Grant the mic its voice along the onboarding road. > The rabbit marks it done and stores it away— > No second tutorial needed, hooray! 🎉 </details> <!-- walkthrough_end --> <!-- tips_start --> --- > [!NOTE] > <details> > <summary>🎁 Summarized by CodeRabbit Free</summary> > > Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting <https://app.coderabbit.ai/login>. > > </details> <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end -->
Sign in to join this conversation.