Skip to content

fix(renderer): don't flash onboarding before projects load (#2514)#2782

Open
fireddd wants to merge 1 commit into
AgentWrapper:mainfrom
fireddd:fix/onboarding-flash-2514
Open

fix(renderer): don't flash onboarding before projects load (#2514)#2782
fireddd wants to merge 1 commit into
AgentWrapper:mainfrom
fireddd:fix/onboarding-flash-2514

Conversation

@fireddd

@fireddd fireddd commented Jul 17, 2026

Copy link
Copy Markdown

Fixes #2514.

Problem

On cold start the Electron app briefly showed the first-run onboarding page, then replaced it with the real project list once daemon discovery settled.

Root cause: fetchWorkspaces() returned [] whenever the daemon's API base URL wasn't trusted yet (its port is discovered at runtime). React Query treated that [] as a successful empty result, so SessionsBoard's isSuccess && all.length === 0 rendered BoardWelcome. When the daemon later became ready, _shell invalidated the query and the real /api/v1/projects list replaced it — i.e. the onboarding page was a transient discovery placeholder, not an authoritative no-projects state.

Fix

Represent "API base not trusted yet" as a pending query instead of a successful empty list:

  • fetchWorkspaces throws ApiBaseUrlNotTrustedError instead of returning [] when the base URL is untrusted, so it can never resolve as an empty success.
  • useWorkspaceQuery gates the query on a trusted base URL, tracked reactively via subscribeApiBaseUrl + useSyncExternalStore, so it flips the moment the daemon reports ready. While untrusted the query stays pending and the board shows its loading shell.
  • The _shell loader skips the ensureQueryData prefetch until the base URL is trusted, so the loader can't cache a bogus empty result either.

No behavior change once the daemon is ready: a genuine empty project list still shows onboarding (real first run).

Before / After

What the evidence shows: both screenshots were taken under the identical condition — the renderer running on the real fetch path with no daemon, so the API base URL is never trusted and the state stays stable (no timing race to catch). Only the fix differs between the two.

  • Before (main): the untrusted [] resolves as a successful empty result → the first-run onboarding page is rendered (this is what would flash over real projects once they load).
  • After (this branch): the query stays pending while the base URL is untrusted → the board loading shell is shown instead of onboarding.
Before (main) — onboarding flashes After (this branch) — loading shell
before after

Tests

  • Rewrote the untrusted-path unit test to the new pending contract and added a cold-start regression test (untrusted → trusted → refetch) in useWorkspaceQuery.test.tsx.
  • Added the new subscribeApiBaseUrl export to the mocks that drive the real hook.
  • Full renderer suite green: 623 passed; tsc --noEmit clean.

Reproduction was by code inspection + a deterministic Playwright screenshot of the two states (renderer on the real fetch path with no daemon); not a live capture of the timing race.

…per#2514)

useWorkspaceQuery returned [] whenever the daemon's API base URL wasn't
trusted yet. React Query treated that as a *successful* empty result, so
SessionsBoard's `isSuccess && all.length === 0` rendered the first-run
onboarding page — which was then replaced by the real project list once
daemon discovery completed and the query was invalidated. On cold start
with existing projects, that's a visible onboarding flash.

Represent "API base not trusted yet" as a pending query instead:

- fetchWorkspaces throws ApiBaseUrlNotTrustedError rather than returning
  [] when the base URL is untrusted, so it can never resolve as an empty
  success.
- useWorkspaceQuery gates the query on a trusted base URL, tracked
  reactively via subscribeApiBaseUrl/useSyncExternalStore so it flips the
  moment the daemon reports ready. While untrusted the query stays
  pending and the board shows its loading shell, not onboarding.
- The _shell loader skips the prefetch until the base URL is trusted, so
  ensureQueryData can't cache a bogus empty result either.

Tests: rewrite the untrusted-path unit test to the pending contract, add
a cold-start regression test (untrusted -> trusted refetch), and add the
new api-client export to the mocks that drive the real hook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@somewherelostt

Copy link
Copy Markdown
Collaborator

Thanks for contributing to Agent Orchestrator.

This PR is being picked up by the current external contributor on-call pair:

If someone is already working on this, please continue as usual.
The on-call pair is added for visibility, tracking, and support, not to take over the work.
If you need help with review, direction, reproduction, or next steps, please tag @neversettle17-101 and @somewherelostt here.

For faster context or live questions, you can also join the AO Discord.

Join the session here:
https://discord.gg/H6ZDcUXmq

Come by if you want to see what is being built, ask questions, or just hang around with the community.

@neversettle17-101

neversettle17-101 commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

@illegalcall can review from your end as well?

@illegalcall

Copy link
Copy Markdown
Collaborator

Hey @fireddd we are thinking of doing it this way.
image
Instead of this part, lets show a

Tips: Did you know you can you CMD + / to see all shortcut commands

We can have many tips, which keep rotating 2-3 secs till it loads/

@illegalcall illegalcall left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

.

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.

Electron briefly shows onboarding before existing projects load

4 participants