Skip to content

feat(ios): add onboarding welcome pager#45054

Merged
ngutman merged 2 commits intomainfrom
feat/ios-onboarding-welcome-pager
Mar 13, 2026
Merged

feat(ios): add onboarding welcome pager#45054
ngutman merged 2 commits intomainfrom
feat/ios-onboarding-welcome-pager

Conversation

@ngutman
Copy link
Copy Markdown
Contributor

@ngutman ngutman commented Mar 13, 2026

Summary

  • add a first-run welcome pager before iOS onboarding opens the pairing flow
  • remove the automatic QR scanner jump and make scanning an explicit action
  • add visible /pair qr instructions on the connect gateway step so pairing can be initiated from chat
  • persist intro-seen state for first run resets and fix the share extension call site needed to build on device

Verification

  • xcodebuild build -project apps/ios/OpenClaw.xcodeproj -scheme OpenClaw -configuration Debug -destination "id=00008140-000848A92EE3001C" -derivedDataPath /tmp/openclaw-ios-run
  • xcrun devicectl device install app --device 6A19D82B-4EA6-5D67-B3A7-0AB3B71B550C /tmp/openclaw-ios-run/Build/Products/Debug-iphoneos/OpenClaw.app
  • xcrun devicectl device process launch --device 6A19D82B-4EA6-5D67-B3A7-0AB3B71B550C ai.openclaw.ios.test.guti-gzs353x62e

@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation app: ios App: ios size: S maintainer Maintainer-authored PR labels Mar 13, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR introduces a first-run welcome pager (introStep) before the existing "Connect Gateway" (welcomeStep) in iOS onboarding, persists whether the intro has been seen via a new onboarding.first_run_intro_seen key in UserDefaults, removes the automatic QR-scanner pop-up on entering the connect step, and surfaces /pair qr instructions as visible text. A secondary fix adds the missing bootstrapToken: nil argument to two gateway.connect call sites in the share extension so the project builds on device.

Key changes:

  • OnboardingStep enum gains a new .intro case (raw value 0), pushing all existing cases up by one; canGoBack is updated to exclude both .intro and .welcome
  • OnboardingWizardView.init reads shouldPresentFirstRunIntro() at construction time to decide whether to start at .intro or .welcome, ensuring returning users skip the pager
  • OnboardingStateStore.reset() clears both the completed and firstRunIntroSeen flags (but intentionally preserves lastMode), and is now called from SettingsTab.resetOnboarding() so re-triggering onboarding from Settings shows the intro again
  • The automatic QR-scanner auto-present on initializeState() is removed; scanning is now an explicit user action
  • Two new unit tests cover firstRunIntroDefaultsToVisibleThenPersists and resetClearsCompletionAndIntroSeen

Confidence Score: 4/5

  • PR is safe to merge; changes are well-scoped and tested with no critical bugs found.
  • The onboarding flow changes are logically sound — canGoBack correctly excludes both .intro and .welcome, the intro-seen flag is properly persisted and cleared on reset, and the share extension build fix is minimal. The one minor style note is a missing comment explaining why advanceFromIntro() intentionally overrides the statusLine set by initializeState(). No regressions or security issues detected.
  • No files require special attention beyond the inline comment on advanceFromIntro() in OnboardingWizardView.swift.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/ios/Sources/Onboarding/OnboardingWizardView.swift
Line: 837-841

Comment:
**Redundant `statusLine` reset may mask `initializeState()` side-effect**

`initializeState()` (called from `.onAppear`) already overwrites `statusLine` to `"No saved pairing found. In your OpenClaw chat, run /pair qr, then scan the code here."` for any fresh install with no saved credentials. The re-assignment in `advanceFromIntro()` intentionally drops the `"No saved pairing found."` prefix for users who just tapped Continue on the intro screen — which is correct UX. However, there's no comment explaining why the override is needed, making it look like dead code. A short inline comment would clarify the intent:

