fix(renderer): don't flash onboarding before projects load (#2514)#2782
fix(renderer): don't flash onboarding before projects load (#2514)#2782fireddd wants to merge 1 commit into
Conversation
…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>
|
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. For faster context or live questions, you can also join the AO Discord. Join the session here: Come by if you want to see what is being built, ask questions, or just hang around with the community. |
|
@illegalcall can review from your end as well? |
|
Hey @fireddd we are thinking of doing it this way. 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/ |

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, soSessionsBoard'sisSuccess && all.length === 0renderedBoardWelcome. When the daemon later became ready,_shellinvalidated the query and the real/api/v1/projectslist 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:
fetchWorkspacesthrowsApiBaseUrlNotTrustedErrorinstead of returning[]when the base URL is untrusted, so it can never resolve as an empty success.useWorkspaceQuerygates the query on a trusted base URL, tracked reactively viasubscribeApiBaseUrl+useSyncExternalStore, so it flips the moment the daemon reports ready. While untrusted the query stays pending and the board shows its loading shell._shellloader skips theensureQueryDataprefetch 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.
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).main) — onboarding flashesTests
useWorkspaceQuery.test.tsx.subscribeApiBaseUrlexport to the mocks that drive the real hook.tsc --noEmitclean.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.