Skip to content

fix(ui): smooth transitions and align settings layout #3596

fix(ui): smooth transitions and align settings layout

fix(ui): smooth transitions and align settings layout #3596

Triggered via pull request July 24, 2026 07:42
Status Failure
Total duration 4m 26s
Artifacts

ci.yml

on: pull_request
CLI Tests (ubuntu-latest)
4m 22s
CLI Tests (ubuntu-latest)
Matrix: rust-build-check
Fit to window
Zoom out
Zoom in

Annotations

2 errors
Frontend Build
Process completed with exit code 1.
src/app/startup/startupPerformanceContract.test.ts > startup performance contract > keeps settings config panels lazy by active tab: src/web-ui/src/app/startup/startupPerformanceContract.test.ts#L397
AssertionError: expected '/**\n * SettingsScene — content-only …' to contain 'lazy(() => import(\'../../../infrastr…' - Expected + Received - lazy(() => import('../../../infrastructure/config/components/AIModelConfig')) + /** + * SettingsScene — content-only renderer for the Settings scene. + * + * The left-side navigation lives in SettingsNav (rendered by NavPanel via + * nav-registry). This component only renders the active config content panel + * driven by settingsStore.activeTab. + */ + + import React, { + Suspense, + useEffect, + useLayoutEffect, + useRef, + useState, + } from 'react'; + import { useSettingsStore } from './settingsStore'; + import type { ConfigTab } from './settingsConfig'; + import { + AcpAgentsConfig, + AIModelConfig, + AppearanceConfig, + ArchivedSessionsConfig, + BasicsConfig, + EditorConfig, + ExternalSourcesConfig, + KeyboardShortcutsTab, + McpToolsConfig, + MemoriesConfig, + QuickActionsConfig, + ReviewConfig, + SessionPermissionsConfig, + SessionPersonalizationConfig, + } from './settingsContentRegistry'; + import './SettingsScene.scss'; + + // Keep in sync with settings-content-exit in SettingsScene.scss. + const SETTINGS_CONTENT_EXIT_DURATION_MS = 180; + + function SettingsSceneLoading() { + return ( + <div className="bitfun-settings-scene__loading" aria-busy="true" aria-hidden="true"> + <div className="bitfun-settings-scene__loading-line bitfun-settings-scene__loading-line--title" /> + <div className="bitfun-settings-scene__loading-line" /> + <div className="bitfun-settings-scene__loading-line" /> + <div className="bitfun-settings-scene__loading-block" /> + </div> + ); + } + + function resolveSettingsContent(tab: ConfigTab): React.ComponentType | null { + switch (tab) { + case 'basics': return BasicsConfig; + case 'appearance': return AppearanceConfig; + case 'models': return AIModelConfig; + case 'archived-sessions': return ArchivedSessionsConfig; + case 'session-personalization': return SessionPersonalizationConfig; + case 'session-permissions': return SessionPermissionsConfig; + case 'quick-actions': return QuickActionsConfig; + case 'review': return ReviewConfig; + case 'memories': return MemoriesConfig; + case 'mcp-tools': return McpToolsConfig; + case 'external-sources': return ExternalSourcesConfig; + case 'acp-agents': return AcpAgentsConfig; + case 'editor': return EditorConfig; + case 'keyboard': return KeyboardShortcutsTab; + default: return null; + } + } + + const SettingsScene: React.FC = () => { + const activeTab = useSettingsStore(s => s.activeTab); + const setActiveTab = useSettingsStore(s => s.setActiveTab); + + const resolvedTab: ConfigTab = + (activeTab as string) === 'session-config' ? 'session-personalization' : activeTab; + const [outgoingTab, setOutgoingTab] = useState<ConfigTab | null>(null); + const previousTabRef = useRef<ConfigTab>(resolvedTab); + + useEffect(() => { + /** Legacy merged session settings tab removed in favor of two panels. */ + if ((activeTab as string) === 'session-config') { + setActiveTab('session-personalization'); + } + }, [activeTab, setActiveTab]); + + // Derive the previous tab during render so React keeps its keyed subtree + // mounted in the same commit that introduces the incoming page. + const renderedOutgoingTab = previousTabRef.current !== resolvedTab + ? previousTabRef.current + : outgoingTab; + + useLayoutEffect(() => { + const previousTab = previousTabRef.current; + previousTabRef.current = resolvedTab; + if (previousTab === resolvedTab) return; + + setOutgoingTab(previousTab); + const exitTimer = window.setTimeout(() => { + setOutgoingTab(current => current === previousTab ? null : current); + }, SETTINGS_CONTEN