New desktop onboarding experience#1681
Conversation
Introduce an onboarding window that guides new users through initial setup. Adds the Onboarding window variant, route, settings flag (has_completed_onboarding), startup logic to show onboarding when needed, and a Help & Tour button in the main window header. Made-with: Cursor
| <button | ||
| type="button" | ||
| onClick={() => { | ||
| commands.showWindow("Onboarding"); |
There was a problem hiding this comment.
This returns a promise; consider handling it to avoid unhandled rejections in the click handler.
| commands.showWindow("Onboarding"); | |
| void commands.showWindow("Onboarding").catch((err) => console.error("Failed to show onboarding window", err)); |
| <button | ||
| class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded" | ||
| onClick={() => commands.showWindow("Setup")} | ||
| onClick={() => commands.showWindow("Onboarding")} |
There was a problem hiding this comment.
Same thing here: showWindow is async, so it’s safer to handle the promise.
| onClick={() => commands.showWindow("Onboarding")} | |
| onClick={() => void commands.showWindow("Onboarding").catch((err) => console.error("Failed to show onboarding window", err))} |
| out our{" "} | ||
| <button | ||
| type="button" | ||
| onClick={() => shell.open("https://cap.so/pricing")} |
There was a problem hiding this comment.
shell.open is async; handling the promise avoids unhandled rejections.
| onClick={() => shell.open("https://cap.so/pricing")} | |
| onClick={() => void shell.open("https://cap.so/pricing").catch((err) => console.error("Failed to open pricing", err))} |
|
|
||
| <button | ||
| type="button" | ||
| onClick={() => shell.open("https://cap.so/pricing")} |
There was a problem hiding this comment.
Same as above: this is async so it’s worth handling errors.
| onClick={() => shell.open("https://cap.so/pricing")} | |
| onClick={() => void shell.open("https://cap.so/pricing").catch((err) => console.error("Failed to open pricing", err))} |
| } | ||
| }); | ||
|
|
||
| createEffect(() => { |
There was a problem hiding this comment.
The 250ms poll interval keeps running while this step is active, even after required permissions are granted. Stopping polling once everything required is permitted reduces background work.
| createEffect(() => { | |
| createEffect(() => { | |
| if (!props.active || initialCheck()) return; | |
| const c = check(); | |
| const allRequired = setupPermissions | |
| .filter((p) => !p.optional) | |
| .every((p) => isPermitted(c?.[p.key])); | |
| if (allRequired) return; | |
| const interval = setInterval(fetchPermissions, 250); | |
| onCleanup(() => clearInterval(interval)); | |
| }); |
| fetchPermissions(); | ||
| }; | ||
|
|
||
| const openSettings = async (permission: OSPermission) => { |
There was a problem hiding this comment.
If openPermissionSettings/ask/relaunch fails, this can currently throw and leave the UI in a stale state. Wrapping in try/finally keeps the local check state consistent.
| const openSettings = async (permission: OSPermission) => { | |
| const openSettings = async (permission: OSPermission) => { | |
| try { | |
| await commands.openPermissionSettings(permission); | |
| if (permission === "screenRecording") { | |
| const shouldRestart = await ask( | |
| "After adding Cap in System Settings, you'll need to restart the app for the permission to take effect.", | |
| { | |
| title: "Restart Required", | |
| kind: "info", | |
| okLabel: "Restart, I've granted permission", | |
| cancelLabel: "No, I still need to add it", | |
| }, | |
| ); | |
| if (shouldRestart) { | |
| await relaunch(); | |
| } | |
| } | |
| } catch (err) { | |
| console.error(`Error opening permission settings: ${err}`); | |
| } finally { | |
| setInitialCheck(false); | |
| fetchPermissions(); | |
| } | |
| }; |
| setStep(target); | ||
| }; | ||
|
|
||
| const handleFinish = async () => { |
There was a problem hiding this comment.
Minor, but getCurrentWindow().close() returns a promise. Awaiting it (and optionally catching) avoids floating promises and makes sequencing deterministic.
| const handleFinish = async () => { | |
| const handleFinish = async () => { | |
| try { | |
| if (!isRevisit()) { | |
| await generalSettingsStore.set({ hasCompletedOnboarding: true }); | |
| } | |
| await commands.showWindow({ Main: { init_target_mode: null } }); | |
| await getCurrentWindow().close(); | |
| } catch (err) { | |
| console.error(`Error finishing onboarding: ${err}`); | |
| } | |
| }; |
| .arg(bundle_id) | ||
| .output() | ||
| .expect("Failed to reset microphone permissions"); | ||
| Command::new("tccutil") |
There was a problem hiding this comment.
This only errors on spawn failure; if tccutil exits non-zero, it’ll still return Ok(()). Capturing the output and checking status.success() makes failures actionable.
| Command::new("tccutil") | |
| let output = Command::new("tccutil") | |
| .arg("reset") | |
| .arg("Microphone") | |
| .arg(bundle_id) | |
| .output() | |
| .map_err(|_| "Failed to reset microphone permissions".to_string())?; | |
| if !output.status.success() { | |
| let stderr = String::from_utf8_lossy(&output.stderr).to_string(); | |
| return Err(if stderr.is_empty() { | |
| "Failed to reset microphone permissions".to_string() | |
| } else { | |
| stderr | |
| }); | |
| } |
| const handleModeClick = (index: number) => { | ||
| setUserClicked(true); | ||
| setActiveMode(index); | ||
| commands.setRecordingMode(modes[index].id); | ||
| }; |
There was a problem hiding this comment.
Recording mode is mutated during the onboarding tour
commands.setRecordingMode(modes[index].id) is called whenever a user clicks an icon in the interactive mode-switcher demo on step 5. This permanently changes the user's active recording mode as a side effect of exploring the tutorial. A first-time user who clicks around will silently leave the tour with a different mode than they started with — likely not the intended UX.
Consider either:
- removing the
setRecordingModecall so the step is purely visual, or - restoring the original mode in a cleanup (
onCleanup) if the user never explicitly committed to a choice.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/routes/(window-chrome)/onboarding.tsx
Line: 811-815
Comment:
**Recording mode is mutated during the onboarding tour**
`commands.setRecordingMode(modes[index].id)` is called whenever a user clicks an icon in the interactive mode-switcher demo on step 5. This permanently changes the user's active recording mode as a side effect of exploring the tutorial. A first-time user who clicks around will silently leave the tour with a different mode than they started with — likely not the intended UX.
Consider either:
- removing the `setRecordingMode` call so the step is purely visual, or
- restoring the original mode in a cleanup (`onCleanup`) if the user never explicitly committed to a choice.
How can I resolve this? If you propose a fix, please make it concise.| getCurrentWindow().close(); | ||
| }; |
There was a problem hiding this comment.
Window.close() is not awaited in handleFinish
getCurrentWindow().close() returns a Promise<void>, but it is called without await. In most cases this is harmless because the window teardown is async anyway, but it means any rejection is silently swallowed and the calling function technically completes before the close operation has been acknowledged by the runtime.
| getCurrentWindow().close(); | |
| }; | |
| await getCurrentWindow().close(); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/routes/(window-chrome)/onboarding.tsx
Line: 355-356
Comment:
**`Window.close()` is not awaited in `handleFinish`**
`getCurrentWindow().close()` returns a `Promise<void>`, but it is called without `await`. In most cases this is harmless because the window teardown is async anyway, but it means any rejection is silently swallowed and the calling function technically completes before the close operation has been acknowledged by the runtime.
```suggestion
await getCurrentWindow().close();
```
How can I resolve this? If you propose a fix, please make it concise.| #[serde(default = "default_true")] | ||
| pub has_completed_onboarding: bool, |
There was a problem hiding this comment.
serde default and Default impl disagree on has_completed_onboarding
The field uses #[serde(default = "default_true")] (deserialises missing field as true) but Default::default() returns false. The intent is clear: existing users whose settings files pre-date this field should be treated as having completed onboarding, while a truly fresh install (no file → Default::default()) must go through the flow. However, the mismatch is a maintenance footgun — any code that constructs a GeneralSettingsStore via Default::default() (e.g., in tests or fallback paths) silently gets false, which would force unexpected onboarding. A comment on the field explaining the deliberate discrepancy would help future contributors avoid breakage.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/general_settings.rs
Line: 152-153
Comment:
**`serde` default and `Default` impl disagree on `has_completed_onboarding`**
The field uses `#[serde(default = "default_true")]` (deserialises missing field as `true`) but `Default::default()` returns `false`. The intent is clear: existing users whose settings files pre-date this field should be treated as having completed onboarding, while a truly fresh install (no file → `Default::default()`) must go through the flow. However, the mismatch is a maintenance footgun — any code that constructs a `GeneralSettingsStore` via `Default::default()` (e.g., in tests or fallback paths) silently gets `false`, which would force unexpected onboarding. A comment on the field explaining the deliberate discrepancy would help future contributors avoid breakage.
How can I resolve this? If you propose a fix, please make it concise.
Paragon SummaryThis pull request review identified 6 issues across 2 categories in 10 files. The review analyzed code changes, potential bugs, security vulnerabilities, performance issues, and code quality concerns using automated analysis tools. This PR introduces a multi-step onboarding window for first-time desktop users that guides them through Cap's recording modes, permissions, shortcuts, and FAQs, while also adding a "Help & Tour" button in the main window header so existing users can revisit the onboarding experience at any time.Analysis submitted successfully. This onboarding PR adds a guided setup experience for new users and a persistent help button for existing users to revisit the tour. Key changes:
Confidence score: 4/5
10 files reviewed, 6 comments Severity breakdown: Medium: 3, Low: 3 |
|
|
||
| const ringLeft = () => PAD + activeMode() * (CIRCLE + GAP); | ||
|
|
||
| const handleModeClick = (index: number) => { |
There was a problem hiding this comment.
Bug: ToggleStep calls setRecordingMode during onboarding tour
ToggleStep calls setRecordingMode during onboarding tour. Revisiting users accidentally change their mode. Guard with an isRevisit check.
View Details
Location: apps/desktop/src/routes/(window-chrome)/onboarding.tsx (lines 811)
Analysis
ToggleStep calls setRecordingMode during onboarding tour
| What fails | Clicking mode icons in the ToggleStep during a revisit via 'Help & Tour' button changes the user's actual recording mode setting. |
| Result | The user's actual recording mode is changed to the clicked mode via commands.setRecordingMode. |
| Expected | During a revisit/tour, clicking mode icons should only animate the UI without changing the actual recording mode. |
| Impact | Users who re-open onboarding as a help tour will unknowingly change their recording mode, disrupting their workflow. |
How to reproduce
1. Complete onboarding once. 2. Click 'Help & Tour' button in main window header. 3. Navigate to the ToggleStep (step 5). 4. Click a different mode icon (e.g., Studio).AI Fix Prompt
Fix this issue: ToggleStep calls setRecordingMode during onboarding tour. Revisiting users accidentally change their mode. Guard with an isRevisit check.
Location: apps/desktop/src/routes/(window-chrome)/onboarding.tsx (lines 811)
Problem: Clicking mode icons in the ToggleStep during a revisit via 'Help & Tour' button changes the user's actual recording mode setting.
Current behavior: The user's actual recording mode is changed to the clicked mode via commands.setRecordingMode.
Expected: During a revisit/tour, clicking mode icons should only animate the UI without changing the actual recording mode.
Steps to reproduce: 1. Complete onboarding once. 2. Click 'Help & Tour' button in main window header. 3. Navigate to the ToggleStep (step 5). 4. Click a different mode icon (e.g., Studio).
Provide a code fix.
Tip: Reply with @paragon-run to automatically fix this issue
| const handleStartupDone = async () => { | ||
| setIsExiting(true); | ||
| await generalSettingsStore.set({ hasCompletedStartup: true }); | ||
| setTimeout(() => { |
There was a problem hiding this comment.
Bug: Non-macOS startup skips permissions step entirely
Non-macOS startup skips permissions step entirely. Windows users may miss camera/mic prompts. Remove the platform-based step skip or show relevant permissions.
View Details
Location: apps/desktop/src/routes/(window-chrome)/onboarding.tsx (lines 361)
Analysis
Non-macOS startup skips permissions step entirely. Windows users may miss camera/mic prompts
| What fails | handleStartupDone jumps to step 1 on non-macOS, completely skipping the permissions step (step 0). If any permissions are needed on Windows, users never see the prompt. |
| Result | Step 0 (permissions) is skipped on Windows. If camera or microphone permissions are needed, the user never sees the grant UI. |
| Expected | Either show the permissions step on all platforms (filtering out notNeeded items) or verify no permissions are needed on Windows before skipping. |
| Impact | Windows users may not grant needed permissions, and the Main window permission guard could redirect them back to onboarding in a loop. |
How to reproduce
1. Run the app on Windows for the first time. 2. Click 'Get Started' on the startup overlay. 3. Observe the onboarding goes directly to step 1 (Modes Overview).AI Fix Prompt
Fix this issue: Non-macOS startup skips permissions step entirely. Windows users may miss camera/mic prompts. Remove the platform-based step skip or show relevant permissions.
Location: apps/desktop/src/routes/(window-chrome)/onboarding.tsx (lines 361)
Problem: handleStartupDone jumps to step 1 on non-macOS, completely skipping the permissions step (step 0). If any permissions are needed on Windows, users never see the prompt.
Current behavior: Step 0 (permissions) is skipped on Windows. If camera or microphone permissions are needed, the user never sees the grant UI.
Expected: Either show the permissions step on all platforms (filtering out notNeeded items) or verify no permissions are needed on Windows before skipping.
Steps to reproduce: 1. Run the app on Windows for the first time. 2. Click 'Get Started' on the startup overlay. 3. Observe the onboarding goes directly to step 1 (Modes Overview).
Provide a code fix.
Tip: Reply with @paragon-run to automatically fix this issue
| .run(move |_handle, event| match event { | ||
| #[cfg(target_os = "macos")] | ||
| tauri::RunEvent::Reopen { .. } => { | ||
| if let Some(onboarding) = CapWindowId::Onboarding.get(_handle) { |
There was a problem hiding this comment.
Bug: Dock Reopen handler always focuses onboarding over all windows
Dock Reopen handler always focuses onboarding over all windows. Users can't access other windows via dock click. Move onboarding check after other window checks.
View Details
Location: apps/desktop/src-tauri/src/lib.rs (lines 3885)
Analysis
Dock Reopen handler always focuses onboarding over all windows
| What fails | When onboarding is open (e.g., via Help & Tour), clicking the dock icon always focuses the onboarding window and returns early, preventing access to editor, settings, or other windows. |
| Result | The onboarding window is focused. The editor or other windows cannot be reached via the dock icon. |
| Expected | Dock click should show/focus the most relevant window, not always trap on onboarding during revisits. |
| Impact | Users who open Help & Tour are effectively locked out of using the dock icon to navigate to other open windows until they close onboarding. |
How to reproduce
1. Open the main window. 2. Click 'Help & Tour' to open onboarding. 3. Open an editor window. 4. Click the dock icon (macOS).AI Fix Prompt
Fix this issue: Dock Reopen handler always focuses onboarding over all windows. Users can't access other windows via dock click. Move onboarding check after other window checks.
Location: apps/desktop/src-tauri/src/lib.rs (lines 3885)
Problem: When onboarding is open (e.g., via Help & Tour), clicking the dock icon always focuses the onboarding window and returns early, preventing access to editor, settings, or other windows.
Current behavior: The onboarding window is focused. The editor or other windows cannot be reached via the dock icon.
Expected: Dock click should show/focus the most relevant window, not always trap on onboarding during revisits.
Steps to reproduce: 1. Open the main window. 2. Click 'Help & Tour' to open onboarding. 3. Open an editor window. 4. Click the dock icon (macOS).
Provide a code fix.
Tip: Reply with @paragon-run to automatically fix this issue
| || !permissions.necessary_granted() | ||
| { | ||
| let _ = ShowCapWindow::Setup.show(&app).await; | ||
| println!("Showing onboarding"); |
There was a problem hiding this comment.
Bug: println
println! used instead of tracing macros for startup logs. Structured logging is bypassed. Use info! or debug! macros instead.
View Details
Location: apps/desktop/src-tauri/src/lib.rs (lines 3530)
Analysis
println! used instead of tracing macros for startup logs. Structured logging is bypassed
| What fails | Startup decision logging uses println! instead of the tracing crate's structured logging macros, so these messages bypass log levels, filtering, and structured output. |
| Result | Messages go to raw stdout, not captured by the tracing subscriber or log files. |
| Expected | Messages should use info!() or debug!() so they appear in structured logs with timestamps and levels. |
| Impact | Debugging startup flow is harder in production since these messages aren't in the app's log files. |
How to reproduce
1. Build and run the desktop app. 2. Observe stdout for 'Showing onboarding' or 'Showing main window' messages. 3. Note these don't appear in structured logs.Patch Details
- println!("Showing onboarding");
+ info!("Showing onboarding");AI Fix Prompt
Fix this issue: println! used instead of tracing macros for startup logs. Structured logging is bypassed. Use info! or debug! macros instead.
Location: apps/desktop/src-tauri/src/lib.rs (lines 3530)
Problem: Startup decision logging uses println! instead of the tracing crate's structured logging macros, so these messages bypass log levels, filtering, and structured output.
Current behavior: Messages go to raw stdout, not captured by the tracing subscriber or log files.
Expected: Messages should use info!() or debug!() so they appear in structured logs with timestamps and levels.
Steps to reproduce: 1. Build and run the desktop app. 2. Observe stdout for 'Showing onboarding' or 'Showing main window' messages. 3. Note these don't appear in structured logs.
Provide a code fix.
Tip: Reply with @paragon-run to automatically fix this issue
| ): () => number { | ||
| const [phase, setPhase] = createSignal(0); | ||
|
|
||
| createEffect(() => { |
There was a problem hiding this comment.
Bug: Permission polling runs every 250ms while active
Permission polling runs every 250ms while active. This is aggressive for a background check. Increase interval to 1000ms or use event-driven updates.
View Details
Location: apps/desktop/src/routes/(window-chrome)/onboarding.tsx (lines 162)
Analysis
Permission polling runs every 250ms while active. This is aggressive for a background check
| What fails | The permissions step polls for permission status every 250ms via setInterval, causing frequent IPC calls to the Rust backend. |
| Result | High-frequency polling with 250ms intervals causes unnecessary IPC overhead and CPU usage. |
| Expected | A 1000ms interval or event-driven notification when permissions change would reduce resource usage. |
| Impact | Minor performance overhead on the permissions step, especially on lower-end machines. |
How to reproduce
1. Open onboarding. 2. Stay on the permissions step. 3. Monitor IPC calls or CPU usage — doPermissionsCheck fires 4 times per second.Patch Details
- const interval = setInterval(fetchPermissions, 250);
+ const interval = setInterval(fetchPermissions, 1000);AI Fix Prompt
Fix this issue: Permission polling runs every 250ms while active. This is aggressive for a background check. Increase interval to 1000ms or use event-driven updates.
Location: apps/desktop/src/routes/(window-chrome)/onboarding.tsx (lines 162)
Problem: The permissions step polls for permission status every 250ms via setInterval, causing frequent IPC calls to the Rust backend.
Current behavior: High-frequency polling with 250ms intervals causes unnecessary IPC overhead and CPU usage.
Expected: A 1000ms interval or event-driven notification when permissions change would reduce resource usage.
Steps to reproduce: 1. Open onboarding. 2. Stay on the permissions step. 3. Monitor IPC calls or CPU usage — doPermissionsCheck fires 4 times per second.
Provide a code fix.
Tip: Reply with @paragon-run to automatically fix this issue
| if !matches!(self, Self::Camera { .. } | Self::InProgressRecording { .. }) | ||
| && let Some(window) = self.id(app).get(app) | ||
| { | ||
| if matches!(self, Self::Main { .. }) |
There was a problem hiding this comment.
Bug: Duplicate permission guard for Main window in existing-window path
Duplicate permission guard for Main window in existing-window path. Same check exists in creation path. Remove the redundant early check or consolidate.
View Details
Location: apps/desktop/src-tauri/src/windows.rs (lines 925)
Analysis
Duplicate permission guard for Main window in existing-window path
| What fails | ShowCapWindow::show() checks permissions::do_permissions_check(false).necessary_granted() for the Main window in two separate locations — once when the window already exists (line 925) and again when creating a new window. |
| Result | Two identical permission checks run in different branches. If the existing Main window is found, the first check runs and redirects to onboarding. If not found, the second check also redirects. |
| Expected | A single permission check before branching on window existence, or a clear comment explaining why both are needed. |
| Impact | Code maintainability — changes to permission logic must be duplicated in two places, risking divergence. |
How to reproduce
1. Read windows.rs show() method. 2. Note the permission check at line 925-929 for existing Main window. 3. Note the same check in the Self::Main creation arm.AI Fix Prompt
Fix this issue: Duplicate permission guard for Main window in existing-window path. Same check exists in creation path. Remove the redundant early check or consolidate.
Location: apps/desktop/src-tauri/src/windows.rs (lines 925)
Problem: ShowCapWindow::show() checks permissions::do_permissions_check(false).necessary_granted() for the Main window in two separate locations — once when the window already exists (line 925) and again when creating a new window.
Current behavior: Two identical permission checks run in different branches. If the existing Main window is found, the first check runs and redirects to onboarding. If not found, the second check also redirects.
Expected: A single permission check before branching on window existence, or a clear comment explaining why both are needed.
Steps to reproduce: 1. Read windows.rs show() method. 2. Note the permission check at line 925-929 for existing Main window. 3. Note the same check in the Self::Main creation arm.
Provide a code fix.
Tip: Reply with @paragon-run to automatically fix this issue
Greptile Summary
This PR replaces the old
Setupwindow with a polished multi-stepOnboardingwindow that walks first-time users through an animated welcome overlay, OS permissions, recording-mode demos (Instant, Studio, Screenshot), a live mode-switcher, a settings overview, and a FAQ accordion. It also adds a persistent "Help & Tour" button in the main window header so existing users can re-open the tour at any time.Key changes:
onboarding.tsx(2 314 lines): full SolidJS onboarding flow with startup audio, cloud animations, looping animated mockups, and a permissions polling loop.windows.rs:Setupvariant replaced byOnboardingwith responsive sizing;ShowCapWindow::Mainis now guarded by a permission re-check that redirects to Onboarding if necessary permissions are revoked.general_settings.rs: addshas_completed_onboardingwithserde(default = "default_true")so existing users are never re-prompted, while new installs default tofalseviaDefault::default().lib.rs: startup logic extended to check bothhas_completed_startupandhas_completed_onboarding, andreset_microphone_permissionsis now correctly gated to macOS.new-main/index.tsx: "Help & Tour" icon button added to the main header.setupreferences intray.rs,debug.tsx, andApp.tsxare renamed toonboarding.Confidence Score: 4/5
setRecordingModeIPC calls, which silently changes the user's active mode as a side effect of exploring the demo. The remaining findings (un-awaited close, println!, serde comment) are style-level cleanups.Important Files Changed
setRecordingModeIPC calls as a side effect of tour navigation.Setupwindow variant withOnboardingthroughout, addsOnboardingbuilder with responsive sizing, hides the Main window when onboarding opens, and guardsShowCapWindow::Mainwith a permission re-check that redirects to Onboarding if needed. Migration is clean and complete.has_completed_onboarding: boolwithserde(default = "default_true")so existing users skip onboarding, whileDefault::default()correctly returnsfalsefor new installs. The intentional disagreement is a maintenance footgun without a comment.has_completed_startupandhas_completed_onboarding, routing to Onboarding when either flag is false or permissions are missing. Thereset_microphone_permissionsfunction is made macOS-conditional, which is an improvement. A bareprintln!debug statement was added in the startup path.is_setup_window_open→is_onboarding_window_openand updating the tray's "Request Permissions" action to open the Onboarding window. No logic changes./setuplazy route and adds/onboardingroute pointing to the newOnboardingPagecomponent. Clean one-for-one substitution.commands.showWindow("Onboarding"). Minimal and correct change."Onboarding"as aShowCapWindowvariant and remove"Setup". No manual changes needed here.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[App Launch] --> B{startup_completed\nAND onboarding_completed\nAND permissions.necessary_granted?} B -- No --> C[ShowCapWindow::Onboarding] B -- Yes --> D[ShowCapWindow::Main] C --> E[OnboardingPage mounts\nready = false] E --> F[doPermissionsCheck] F --> G{hasCompletedStartup?} G -- No --> H[Show StartupOverlay\nwith audio & animations] G -- Yes --> I[Skip overlay\nstep = 0] H --> J[User clicks Get Started] J --> K{macOS?} K -- Yes --> I K -- No --> L[goToStep 1\nskip permissions UI] I --> M[Step 0: PermissionsStep\ngrant screen rec + accessibility] M --> N{allRequired\npermissions granted?} N -- No --> M N -- Yes --> O[permsGranted = true\nNext button enabled] O --> P[Steps 1-7: Modes / Tour / FAQ] L --> P P --> Q[handleFinish\nset hasCompletedOnboarding = true] Q --> R[showWindow Main] R --> S{Main window exists\nAND permissions missing?} S -- Yes --> C S -- No --> T[Show Main Window\nClose Onboarding] D --> T subgraph Revisit via Help & Tour U[Main Window\nHelp & Tour button] --> V[showWindow Onboarding] V --> W{permissionsNeeded\nAND isRevisit?} W -- Yes --> X[permissionsOnly mode\n1-step flow] W -- No --> Y[Full 8-step tour\nisRevisit = true] X --> Q Y --> Q endComments Outside Diff (1)
apps/desktop/src-tauri/src/lib.rs, line 106-107 (link)println!left in startup pathA bare
println!("Showing onboarding")was added to the app startup path. The surrounding context already removed a similar println for the main-window branch — the new one for onboarding should use the project's tracing/logging infrastructure (info!ortracing::info!) rather thanprintln!, which bypasses therecording_logging_handlewired up at the start ofrun().Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "Rename Setup window to Onboarding" | Re-trigger Greptile