Skip to content

fix(desktop): onboarding step flow improvements#6046

Merged
kodjima33 merged 6 commits into
mainfrom
worktree-onboarding-fixes
Mar 25, 2026
Merged

fix(desktop): onboarding step flow improvements#6046
kodjima33 merged 6 commits into
mainfrom
worktree-onboarding-fixes

Conversation

@kodjima33
Copy link
Copy Markdown
Collaborator

Summary

  • Remove purple colors from onboarding — replaced with black/white theme
  • Fix graph edges not persisting (nodes saved but edges dropped due to validation bug)
  • Move Screen Recording permission before Full Disk Access so restart happens before scanning
  • Move "Your 2nd brain is live" + Goal steps to end of onboarding for better timing
  • Start Gmail/Calendar/web research in parallel with file scan using Task.detached
  • Fix Screen Recording button to actually open System Settings
  • Add auto-relaunch when app is terminated mid-onboarding (FDA "Quit & Reopen")
  • Clear graph on onboarding start so stale data doesn't appear
  • Remove redundant "Mapped" card and helper text from permission steps
  • Rename "Full Disk Access" to "Disk Access"
  • Add privaterelay.appleid.com to public domains to fix garbage web search for Apple relay users
  • Remove keychain cache persistence that triggered extra password prompts
  • Add enrichment goals logging
  • Re-trigger insights on research step if app restarted mid-onboarding

Test plan

  • Sign in and go through full onboarding flow
  • Verify no purple colors in any step
  • Verify Screen Recording step appears before disk access
  • Verify file scan + Gmail/Calendar run in parallel
  • Verify graph shows connected nodes with edges
  • Verify goal suggestions include smart enrichment-based goals
  • Verify no keychain password prompts appear
  • Verify app relaunches after FDA "Quit & Reopen"

🤖 Generated with Claude Code

kodjima33 and others added 6 commits March 25, 2026 19:17
…assword prompt

SecItemCopyMatching triggers a macOS Keychain password dialog because
the app's code signature isn't in the browser's Safe Storage ACL.
The original `security find-generic-password` CLI approach doesn't
prompt because it's a trusted system binary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d password prompt

Same fix as GmailReaderService — use `security find-generic-password`
instead of SecItemCopyMatching to avoid the Keychain password dialog.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rd prompts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s, rename to Disk Access

Move screen recording step before full disk access so the restart
happens before any file scanning or insights start. Rename "Full Disk
Access" to "Disk Access" for clarity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@kodjima33 kodjima33 merged commit f919c4e into main Mar 25, 2026
2 checks passed
@kodjima33 kodjima33 deleted the worktree-onboarding-fixes branch March 25, 2026 23:32
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 25, 2026

Greptile Summary

This PR refactors the macOS desktop onboarding flow with several quality-of-life improvements: reordering permission steps (Screen Recording now appears before Full Disk Access), moving the Research and Goal steps to the end of the flow, parallelizing file scan with Gmail/Calendar enrichment, fixing graph edge persistence, replacing Security-framework keychain calls with the /usr/bin/security CLI to suppress password prompts, adding auto-relaunch after mid-onboarding termination, and cleaning up the visual theme.

Key changes:

  • ScreenRecording permission moved to step 3 (before Full Disk Access at step 4) so the mandatory restart happens before file scanning begins
  • Research (step 14) and Goal (step 15) moved after the floating bar / voice demo steps for better pacing
  • Graph edge validation bug fixed: edges to nodes already persisted in the DB are no longer dropped
  • Task.detached used to launch Gmail/Calendar/web research concurrently with the file scan
  • SecItemCopyMatching replaced by /usr/bin/security subprocess to avoid UI keychain password prompts
  • KnowledgeGraphStorage.clearAll() called at onboarding start to prevent stale graph data

Issues found:

  • OnboardingFlow.introStepCount is still 12, but Research (stepIndex: 14) and Goal (stepIndex: 15) both pass totalSteps: OnboardingFlow.introStepCount. Because all progress-bar indices 0–11 satisfy index <= stepIndex, every capsule renders white and the active-step capsule (only drawn at index == stepIndex) is never rendered — the progress indicator appears fully completed for these two steps.
  • The Goal step's onContinue closure inlines startMonitoring but omits the AssistantSettings.shared.screenAnalysisEnabled = true call that startMonitoringIfNeeded() performs, which is inconsistent with the rest of the flow.
  • process.waitUntilExit() in the actor-isolated getKeychainPassword blocks the actor's serial executor; this is mitigated by the in-memory BrowserKeychainCache (called once per service), but is still an antipattern.
  • The auto-relaunch shell command interpolates bundleURL.path without full shell escaping; while low-risk in practice, direct use of /usr/bin/open without a shell would be more robust.

