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
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 -->
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.
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.
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.
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 -->
[](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 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
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) scenariosHOLD_THRESHOLD_MS(250ms) to lib/constants for consistency between onboarding and actual recording behaviorNotable Implementation Details
requestAnimationFramefor smooth progress visualization and respectsisTypingTarget()to avoid interfering with text inputshttps://claude.ai/code/session_01A9AHySdLb1zfZAEm6SuaQP
Summary by CodeRabbit
ℹ️ 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.tsxjs/desktop/src/features/compose/compose-overlay.tsxjs/desktop/src/features/onboarding/hold-step.tsxjs/desktop/src/features/onboarding/onboarding-keyboard.tsxjs/desktop/src/features/onboarding/onboarding-overlay.tsxjs/desktop/src/features/onboarding/onboarding-step.tsxjs/desktop/src/features/onboarding/permission-step.tsxjs/desktop/src/features/onboarding/tap-step.tsxjs/desktop/src/features/onboarding/toggle-step.tsxjs/desktop/src/features/onboarding/use-hold-gesture.tsjs/desktop/src/features/onboarding/use-tap-gesture.tsjs/desktop/src/lib/constants.tsjs/desktop/src/stores/onboarding-store.ts✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (12)
📝 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
useHoldGestureanduseTapGesturehooks, sharedOnboardingStep/OnboardingKeyboardUI primitives, four gesture-driven step components, auseOnboardingStoreZustand store withlocalStoragepersistence, and wires the overlay intoApp.tsx.Changes
First-run onboarding wizard
js/desktop/src/stores/onboarding-store.ts,js/desktop/src/lib/constants.ts,js/desktop/src/features/compose/compose-overlay.tsxuseOnboardingStorewithhasCompletedOnboarding,markComplete, andresetbacked bylocalStorage. AddsHOLD_THRESHOLD_MS = 250to the shared constants module and removes the duplicate inline definition fromcompose-overlay.tsx, updating its import accordingly.js/desktop/src/features/onboarding/use-hold-gesture.ts,js/desktop/src/features/onboarding/use-tap-gesture.tsuseHoldGesturetracksidle → holding → successwithrequestAnimationFrame-driven progress, key-repeat/typing-target guards, andpreventDefault.useTapGesturecounts discrete keydowns up to a threshold and exposes{ taps, satisfied }with a liveonTapref.js/desktop/src/features/onboarding/onboarding-step.tsx,js/desktop/src/features/onboarding/onboarding-keyboard.tsxOnboardingStepexportsSUCCESS_DWELL_MS = 2000and renders a shared layout witharia-live="polite"status and optional slots.OnboardingKeyboardrenders a small keyboard with aKeyCapsubcomponent that applies state-based styling and a progress fill bar.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.tsxHoldStepteaches backquote hold at 600 ms.TapSteprequires two backquote taps.ToggleStepuses a'v'tap to switchrecordingModeand restores it on unmount.PermissionStepcallsrequestLabels(), auto-advances on grant, and offers "Continue anyway" on denial. All share the dwell-then-advance pattern.js/desktop/src/features/onboarding/onboarding-overlay.tsx,js/desktop/src/App.tsxOnboardingOverlaysequenceswelcome → hold → tap → toggle → permissionwithgoNext/goBack, focus management for gesture steps, and a globalkeydownhandler (Escskips,Enteradvances from welcome, arrow/backspace/delete go back).App.tsxrenders the overlay instead of the authenticated UI whenhasCompletedOnboardingis false.Sequence Diagram(s)
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Poem
Comment
@coderabbitai helpto get the list of available commands and usage tips.