```suggestion
    private func advanceFromIntro() {
        OnboardingStateStore.markFirstRunIntroSeen()
        // Override the "No saved pairing found." prefix that initializeState() may have set,
        // since first-run users haven't had a chance to pair yet and the prefix would be confusing.
        self.statusLine = "In your OpenClaw chat, run /pair qr, then scan the code here."
        self.step = .welcome
    }
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 6fcdda2

@ngutman
Copy link
Copy Markdown
Contributor Author

ngutman commented Mar 13, 2026

Addressed the review feedback in 881ea16.

  • restored .welcome as the onboarding root so returning users cannot navigate back into the first-run intro
  • removed the unreachable inner case .intro branch from the form switch
  • re-ran the iOS device-target build successfully

@greptile-apps please re-review this PR
@codex review

@ngutman ngutman force-pushed the feat/ios-onboarding-welcome-pager branch from 881ea16 to d5cd523 Compare March 13, 2026 12:09
@openclaw-barnacle openclaw-barnacle bot removed the docs Improvements or additions to documentation label Mar 13, 2026
Comment on lines +53 to +65
@Test func resetClearsCompletionAndIntroSeen() {
let testDefaults = self.makeDefaults()
let defaults = testDefaults.defaults
defer { self.reset(testDefaults) }

OnboardingStateStore.markCompleted(mode: .homeNetwork, defaults: defaults)
OnboardingStateStore.markFirstRunIntroSeen(defaults: defaults)

OnboardingStateStore.reset(defaults: defaults)

#expect(OnboardingStateStore.shouldPresentFirstRunIntro(defaults: defaults))
#expect(OnboardingStateStore.lastMode(defaults: defaults) == .homeNetwork)
}
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.

Test name promises completion cleared but doesn't assert it

resetClearsCompletionAndIntroSeen verifies that shouldPresentFirstRunIntro becomes true again (intro-seen cleared ✓) and that lastMode is preserved ✓. However it never asserts that the completed flag was actually cleared — the "ClearsCompletion" half of the name is unverified.

Since shouldPresentOnLaunch requires @MainActor and an AppModel, the simplest addition is a dedicated @MainActor assertion:

// inside the existing test, add after reset():
let appModel = NodeAppModel()
appModel.gatewayServerName = nil
#expect(OnboardingStateStore.shouldPresentOnLaunch(appModel: appModel, defaults: defaults))

Without this, a future regression that accidentally skips the completedDefaultsKey reset inside reset() would go undetected by this suite.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/ios/Tests/OnboardingStateStoreTests.swift
Line: 53-65

Comment:
**Test name promises completion cleared but doesn't assert it**

`resetClearsCompletionAndIntroSeen` verifies that `shouldPresentFirstRunIntro` becomes `true` again (intro-seen cleared ✓) and that `lastMode` is preserved ✓. However it never asserts that the `completed` flag was actually cleared — the "ClearsCompletion" half of the name is unverified.

Since `shouldPresentOnLaunch` requires `@MainActor` and an `AppModel`, the simplest addition is a dedicated `@MainActor` assertion:

```swift
// inside the existing test, add after reset():
let appModel = NodeAppModel()
appModel.gatewayServerName = nil
#expect(OnboardingStateStore.shouldPresentOnLaunch(appModel: appModel, defaults: defaults))
```

Without this, a future regression that accidentally skips the `completedDefaultsKey` reset inside `reset()` would go undetected by this suite.

How can I resolve this? If you propose a fix, please make it concise.

@ngutman ngutman force-pushed the feat/ios-onboarding-welcome-pager branch from d5cd523 to 6fcdda2 Compare March 13, 2026 12:13
@ngutman
Copy link
Copy Markdown
Contributor Author

ngutman commented Mar 13, 2026

Addressed the latest follow-up review points in 6fcdda2.

  • removed the duplicate horizontal padding on the welcome status line
  • extended resetClearsCompletionAndIntroSeen to assert the completion flag is actually cleared
  • re-ran the iOS device-target build successfully

@greptile-apps please re-review this PR
@codex review

Comment on lines +837 to +841
private func advanceFromIntro() {
OnboardingStateStore.markFirstRunIntroSeen()
self.statusLine = "In your OpenClaw chat, run /pair qr, then scan the code here."
self.step = .welcome
}
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.

Redundant statusLine reset may mask initializeState() side-effect

initializeState() (called from .onAppear) already overwrites statusLine to "No saved pairing found. In your OpenClaw chat, run /pair qr, then scan the code here." for any fresh install with no saved credentials. The re-assignment in advanceFromIntro() intentionally drops the "No saved pairing found." prefix for users who just tapped Continue on the intro screen — which is correct UX. However, there's no comment explaining why the override is needed, making it look like dead code. A short inline comment would clarify the intent:

Suggested change
private func advanceFromIntro() {
OnboardingStateStore.markFirstRunIntroSeen()
self.statusLine = "In your OpenClaw chat, run /pair qr, then scan the code here."
self.step = .welcome
}
private func advanceFromIntro() {
OnboardingStateStore.markFirstRunIntroSeen()
// Override the "No saved pairing found." prefix that initializeState() may have set,
// since first-run users haven't had a chance to pair yet and the prefix would be confusing.
self.statusLine = "In your OpenClaw chat, run /pair qr, then scan the code here."
self.step = .welcome
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/ios/Sources/Onboarding/OnboardingWizardView.swift
Line: 837-841

Comment:
**Redundant `statusLine` reset may mask `initializeState()` side-effect**

`initializeState()` (called from `.onAppear`) already overwrites `statusLine` to `"No saved pairing found. In your OpenClaw chat, run /pair qr, then scan the code here."` for any fresh install with no saved credentials. The re-assignment in `advanceFromIntro()` intentionally drops the `"No saved pairing found."` prefix for users who just tapped Continue on the intro screen — which is correct UX. However, there's no comment explaining why the override is needed, making it look like dead code. A short inline comment would clarify the intent:

```suggestion
    private func advanceFromIntro() {
        OnboardingStateStore.markFirstRunIntroSeen()
        // Override the "No saved pairing found." prefix that initializeState() may have set,
        // since first-run users haven't had a chance to pair yet and the prefix would be confusing.
        self.statusLine = "In your OpenClaw chat, run /pair qr, then scan the code here."
        self.step = .welcome
    }
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ngutman ngutman force-pushed the feat/ios-onboarding-welcome-pager branch from 6fcdda2 to b54815d Compare March 13, 2026 12:21
@ngutman ngutman merged commit 496176d into main Mar 13, 2026
22 checks passed
@ngutman ngutman deleted the feat/ios-onboarding-welcome-pager branch March 13, 2026 12:24
@ngutman
Copy link
Copy Markdown
Contributor Author

