LCOV - code coverage report
Current view: top level - dashql-app/src/config - app_settings_sync.tsx Coverage Total Hit
Test: DashQL 6e74948 Lines: 100.0 % 58 58
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : import * as React from 'react';
       2              : 
       3              : import { useAppConfig, useAppReconfigure } from './app_config.js';
       4              : import { useStorageReader } from '../storage/storage_provider.js';
       5              : import { useLogger } from '../platform/logger/logger_provider.js';
       6              : import { useAppFlavorSelect } from './flavor_provider.js';
       7              : import { applyColorSchemeToDocument } from './color_scheme.js';
       8              : 
       9            1 : const LOG_CTX = 'app_settings_sync';
      10              : 
      11              : type Props = {
      12              :     children: React.ReactElement;
      13              : };
      14              : 
      15            1 : const APP_SETTINGS_READY_CTX = React.createContext(false);
      16              : 
      17            1 : export const useAppSettingsReady = (): boolean => React.useContext(APP_SETTINGS_READY_CTX);
      18              : 
      19              : /// Hydrates AppConfig.settings from the persisted manifest once StorageProvider
      20              : /// is ready, and persists subsequent changes back to the manifest.
      21              : /// Selects the flavor of the settings, a flavor requested by the url still wins.
      22            1 : export const AppSettingsSync: React.FC<Props> = (props: Props) => {
      23            1 :     const config = useAppConfig();
      24            1 :     const reconfigure = useAppReconfigure();
      25            1 :     const storageReader = useStorageReader();
      26            1 :     const logger = useLogger();
      27            1 :     const selectFlavor = useAppFlavorSelect();
      28              : 
      29            1 :     const [hydrated, setHydrated] = React.useState(false);
      30            1 :     const hydrating = React.useRef(false);
      31              : 
      32            1 :     React.useEffect(() => {
      33            1 :         if (hydrating.current) return;
      34            1 :         if (config == null) return;
      35            1 :         hydrating.current = true;
      36            1 :         storageReader.backend.loadAppSettings().then(stored => {
      37            1 :             if (stored != null) {
      38            1 :                 logger.info("Hydrated app settings from manifest", {}, LOG_CTX);
      39              :                 // Drop retired settings, the AI provider settings may hold credentials.
      40              :                 // The dashboards and the dataflow were experimental views and are always shown now.
      41            1 :                 const {
      42            1 :                     lastOpenedSessionId: _lastOpenedSessionId,
      43            1 :                     aiProvider: _aiProvider,
      44            1 :                     experimental_dashboards: _experimentalDashboards,
      45            1 :                     experimental_dataflow: _experimentalDataflow,
      46            1 :                     ...settings
      47            1 :                 } = stored as typeof stored & {
      48              :                     lastOpenedSessionId?: string;
      49              :                     aiProvider?: unknown;
      50              :                     experimental_dashboards?: boolean;
      51              :                     experimental_dataflow?: boolean;
      52              :                 };
      53            1 :                 reconfigure(c => c == null ? null : {
      54            1 :                     ...c,
      55            1 :                     settings: { ...(c.settings ?? {}), ...settings },
      56            1 :                 });
      57              :                 // Select the flavor in the same render as the hydration, the app setup loads the fonts of the flavor
      58            1 :                 selectFlavor(settings.flavor ?? config.settings?.flavor);
      59            1 :             }
      60            1 :         }).catch(e => {
      61            1 :             logger.warn("Failed to load app settings from manifest", { error: String(e) }, LOG_CTX);
      62            1 :         }).finally(() => {
      63            1 :             setHydrated(true);
      64            1 :         });
      65            1 :     }, [config, storageReader, reconfigure, selectFlavor, logger]);
      66              : 
      67              :     // Select the flavor when the settings change it
      68            1 :     const flavorId = config?.settings?.flavor;
      69            1 :     React.useLayoutEffect(() => {
      70            1 :         selectFlavor(flavorId);
      71            1 :     }, [flavorId, selectFlavor]);
      72              : 
      73              :     // Apply the color scheme of the settings, the document follows the OS until they are hydrated
      74            1 :     const colorScheme = config?.settings?.colorScheme ?? null;
      75            1 :     React.useLayoutEffect(() => {
      76            1 :         applyColorSchemeToDocument(colorScheme);
      77            1 :     }, [colorScheme]);
      78              : 
      79            1 :     React.useEffect(() => {
      80            1 :         if (!hydrated) return;
      81            1 :         if (config?.settings == null) return;
      82            1 :         const settings = config.settings;
      83            1 :         const handle = setTimeout(() => {
      84            1 :             storageReader.backend.saveAppSettings(settings).catch(e => {
      85            1 :                 logger.warn("Failed to persist app settings", { error: String(e) }, LOG_CTX);
      86            1 :             });
      87            1 :         }, 250);
      88            1 :         return () => clearTimeout(handle);
      89            1 :     }, [hydrated, config?.settings, storageReader, logger]);
      90              : 
      91            1 :     return <APP_SETTINGS_READY_CTX.Provider value={hydrated}>{props.children}</APP_SETTINGS_READY_CTX.Provider>;
      92            1 : };
        

Generated by: LCOV version 2.0-1