Confidence Score: 3/5

  • The PR is close to merge-ready but has two concrete bugs: a broken progress indicator on the Research and Goal steps, and a missing screenAnalysisEnabled flag assignment in the Goal step, both of which need fixes before shipping.
  • Core functional improvements (edge persistence fix, parallel enrichment, permission reordering, auto-relaunch) are all sound. However, the introStepCount/stepIndex mismatch is a guaranteed visual regression visible to every new user on the two most important late-stage onboarding steps, and the missing screenAnalysisEnabled=true call is a subtle behavioral regression for returning users. Both are straightforward one-line fixes but need to land before this ships.
  • desktop/Desktop/Sources/OnboardingView.swift (progress bar and screenAnalysisEnabled regressions) and desktop/Desktop/Sources/OnboardingFlow.swift (introStepCount not updated)

Important Files Changed

Filename Overview
desktop/Desktop/Sources/OnboardingView.swift Renumbers all onboarding steps to insert ScreenRecording at step 3 and moves Research/Goal to the end. Two bugs: (1) Research (stepIndex=14) and Goal (stepIndex=15) still pass totalSteps=introStepCount (12), breaking the progress bar; (2) Goal's onContinue omits the screenAnalysisEnabled=true assignment present in startMonitoringIfNeeded().
desktop/Desktop/Sources/OnboardingFlow.swift Reorders steps array to move ScreenRecording before FullDiskAccess and places Research/Goal at the end. introStepCount remains 12 but is now inconsistent with the new positions of Research (14) and Goal (15), contributing to the progress bar bug in OnboardingView.
desktop/Desktop/Sources/OmiApp.swift Adds auto-relaunch on mid-onboarding app termination using a shell subprocess. The bundleURL.path string interpolation in the shell command is fragile for paths containing special characters, but the practical risk is low.
desktop/Desktop/Sources/CalendarReaderService.swift Replaces Security framework keychain API with the /usr/bin/security CLI subprocess to avoid UI password prompts. The blocking process.waitUntilExit() stalls the actor executor, but BrowserKeychainCache caches results so this only happens once per service per session.
desktop/Desktop/Sources/GmailReaderService.swift Same keychain change as CalendarReaderService — replaces SecItemCopyMatching with a /usr/bin/security subprocess. Same blocking-actor concern applies.
desktop/Desktop/Sources/Providers/ChatToolExecutor.swift Fixes two issues: (1) Graph edge validation now only drops self-referencing edges, allowing edges to nodes from prior DB saves to persist — correct fix for the reported bug; (2) Screen Recording permission flow now also opens System Settings URL for the correct preference pane.
desktop/Desktop/Sources/OnboardingPagedIntroCoordinator.swift Launches Gmail/Calendar/web research in parallel with file scan using Task.detached; clears knowledge graph on onboarding start; adds enrichment goals logging; re-triggers insights on the research step if app restarted mid-onboarding. Logic is sound.
desktop/Desktop/Sources/OnboardingResearchStepView.swift Renames title to "Your 2nd brain is live." and adds re-trigger of background insights if coordinator.isResearchComplete is false (handles mid-onboarding restart). Clean change.
desktop/Desktop/Sources/OnboardingPermissionStepView.swift Removes two helper text labels ("macOS may relaunch Omi here" and "This page advances automatically"). Minor cleanup, no functional impact.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Trust] --> B[Name]
    B --> C[Language]
    C --> D["ScreenRecording ★ moved here"]
    D -->|"Grant / Skip"| E["FullDiskAccess (renamed: Disk Access)"]
    E --> F[FileScan]
    F -->|parallel start| G["Task.detached: Gmail + Calendar + Web Research"]
    F --> H[Microphone]
    H --> I[Notifications]
    I --> J[Accessibility]
    J --> K[Automation]
    K --> L[FloatingBarShortcut]
    L --> M[FloatingBar]
    M --> N[VoiceShortcut]
    N --> O[VoiceDemo]
    O --> P["Research ★ moved here (step 14)"]
    P --> Q["Goal ★ moved here (step 15)"]
    Q --> R[Tasks / Complete]

    G -.->|"results ready by step 14"| P

    style D fill:#f90,color:#000
    style P fill:#f90,color:#000
    style Q fill:#f90,color:#000
Loading

Comments Outside Diff (2)

  1. desktop/Desktop/Sources/OnboardingView.swift, line 373-381 (link)

    P1 Progress bar broken for Research & Goal steps

    introStepCount is 12, but Research is now at stepIndex: 14 and Goal at stepIndex: 15. In OnboardingStepScaffold.progressRow, the loop is ForEach(0..<totalSteps, ...) — only indices 0–11 are rendered. Since every index in that range satisfies index <= 14, all 12 capsules render as fully white, and the active/expanded capsule (only drawn when index == stepIndex) is never shown because index 14 is outside the loop.

    This is a visual regression: both the Research and Goal steps will show a fully-completed, all-white progress bar with no current-step indicator.

    The simplest fix is to pass the actual total step count (OnboardingFlow.steps.count = 17) for these two steps, or update introStepCount:

  2. desktop/Desktop/Sources/OnboardingView.swift, line 393-406 (link)

    P1 screenAnalysisEnabled not set in Goal step onContinue

    startMonitoringIfNeeded() does two things:

    AssistantSettings.shared.screenAnalysisEnabled = true
    ProactiveAssistantsPlugin.shared.startMonitoring { _, _ in }

    The inlined Goal step closure only calls startMonitoring, omitting the screenAnalysisEnabled = true assignment. In the old flow, Goal's onContinue always called startMonitoringIfNeeded(), ensuring the flag was set even for users who skipped ScreenRecording. Since @AppStorage("screenAnalysisEnabled") defaults to true, new installs are fine — but users upgrading mid-onboarding or who have screenAnalysisEnabled = false set from a prior session may silently stay with analysis disabled after completing onboarding.

    Consider replacing the inline code with the existing helper to keep behavior consistent:

