fix(badges): fix stale-Dexie race in font size initialization

The old guard locked on badge ID after the first liveQuery tick. If
Dexie had a cached badge without cfg_json.font_sizes, the guard fired
with no sizes to apply, then blocked the SWR background refresh that
delivered the real saved sizes. Result: font sizes appeared unsaved on
any browser that had visited the badge before sizes were set.

Fix: track the cfg_json string last applied (_font_sizes_applied_cfg)
instead of just the badge ID. Re-applies whenever cfg_json changes on
a background refresh, but skips if local sizes have drifted from the
last apply (user is mid-adjustment — auto-save will sync shortly).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-04-14 21:51:18 -04:00
parent 1df17e68bb
commit e4265f69af

View File

@@ -103,36 +103,76 @@ function send_review_email() {
// the values can be forwarded to both the controls and the badge render.
//
// Initialization order: badge cfg_json.font_sizes takes precedence (persisted per-badge),
// falling back to null (auto) on first load. The controls panel's "Lock Sizes" button
// saves current sizes back to cfg_json so they survive page reloads and cross-kiosk.
// falling back to null (auto) on first load. The auto-save in the controls panel writes
// adjusted sizes back to cfg_json so they survive page reloads and are shared cross-kiosk.
let font_size_name: number | null = $state(null);
let font_size_title: number | null = $state(null);
let font_size_affiliations: number | null = $state(null);
let font_size_location: number | null = $state(null);
// Track whether we've applied the saved sizes for this badge yet.
// Prevents re-applying on every liveQuery tick after the user adjusts sizes.
// Initialization tracking:
// _font_sizes_loaded_for — badge ID we last initialized for; reset on navigation.
// _font_sizes_applied_cfg — the cfg_json string we last read font_sizes from.
// Comparing this (not just the badge ID) lets us re-apply when a SWR background
// refresh delivers newly-saved sizes — fixing the stale-Dexie race where the first
// liveQuery tick has a cached badge without font_sizes, locking the badge-ID guard
// before the API refresh arrives with the real saved values.
let _font_sizes_loaded_for: string | null = null;
let _font_sizes_applied_cfg: string = '$unset'; // sentinel — never equals a real cfg_json value
$effect(() => {
const badge = $lq__event_badge_obj;
if (!badge?.event_badge_id) return;
// Only apply once per badge ID — don't clobber user adjustments on background refreshes
if (_font_sizes_loaded_for === badge.event_badge_id) return;
_font_sizes_loaded_for = badge.event_badge_id;
const current_cfg: string | null = badge.cfg_json ?? null;
// New badge — reset everything and fall through to apply.
if (_font_sizes_loaded_for !== badge.event_badge_id) {
_font_sizes_loaded_for = badge.event_badge_id;
_font_sizes_applied_cfg = '$unset';
font_size_name = null;
font_size_title = null;
font_size_affiliations = null;
font_size_location = null;
}
// cfg_json hasn't changed since we last applied — nothing to do.
if (_font_sizes_applied_cfg === current_cfg) return;
// cfg_json changed (SWR refresh delivered new data). Before applying, check
// whether local sizes have drifted from what we last applied. If the user has
// made adjustments since our last apply, the auto-save will sync them back to
// cfg_json shortly — don't clobber their in-progress work.
if (_font_sizes_applied_cfg !== '$unset') {
try {
const prev_cfg = JSON.parse(_font_sizes_applied_cfg ?? 'null') ?? {};
const prev_fs = prev_cfg?.font_sizes ?? null;
const user_adjusted =
font_size_name !== (prev_fs?.name ?? null) ||
font_size_title !== (prev_fs?.title ?? null) ||
font_size_affiliations !== (prev_fs?.affiliations ?? null) ||
font_size_location !== (prev_fs?.location ?? null);
if (user_adjusted) {
// Advance the marker so we don't re-check on the next identical cfg_json.
_font_sizes_applied_cfg = current_cfg ?? '$unset';
return;
}
} catch { /* ignore parse errors — fall through and apply */ }
}
// Apply font sizes from the fresh cfg_json.
_font_sizes_applied_cfg = current_cfg ?? '$unset';
try {
const cfg = typeof badge.cfg_json === 'string'
? JSON.parse(badge.cfg_json)
: (badge.cfg_json ?? {});
const fs = cfg?.font_sizes;
if (fs) {
font_size_name = fs.name ?? null;
font_size_title = fs.title ?? null;
font_size_affiliations = fs.affiliations ?? null;
font_size_location = fs.location ?? null;
}
font_size_name = fs?.name ?? null;
font_size_title = fs?.title ?? null;
font_size_affiliations = fs?.affiliations ?? null;
font_size_location = fs?.location ?? null;
} catch {
// Malformed cfg_json — stay with null (auto) for all fields
// Malformed cfg_json — stay with current values
}
});