Add onboarding wizard for teaching the record key (#301)

* Add first-run onboarding wizard for recording mechanics

Introduce a minimal, skippable first-run wizard that teaches the record
key. On first authenticated launch it renders as a full-screen takeover
(gated above PusherProvider) so the stream/compose keyboard handlers are
not mounted to compete for the same keys.

Steps: welcome, hold ` to record, tap V to switch audio/video, tap ` to
toggle recording, mic/camera permission priming, and a recap. The lessons
detect the real gestures and never block — Esc or the Skip control
dismisses at any point.

Reuses existing pieces: VideoAudioToggle, useMediaDevices().requestLabels
for permission priming, KeyHint, and isTypingTarget. Extracts the shared
HOLD_THRESHOLD_MS into lib/constants and reuses it in the compose overlay.

Resolves #259

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01A9AHySdLb1zfZAEm6SuaQP

* nits

* light mode support for onboarding

---------

Co-authored-by: Claude <[email protected]>
This commit was merged in pull request #301.
This commit is contained in:
Arjun Patel
2026-06-21 11:45:14 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent d14cec5885
commit fec7f5d6d8
13 changed files with 658 additions and 3 deletions
+22
View File
@@ -0,0 +1,22 @@
import { create } from 'zustand';
const KEY = 'llink:onboarding-completed';
interface OnboardingState {
hasCompletedOnboarding: boolean;
markComplete: () => void;
/** Clears the flag so the wizard runs again (handy for QA). */
reset: () => void;
}
export const useOnboardingStore = create<OnboardingState>((set) => ({
hasCompletedOnboarding: localStorage.getItem(KEY) === 'true',
markComplete: () => {
localStorage.setItem(KEY, 'true');
set({ hasCompletedOnboarding: true });
},
reset: () => {
localStorage.removeItem(KEY);
set({ hasCompletedOnboarding: false });
},
}));