fix(idaa/recovery_meetings): fix weekday chips, recurring fields, and timezone lookup

- Weekday chips: replace bind:checked (unreliable with dynamic bracket notation in
  {#each}) with explicit onchange handlers + class: directives; read weekdays from
  state in submit handler instead of FormData
- Recurring pattern/times: bind select and time inputs to working copy
  so values display and edit correctly
- Times clearing: map empty string to null so times can be cleared once set
- liveQuery guard: skip event_obj sync while edit form is open to prevent
  background refresh from overwriting in-progress user changes
- Timezone lookup: forward order_by_li, limit, offset through the full call chain
  so priority sort and result count params are actually sent to the API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-03-23 16:05:16 -04:00
parent 5bed167829
commit f3ab1c1050
5 changed files with 79 additions and 26 deletions

View File

@@ -16,6 +16,9 @@ export async function get_ae_lookup_li_v3({
for_id,
include_disabled = false,
only_priority = false,
order_by_li = null,
limit = null,
offset = null,
params = {},
headers = {},
log_lvl = 0
@@ -27,6 +30,9 @@ export async function get_ae_lookup_li_v3({
for_id?: string;
include_disabled?: boolean;
only_priority?: boolean;
order_by_li?: Record<string, 'ASC' | 'DESC'> | null;
limit?: number | null;
offset?: number | null;
params?: key_val;
headers?: Record<string, string>;
log_lvl?: number;
@@ -43,6 +49,9 @@ export async function get_ae_lookup_li_v3({
if (for_id) params['for_id'] = for_id;
if (include_disabled) params['include_disabled'] = true;
if (only_priority) params['only_priority'] = true;
if (order_by_li) params['order_by_li'] = JSON.stringify(order_by_li);
if (limit != null) params['limit'] = limit;
if (offset != null) params['offset'] = offset;
// Lookup data is often global; ensure account context is handled if needed,
// but GUIDE says it uses site Whitelist Policy.

View File

@@ -48,6 +48,7 @@ export async function load_ae_obj_li__time_zone({
hidden: hidden,
limit: limit,
offset: offset,
order_by_li: order_by_li,
params: params,
only_priority: only_priority,
log_lvl: log_lvl

View File

@@ -80,6 +80,9 @@ export const get_ae_obj_li_for_lu = async function get_ae_obj_li_for_lu({
lu_type: for_lu_type,
include_disabled: enabled === 'all',
only_priority: only_priority,
order_by_li: order_by_li ?? undefined,
limit: limit ?? undefined,
offset: offset ?? undefined,
params,
headers: merged_headers,
log_lvl

View File

@@ -63,8 +63,10 @@
}
let results = await db_events.event.get($idaa_slct?.event_id ?? ''); // null or undefined does not reset things like '' does
// Check if results are different than the current $idaa_slct.event_obj
if ($idaa_slct.event_obj && results) {
// Check if results are different than the current $idaa_slct.event_obj.
// Skip the sync while the edit form is open — overwriting would discard
// the user's in-progress changes (e.g. weekday chips, pattern, times).
if ($idaa_slct.event_obj && results && !$idaa_sess.recovery_meetings.edit__event_obj) {
if (
JSON.stringify($idaa_slct.event_obj) !==
JSON.stringify(results)

View File

@@ -12,7 +12,7 @@
}: Props = $props();
// *** Import Svelte specific
import { onMount } from 'svelte';
import { onMount, untrack } from 'svelte';
import { fade } from 'svelte/transition';
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
@@ -72,6 +72,33 @@
let disable_submit_btn = $state(true);
// Weekday chip state — local $state is required because Svelte 5 does not reliably
// propagate reactivity through dynamic bracket notation ($store[dynamicKey]) on
// writable stores. Direct property access (.physical, .virtual) works fine; loop-variable
// bracket access does not trigger class re-renders. $state is deep-reactive and does.
const WEEKDAY_KEYS = [
'weekday_sunday', 'weekday_monday', 'weekday_tuesday', 'weekday_wednesday',
'weekday_thursday', 'weekday_friday', 'weekday_saturday'
] as const;
let weekdays = $state<Record<string, boolean>>({
weekday_sunday: false, weekday_monday: false, weekday_tuesday: false,
weekday_wednesday: false, weekday_thursday: false, weekday_friday: false,
weekday_saturday: false
});
// Seed once from liveQuery when data first arrives. Plain JS flag (not $state) so
// it doesn't cause the effect to re-run after the one-time init.
let weekdays_initialized = false;
$effect(() => {
if (!weekdays_initialized && $lq__event_obj?.event_id) {
untrack(() => {
for (const key of WEEKDAY_KEYS) {
weekdays[key] = !!$lq__event_obj[key];
}
weekdays_initialized = true;
});
}
});
// Contact 1 is locked by default on existing meetings for non-trusted users.
// This prevents accidental override of the meeting owner's contact info.
// Trusted+ users see no lock UI; new meetings start unlocked.
@@ -406,21 +433,21 @@
event_do['recurring'] = true;
event_do['recurring_pattern'] = event_meeting_fd.recurring_pattern;
// !! converts to boolean based on truthiness
event_do['weekday_sunday'] = !!event_meeting_fd.weekday_sunday;
event_do['weekday_monday'] = !!event_meeting_fd.weekday_monday;
event_do['weekday_tuesday'] = !!event_meeting_fd.weekday_tuesday;
event_do['weekday_wednesday'] = !!event_meeting_fd.weekday_wednesday;
event_do['weekday_thursday'] = !!event_meeting_fd.weekday_thursday;
event_do['weekday_friday'] = !!event_meeting_fd.weekday_friday;
event_do['weekday_saturday'] = !!event_meeting_fd.weekday_saturday;
// Read weekdays from local $state (not FormData) — bind:checked with dynamic bracket
// notation in {#each} is unreliable in Svelte 5; explicit onchange handlers update
// the weekdays $state object directly, so read from there instead.
event_do['weekday_sunday'] = weekdays.weekday_sunday;
event_do['weekday_monday'] = weekdays.weekday_monday;
event_do['weekday_tuesday'] = weekdays.weekday_tuesday;
event_do['weekday_wednesday'] = weekdays.weekday_wednesday;
event_do['weekday_thursday'] = weekdays.weekday_thursday;
event_do['weekday_friday'] = weekdays.weekday_friday;
event_do['weekday_saturday'] = weekdays.weekday_saturday;
if (event_meeting_fd['recurring_start_time']) {
event_do['recurring_start_time'] = event_meeting_fd.recurring_start_time;
}
if (event_meeting_fd['recurring_end_time']) {
event_do['recurring_end_time'] = event_meeting_fd.recurring_end_time;
}
// Send null explicitly so the user can clear a time once set.
// An empty string from the time input means "no time" — map that to null.
event_do['recurring_start_time'] = event_meeting_fd.recurring_start_time || null;
event_do['recurring_end_time'] = event_meeting_fd.recurring_end_time || null;
if (typeof recurring_text_new_html === 'string') {
event_do['recurring_text'] = recurring_text_new_html;
@@ -620,8 +647,10 @@ Copy and paste link: <a href="${link_base_url}?event_id=${event_do.event_id}">${
});
}
// Track whether the form has unsaved changes relative to the original loaded object
// Track whether the form has unsaved changes relative to the original loaded object.
// weekdays is checked separately since it's a local $state, not part of $idaa_slct.event_obj.
$effect(() => {
const weekdays_changed = WEEKDAY_KEYS.some(k => !!weekdays[k] !== !!orig_event_obj?.[k]);
if (orig_event_obj === null || orig_event_obj === undefined) {
obj_changed = false;
} else if (
@@ -629,7 +658,8 @@ Copy and paste link: <a href="${link_base_url}?event_id=${event_do.event_id}">${
orig_event_obj?.id &&
(JSON.stringify($idaa_slct.event_obj) !== JSON.stringify(orig_event_obj) ||
description_changed ||
notes_changed)
notes_changed ||
weekdays_changed)
) {
obj_changed = true;
} else if (
@@ -637,7 +667,8 @@ Copy and paste link: <a href="${link_base_url}?event_id=${event_do.event_id}">${
orig_event_obj?.id &&
JSON.stringify($idaa_slct.event_obj) === JSON.stringify(orig_event_obj) &&
!description_changed &&
!notes_changed
!notes_changed &&
!weekdays_changed
) {
obj_changed = false;
}
@@ -1365,13 +1396,14 @@ Copy and paste link: <a href="${link_base_url}?event_id=${event_do.event_id}">${
Schedule
</h3>
<!-- Recurring pattern -->
<!-- Recurring pattern — bind to working copy so user changes aren't
overwritten when $lq__event_obj fires during editing -->
<label class="flex flex-col gap-1 text-sm font-semibold text-surface-700-300" for="recurring_pattern">
Repeats
<select
id="recurring_pattern"
name="recurring_pattern"
value={$lq__event_obj?.recurring_pattern}
bind:value={$idaa_slct.event_obj.recurring_pattern}
class="select p-1 preset-tonal-surface hover:preset-filled-surface-100-900 w-48"
>
<option value="weekly">Weekly</option>
@@ -1397,14 +1429,19 @@ Copy and paste link: <a href="${link_base_url}?event_id=${event_do.event_id}">${
] as day (day.name)}
<label
for={day.name}
class="flex items-center justify-center w-12 py-2 rounded-lg cursor-pointer text-sm font-semibold preset-tonal-surface border border-surface-300-700 hover:preset-filled-surface-200-800 transition-all {$idaa_slct.event_obj[day.name] ? 'preset-filled-primary-200-800 border-primary-400' : ''}"
class="flex items-center justify-center w-12 py-2 rounded-lg cursor-pointer text-sm font-semibold border transition-all"
class:preset-filled-primary-200-800={weekdays[day.name]}
class:border-primary-400={weekdays[day.name]}
class:preset-tonal-surface={!weekdays[day.name]}
class:border-surface-300-700={!weekdays[day.name]}
>
{day.label}
<input
type="checkbox"
id={day.name}
name={day.name}
bind:checked={$idaa_slct.event_obj[day.name]}
checked={weekdays[day.name]}
onchange={() => { weekdays[day.name] = !weekdays[day.name]; }}
class="sr-only"
/>
</label>
@@ -1442,13 +1479,14 @@ Copy and paste link: <a href="${link_base_url}?event_id=${event_do.event_id}">${
{/if}
</label>
<!-- bind to working copy (same reason as recurring_pattern above) -->
<label class="flex flex-col gap-1 text-sm font-semibold text-surface-700-300" for="recurring_start_time">
Start Time
<input
type="time"
id="recurring_start_time"
name="recurring_start_time"
value={$lq__event_obj?.recurring_start_time}
bind:value={$idaa_slct.event_obj.recurring_start_time}
class="input preset-tonal-surface hover:preset-filled-surface-100-900 w-36"
/>
</label>
@@ -1459,7 +1497,7 @@ Copy and paste link: <a href="${link_base_url}?event_id=${event_do.event_id}">${
type="time"
id="recurring_end_time"
name="recurring_end_time"
value={$lq__event_obj?.recurring_end_time}
bind:value={$idaa_slct.event_obj.recurring_end_time}
class="input preset-tonal-surface hover:preset-filled-surface-100-900 w-36"
/>
</label>