fix(settings): keep the parental lock switch on the saved state and roll back to the recovered value

The Settings switch snaps back to the saved state when clicked and
follows it once the PIN action succeeds, so a cancelled or refused PIN
no longer leaves it showing the opposite state. A failed switch write is
undone to the value read after the settings retry instead of the
hard-coded inverse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-27 15:53:41 +02:00
1 parent ed48e7fd2b
commit 8d11f674cf
6 files changed
+129 -5

No files matched your search

@@ -0,0 +1,51 @@
import { persistParentalLockEnabled } from './parental-lock-settings-writer';
describe('persistParentalLockEnabled', () => {
it('rolls a failed write back to the value recovered by the settings retry', async () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined);
let failure: 'load' | 'save' | null = 'load';
let stored: boolean | undefined = undefined;
const settings = {
storageFailure: () => failure,
parentalLockEnabled: () => stored,
// The retried read recovers: the switch was already off.
loadSettings: jest.fn(async () => {
failure = null;
stored = false;
}),
updateSettings: jest
.fn()
.mockRejectedValueOnce(new Error('disk full'))
.mockResolvedValue(undefined),
};
await expect(persistParentalLockEnabled(settings, false)).resolves.toBe(
false
);
// Undone to the recovered `false`, not the hard-coded inverse.
expect(settings.updateSettings).toHaveBeenLastCalledWith({
parentalLockEnabled: false,
});
});
it('undoes to the inverse when the switch was never persisted', async () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined);
const settings = {
storageFailure: () => null,
loadSettings: jest.fn(),
updateSettings: jest
.fn()
.mockRejectedValueOnce(new Error('disk full'))
.mockResolvedValue(undefined),
};
await expect(persistParentalLockEnabled(settings, true)).resolves.toBe(
false
);
expect(settings.updateSettings).toHaveBeenLastCalledWith({
parentalLockEnabled: false,
});
});
});
@@ -5,7 +5,10 @@ import { mirrorParentalLockEnabledSetting } from './parental-lock-bridge';
type SettingsWriter = Pick<
InstanceType<typeof SettingsStore>,
'updateSettings' | 'loadSettings'
> & { storageFailure?: () => 'load' | 'save' | null };
> & {
storageFailure?: () => 'load' | 'save' | null;
parentalLockEnabled?: () => boolean | undefined;
};
/**
* `updateSettings` writes the WHOLE settings object. After a failed startup
@@ -39,9 +42,14 @@ export async function persistParentalLockEnabled(
if (!(await ensureParentalLockSettingsReadable(settings))) {
return false;
}
// Read AFTER the retry, as for the relock timeout: a recovered read may
// already hold `enabled` (e.g. disable() entered while the unknown
// switch followed the stored PIN), and undoing to the inverse would
// then write the opposite of what was persisted.
const previous = settings.parentalLockEnabled?.() ?? !enabled;
const undo = () =>
settings
.updateSettings({ parentalLockEnabled: !enabled })
.updateSettings({ parentalLockEnabled: previous })
.catch(() => undefined);
try {
await settings.updateSettings({ parentalLockEnabled: enabled });