perf(state/assets): debounce saveState, defer SheetJS script (#11)

- saveState is now debounced (~400ms) to coalesce rapid changes; a pending
  write is flushed on beforeunload and on visibilitychange→hidden so the last
  change is never lost.
- The SheetJS <script> gets defer so it no longer blocks initial parse; the
  typeof XLSX guard already covers any not-yet-available case.

Verified headless: app loads without JS/CSP errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ferdi2goandClaude Opus 4.8 committed 2026-06-03 12:54:41 +00:00
1 parent 8cd8908f38
commit 0efb9cb853
2 files changed
+18 -3

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js" integrity="sha384-EnyY0/GSHQGSxSgMwaIPzSESbqoOLSexfnSMN2AP+39Ckmn92stwABZynq1JyzdT" crossorigin="anonymous"></script> <script defer src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js" integrity="sha384-EnyY0/GSHQGSxSgMwaIPzSESbqoOLSexfnSMN2AP+39Ckmn92stwABZynq1JyzdT" crossorigin="anonymous"></script>
</head> </head>
<body> <body>
<header> <header>
+17 -2
View File
@@ -30,9 +30,11 @@ try {
// Corrupt storage — start fresh // Corrupt storage — start fresh
} }
// Call after any state mutation to persist automatically // Persist immediately. Used by the debounced saveState and the flush handlers.
let storageWarned = false; let storageWarned = false;
export function saveState() { let saveTimer = null;
function writeState() {
if (saveTimer !== null) { clearTimeout(saveTimer); saveTimer = null; }
try { try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ localStorage.setItem(STORAGE_KEY, JSON.stringify({
employees: state.employees, employees: state.employees,
@@ -52,6 +54,19 @@ export function saveState() {
} }
} }
// Call after any state mutation. Debounced to coalesce rapid changes; a pending
// write is flushed on page hide/unload so the last change is never lost.
export function saveState() {
if (saveTimer !== null) clearTimeout(saveTimer);
saveTimer = setTimeout(writeState, 400);
}
const flushPending = () => { if (saveTimer !== null) writeState(); };
window.addEventListener('beforeunload', flushPending);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flushPending();
});
// Tab switching // Tab switching
document.querySelectorAll('.tab-btn').forEach(btn => { document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {