dify - ✅(Solved) Fix Refactor: kill useGlobalPublicStore, Splash, useIsLogin — unify on useSuspenseQuery + Next.js loading/error conventions [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langgenius/dify#35414Fetched 2026-04-20 12:16:04
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
1
Author
Participants
Assignees
Timeline (top)
assigned ×1closed ×1cross-referenced ×1

Error Message

  • (commonLayout)/error.tsx for unrecoverable errors (renders Loading on 401 since service/base.ts already triggered jumpTo)
  • Add 4 framework files (loading.tsx / error.tsx × 2 routes), 1 soft-fallback helper, 1 test util

Fix Action

Fixed

PR fix notes

PR #35394: refactor(web): unify app-shell bootstrap on TanStack Query + Next.js route conventions

Description (problem / solution / changelog)

Summary

Closes #35414.

Migrate the systemFeatures / userProfile bootstrap layer from a custom <GlobalPublicStoreProvider> + zustand + <Splash> + useIsLogin stack to a uniform useSuspenseQuery + Next.js loading.tsx / error.tsx architecture. Transport-layer logic (401 / refresh-token / SSE in service/base.ts) is unchanged.

Replaces 5 custom abstractions with 0:

  • useGlobalPublicStore (zustand) → useSuspenseQuery(systemFeaturesQueryOptions())
  • useUserProfile + useIsLogin → shared userProfileQueryOptions() (consumed via useSuspenseQuery inside (commonLayout) and via useQuery + throwOnError: !isLegacyBase401 on signin/oauth pages)
  • <GlobalPublicStoreProvider> blocking render → app/loading.tsx
  • <Splash>(commonLayout)/loading.tsx
  • useSystemFeaturesQuery / useIsSystemFeaturesPending thin wrappers → direct query options

Soft-fallback helper web/service/system-features.ts keeps the dashboard usable when /system-features 5xxs (matches the prior zustand "silent degrade" behavior, but the failure is now logged via console.error for observability).

89 test files migrated to seedSystemFeatures(queryClient, …) + QueryClientProvider via the new web/__tests__/utils/mock-system-features.tsx util.

Screenshots

BeforeAfter
......

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran `make lint && make type-check` (backend) and `cd web && pnpm exec vp staged` (frontend) to appease the lint gods

Changed files

  • eslint-suppressions.json (modified, +1/-6)
  • web/__tests__/app/app-access-control-flow.test.tsx (modified, +7/-29)
  • web/__tests__/app/app-publisher-flow.test.tsx (modified, +7/-29)
  • web/__tests__/apps/app-card-operations-flow.test.tsx (modified, +6/-11)
  • web/__tests__/apps/app-list-browsing-flow.test.tsx (modified, +20/-15)
  • web/__tests__/apps/create-app-flow.test.tsx (modified, +14/-10)
  • web/__tests__/base/chat-flow.test.tsx (modified, +2/-1)
  • web/__tests__/embedded-user-id-store.test.tsx (modified, +4/-36)
  • web/__tests__/explore/explore-app-list-flow.test.tsx (modified, +2/-1)
  • web/__tests__/header/account-dropdown-flow.test.tsx (modified, +8/-26)
  • web/__tests__/plugins/plugin-marketplace-to-install.test.tsx (modified, +1/-10)
  • web/__tests__/plugins/plugin-page-shell-flow.test.tsx (modified, +27/-19)
  • web/__tests__/share/text-generation-index-flow.test.tsx (modified, +5/-9)
  • web/__tests__/tools/provider-list-shell-flow.test.tsx (modified, +14/-11)
  • web/__tests__/tools/tool-browsing-and-filtering.test.tsx (modified, +4/-10)
  • web/__tests__/utils/mock-system-features.tsx (added, +127/-0)
  • web/app/(commonLayout)/error.tsx (added, +33/-0)
  • web/app/(commonLayout)/layout.tsx (modified, +0/-2)
  • web/app/(commonLayout)/loading.tsx (added, +9/-0)
  • web/app/(shareLayout)/webapp-reset-password/layout.tsx (modified, +3/-2)
  • web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx (modified, +3/-2)
  • web/app/(shareLayout)/webapp-signin/layout.tsx (modified, +3/-2)
  • web/app/(shareLayout)/webapp-signin/normalForm.tsx (modified, +3/-2)
  • web/app/(shareLayout)/webapp-signin/page.tsx (modified, +3/-2)
  • web/app/account/(commonLayout)/account-page/index.tsx (modified, +7/-6)
  • web/app/account/(commonLayout)/avatar.tsx (modified, +5/-3)
  • web/app/account/(commonLayout)/header.tsx (modified, +3/-2)
  • web/app/account/oauth/authorize/layout.tsx (modified, +13/-6)
  • web/app/account/oauth/authorize/page.tsx (modified, +11/-5)
  • web/app/activate/page.tsx (modified, +3/-2)
  • web/app/components/__tests__/splash.spec.tsx (removed, +0/-59)
  • web/app/components/app-initializer.tsx (modified, +2/-1)
  • web/app/components/app/app-access-control/__tests__/access-control.spec.tsx (modified, +2/-1)
  • web/app/components/app/app-access-control/__tests__/index.spec.tsx (modified, +13/-14)
  • web/app/components/app/app-access-control/index.tsx (modified, +3/-2)
  • web/app/components/app/app-publisher/__tests__/index.spec.tsx (modified, +6/-11)
  • web/app/components/app/app-publisher/index.tsx (modified, +3/-2)
  • web/app/components/app/create-app-dialog/app-card/__tests__/index.spec.tsx (modified, +2/-1)
  • web/app/components/app/create-app-dialog/app-card/index.tsx (modified, +3/-2)
  • web/app/components/app/overview/__tests__/app-card.spec.tsx (modified, +7/-12)
  • web/app/components/app/overview/app-card.tsx (modified, +3/-2)
  • web/app/components/apps/__tests__/app-card.spec.tsx (modified, +12/-11)
  • web/app/components/apps/__tests__/list.spec.tsx (modified, +8/-11)
  • web/app/components/apps/app-card.tsx (modified, +4/-3)
  • web/app/components/apps/list.tsx (modified, +3/-2)
  • web/app/components/base/chat/chat-with-history/__tests__/header-in-mobile.spec.tsx (modified, +2/-1)
  • web/app/components/base/chat/chat-with-history/__tests__/index.spec.tsx (modified, +2/-1)
  • web/app/components/base/chat/chat-with-history/sidebar/__tests__/index.spec.tsx (modified, +9/-49)
  • web/app/components/base/chat/chat-with-history/sidebar/index.tsx (modified, +3/-2)
  • web/app/components/base/chat/embedded-chatbot/__tests__/index.spec.tsx (modified, +12/-30)
  • web/app/components/base/chat/embedded-chatbot/header/__tests__/index.spec.tsx (modified, +12/-90)
  • web/app/components/base/chat/embedded-chatbot/header/index.tsx (modified, +3/-2)
  • web/app/components/base/chat/embedded-chatbot/index.tsx (modified, +3/-2)
  • web/app/components/custom/custom-page/__tests__/index.spec.tsx (modified, +12/-21)
  • web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx (modified, +16/-27)
  • web/app/components/custom/custom-web-app-brand/hooks/use-web-app-brand.ts (modified, +3/-2)
  • web/app/components/datasets/create-from-pipeline/list/__tests__/built-in-pipeline-list.spec.tsx (modified, +7/-8)
  • web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx (modified, +6/-2)
  • web/app/components/datasets/list/__tests__/index.spec.tsx (modified, +11/-21)
  • web/app/components/datasets/list/index.tsx (modified, +4/-3)
  • web/app/components/devtools/react-scan/loader.tsx (modified, +1/-1)
  • web/app/components/explore/app-card/__tests__/index.spec.tsx (modified, +2/-1)
  • web/app/components/explore/app-card/index.tsx (modified, +3/-2)
  • web/app/components/explore/app-list/__tests__/index.spec.tsx (modified, +22/-11)
  • web/app/components/explore/app-list/index.tsx (modified, +3/-2)
  • web/app/components/explore/try-app/__tests__/index.spec.tsx (modified, +2/-1)
  • web/app/components/explore/try-app/index.tsx (modified, +3/-2)
  • web/app/components/header/__tests__/index.spec.tsx (modified, +25/-28)
  • web/app/components/header/account-about/__tests__/index.spec.tsx (modified, +19/-45)
  • web/app/components/header/account-about/index.tsx (modified, +4/-3)
  • web/app/components/header/account-dropdown/__tests__/index.spec.tsx (modified, +31/-40)
  • web/app/components/header/account-dropdown/index.tsx (modified, +3/-2)
  • web/app/components/header/account-setting/__tests__/index.spec.tsx (modified, +10/-37)
  • web/app/components/header/account-setting/data-source-page-new/__tests__/index.spec.tsx (modified, +21/-50)
  • web/app/components/header/account-setting/data-source-page-new/index.tsx (modified, +6/-2)
  • web/app/components/header/account-setting/members-page/__tests__/index.spec.tsx (modified, +24/-25)
  • web/app/components/header/account-setting/members-page/__tests__/invite-button.spec.tsx (modified, +16/-17)
  • web/app/components/header/account-setting/members-page/index.tsx (modified, +3/-2)
  • web/app/components/header/account-setting/members-page/invite-button.tsx (modified, +3/-2)
  • web/app/components/header/account-setting/members-page/operation/__tests__/transfer-ownership.spec.tsx (modified, +19/-17)
  • web/app/components/header/account-setting/members-page/operation/transfer-ownership.tsx (modified, +3/-2)
  • web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx (modified, +37/-25)
  • web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx (modified, +46/-36)
  • web/app/components/header/account-setting/model-provider-page/index.tsx (modified, +4/-4)
  • web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup.spec.tsx (modified, +33/-29)
  • web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx (modified, +4/-3)
  • web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/credential-panel.spec.tsx (modified, +31/-36)
  • web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/quota-panel.spec.tsx (modified, +15/-15)
  • web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/use-credential-panel-state.spec.ts (modified, +24/-23)
  • web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx (modified, +4/-3)
  • web/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state.ts (modified, +4/-3)
  • web/app/components/header/index.tsx (modified, +3/-2)
  • web/app/components/header/license-env/__tests__/index.spec.tsx (modified, +8/-23)
  • web/app/components/header/license-env/index.tsx (modified, +3/-2)
  • web/app/components/plugins/install-plugin/hooks/__tests__/use-install-plugin-limit.spec.ts (modified, +2/-14)
  • web/app/components/plugins/install-plugin/hooks/use-install-plugin-limit.tsx (modified, +3/-2)
  • web/app/components/plugins/install-plugin/install-bundle/__tests__/index.spec.tsx (modified, +2/-6)
  • web/app/components/plugins/install-plugin/install-bundle/steps/__tests__/install-multi.spec.tsx (modified, +2/-6)
  • web/app/components/plugins/install-plugin/install-bundle/steps/hooks/__tests__/use-install-multi-state.spec.ts (modified, +2/-5)
  • web/app/components/plugins/install-plugin/install-bundle/steps/hooks/use-install-multi-state.ts (modified, +3/-2)
RAW_BUFFERClick to expand / collapse

Problem

The dashboard bootstrap layer relies on five hand-rolled abstractions for what is essentially "load global config + show loading + handle failure":

  • useGlobalPublicStore — zustand store mirroring /system-features data, kept in sync by a manual setSystemFeatures(...) call inside the queryFn (dual source of truth)
  • <GlobalPublicStoreProvider> — root-level provider that blocks children render until the first fetch completes
  • <Splash> — analogous fixed overlay for useUserProfile pending state
  • useIsLogin — silent /account/profile probe that try/catches all errors into { logged_in: false }
  • useSystemFeaturesQuery / useIsSystemFeaturesPending — thin wrappers around useQuery

This costs:

  • Dual source of truth: zustand and react-query cache get out of sync if either is mutated externally.
  • Provider waterfall: provider waits → children mount → children's own queries fire (serial).
  • Silent degrade on failure: callsites read defaultSystemFeatures without any signal that something failed, making outages hard to diagnose.
  • Test boilerplate: ~70 spec files each mock zustand independently.
  • Not extensible: any new "global config" needs another provider + zustand store.

Architecture

Replace all five with React 19 + Next.js 16 + TanStack Query primitives:

  • useSuspenseQuery(systemFeaturesQueryOptions()) for systemFeatures (default for ~75 callsites)
  • useSuspenseQuery(userProfileQueryOptions()) for userProfile inside (commonLayout)
  • useQuery({ ...userProfileQueryOptions(), throwOnError: err => !isLegacyBase401(err) }) for signin/oauth probes — 401 stays as state, 5xx bubbles
  • app/loading.tsx + (commonLayout)/loading.tsx for Suspense fallback
  • (commonLayout)/error.tsx for unrecoverable errors (renders Loading on 401 since service/base.ts already triggered jumpTo)
  • web/service/system-features.ts soft-fallback helper: queryFn try/catches and returns defaultSystemFeatures, so a flaky /system-features cannot red-screen the dashboard

Decision rule for callers: default to useSuspenseQuery. Only fall back to useQuery for one of three documented reasons — service hook (avoid contagious suspension), need to read isPending, or 401-as-state (signin probe).

Transport-layer behavior (401 / refresh-token / SSE in service/base.ts) is unchanged.

Scope

  • Migrate ~80 callsites and ~90 test files
  • Delete web/context/global-public-context.tsx, web/app/components/splash.tsx, useIsLogin
  • Add 4 framework files (loading.tsx / error.tsx × 2 routes), 1 soft-fallback helper, 1 test util

Implementation: #35394

extent analysis

TL;DR

Replace the existing hand-rolled abstractions with React 19 + Next.js 16 + TanStack Query primitives to simplify the dashboard bootstrap layer and improve maintainability.

Guidance

  • Identify the ~80 callsites that need to be migrated to use useSuspenseQuery or useQuery with the new architecture.
  • Update the web/service/system-features.ts file to include a soft-fallback helper that returns defaultSystemFeatures in case of a failed query.
  • Remove the dual source of truth by deleting the zustand store and related components, such as useGlobalPublicStore and <GlobalPublicStoreProvider>.
  • Implement the new app/loading.tsx and (commonLayout)/loading.tsx components to handle Suspense fallbacks, and (commonLayout)/error.tsx for unrecoverable errors.

Example

// Before
import { useGlobalPublicStore } from './useGlobalPublicStore';

// After
import { useSuspenseQuery } from '@tanstack/react-query';
import { systemFeaturesQueryOptions } from './systemFeaturesQueryOptions';

const systemFeatures = useSuspenseQuery(systemFeaturesQueryOptions());

Notes

The implementation of this change requires careful consideration of the existing codebase and may involve updating numerous files and components. It's essential to thoroughly test the changes to ensure that the new architecture works as expected.

Recommendation

Apply the workaround by replacing the existing abstractions with the new architecture, as it simplifies the codebase and improves maintainability. This approach also reduces the dual source of truth issue and provides a more scalable solution for handling global config and loading states.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

dify - ✅(Solved) Fix Refactor: kill useGlobalPublicStore, Splash, useIsLogin — unify on useSuspenseQuery + Next.js loading/error conventions [1 pull requests, 1 participants]