ngutman commented Mar 13, 2026

Landed via temp rebase onto main.

  • Gate: xcodebuild build -project apps/ios/OpenClaw.xcodeproj -scheme OpenClaw -configuration Debug -destination 'id=00008140-000848A92EE3001C' -derivedDataPath /tmp/openclaw-ios-run
  • Land commit: b54815d
  • Merge commit: 496176d

Thanks @ngutman!

mrosmarin added a commit to mrosmarin/openclaw that referenced this pull request Mar 13, 2026
* main: (168 commits)
  fix: stabilize macos daemon onboarding
  fix(ui): keep shared auth on insecure control-ui connects (openclaw#45088)
  docs(plugins): clarify workspace shadowing
  fix(node-host): harden perl approval binding
  fix(node-host): harden pnpm approval binding
  fix(discovery): add missing domain to wideArea Zod config schema (openclaw#35615)
  chore(gitignore): add docker-compose override (openclaw#42879)
  feat(ios): add onboarding welcome pager (openclaw#45054)
  fix(signal): add groups config to Signal channel schema (openclaw#27199)
  fix: restore web fetch firecrawl config in runtime zod schema (openclaw#42583)
  fix: polish Android QR scanner onboarding (openclaw#45021)
  fix(android): use Google Code Scanner for onboarding QR
  fix(config): add missing params field to agents.list[] validation schema (openclaw#41171)
  docs(contributing): update Android app ownership
  fix(agents): rephrase session reset prompt to avoid Azure content filter (openclaw#43403)
  test(config): cover requiresOpenAiAnthropicToolPayload in compat schema fixture
  fix(agents): respect explicit user compat overrides for non-native openai-completions (openclaw#44432)
  Android: fix HttpURLConnection leak in TalkModeVoiceResolver (openclaw#43780)
  Docker: add OPENCLAW_TZ timezone support (openclaw#34119)
  fix(agents): avoid injecting memory file twice on case-insensitive mounts (openclaw#26054)
  ...
z-hao-wang pushed a commit to z-hao-wang/openclaw that referenced this pull request Mar 13, 2026
* feat(ios): add onboarding welcome pager

* feat(ios): add onboarding welcome pager (openclaw#45054) (thanks @ngutman)
frankekn pushed a commit to xinhuagu/openclaw that referenced this pull request Mar 14, 2026
* feat(ios): add onboarding welcome pager

* feat(ios): add onboarding welcome pager (openclaw#45054) (thanks @ngutman)
hougangdev pushed a commit to hougangdev/clawdbot that referenced this pull request Mar 14, 2026
* feat(ios): add onboarding welcome pager

* feat(ios): add onboarding welcome pager (openclaw#45054) (thanks @ngutman)
ecochran76 pushed a commit to ecochran76/openclaw that referenced this pull request Mar 14, 2026
* feat(ios): add onboarding welcome pager

* feat(ios): add onboarding welcome pager (openclaw#45054) (thanks @ngutman)
Interstellar-code pushed a commit to Interstellar-code/operator1 that referenced this pull request Mar 16, 2026
* feat(ios): add onboarding welcome pager

* feat(ios): add onboarding welcome pager (openclaw#45054) (thanks @ngutman)

(cherry picked from commit 496176d)
sbezludny pushed a commit to sbezludny/openclaw that referenced this pull request Mar 27, 2026
* feat(ios): add onboarding welcome pager

* feat(ios): add onboarding welcome pager (openclaw#45054) (thanks @ngutman)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: ios App: ios maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant