feat(FN-1277): hydrate dashboard theme settings from backend

- Hydrate useTheme from global settings while seeding initial state from localStorage cache to prevent flash
- Add write-through theme persistence so setter updates local cache immediately and asynchronously saves via updateGlobalSettings
- Clarify Settings modal save behavior for intentional idempotent global theme writes
- Expand useTheme and App tests to cover backend hydration, fallback handling, and new global settings fetch expectations
This commit is contained in:
gsxdsm
2026-04-08 11:58:18 -07:00
parent 9ac5b19646
commit 9219323bc0
4 changed files with 293 additions and 75 deletions

View File

@@ -500,7 +500,10 @@ export function SettingsModal({
}
}
// Save both scopes in parallel if they have changes
// Save both scopes in parallel if they have changes.
// Note: themeMode/colorTheme may also be write-through via useTheme callbacks
// in the Appearance section; duplicate global writes are intentional/idempotent,
// while this save path persists the full settings form in one action.
await Promise.all([
Object.keys(globalPatch).length > 0 ? updateGlobalSettings(globalPatch) : Promise.resolve(),
Object.keys(projectPatch).length > 0 ? updateSettings(projectPatch, projectId) : Promise.resolve(),

View File

@@ -538,7 +538,7 @@ describe("App auto-open Settings on unauthenticated", () => {
});
it("auto-opens Settings to Authentication tab when all providers are unauthenticated but onboarding IS complete", async () => {
(fetchGlobalSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
(fetchGlobalSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
modelOnboardingComplete: true,
});
@@ -567,7 +567,7 @@ describe("App auto-open Settings on unauthenticated", () => {
{ id: "github", name: "GitHub", authenticated: false },
],
});
(fetchGlobalSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
(fetchGlobalSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
defaultProvider: "anthropic",
defaultModelId: "claude-sonnet-4-5",
});
@@ -591,7 +591,7 @@ describe("App auto-open Settings on unauthenticated", () => {
],
});
// No defaultProvider or defaultModelId → setup incomplete
(fetchGlobalSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
(fetchGlobalSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
modelOnboardingComplete: false,
});