Reviews (1): Last reviewed commit: "fix(desktop): reorder onboarding - scree..." | Re-trigger Greptile

Comment on lines +1064 to +1070
if OnboardingChatPersistence.isMidOnboarding {
let bundleURL = Bundle.main.bundleURL
let task = Process()
task.executableURL = URL(fileURLWithPath: "/bin/sh")
task.arguments = ["-c", "sleep 1 && open \"\(bundleURL.path)\""]
try? task.run()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Shell injection risk in auto-relaunch command

bundleURL.path is interpolated directly into a shell command string:

task.arguments = ["-c", "sleep 1 && open \"\(bundleURL.path)\""]

While Bundle.main.bundleURL is OS-controlled and unlikely to contain adversarial content in practice, the double-quote-only escaping doesn't account for characters like $, backticks, or spaces inside directory names (e.g., if the app is installed in /Applications/My Apps/Omi.app). A path with spaces would break the shell argument even with the surrounding quotes since the path itself may contain further embedded quotes.

A more robust approach avoids shell interpolation entirely by using open directly:

let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/open")
task.arguments = [bundleURL.path]
// Use a detached sleep-then-launch so the current process can exit first
let delayedTask = Process()
delayedTask.executableURL = URL(fileURLWithPath: "/bin/sh")
delayedTask.arguments = ["-c", "sleep 1"]

Or, even simpler, just use NSWorkspace after a short delay by scheduling a detached task that has no shell injection surface. At minimum, the path should be properly quoted using ProcessInfo-safe methods.

Comment on lines 589 to +603
BrowserKeychainCache.shared.password(for: service) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let data = result as? Data,
let password = String(data: data, encoding: .utf8)
else {
if status != errSecItemNotFound {
log(
"CalendarReaderService: Keychain lookup for '\(service)' failed with status \(status)")
}
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/security")
process.arguments = ["find-generic-password", "-s", service, "-w"]
let pipe = Pipe()
let errPipe = Pipe()
process.standardOutput = pipe
process.standardError = errPipe
do {
try process.run()
process.waitUntilExit()
guard process.terminationStatus == 0 else { return nil }
let output = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines)
return output?.isEmpty == false ? output : nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Blocking actor executor with process.waitUntilExit()

getKeychainPassword is a synchronous function on a Swift actor. When it calls process.waitUntilExit(), it blocks the actor's serial executor thread for the duration of the subprocess (the security CLI tool). This prevents the actor from processing any other messages while waiting, potentially causing queued operations (e.g., concurrent email reads) to stall.

The same issue exists in GmailReaderService.swift at the equivalent getKeychainPassword implementation.

The fix is to make the function async and replace the blocking wait with an async-safe subprocess runner (e.g., using AsyncStream/withCheckedContinuation around terminationHandler):

process.terminationHandler = { _ in
    continuation.resume(returning: ...)
}
try process.run()
// await continuation instead of process.waitUntilExit()

Alternatively, since BrowserKeychainCache caches the result, the blocking call only happens once per service per session — but it's still best practice not to block an actor's executor.

Glucksberg pushed a commit to Glucksberg/omi-local that referenced this pull request Apr 28, 2026
## Summary
- Remove purple colors from onboarding — replaced with black/white theme
- Fix graph edges not persisting (nodes saved but edges dropped due to
validation bug)
- Move Screen Recording permission before Full Disk Access so restart
happens before scanning
- Move "Your 2nd brain is live" + Goal steps to end of onboarding for
better timing
- Start Gmail/Calendar/web research in parallel with file scan using
Task.detached
- Fix Screen Recording button to actually open System Settings
- Add auto-relaunch when app is terminated mid-onboarding (FDA "Quit &
Reopen")
- Clear graph on onboarding start so stale data doesn't appear
- Remove redundant "Mapped" card and helper text from permission steps
- Rename "Full Disk Access" to "Disk Access"
- Add privaterelay.appleid.com to public domains to fix garbage web
search for Apple relay users
- Remove keychain cache persistence that triggered extra password
prompts
- Add enrichment goals logging
- Re-trigger insights on research step if app restarted mid-onboarding

## Test plan
- [ ] Sign in and go through full onboarding flow
- [ ] Verify no purple colors in any step
- [ ] Verify Screen Recording step appears before disk access
- [ ] Verify file scan + Gmail/Calendar run in parallel
- [ ] Verify graph shows connected nodes with edges
- [ ] Verify goal suggestions include smart enrichment-based goals
- [ ] Verify no keychain password prompts appear
- [ ] Verify app relaunches after FDA "Quit & Reopen"

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant