mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-13 04:06:59 +00:00
fix(settings): clear stale pending and unsaved indicators after save (#1370)
* fix(settings): clear stale pending and unsaved indicators after save Settings sections held their saved baseline in a mutable ref and computed the dirty count with useMemo keyed on the live values, so updating the ref on a successful save never re-ran the calculation. The masthead pending count and the sidebar unsaved dot stayed stale until the section remounted, making operators think the save had failed. Move the baseline into state behind a shared useSettingsDirty hook with separate load (reset) and save-success (markSaved) operations. markSaved adopts the submitted snapshot as the baseline only, so an edit made while a save is in flight survives and a failed save stays dirty and retryable. Migrate the five sections that used the pattern. * test(settings): await the save-failure retry assertion to avoid a race In the failed-save reconcile test, wait for the Save button to re-enable after the PATCH settles instead of asserting synchronously, so the retry check cannot race the isSaving reset.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { TogglePill } from '@/components/ui/toggle-pill';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
@@ -14,6 +14,7 @@ import { SettingsSection } from './SettingsSection';
|
||||
import { SettingsField } from './SettingsField';
|
||||
import { SettingsActions, SettingsPrimaryButton } from './SettingsActions';
|
||||
import { useMastheadStats } from './MastheadStatsContext';
|
||||
import { useSettingsDirty } from './useSettingsDirty';
|
||||
|
||||
interface DeveloperSectionProps {
|
||||
onDirtyChange?: (dirty: boolean) => void;
|
||||
@@ -37,13 +38,10 @@ export function DeveloperSection({ onDirtyChange }: DeveloperSectionProps) {
|
||||
const { isAdmin } = useAuth();
|
||||
const { activeNode } = useNodes();
|
||||
const readOnly = !isAdmin;
|
||||
const [settings, setSettings] = useState<DeveloperFields>({ ...DEFAULT_DEVELOPER });
|
||||
const serverSettingsRef = useRef<DeveloperFields>({ ...DEFAULT_DEVELOPER });
|
||||
const { settings, setSettings, hasChanges, reset, markSaved } = useSettingsDirty<DeveloperFields>({ ...DEFAULT_DEVELOPER });
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const hasChanges = settings.developer_mode !== serverSettingsRef.current.developer_mode;
|
||||
|
||||
useEffect(() => {
|
||||
onDirtyChange?.(hasChanges);
|
||||
}, [hasChanges, onDirtyChange]);
|
||||
@@ -69,8 +67,7 @@ export function DeveloperSection({ onDirtyChange }: DeveloperSectionProps) {
|
||||
const safe: DeveloperFields = {
|
||||
developer_mode: (nodeData.developer_mode as '0' | '1') ?? DEFAULT_SETTINGS.developer_mode,
|
||||
};
|
||||
setSettings(safe);
|
||||
serverSettingsRef.current = { ...safe };
|
||||
reset(safe);
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch developer settings', e);
|
||||
} finally {
|
||||
@@ -86,8 +83,9 @@ export function DeveloperSection({ onDirtyChange }: DeveloperSectionProps) {
|
||||
};
|
||||
|
||||
const saveSettings = async () => {
|
||||
const submitted = { ...settings };
|
||||
const payload = {
|
||||
developer_mode: settings.developer_mode,
|
||||
developer_mode: submitted.developer_mode,
|
||||
};
|
||||
setIsSaving(true);
|
||||
try {
|
||||
@@ -100,7 +98,7 @@ export function DeveloperSection({ onDirtyChange }: DeveloperSectionProps) {
|
||||
toast.error(err?.error || err?.message || 'Failed to save settings.');
|
||||
return;
|
||||
}
|
||||
serverSettingsRef.current = { ...settings };
|
||||
markSaved(submitted);
|
||||
toast.success('Developer settings saved.');
|
||||
window.dispatchEvent(new CustomEvent<SenchoSettingsChangedDetail>(SENCHO_SETTINGS_CHANGED, {
|
||||
detail: { changedKeys: Object.keys(payload) },
|
||||
|
||||
Reference in New Issue
Block a user