feat(idaa): add guided empty state for filtered results + star button on meeting detail

- +page.svelte: when search returns zero results and filters are active, show
  "No meetings found for these filters" with a one-click "Clear all filters" button
  instead of the bare no-results message. The 8s cache-reset escape hatch is
  unchanged and still fires only when zero results appear with no filters set.
- [event_id]/+page.svelte: add star/favorites button to the detail page nav bar
  alongside Back/Edit. Loads the same idaa_meetings_favorites data_store record
  on mount; PATCHes the shared record on toggle. State is optimistic with rollback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-18 17:00:51 -04:00
parent 730fb19d60
commit 3a81887c56
2 changed files with 125 additions and 13 deletions

View File

@@ -73,6 +73,14 @@ let no_results_no_filters = $derived(
!($idaa_loc.recovery_meetings.qry__fulltext_str?.trim())
);
// True when any filter dimension is active — drives the guided empty state.
let has_active_filters = $derived(
!!$idaa_loc.recovery_meetings.qry__physical ||
!!$idaa_loc.recovery_meetings.qry__virtual ||
!!$idaa_loc.recovery_meetings.qry__type ||
!!($idaa_loc.recovery_meetings.qry__fulltext_str?.trim())
);
let show_cache_reset_btn = $state(false);
let cache_reset_timer: any = null;
@@ -111,6 +119,13 @@ async function handle_cache_reset() {
}
$idaa_sess.recovery_meetings.search_version++;
}
function clear_filters() {
$idaa_loc.recovery_meetings.qry__physical = null;
$idaa_loc.recovery_meetings.qry__virtual = null;
$idaa_loc.recovery_meetings.qry__type = null;
$idaa_loc.recovery_meetings.qry__fulltext_str = null;
$idaa_sess.recovery_meetings.search_version++;
}
// ─────────────────────────────────────────────────────────────────────────────────────
// Standardized Reactive Search Pattern (Aether UI V3)
@@ -441,24 +456,40 @@ if (browser) {
</button>
</div>
{:else}
<div
class="ae_highlight ae_padding_md ae_row ae_flex_justify_center">
No recovery meetings found matching your criteria.
</div>
{#if show_cache_reset_btn}
<!-- Escape hatch: surfaces after 8s when zero results + no active filters.
With ~140 active meetings, zero unfiltered results always indicates
stale IDB data. Clears the event cache and triggers a fresh API fetch. -->
{#if has_active_filters}
<!-- Guided empty state: filters are active but returned no results.
Distinct from the zero-unfiltered-results path (which indicates a data problem).
Here the member may simply have narrowed too far — offer a one-click escape. -->
<div class="ae_highlight ae_padding_md ae_row ae_flex_justify_center flex-col gap-2 text-center">
<p class="text-sm opacity-75">Still not seeing meetings? Your local cache may be out of date.</p>
<p>No meetings found for these filters.</p>
<button
type="button"
class="btn btn-sm preset-tonal-warning m-auto"
onclick={handle_cache_reset}>
<span class="fas fa-sync-alt m-1"></span>
Refresh Meeting Cache
class="btn btn-sm preset-tonal-primary m-auto"
onclick={clear_filters}>
<span class="fas fa-times m-1"></span>
Clear all filters
</button>
</div>
{:else}
<div
class="ae_highlight ae_padding_md ae_row ae_flex_justify_center">
No recovery meetings found matching your criteria.
</div>
{#if show_cache_reset_btn}
<!-- Escape hatch: surfaces after 8s when zero results + no active filters.
With ~140 active meetings, zero unfiltered results always indicates
stale IDB data. Clears the event cache and triggers a fresh API fetch. -->
<div class="ae_highlight ae_padding_md ae_row ae_flex_justify_center flex-col gap-2 text-center">
<p class="text-sm opacity-75">Still not seeing meetings? Your local cache may be out of date.</p>
<button
type="button"
class="btn btn-sm preset-tonal-warning m-auto"
onclick={handle_cache_reset}>
<span class="fas fa-sync-alt m-1"></span>
Refresh Meeting Cache
</button>
</div>
{/if}
{/if}
{/if}
</div>