add provider-first onboarding - #9039
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e60906e192
ℹ️ 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".
Signed-off-by: tulsi <tulsi@block.xyz>
Signed-off-by: tulsi <tulsi@block.xyz>
Signed-off-by: tulsi <tulsi@block.xyz>
Signed-off-by: tulsi <tulsi@block.xyz>
Signed-off-by: tulsi <tulsi@block.xyz>
Signed-off-by: tulsi <tulsi@block.xyz>
73bbd4f to
22d5db4
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22d5db4e66
ℹ️ 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".
Signed-off-by: tulsi <tulsi@block.xyz>
Signed-off-by: tulsi <tulsi@block.xyz>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3178f8d5b4
ℹ️ 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".
| } | ||
| return a.displayName.localeCompare(b.displayName); | ||
| }); | ||
| }, [distro]); |
There was a problem hiding this comment.
Subscribe provider list to catalog updates
modelProviders is memoized with [distro] only, but it is derived from getModelProviders() (an external store snapshot). Startup intentionally loads the provider catalog in the background, so onboarding can render before catalog entries arrive; when that happens, this memo stays empty and the provider step can render no setup rows, leaving users unable to pick/configure a provider and unable to proceed.
Useful? React with 👍 / 👎.
| filterModelProvidersForDistro(getModelProviders(), distro).map( | ||
| (provider) => provider.id, | ||
| ), | ||
| ), | ||
| [distro], |
There was a problem hiding this comment.
Recompute model provider IDs when catalog changes
This memo also reads getModelProviders() from external store state but only depends on distro, so it can retain an empty/stale ID set if the catalog loads after initial render. The later readiness filter requires modelProviderIds.has(entry.providerId), which can incorrectly exclude configured non-custom providers and misclassify readiness, causing unnecessary onboarding gating for otherwise usable setups.
Useful? React with 👍 / 👎.
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding boolean depended on BOTH `readiness.isUsable` (does the user have a configured provider with models?) AND `readiness.hasCompletedOnboarding` (is localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider (which the model picker triggers) re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working openrouter (or any other) provider. Drop the !hasCompletedOnboarding check from shouldShowOnboarding. The gate is now purely capability-based: shouldShowOnboarding: startupReady && !readiness.isUsable Behavior matrix: | user | isUsable | result | |----------------------------------------|----------|---------------| | new user (no provider) | false | onboarding | | pre-existing user (provider works) | true | normal app | | returning user (completed onboarding) | true | normal app | | user who deleted all providers | false | onboarding | The completion record is still written when a user finishes OnboardingFlow's ReadyStep (via AppShell.tsx:741) — it just no longer gates whether onboarding shows. The hasCompletedOnboarding field stays on OnboardingReadiness for future analytics/UX hints. Updated test "shows onboarding after startup when there is no completed state" to reflect the new grandfathering behavior, and added a separate test for the new-user path (no usable provider, no completion → flow appears).
… pre-existing users Two related issues with PR aaif-goose#9039's onboarding gate: 1. modelProviderIds was permanently empty. The memo called getModelProviders() (which reads via .getState()) but only listed [distro] as a dependency. When the catalog populated from the backend (1 entry → 27 entries), the memo never recomputed, so the configuredEntry fallback in readiness never matched any provider. Same pattern in useOnboardingProviderStep.ts (modelProviders and usableAgentEntries memos). Fix: subscribe to catalogEntries via useProviderCatalogStore, switch call sites to the *FromEntries pure variants, and add catalogEntries to the affected memo deps. 2. Pre-existing users (installed before aaif-goose#9039) were trapped on ImportStep because hasCompletedOnboarding stayed false forever. The previous fix dropped the completion check from the gate, but that silently broke the "Reset onboarding" button — users who clicked it couldn't get back into the flow because their setup was still isUsable. Fix: restore the original (!hasCompletedOnboarding || !isUsable) gate, plus an auto-grandfathering effect that synthesizes a completion record once for users with a usable provider but no record. A separate ONBOARDING_GRANDFATHERED_KEY persists a "we've seen this user" marker so the effect doesn't re-fire after an explicit reset (resetOnboardingCompletion now sets that flag too). Behaviour matrix: | user | result | |------------------------------|------------------------------| | new (no provider) | onboarding shows | | pre-existing (usable, no | auto-grandfathered, normal | | completion record) | app | | returning (completion + ok) | normal app | | reset → reload (usable) | onboarding shows again | | provider deleted | onboarding shows | Tests: existing 7 cases keep passing; renamed grandfathering test to assert the new auto-completion behaviour and added a new test for the post-reset path.
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding boolean depended on BOTH `readiness.isUsable` (does the user have a configured provider with models?) AND `readiness.hasCompletedOnboarding` (is localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider (which the model picker triggers) re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working openrouter (or any other) provider. Drop the !hasCompletedOnboarding check from shouldShowOnboarding. The gate is now purely capability-based: shouldShowOnboarding: startupReady && !readiness.isUsable Behavior matrix: | user | isUsable | result | |----------------------------------------|----------|---------------| | new user (no provider) | false | onboarding | | pre-existing user (provider works) | true | normal app | | returning user (completed onboarding) | true | normal app | | user who deleted all providers | false | onboarding | The completion record is still written when a user finishes OnboardingFlow's ReadyStep (via AppShell.tsx:741) — it just no longer gates whether onboarding shows. The hasCompletedOnboarding field stays on OnboardingReadiness for future analytics/UX hints. Updated test "shows onboarding after startup when there is no completed state" to reflect the new grandfathering behavior, and added a separate test for the new-user path (no usable provider, no completion → flow appears).
… pre-existing users Two related issues with PR aaif-goose#9039's onboarding gate: 1. modelProviderIds was permanently empty. The memo called getModelProviders() (which reads via .getState()) but only listed [distro] as a dependency. When the catalog populated from the backend (1 entry → 27 entries), the memo never recomputed, so the configuredEntry fallback in readiness never matched any provider. Same pattern in useOnboardingProviderStep.ts (modelProviders and usableAgentEntries memos). Fix: subscribe to catalogEntries via useProviderCatalogStore, switch call sites to the *FromEntries pure variants, and add catalogEntries to the affected memo deps. 2. Pre-existing users (installed before aaif-goose#9039) were trapped on ImportStep because hasCompletedOnboarding stayed false forever. The previous fix dropped the completion check from the gate, but that silently broke the "Reset onboarding" button — users who clicked it couldn't get back into the flow because their setup was still isUsable. Fix: restore the original (!hasCompletedOnboarding || !isUsable) gate, plus an auto-grandfathering effect that synthesizes a completion record once for users with a usable provider but no record. A separate ONBOARDING_GRANDFATHERED_KEY persists a "we've seen this user" marker so the effect doesn't re-fire after an explicit reset (resetOnboardingCompletion now sets that flag too). Behaviour matrix: | user | result | |------------------------------|------------------------------| | new (no provider) | onboarding shows | | pre-existing (usable, no | auto-grandfathered, normal | | completion record) | app | | returning (completion + ok) | normal app | | reset → reload (usable) | onboarding shows again | | provider deleted | onboarding shows | Tests: existing 7 cases keep passing; renamed grandfathering test to assert the new auto-completion behaviour and added a new test for the post-reset path.
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding boolean depended on BOTH `readiness.isUsable` (does the user have a configured provider with models?) AND `readiness.hasCompletedOnboarding` (is localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider (which the model picker triggers) re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working openrouter (or any other) provider. Drop the !hasCompletedOnboarding check from shouldShowOnboarding. The gate is now purely capability-based: shouldShowOnboarding: startupReady && !readiness.isUsable Behavior matrix: | user | isUsable | result | |----------------------------------------|----------|---------------| | new user (no provider) | false | onboarding | | pre-existing user (provider works) | true | normal app | | returning user (completed onboarding) | true | normal app | | user who deleted all providers | false | onboarding | The completion record is still written when a user finishes OnboardingFlow's ReadyStep (via AppShell.tsx:741) — it just no longer gates whether onboarding shows. The hasCompletedOnboarding field stays on OnboardingReadiness for future analytics/UX hints. Updated test "shows onboarding after startup when there is no completed state" to reflect the new grandfathering behavior, and added a separate test for the new-user path (no usable provider, no completion → flow appears).
… pre-existing users Two related issues with PR aaif-goose#9039's onboarding gate: 1. modelProviderIds was permanently empty. The memo called getModelProviders() (which reads via .getState()) but only listed [distro] as a dependency. When the catalog populated from the backend (1 entry → 27 entries), the memo never recomputed, so the configuredEntry fallback in readiness never matched any provider. Same pattern in useOnboardingProviderStep.ts (modelProviders and usableAgentEntries memos). Fix: subscribe to catalogEntries via useProviderCatalogStore, switch call sites to the *FromEntries pure variants, and add catalogEntries to the affected memo deps. 2. Pre-existing users (installed before aaif-goose#9039) were trapped on ImportStep because hasCompletedOnboarding stayed false forever. The previous fix dropped the completion check from the gate, but that silently broke the "Reset onboarding" button — users who clicked it couldn't get back into the flow because their setup was still isUsable. Fix: restore the original (!hasCompletedOnboarding || !isUsable) gate, plus an auto-grandfathering effect that synthesizes a completion record once for users with a usable provider but no record. A separate ONBOARDING_GRANDFATHERED_KEY persists a "we've seen this user" marker so the effect doesn't re-fire after an explicit reset (resetOnboardingCompletion now sets that flag too). Behaviour matrix: | user | result | |------------------------------|------------------------------| | new (no provider) | onboarding shows | | pre-existing (usable, no | auto-grandfathered, normal | | completion record) | app | | returning (completion + ok) | normal app | | reset → reload (usable) | onboarding shows again | | provider deleted | onboarding shows | Tests: existing 7 cases keep passing; renamed grandfathering test to assert the new auto-completion behaviour and added a new test for the post-reset path.
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding boolean depended on BOTH `readiness.isUsable` (does the user have a configured provider with models?) AND `readiness.hasCompletedOnboarding` (is localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider (which the model picker triggers) re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working openrouter (or any other) provider. Drop the !hasCompletedOnboarding check from shouldShowOnboarding. The gate is now purely capability-based: shouldShowOnboarding: startupReady && !readiness.isUsable Behavior matrix: | user | isUsable | result | |----------------------------------------|----------|---------------| | new user (no provider) | false | onboarding | | pre-existing user (provider works) | true | normal app | | returning user (completed onboarding) | true | normal app | | user who deleted all providers | false | onboarding | The completion record is still written when a user finishes OnboardingFlow's ReadyStep (via AppShell.tsx:741) — it just no longer gates whether onboarding shows. The hasCompletedOnboarding field stays on OnboardingReadiness for future analytics/UX hints. Updated test "shows onboarding after startup when there is no completed state" to reflect the new grandfathering behavior, and added a separate test for the new-user path (no usable provider, no completion → flow appears).
…-refactor * origin/main: refactor: switch to official new rust-sdk for ACP (#9062) refactor(goose2): remove attachment preamble (#9052) Align CODEOWNERS with pull request review rules in GOVERNANCE.md (#9056) bring MAINTAINERS.md up to date (#9053) feat(acp): expose built-in skills through sources list acp calls (#9045) add provider-first onboarding (#9039) feat: ACP streamable http spec compliance (#9034) Skip automatic fix which crashes (#9036) fix(openai): accept null tool_call arguments in streaming chunks (#9035) Signed-off-by: Lifei Zhou <lifei@squareup.com> # Conflicts: # ui/goose2/src/features/chat/hooks/useChat.ts
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding depended on both \`readiness.isUsable\` (does the user have a configured provider with models?) AND \`readiness.hasCompletedOnboarding\` (localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working provider. Collapse the gate to a single reset-flag: shouldShowOnboarding = startupReady && (resetRequested || !isUsable) resetOnboarding() sets the flag; completeOnboarding() clears it. The completion payload (providerId/modelId) — never read outside the hook — is no longer persisted, and completeOnboarding's signature drops to () => void. resetOnboardingCompletion opportunistically removes the legacy ONBOARDING_STORAGE_KEY for users on the upgrade path. Behavior matrix: | user | result | |----------------------------------------|---------------| | new user (no provider) | onboarding | | pre-existing user (provider works) | normal app | | returning user (completed onboarding) | normal app | | user who deleted all providers | onboarding | | user who clicked "Reset onboarding" | onboarding | Drops hasCompletedOnboarding from OnboardingReadiness and the "not_completed" reason it implied. Tests updated for the new state model. Signed-off-by: Erik Nilsen <enilsen16@live.com>
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding depended on both \`readiness.isUsable\` (does the user have a configured provider with models?) AND \`readiness.hasCompletedOnboarding\` (localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working provider. Collapse the gate to a single reset-flag: shouldShowOnboarding = startupReady && (resetRequested || !isUsable) resetOnboarding() sets the flag; completeOnboarding() clears it. The completion payload (providerId/modelId) — never read outside the hook — is no longer persisted, and completeOnboarding's signature drops to () => void. resetOnboardingCompletion opportunistically removes the legacy ONBOARDING_STORAGE_KEY for users on the upgrade path. Behavior matrix: | user | result | |----------------------------------------|---------------| | new user (no provider) | onboarding | | pre-existing user (provider works) | normal app | | returning user (completed onboarding) | normal app | | user who deleted all providers | onboarding | | user who clicked "Reset onboarding" | onboarding | Drops hasCompletedOnboarding from OnboardingReadiness and the "not_completed" reason it implied. Tests updated for the new state model. Signed-off-by: Erik Nilsen <enilsen16@live.com>
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding depended on both \`readiness.isUsable\` (does the user have a configured provider with models?) AND \`readiness.hasCompletedOnboarding\` (localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working provider. Collapse the gate to a single reset-flag: shouldShowOnboarding = startupReady && (resetRequested || !isUsable) resetOnboarding() sets the flag; completeOnboarding() clears it. The completion payload (providerId/modelId) — never read outside the hook — is no longer persisted, and completeOnboarding's signature drops to () => void. resetOnboardingCompletion opportunistically removes the legacy ONBOARDING_STORAGE_KEY for users on the upgrade path. Behavior matrix: | user | result | |----------------------------------------|---------------| | new user (no provider) | onboarding | | pre-existing user (provider works) | normal app | | returning user (completed onboarding) | normal app | | user who deleted all providers | onboarding | | user who clicked "Reset onboarding" | onboarding | Drops hasCompletedOnboarding from OnboardingReadiness and the "not_completed" reason it implied. Tests updated for the new state model. Signed-off-by: Erik Nilsen <enilsen16@live.com>
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding depended on both \`readiness.isUsable\` (does the user have a configured provider with models?) AND \`readiness.hasCompletedOnboarding\` (localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working provider. Collapse the gate to a single reset-flag: shouldShowOnboarding = startupReady && (resetRequested || !isUsable) resetOnboarding() sets the flag; completeOnboarding() clears it. The completion payload (providerId/modelId) — never read outside the hook — is no longer persisted, and completeOnboarding's signature drops to () => void. resetOnboardingCompletion opportunistically removes the legacy ONBOARDING_STORAGE_KEY for users on the upgrade path. Behavior matrix: | user | result | |----------------------------------------|---------------| | new user (no provider) | onboarding | | pre-existing user (provider works) | normal app | | returning user (completed onboarding) | normal app | | user who deleted all providers | onboarding | | user who clicked "Reset onboarding" | onboarding | Drops hasCompletedOnboarding from OnboardingReadiness and the "not_completed" reason it implied. Tests updated for the new state model. Signed-off-by: Erik Nilsen <enilsen16@live.com>
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding depended on both \`readiness.isUsable\` (does the user have a configured provider with models?) AND \`readiness.hasCompletedOnboarding\` (localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working provider. Collapse the gate to a single reset-flag: shouldShowOnboarding = startupReady && (resetRequested || !isUsable) resetOnboarding() sets the flag; completeOnboarding() clears it. The completion payload (providerId/modelId) — never read outside the hook — is no longer persisted, and completeOnboarding's signature drops to () => void. resetOnboardingCompletion opportunistically removes the legacy ONBOARDING_STORAGE_KEY for users on the upgrade path. Behavior matrix: | user | result | |----------------------------------------|---------------| | new user (no provider) | onboarding | | pre-existing user (provider works) | normal app | | returning user (completed onboarding) | normal app | | user who deleted all providers | onboarding | | user who clicked "Reset onboarding" | onboarding | Drops hasCompletedOnboarding from OnboardingReadiness and the "not_completed" reason it implied. Tests updated for the new state model. Signed-off-by: Erik Nilsen <enilsen16@live.com>
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding depended on both \`readiness.isUsable\` (does the user have a configured provider with models?) AND \`readiness.hasCompletedOnboarding\` (localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working provider. Collapse the gate to a single reset-flag: shouldShowOnboarding = startupReady && (resetRequested || !isUsable) resetOnboarding() sets the flag; completeOnboarding() clears it. The completion payload (providerId/modelId) — never read outside the hook — is no longer persisted, and completeOnboarding's signature drops to () => void. resetOnboardingCompletion opportunistically removes the legacy ONBOARDING_STORAGE_KEY for users on the upgrade path. Behavior matrix: | user | result | |----------------------------------------|---------------| | new user (no provider) | onboarding | | pre-existing user (provider works) | normal app | | returning user (completed onboarding) | normal app | | user who deleted all providers | onboarding | | user who clicked "Reset onboarding" | onboarding | Drops hasCompletedOnboarding from OnboardingReadiness and the "not_completed" reason it implied. Tests updated for the new state model. Signed-off-by: Erik Nilsen <enilsen16@live.com>
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding depended on both \`readiness.isUsable\` (does the user have a configured provider with models?) AND \`readiness.hasCompletedOnboarding\` (localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working provider. Collapse the gate to a single reset-flag: shouldShowOnboarding = startupReady && (resetRequested || !isUsable) resetOnboarding() sets the flag; completeOnboarding() clears it. The completion payload (providerId/modelId) — never read outside the hook — is no longer persisted, and completeOnboarding's signature drops to () => void. resetOnboardingCompletion opportunistically removes the legacy ONBOARDING_STORAGE_KEY for users on the upgrade path. Behavior matrix: | user | result | |----------------------------------------|---------------| | new user (no provider) | onboarding | | pre-existing user (provider works) | normal app | | returning user (completed onboarding) | normal app | | user who deleted all providers | onboarding | | user who clicked "Reset onboarding" | onboarding | Drops hasCompletedOnboarding from OnboardingReadiness and the "not_completed" reason it implied. Tests updated for the new state model. Signed-off-by: Erik Nilsen <enilsen16@live.com>
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding depended on both \`readiness.isUsable\` (does the user have a configured provider with models?) AND \`readiness.hasCompletedOnboarding\` (localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working provider. Collapse the gate to a single reset-flag: shouldShowOnboarding = startupReady && (resetRequested || !isUsable) resetOnboarding() sets the flag; completeOnboarding() clears it. The completion payload (providerId/modelId) — never read outside the hook — is no longer persisted, and completeOnboarding's signature drops to () => void. resetOnboardingCompletion opportunistically removes the legacy ONBOARDING_STORAGE_KEY for users on the upgrade path. Behavior matrix: | user | result | |----------------------------------------|---------------| | new user (no provider) | onboarding | | pre-existing user (provider works) | normal app | | returning user (completed onboarding) | normal app | | user who deleted all providers | onboarding | | user who clicked "Reset onboarding" | onboarding | Drops hasCompletedOnboarding from OnboardingReadiness and the "not_completed" reason it implied. Tests updated for the new state model. Signed-off-by: Erik Nilsen <enilsen16@live.com>
PR aaif-goose#9039 added a useOnboardingGate hook whose shouldShowOnboarding depended on both \`readiness.isUsable\` (does the user have a configured provider with models?) AND \`readiness.hasCompletedOnboarding\` (localStorage[goose:onboarding:v1] set?). The completion record only gets written when a user finishes the new ReadyStep — so users who installed goose2 before the gate existed have hasCompletedOnboarding permanently false even though their setup works fine. Result: every change to selectedProvider re-evaluated the gate and snapped them back into OnboardingFlow's default ImportStep, even though they already had a working provider. Collapse the gate to a single reset-flag: shouldShowOnboarding = startupReady && (resetRequested || !isUsable) resetOnboarding() sets the flag; completeOnboarding() clears it. The completion payload (providerId/modelId) — never read outside the hook — is no longer persisted, and completeOnboarding's signature drops to () => void. resetOnboardingCompletion opportunistically removes the legacy ONBOARDING_STORAGE_KEY for users on the upgrade path. Behavior matrix: | user | result | |----------------------------------------|---------------| | new user (no provider) | onboarding | | pre-existing user (provider works) | normal app | | returning user (completed onboarding) | normal app | | user who deleted all providers | onboarding | | user who clicked "Reset onboarding" | onboarding | Drops hasCompletedOnboarding from OnboardingReadiness and the "not_completed" reason it implied. Tests updated for the new state model. Signed-off-by: Erik Nilsen <enilsen16@live.com>
Signed-off-by: tulsi <tulsi@block.xyz>
Category: new-feature
User Impact: New users get a guided first-run setup that imports existing tools when available, connects a usable provider, and lands them on Home ready to chat.
Problem: First-run setup could leave users in Goose without a usable model/provider, and provider auth/import state was scattered across UI-only flows. That made setup confusing for non-technical users and brittle for native OAuth providers like ChatGPT.
Solution: This adds a provider-first onboarding gate backed by typed ACP methods, real import scanning/apply behavior, shared provider credential plumbing, and a concise ready screen that explains the selected default plus imported skills/extensions.
File changes
crates/goose-sdk/src/custom_requests.rs
Adds typed ACP contracts for saving defaults, onboarding import scan/apply, and provider-native authentication.
crates/goose/acp-meta.json
Regenerates ACP metadata so the new onboarding/default/auth custom requests are exposed to SDK generation.
crates/goose/acp-schema.json
Regenerates the ACP JSON schema for the new typed request and response shapes.
crates/goose/src/acp/server.rs
Registers the onboarding server module.
crates/goose/src/acp/server/config.rs
Implements
_goose/defaults/savewith provider/model validation before writing Goose defaults.crates/goose/src/acp/server/custom_dispatch.rs
Dispatches the new defaults, onboarding import, and provider auth custom methods.
crates/goose/src/acp/server/onboarding.rs
Adds backend scan/apply logic for existing Goose config and Claude Desktop MCP tools, including duplicate-safe extension and skill import behavior.
crates/goose/src/acp/server/providers.rs
Adds provider-owned native authentication through ACP and refreshes provider inventory after auth completes.
crates/goose/src/providers/chatgpt_codex.rs
Marks ChatGPT/Codex configured when the OAuth token cache exists so inventory reflects successful browser auth.
crates/goose/tests/acp_custom_provider_methods_test.rs
Adds coverage that native auth rejects providers without an OAuth flow.
ui/goose2/src/app/AppShell.tsx
Gates the app behind onboarding after startup data is loaded and prevents Home session creation while setup is active.
ui/goose2/src/app/hooks/useAppStartup.ts
Returns startup readiness and error state so onboarding waits for initial ACP, provider, persona, session, and distro loading.
ui/goose2/src/features/agents/ui/AvatarDropZone.tsx
Removes setup-surface shadows for the flatter onboarding/settings visual direction.
ui/goose2/src/features/chat/ui/ChatContextPanel.tsx
Removes a toolbar shadow to match the updated surface treatment.
ui/goose2/src/features/onboarding/api/onboarding.ts
Adds a feature API wrapper around typed onboarding/default ACP SDK calls.
ui/goose2/src/features/onboarding/hooks/useOnboardingFlow.ts
Coordinates the three onboarding steps and final completion state without putting workflow logic in the view shell.
ui/goose2/src/features/onboarding/hooks/useOnboardingGate.test.tsx
Covers fresh-state gating, completed usable setup, unusable completed setup, ACP agent setup, and completion persistence.
ui/goose2/src/features/onboarding/hooks/useOnboardingGate.ts
Computes whether onboarding should show based on local completion plus actual usable provider/model or ACP agent inventory.
ui/goose2/src/features/onboarding/hooks/useOnboardingImportStep.ts
Owns import scan/apply state, selected candidates, setup counts, and imported provider defaults.
ui/goose2/src/features/onboarding/hooks/useOnboardingProviderStep.ts
Owns provider/default selection state, promoted provider filtering, credential setup wiring, and model/agent selection.
ui/goose2/src/features/onboarding/hooks/useOnboardingReadyStep.ts
Loads current skill count for the ready summary only when the ready step is shown.
ui/goose2/src/features/onboarding/lib/importCounts.ts
Extracts import count formatting and summing helpers for reuse across onboarding UI.
ui/goose2/src/features/onboarding/lib/providerDefaults.ts
Extracts provider ordering and first-usable-model selection helpers.
ui/goose2/src/features/onboarding/types.ts
Defines onboarding completion, readiness, setup, step, and import-related feature types.
ui/goose2/src/features/onboarding/ui/ImportStep.tsx
Renders the import step with neutral unselected cards and selected-only emphasis.
ui/goose2/src/features/onboarding/ui/OnboardingFlow.tsx
Keeps the full-screen onboarding shell small and delegates workflow and step rendering to hooks/components.
ui/goose2/src/features/onboarding/ui/ProviderStep.tsx
Renders connected defaults, provider setup rows, and the “show more” provider expansion path.
ui/goose2/src/features/onboarding/ui/ReadyStep.tsx
Renders the final ready summary with the selected default, extension count, and skill count.
ui/goose2/src/features/providers/api/credentials.test.ts
Adds coverage for the new ACP provider-auth API wrapper.
ui/goose2/src/features/providers/api/credentials.ts
Adds
authenticateProviderConfigthrough the generated SDK.ui/goose2/src/features/providers/api/modelSetup.ts
Routes native provider auth through ACP instead of the old Tauri invoke path.
ui/goose2/src/features/providers/hooks/useCredentials.test.tsx
Covers applying an ACP native-auth result without doing an extra status refresh.
ui/goose2/src/features/providers/hooks/useCredentials.ts
Accepts provider auth results directly, updates status, and starts inventory sync from the backend-provided refresh plan.
ui/goose2/src/features/settings/ui/AppearanceSettings.tsx
Removes selected-control shadows from appearance settings.
ui/goose2/src/features/settings/ui/GeneralSettings.tsx
Adds a settings action to reset onboarding completion for retesting or rerunning setup.
ui/goose2/src/features/settings/ui/ModelProviderRow.tsx
Passes native auth results into shared credential handling and keeps setup output keys unique across retries.
ui/goose2/src/features/settings/ui/SettingsModal.tsx
Removes active-sidebar shadows to match the flatter settings style.
ui/goose2/src/shared/i18n/constants.ts
Registers the onboarding i18n namespace.
ui/goose2/src/shared/i18n/i18n.ts
Loads onboarding translations into the app i18n resources.
ui/goose2/src/shared/i18n/locales/en/onboarding.json
Adds English onboarding copy for import, provider setup, counts, and ready summary.
ui/goose2/src/shared/i18n/locales/en/settings.json
Adds English settings copy for resetting onboarding.
ui/goose2/src/shared/i18n/locales/es/onboarding.json
Adds Spanish onboarding translations.
ui/goose2/src/shared/i18n/locales/es/settings.json
Adds Spanish settings copy for resetting onboarding.
ui/goose2/src/shared/ui/button.tsx
Removes default/outline/destructive button shadows.
ui/goose2/tests/e2e/fixtures/tauri-mock.ts
Seeds mocked onboarding completion/default model state and handles the new onboarding/default ACP methods.
ui/goose2/tests/e2e/smoke.spec.ts
Runs smoke tests through the Tauri mock fixture so onboarding does not block the Home assertions.
ui/sdk/src/generated/client.gen.ts
Regenerates typed SDK client methods for defaults save, onboarding scan/apply, and provider auth.
ui/sdk/src/generated/index.ts
Regenerates SDK exports and method metadata for the new ACP methods.
ui/sdk/src/generated/types.gen.ts
Regenerates TypeScript DTOs for onboarding imports, defaults save, and provider auth.
ui/sdk/src/generated/zod.gen.ts
Regenerates Zod schemas for the new ACP request and response types.
Reproduction Steps
goose:onboarding:v1in local storage or use Settings → General → Reset onboarding.Screenshots/Demos
Screen.Recording.2026-05-05.at.5.42.43.PM.mov