Skip to content

Fix inconsistent UI state and concurrent setup flow in Toolbox wizard#302

Merged
fioan89 merged 11 commits into
mainfrom
fix-page-rendering-state-system
May 14, 2026
Merged

Fix inconsistent UI state and concurrent setup flow in Toolbox wizard#302
fioan89 merged 11 commits into
mainfrom
fix-page-rendering-state-system

Conversation

@fioan89
Copy link
Copy Markdown
Collaborator

@fioan89 fioan89 commented May 5, 2026

Toolbox currently relies on getOverrideUiPage to decide whether to render a custom page or fall back to environments, but this logic is stateless and leads to inconsistent behavior when the window is closed and reopened.

If the user exits Toolbox mid-setup (e.g., during CLI download in ConnectStep) and reopens it, a new wizard instance is created even though the previous setup process is still running in the background. This results in multiple overlapping setup flows, race conditions, and UI inconsistencies such as:

  • Returning to the initial URL step while setup is still in progress
  • Conflicting login attempts when restarting the wizard
  • Background processes (CLI download, HTTP setup) continuing without visible UI
  • Security dialogs triggered due to shared/overwritten resources
  • Undefined behavior when switching deployments mid-setup

The root cause is lack of persistent, centralized state management for the setup flow. Current reliance on static state (e.g., CoderSetupWizardState) and recomputation via getOverrideUiPage does not properly track in-progress operations or restore the correct UI state.

fioan89 added 5 commits May 6, 2026 00:33
Toolbox currently relies on getOverrideUiPage to decide whether to render a custom page or fall back to environments, but this logic is stateless and leads to inconsistent behavior when the window is closed and reopened.

If the user exits Toolbox mid-setup (e.g., during CLI download in ConnectStep) and reopens it, a new wizard instance is created even though the previous setup process is still running in the background. This results in multiple overlapping setup flows, race conditions, and UI inconsistencies such as:

- Returning to the initial URL step while setup is still in progress
- Conflicting login attempts when restarting the wizard
- Background processes (CLI download, HTTP setup) continuing without visible UI
- Security dialogs triggered due to shared/overwritten resources
- Undefined behavior when switching deployments mid-setup

The root cause is lack of persistent, centralized state management for the setup flow. Current reliance on static
state (e.g., CoderSetupWizardState) and recomputation via getOverrideUiPage does not properly track in-progress
operations or restore the correct UI state.

- resolves https://linear.app/codercom/issue/DEVEX-223/
URI handler now schedules pages to be displayed via pager router
and at the end trigger the window to show up which instead it
will call getOverrideUiPage.
in order to abstract even further some of the steps
necessary for creating a step and navigating to it.
ConnectStep should not care if it was it is the first screen
in the login process (auto login) or not. Instead, the wizard
page should orchestrate the behavior of the back button depending
on whether it is auto login or not
@fioan89 fioan89 marked this pull request as ready for review May 12, 2026 21:18
Comment thread src/main/kotlin/com/coder/toolbox/cli/downloader/CoderDownloadService.kt Outdated
Copy link
Copy Markdown

@jeremyruppel jeremyruppel left a comment

Choose a reason for hiding this comment

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

one small comment about the delay but the rest of this looks great! 👍

fioan89 added 4 commits May 13, 2026 22:08
with a different URL from the one that is already opened.
but the user disabled the "Prefer OAuth2 option..."
Route OAuth callbacks through a pending OAuth connection instead of reading
wizard model state directly, then create and schedule a fresh connect-step
wizard after token exchange.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces per-provider, persistent routing/state for the Coder setup wizard to prevent overlapping setup flows and inconsistent UI when the Toolbox window is closed/reopened or when deep links/OAuth callbacks arrive mid-setup.

Changes:

  • Added a PageRouter to persist and reuse a single wizard instance across visibility cycles, with explicit disposal when replaced.
  • Replaced global singleton wizard state (CoderSetupWizardState / CoderSetupWizardContext) with per-wizard WizardModel.
  • Updated deep-link and OAuth callback handling to drive routing via the router and rebuild the wizard in the correct step with the correct credentials.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt Uses PageRouter to persist/replace wizard pages and to safely handle deep links + OAuth callbacks without spawning overlapping flows.
src/main/kotlin/com/coder/toolbox/views/CoderSetupWizardPage.kt Converts wizard to use WizardModel, adds disposal, and provides factory constructors for URL vs connect step startup.
src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt Migrates to WizardModel and adds dispose(), updates navigation behavior.
src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt Migrates URL/OAuth session state to WizardModel.
src/main/kotlin/com/coder/toolbox/views/TokenStep.kt Migrates token handling to WizardModel.
src/main/kotlin/com/coder/toolbox/views/state/PageRouter.kt New centralized router to preserve the active wizard page across Toolbox lifecycle changes.
src/main/kotlin/com/coder/toolbox/views/state/WizardModel.kt New per-wizard model for step + credential state, plus supporting types.
src/main/kotlin/com/coder/toolbox/views/state/OAuthSessionContext.kt Extracted OAuth session types and added StoredOAuthSession.toSessionContext().
src/main/kotlin/com/coder/toolbox/views/state/CoderSetupWizardState.kt Removed singleton wizard step state.
src/main/kotlin/com/coder/toolbox/views/state/CoderSetupWizardContext.kt Removed singleton wizard context (URL/token/OAuth session).
src/test/kotlin/com/coder/toolbox/CoderRemoteProviderTest.kt Adds coverage for router overrides and credential precedence in auto-setup.
src/test/kotlin/com/coder/toolbox/views/state/PageRouterTest.kt Adds unit tests for PageRouter.pendingOAuthConnection behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/kotlin/com/coder/toolbox/views/TokenStep.kt Outdated
Comment thread src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt Outdated
fioan89 added 2 commits May 14, 2026 19:03
The error never reached the notifier because of
the lambda return statement.
connect() catches Exception and always calls navigateBack().
This includes CancellationException triggered by onBack()
and dispose(), which means navigation can happen twice (once
in onBack()'s finally, and again in the catch), and dispose()
no longer actually cancels “without navigating” as the docstring
states. Handle CancellationException separately (and avoid calling
navigateBack() for dispose / already-handled back navigation), or
rethrow cancellation to stop the coroutine without side effects.
@fioan89 fioan89 merged commit c8291f7 into main May 14, 2026
6 checks passed
@fioan89 fioan89 deleted the fix-page-rendering-state-system branch May 14, 2026 19:16
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.

3 participants