From b86986520d0b00f15761bcd7bbfcc34495a34736 Mon Sep 17 00:00:00 2001 From: Public Profile Date: Sat, 18 Jul 2026 17:54:27 -0700 Subject: [PATCH 1/6] feat: add welcome screen and organize help --- README.md | 1 + src/app.tsx | 27 ++++- src/components/connection-control.tsx | 1 - src/components/keyboard-shortcuts-dialog.tsx | 59 ++++------ src/components/welcome-dialog.tsx | 117 +++++++++++++++++++ src/lib/keyboard.ts | 20 +++- src/lib/welcome.ts | 20 ++++ tests/components.test.tsx | 50 ++++++-- tests/keyboard.test.ts | 28 ++++- tests/welcome.test.ts | 35 ++++++ 10 files changed, 302 insertions(+), 56 deletions(-) create mode 100644 src/components/welcome-dialog.tsx create mode 100644 src/lib/welcome.ts create mode 100644 tests/welcome.test.ts diff --git a/README.md b/README.md index 1e195cd..051c7b2 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Bike trainer control web app using Web Bluetooth. Tested with Wahoo Kickr Core 2 ## Features +- Welcomes first-time visitors with a concise introduction, open-source and local-data privacy details, a direct source-code link, and an optional “Don't show again” preference stored in the browser; the welcome screen remains available from the Ride Control footer link. - Connects to compatible bike trainers through Web Bluetooth, remembers authorized devices, and automatically reconnects when possible. - Shows live speed, power, cadence, heart rate, elapsed time, distance, and estimated calories, with MPH and KM/H display modes. - Provides direct resistance control with buttons, a slider, and keyboard shortcuts while recording resistance changes alongside the other ride metrics. diff --git a/src/app.tsx b/src/app.tsx index 5a9f79d..1df2765 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -8,6 +8,7 @@ import { ResistanceControl } from './components/resistance-control'; import { SessionChart } from './components/session-chart'; import { SessionHistory } from './components/session-history'; import { SessionSaveDialog } from './components/session-save-dialog'; +import { WelcomeDialog } from './components/welcome-dialog'; import { useSession } from './hooks/use-session'; import { useTrainer } from './hooks/use-trainer'; import { formatAggregateAverage, formatDuration } from './lib/format'; @@ -17,6 +18,7 @@ import { requestPersistentSessionStorage, saveSession, } from './lib/saved-sessions'; +import { rememberWelcomeDismissal, shouldShowWelcome } from './lib/welcome'; import type { RoutePoint, SavedSession, SessionMetadata, SpeedUnit } from './types'; const EMPTY_ROUTE: RoutePoint[] = []; @@ -48,6 +50,7 @@ export function App() { ); const [historyOpen, setHistoryOpen] = useState(false); const [shortcutsOpen, setShortcutsOpen] = useState(false); + const [welcomeOpen, setWelcomeOpen] = useState(shouldShowWelcome); const [saveDialogOpen, setSaveDialogOpen] = useState(() => session.ended && !sessionIsSaved); const [saving, setSaving] = useState(false); const [startAfterSave, setStartAfterSave] = useState(false); @@ -118,8 +121,8 @@ export function App() { }, []); useEffect(() => { - trainer.setKeyboardControlsEnabled(!(historyOpen || shortcutsOpen)); - }, [historyOpen, shortcutsOpen, trainer.setKeyboardControlsEnabled]); + trainer.setKeyboardControlsEnabled(!(historyOpen || shortcutsOpen || welcomeOpen)); + }, [historyOpen, shortcutsOpen, trainer.setKeyboardControlsEnabled, welcomeOpen]); useEffect(() => { const shortcutHandlers: Record void> = { @@ -160,7 +163,7 @@ export function App() { }, }; const handleShortcut = (event: KeyboardEvent) => { - if (shouldIgnoreShortcut(event)) { + if (welcomeOpen || shouldIgnoreShortcut(event)) { return; } if (historyOpen) { @@ -181,6 +184,7 @@ export function App() { session.ended, session.togglePause, shortcutsOpen, + welcomeOpen, ]); function selectSpeedUnit(unit: SpeedUnit) { @@ -223,6 +227,12 @@ export function App() { const closeHistory = useCallback(() => setHistoryOpen(false), []); const closeShortcuts = useCallback(() => setShortcutsOpen(false), []); + const closeWelcome = useCallback((dontShowAgain: boolean) => { + if (dontShowAgain) { + rememberWelcomeDismissal(); + } + setWelcomeOpen(false); + }, []); const startNewSessionFromHistory = useCallback( (savedSession: SavedSession) => { setHistoryOpen(false); @@ -423,7 +433,7 @@ export function App() { @@ -448,7 +458,13 @@ export function App() {