fix(idaa): resolve ~1-year 'no meetings found' bug on recovery meetings page
Root cause: stale IDB records from prior deploys persisted indefinitely. Fast path returned 0 (account_id mismatch), API errored silently, and the error state showed the same message as a genuinely empty result — making the failure indistinguishable from real data. Fix is layered defense: - Bump IDB_CONTENT_VERSIONS.events.event to 2 (one-time force-clear for all users) - Add check_and_clear_idb_table() helper to store_versions.ts; wire it in (idaa)/+layout.svelte to catch future version mismatches on session start - One silent auto-retry (3s) on API failure before surfacing error UI - Distinct error state (Unable to load meetings) separate from empty state - Escape-hatch cache-reset button after 8s when zero results + no active filters - Document root cause and fix in README.md and BOOTSTRAP__AI_Agent_Quickstart.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,29 +38,47 @@ export const AE_BADGES_LOC_VERSION = 1; // Added 2026-04-02: promoted from e
|
||||
export const AE_LEADS_LOC_VERSION = 1; // Added 2026-04-03: promoted from events_loc.leads
|
||||
|
||||
/**
|
||||
* IDB_CONTENT_VERSIONS — per-table Dexie content version tracking.
|
||||
* IDB_CONTENT_VERSIONS — per-table Dexie (IndexedDB) content version tracking.
|
||||
*
|
||||
* NOT YET ACTIVE. Wiring task tracked in TODO__Agents.md (post June 10).
|
||||
* BACKGROUND:
|
||||
* Stale IDB records persisting across deploys were the root cause of the "no meetings
|
||||
* found" bug on the IDAA Recovery Meetings page — a ~1-year unresolved issue (2025-2026).
|
||||
* After a deploy that changed properties_to_save, old cached event records stayed in IDB
|
||||
* with wrong or missing fields. The fast path returned them, filtered to 0 (account_id
|
||||
* mismatch), the API call errored silently, and the error state showed the same message
|
||||
* as a genuinely empty result. Fixed 2026-05-16.
|
||||
*
|
||||
* HOW IT WILL WORK (when wired):
|
||||
* Each db_*.ts will call a helper (core__idb_dexie.ts) on open that checks a
|
||||
* lightweight `_meta` table. If the stored version for a table doesn't match
|
||||
* the constant here, the table is cleared and the version record is updated.
|
||||
* The SWR pattern then repopulates from the API on next access.
|
||||
* WIRING STATUS (as of 2026-05-16):
|
||||
* events.event → ACTIVE — wired in src/routes/idaa/(idaa)/+layout.svelte
|
||||
* All other tables → NOT YET WIRED (see instructions below)
|
||||
*
|
||||
* HOW TO USE (once active):
|
||||
* Bump a table's version here when properties_to_save changes in a way that
|
||||
* makes existing cached records stale (e.g. adding/removing stored fields,
|
||||
* changing computed field behavior). Do NOT bump for schema-only changes
|
||||
* (new indexes, new tables) — those belong in db.version() Dexie migrations.
|
||||
* HOW IT WORKS (for wired tables):
|
||||
* check_and_clear_idb_table() (bottom of this file) is called on module/page load.
|
||||
* It reads a localStorage key `ae_idb_ver__<db_name>__<table_name>` and compares it
|
||||
* to the version here. On mismatch, the Dexie table is cleared and the key is updated.
|
||||
* The SWR pattern then repopulates from the API on next access. On a version match
|
||||
* (the normal case after first load) the cost is a single localStorage read — free.
|
||||
*
|
||||
* HOW TO WIRE A NEW TABLE:
|
||||
* 1. Import check_and_clear_idb_table from '$lib/stores/store_versions' in the relevant
|
||||
* layout or page (the one that owns that module's data lifecycle).
|
||||
* 2. Call it on mount, non-blocking:
|
||||
* check_and_clear_idb_table(db_X.table_name, 'db_key', 'table_name').catch(() => {});
|
||||
* Use top-level `if (browser) { ... }` or an `$effect` with no reactive deps.
|
||||
* 3. That's it. Bumping the version constant below is all that's needed for future clears.
|
||||
*
|
||||
* WHEN TO BUMP A VERSION:
|
||||
* Bump when properties_to_save in an object file changes in a way that makes existing
|
||||
* cached records stale — e.g. adding/removing stored fields, changing how a computed
|
||||
* field (like tmp_sort_1) is built, or a backend change that alters field semantics.
|
||||
* Do NOT bump for Dexie schema-only changes (new indexes, new tables) — those belong
|
||||
* in db.version() Dexie migrations in the db_*.ts file.
|
||||
*
|
||||
* IDAA NOTES:
|
||||
* IDAA tables (posts, archives) are already cleared aggressively via
|
||||
* indexedDB.deleteDatabase() on sign-out and on auth failure in (idaa)/+layout.svelte.
|
||||
* The content version check is a complementary mechanism for deploy-time resets,
|
||||
* NOT a replacement for the auth-driven wipe. When wiring, ensure IDAA tables are
|
||||
* only cleared on IDB open (not mid-session), and that the _meta table itself is
|
||||
* also cleared when deleteDatabase() is called.
|
||||
* IDAA tables (posts, archives, events) are already cleared aggressively via
|
||||
* indexedDB.deleteDatabase() on auth failure in (idaa)/+layout.svelte.
|
||||
* The content version check is a deploy-time complement — it handles the case where
|
||||
* auth succeeds but IDB data is stale from a prior deploy. Both mechanisms are needed.
|
||||
*/
|
||||
export const IDB_CONTENT_VERSIONS = {
|
||||
journals: {
|
||||
@@ -68,7 +86,7 @@ export const IDB_CONTENT_VERSIONS = {
|
||||
journal_entry: 3 // 2026-05-14: removed content_md_html + history_md_html from properties_to_save
|
||||
},
|
||||
events: {
|
||||
event: 1,
|
||||
event: 2, // Bumped 2026-05-16: force-clear stale IDB data causing "no meetings found" on IDAA
|
||||
event_session: 1,
|
||||
event_presenter: 1,
|
||||
event_badge: 1,
|
||||
@@ -135,3 +153,49 @@ function _check_and_wipe(key: string, expected_version: number): void {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the IDB table content version against the expected version in IDB_CONTENT_VERSIONS.
|
||||
* If the stored version is stale (or missing), clears the entire table and records the new
|
||||
* version so the next call is a no-op.
|
||||
*
|
||||
* @param db_table - A Dexie table reference (e.g. db_events.event)
|
||||
* @param db_name - The top-level key in IDB_CONTENT_VERSIONS (e.g. 'events')
|
||||
* @param table_name - The table key inside that group (e.g. 'event')
|
||||
*
|
||||
* WHY localStorage for version tracking (not a _meta IDB table):
|
||||
* Simpler — no Dexie schema migration needed. localStorage and IDB are cleared together
|
||||
* by Full Reset (e_app_help_tech.svelte), so they stay in sync across manual wipes.
|
||||
*
|
||||
* USAGE: call on mount, non-blocking. Example from (idaa)/+layout.svelte:
|
||||
* check_and_clear_idb_table(db_events.event, 'events', 'event').catch(() => {});
|
||||
*
|
||||
* After clearing, the SWR pattern in the relevant page automatically re-fetches from
|
||||
* the API and repopulates the table. No explicit reload is needed.
|
||||
*/
|
||||
export async function check_and_clear_idb_table(
|
||||
db_table: { clear(): Promise<void> },
|
||||
db_name: string,
|
||||
table_name: string
|
||||
): Promise<void> {
|
||||
if (typeof localStorage === 'undefined') return;
|
||||
|
||||
const versions = IDB_CONTENT_VERSIONS as Record<string, Record<string, number>>;
|
||||
const expected_ver = versions[db_name]?.[table_name];
|
||||
if (expected_ver == null) return;
|
||||
|
||||
const key = `ae_idb_ver__${db_name}__${table_name}`;
|
||||
const stored_ver = parseInt(localStorage.getItem(key) ?? '0', 10);
|
||||
|
||||
if (stored_ver !== expected_ver) {
|
||||
try {
|
||||
await db_table.clear();
|
||||
localStorage.setItem(key, String(expected_ver));
|
||||
console.info(
|
||||
`[idb_versions] '${db_name}.${table_name}' cleared — v${stored_ver} → v${expected_ver}`
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn(`[idb_versions] Failed to clear '${db_name}.${table_name}':`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user