fix(launcher): fix cfg modal default-open and outside-click persistence

Two bugs in the Launcher Config Modal after the Drawer→Modal migration:
1. Pre-existing persisted configs (missing hide_drawer__cfg field) caused
   !undefined = true, opening the modal on every fresh load. Fixed by adding
   a field-level initialization guard after the full-object guard.
2. $-syntax writes inside untrack() were suppressed by svelte-persisted-store,
   so outside-click closure was never persisted. Fixed by using events_loc.update()
   directly to ensure the write reaches localStorage serialization. Added equality
   guard to effect 1 to prevent spurious modal flicker from whole-store re-fires.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-26 21:36:01 -04:00
parent 6282fb167f
commit 25d17841e4

View File

@@ -91,6 +91,36 @@ if (!$events_loc?.launcher) {
hide_drawer__debug: true
};
}
// WHY: The initialization block above only runs when launcher is completely absent.
// If the user has an older persisted config (from before the Modal migration),
// hide_drawer__cfg may be missing → undefined → !undefined = true → modal opens
// on every load. Explicitly initialize it here to ensure it is always a boolean.
if ($events_loc.launcher.hide_drawer__cfg === undefined) {
$events_loc.launcher.hide_drawer__cfg = true;
}
let modal_cfg_open = $state(!$events_loc.launcher.hide_drawer__cfg);
// Sync store → modal: biohazard button writes hide_drawer__cfg = false to open.
// Equality guard prevents spurious writes from unrelated $events_loc updates
// (Svelte 4 whole-store subscription fires on every field write to the store).
$effect(() => {
const should_open = !$events_loc.launcher.hide_drawer__cfg;
if (modal_cfg_open !== should_open) {
modal_cfg_open = should_open;
}
});
// Sync modal → store: use events_loc.update() directly rather than $-syntax so
// the write always reaches the persisted store's serialization. $-syntax writes
// inside $effect contexts may be suppressed and not trigger localStorage persistence.
$effect(() => {
const should_hide = !modal_cfg_open;
events_loc.update((loc) => {
if (loc.launcher) loc.launcher.hide_drawer__cfg = should_hide;
return loc;
});
});
// Generate a stable per-device client ID on first load and persist it.
// events_loc is backed by svelte-persisted-store (localStorage) so this
@@ -898,59 +928,41 @@ $effect(() => {
</button>
</div>
<Drawer
dismissable={false}
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
class="w-full border border-gray-300 bg-orange-50 opacity-90 transition-all duration-300 hover:opacity-97 md:w-96 lg:w-[32rem] dark:border-gray-600 dark:bg-slate-800"
placement="left"
{...{
transitionType: 'fly',
transitionParams: {
x: -520,
duration: 200,
easing: sineIn
}
}}
bind:hidden={$events_loc.launcher.hide_drawer__cfg}
id="sidebar1">
<!-- Stop-propagation wrapper: prevents clicks inside the visual panel from
bubbling up to the <dialog> element. The onclick on the <Drawer> above
is spread through to the native <dialog> by Flowbite, overriding its
broken outsideclose detection. With this wrapper, ONLY genuine backdrop
clicks (outside the visible panel) reach the dialog and close the drawer. -->
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<div role="presentation" onclick={(e) => e.stopPropagation()}>
<Launcher_cfg></Launcher_cfg>
<Modal
bind:open={modal_cfg_open}
autoclose={false}
outsideclose
size="xl"
class="relative flex flex-col items-center justify-center p-0 overflow-hidden"
id="modal_cfg">
<Launcher_cfg></Launcher_cfg>
<hr class="my-2 border-gray-300 dark:border-gray-600" />
<div
class="flex max-w-md flex-row flex-wrap items-center justify-center gap-0.5">
<div
class="bg-surface-100-900 flex max-w-full flex-row flex-wrap items-center justify-center gap-2 border-t border-surface-500/10 p-4">
<a
href="/events/{$events_slct.event_id}"
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
<Search size="1em" class="m-1" />
Session Search
</a>
{#if $events_slct?.event_location_id}
<a
href="/events/{$events_slct.event_id}"
href="/events/{$events_slct.event_id}/location/{$events_slct.event_location_id}"
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
<Search size="1em" class="m-1" />
Session Search
<MapPin size="1em" class="m-1" />
View Selected Location
</a>
{#if $events_slct?.event_location_id}
<a
href="/events/{$events_slct.event_id}/location/{$events_slct.event_location_id}"
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
<MapPin size="1em" class="m-1" />
View Selected Location
</a>
{/if}
{#if $events_slct?.event_session_id}
<a
href="/events/{$events_slct.event_id}/session/{$events_slct.event_session_id}"
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
<GraduationCap size="1em" class="m-1" />
View Selected Session
</a>
{/if}
</div>
{/if}
{#if $events_slct?.event_session_id}
<a
href="/events/{$events_slct.event_id}/session/{$events_slct.event_session_id}"
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500">
<GraduationCap size="1em" class="m-1" />
View Selected Session
</a>
{/if}
</div>
</Drawer>
</Modal>
<Drawer
activateClickOutside={false}