fix(settings): Preferences stopped lagging behind the keyboard
Typing a cluster macro cost two things per character. The state lived on SettingsModal, so every keystroke in one of those twenty-four boxes re-rendered the WHOLE preferences dialog — every list, every form, every panel. And the save wrote through to Go and into the database on each one: a round trip per character. The editor is now its own module-scoped component with its own state, so a keystroke re-renders twelve rows. And writeUiPrefDebounced holds the database write until the typing stops, while the local cache — which is what everything reads back — is still written at once. Pending values are flushed when the page goes away, so typing and immediately closing does not lose the last word. The behaviour is unchanged: still saved as you type, still no Save button, because a text box whose contents only take effect on some other button is how work gets lost. Also folds the satellite changelog into one [NEW] entry. Satellites are new in this version — nobody reading the notes has seen any of it — so a running account of how it was built, tab then tracking then rotator then where the settings moved to, is the wrong shape. One entry saying what it does.
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
// Stored through writeUiPref like every other portable preference, so the
|
||||
// buttons travel with data/ rather than living in one browser profile.
|
||||
|
||||
import { writeUiPref } from '@/lib/uiPref';
|
||||
import { writeUiPrefDebounced } from '@/lib/uiPref';
|
||||
|
||||
export type ClusterMacro = {
|
||||
label: string; // what the button says
|
||||
@@ -43,8 +43,12 @@ export function loadClusterMacros(): ClusterMacro[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Debounced, because this is called on every keystroke in twenty-four text
|
||||
// boxes. The local cache is written at once — it is what everything reads back
|
||||
// — and only the database write waits for the typing to stop. A round trip into
|
||||
// Go per character is what "the letters appear after I have moved on" was.
|
||||
export function saveClusterMacros(macros: ClusterMacro[]): void {
|
||||
writeUiPref(clusterMacrosKey, JSON.stringify(macros));
|
||||
writeUiPrefDebounced(clusterMacrosKey, JSON.stringify(macros));
|
||||
}
|
||||
|
||||
// visibleClusterMacros drops the slots that would send nothing. The COMMAND is
|
||||
|
||||
@@ -93,6 +93,50 @@ export async function syncPortablePrefs(): Promise<void> {
|
||||
}));
|
||||
}
|
||||
|
||||
// writeUiPrefDebounced is writeUiPref for a value that changes AS SOMEBODY
|
||||
// TYPES.
|
||||
//
|
||||
// The local cache is written at once, because that is what the interface reads
|
||||
// back and it costs nothing. The DATABASE write is held until the typing stops:
|
||||
// writeUiPref crosses into Go and writes a row, and doing that per character in
|
||||
// a text box is a round trip per keystroke — twenty-four boxes of cluster
|
||||
// macros was exactly that, and it showed as characters appearing after the
|
||||
// finger had left the key.
|
||||
//
|
||||
// Pending writes are flushed when the page goes away, so a value typed and
|
||||
// immediately followed by a close is not lost.
|
||||
const pendingPrefs = new Map<string, { value: string; timer: number }>();
|
||||
|
||||
export function writeUiPrefDebounced(key: string, value: string, ms = 400): void {
|
||||
try { localStorage.setItem(key, value); } catch { /* quota / private mode */ }
|
||||
const prev = pendingPrefs.get(key);
|
||||
if (prev) window.clearTimeout(prev.timer);
|
||||
const timer = window.setTimeout(() => {
|
||||
pendingPrefs.delete(key);
|
||||
SetUIPref(key, value).catch((e: any) => {
|
||||
try { LogUIError('ui pref', 'could not store ' + key + ': ' + String(e?.message ?? e), ''); } catch { /* nothing left to try */ }
|
||||
});
|
||||
}, ms);
|
||||
pendingPrefs.set(key, { value, timer });
|
||||
}
|
||||
|
||||
// flushUiPrefs writes every pending value immediately.
|
||||
export function flushUiPrefs(): void {
|
||||
for (const [key, p] of pendingPrefs) {
|
||||
window.clearTimeout(p.timer);
|
||||
SetUIPref(key, p.value).catch(() => { /* the local cache still holds it */ });
|
||||
}
|
||||
pendingPrefs.clear();
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('beforeunload', flushUiPrefs);
|
||||
// Closing the app does not always fire beforeunload in a WebView; a hidden
|
||||
// page is the earlier and more reliable signal.
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'hidden') flushUiPrefs();
|
||||
});
|
||||
}
|
||||
|
||||
// writeUiPref write-throughs a value to the local cache AND the portable DB.
|
||||
// Use it everywhere these keys are written instead of localStorage.setItem.
|
||||
export function writeUiPref(key: string, value: string): void {
|
||||
|
||||
Reference in New Issue
Block a user