From ffc430a727410622b77e6b2b527a4e8f0358f881 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Tue, 10 Mar 2026 14:20:57 -0400 Subject: [PATCH] Perf: replace JSON.stringify comparisons with efficient identity checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JSON.stringify on large store objects (ae_loc, ae_api, slct, event lists) was running on every navigation, comparing serialized strings of potentially deep objects. Replace with targeted comparators: - Root +layout.svelte: add shallow_equal() helper — O(n keys) key-by-key identity check instead of O(serialized bytes). Used for ae_api, ae_loc, and slct sync guards. - Launcher +layout.svelte: ID-list join-compare for event_location_obj_li and id_li__event_location (O(n) string vs O(n*m) stringify). Refactor liveQuery closures to be pure (data-only, no store reads/writes inside the async Dexie context where Svelte reactivity tracking is undefined). Move store sync into separate $effects that compare updated_on + id (O(1)) or ID-join (O(n)) rather than full object serialization. --- src/routes/+layout.svelte | 22 ++++- .../(launcher)/launcher/+layout.svelte | 98 ++++++++++++------- 2 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d4fb9bfb..7912d0c3 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,7 +2,7 @@ /** @type {import('./$types').LayoutData} */ // /** @type {import('./$types').LayoutProps} */ - let log_lvl: number = 1; + let log_lvl: number = 0; // *** Import Svelte specific import { untrack } from 'svelte'; @@ -71,6 +71,20 @@ let last_reload_time = 0; + // Shallow equality guard — avoids triggering Svelte store updates when the merged + // object is functionally identical to the current one. Comparing JSON.stringify on + // large objects like $ae_loc (site config, device info, flags) is expensive and + // runs on every navigation. Key-by-key identity check is O(n keys), not O(n chars). + function shallow_equal(a: Record, b: Record): boolean { + const keys_a = Object.keys(a); + const keys_b = Object.keys(b); + if (keys_a.length !== keys_b.length) return false; + for (const k of keys_a) { + if (a[k] !== b[k]) return false; + } + return true; + } + // 1. CONSOLIDATED SYNC EFFECT (One single point of entry for store updates) $effect(() => { if (!browser || !ae_acct) return; @@ -83,7 +97,7 @@ const new_api = { ...current_api, ...(ae_acct.api || {}) }; // Deep check for JWT specifically to avoid extra triggers if (current_api.jwt !== $ae_loc.jwt) new_api.jwt = $ae_loc.jwt; - if (JSON.stringify(current_api) !== JSON.stringify(new_api)) { + if (!shallow_equal(current_api, new_api)) { $ae_api = new_api; } @@ -93,13 +107,13 @@ if (!new_loc.sys_menu) new_loc.sys_menu = { hide: false, hide_access_type: false, expand_access_type: false, hide_edit_mode: false, expand_edit_mode: false, hide_user: false, expand_user: false, hide_theme: false, expand_theme: false, hide_cfg: false, expand_cfg: false }; if (!new_loc.debug_menu) new_loc.debug_menu = { hide: false, expand: false }; - if (JSON.stringify(current_loc) !== JSON.stringify(new_loc)) { + if (!shallow_equal(current_loc, new_loc)) { $ae_loc = new_loc; } const current_slct = $slct; const new_slct = { ...current_slct, ...(ae_acct.slct || {}) }; - if (JSON.stringify(current_slct) !== JSON.stringify(new_slct)) { + if (!shallow_equal(current_slct, new_slct)) { $slct = new_slct; } diff --git a/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte b/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte index d1c6714f..554f56d1 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher/+layout.svelte @@ -1,5 +1,5 @@