From 3ed1a2a6c42c1a6f56402e20497959e9fdeb0704 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Mon, 18 May 2026 08:56:53 -0400 Subject: [PATCH] Documentation updates with IDAA ideas. --- documentation/AE__UI_UX_future_ideas.md | 515 ++++++++++++++++++++++++ documentation/TODO__Agents.md | 39 +- 2 files changed, 538 insertions(+), 16 deletions(-) create mode 100644 documentation/AE__UI_UX_future_ideas.md diff --git a/documentation/AE__UI_UX_future_ideas.md b/documentation/AE__UI_UX_future_ideas.md new file mode 100644 index 00000000..018bba7c --- /dev/null +++ b/documentation/AE__UI_UX_future_ideas.md @@ -0,0 +1,515 @@ +# Aether UI/UX — Future Ideas + +> Collection of concrete UX improvements for the Aether frontend. Each entry includes +> the rationale, current behavior, proposed change, and implementation notes. +> **Date:** 2026-05-17 + +--- + +## IDAA Recovery Meetings + +### 1. Guided empty state with active filters + +**Current behavior:** When filters return 0 results, the page shows: +"No recovery meetings found matching your criteria." +The member has no indication whether this is a bug, genuinely no data, or just +overly narrow filters. + +**Proposed change:** When filters are active AND the result count is 0, show a +helpful prompt instead of the bare message: + +``` +No meetings found for these filters. +Try broadening your search or [Clear all filters →] +``` + +**Implementation notes:** +- Add a `has_active_filters` derived in `+page.svelte` that checks whether any of + `qry__physical`, `qry__virtual`, `qry__type`, or `qry__fulltext_str` is set. +- In the template's `{:else}` block (line ~443), branch on `has_active_filters`: + - `true` → show the guided message + "Clear Filters" button + - `false` → show the existing escape-hatch flow (timed "Refresh Meeting Cache" button + after 8 seconds, since zero unfiltered results always indicates a problem) +- The "Clear Filters" button resets all four filter fields to `null`/`''` and bumps + `search_version` to trigger a fresh unfiltered search. +- Distinct from the `error` state — this is a successful search (`qry__status === 'done'`) + with an empty result set. + +--- + +### 2. Quick-filter chips below the search bar + +**Current behavior:** Members toggle filters via small checkboxes (Virtual, In-person) +and radio buttons (All, IDAA, Caduceus, Family Recovery). These require precise +mouse/tap targeting and scanning several lines of filter UI to discover and use. + +**Proposed change:** Add a row of preset chip buttons directly below the search input: + +``` +[🖥 Virtual] [🏠 In-Person] [🩺 IDAA] [Caduceus] [Family Recovery] [All Types] +``` + +- Each chip toggles the corresponding filter (`qry__virtual`, `qry__physical`, `qry__type`) + and triggers an immediate search. +- Selected chips get a filled/pressed style; unselected chips are outlined. +- "All Types" is the default selected state (no type filter). Clicking another type + chip deselects "All Types" (radio behavior for the type dimension). Virtual and + In-person are independent toggles (checkbox behavior — can select both). +- The existing checkboxes/radio buttons remain as the underlying state storage + (`$idaa_loc.recovery_meetings.*`). The chips are a convenience layer — they write + to the same store fields and call `handle_search_trigger()`. + +**Implementation notes:** +- Place in `ae_idaa_comp__event_obj_qry.svelte` between the search input row and the + current filter rows. +- Optionally hide the existing checkbox/radio filter rows when the chips are present + (or keep both — the checkboxes serve as accessible form controls; the chips are + the primary visual interaction). +- On mobile, chips wrap to a second row naturally with `flex-wrap`. + +--- + +### 3. Language: "Searching..." vs "Loading..." + +**Current behavior:** The loading state always shows the same message: + +``` +🔄 Searching... +``` + +This appears on initial page load (when the user hasn't typed anything) and after +the user clicks Search or toggles a filter. The word "Searching" implies the user +initiated a search, which is misleading on initial page load — it's a cold cache +load, not an active search. + +**Proposed change:** Distinguish the two loading contexts: + +| Context | Message | +|---------|---------| +| Initial page load (no filters, no search text) | "Loading meetings..." | +| User clicked Search or toggled a filter | "Searching..." (keep current) | + +**Implementation notes:** +- In `+page.svelte` template around line 422, check whether `qry__fulltext_str` is + empty AND no filter checkboxes/radios are active. If so, show "Loading meetings..."; + otherwise show "Searching...". +- This is purely a label change — no logic changes needed. The condition can be the + same `has_active_filters` derived from item #1. +- Also update the list component's standalone loading state in + `ae_idaa_comp__event_obj_li.svelte` line 556-558 to use the same distinction. + +--- + +### 4. Filter row collapsing on mobile + +**Current behavior:** The query bar has three filter rows (Location checkboxes, +Type radios, Max/Sort selects) plus the search input row and the action button row. +Combined, this takes roughly 200px of vertical space. On mobile — especially inside +the Novi iframe on a phone — meeting cards are pushed below the fold. + +**Proposed change:** On viewports below `md` (768px), collapse the Location and Type +filter rows behind a "Filters ▾" toggle. The Max Results and Sort selects stay visible +since they're used frequently. The action buttons (Show Hidden, Create Meeting, Export) +move inside the collapsed panel or stay visible based on available width. + +``` +[Search input............................] [Search] + +[Filters ▾] [Max: 150 ▾] [Sort: Last Updated ▾] +``` + +Clicking "Filters ▾" expands the panel with Location checkboxes and Type radios. + +**Implementation notes:** +- Use a `$state` boolean `show_filters` (session-only, resets on page load). +- Wrap the filter rows in a `{#if show_filters}` block. +- Persist in `$idaa_sess.recovery_meetings.show_filters_expanded` if you want the + state to survive navigation within the module (same tab session). +- The Tailwind `md:` breakpoint works for the collapse trigger: `class:hidden={!show_filters}` + combined with `class:md:block` to always show on desktop. +- Test inside the Novi iframe — Bootstrap v3 may add its own `hidden` behavior on + `md` breakpoints that conflicts with Tailwind's. + +--- + +### 5. Human-readable schedule line on cards + +**Current behavior:** The meeting card displays weekdays as a flat, dense span list: + +``` +Sunday Monday Wednesday Friday +``` + +The timezone is shown separately as `(America/Chicago)`, and the start time is in +a compact `7:00 PM` format. These three pieces of information are visually separated +and require the member to mentally assemble the schedule. + +**Proposed change:** Render a computed one-liner that combines them: + +``` +🕐 Mondays, Wednesdays, Fridays at 7:00 PM CT +``` + +- Weekday names are built from the `weekday_*` booleans on the event object. +- "Mondays, Wednesdays" uses the range-joining convention (comma-separated, "and" + before the last item for two days; "Mondays through Fridays" for consecutive spans + of 3+ days). +- Timezone abbreviation is extracted from `timezone` (e.g., `America/Chicago` → `CT`, + `America/New_York` → `ET`). A small lookup table handles the common ones; fall back + to the raw timezone string for unknown values. +- If `timezone` is null/missing, fall back to the current flat display — don't + silently drop information. +- Today's meetings could optionally get a subtle "Today" badge or highlight (extra + polish, not required for the initial version). + +**Implementation notes:** +- Add a `$derived` in `ae_idaa_comp__event_obj_li.svelte` that computes the schedule + string from the event object's `weekday_*` fields, `recurring_start_time`, and + `timezone`. +- Helper function in `ae_util` for the weekday list → natural language string + (e.g., `['Monday', 'Wednesday', 'Friday']` → `"Mondays, Wednesdays, and Fridays"`). +- Helper function or small lookup for timezone → abbreviation. +- Fall back to the current flat display when `timezone` is missing to avoid losing + information. + +--- + +### 6. Show result count during search, not just after + +**Current behavior:** The result count badge ("Results: 25") only appears inside the +list wrapper component (`ae_idaa_comp__event_obj_li.svelte` line 98-108) when the +visible result list is non-empty. During loading, the user sees only a spinner with +no indication of how many meetings exist or what the search is operating on. + +**Proposed change:** Show a result count line at the page level (in `+page.svelte`) +that is always visible once the first search completes: + +``` +25 of 140 meetings ← after search completes, with result count + total +Searching 140 meetings... ← during initial load (cold cache, no prior result) +0 results for these filters ← empty but filters are active (ties into item #1) +``` + +**Implementation notes:** +- Lift the count display from the list component to `+page.svelte`, placed between + the query bar (`Comp__event_obj_qry`) and the list wrapper. +- The total count is available from the IDB fast path: after the initial unfiltered + search populates `db_events.event`, the total is `db_events.event.count()` (or + the count of records matching `account_id`). +- The visible count is `event_id_li.length` after search completes. +- Store the last known total in a `$state` variable so it persists across searches + (the total changes infrequently). Refresh the total on the first search after + page load. +- Format: `{visible} of {total} meetings` when filters/search are active; + `{visible} meetings` when browsing all (no active filters). +- During loading with no prior results: show "Loading meetings..." (from item #3) + rather than a count. + +--- + +### 7. "Live Now" and "Starting Soon" indicators + +**Current behavior:** Meetings are shown in a static list. To find one happening +now, a member must scan the "When" line of multiple cards and compare the time +to their own clock. + +**Proposed change:** Add a high-visibility badge or pulse indicator for meetings +that are currently in progress or starting in the next 15 minutes. + +- "LIVE NOW" (Green pulse badge) → if `current_time` is within `[start, start + 1 hour]`. +- "STARTING SOON" (Yellow badge) → if `current_time` is within `[start - 15 min, start]`. +- On the card, move the "Join Zoom" or "Join Jitsi" button to the very top or + make it significantly larger when the meeting is live. + +**Implementation notes:** +- Add a `$derived` state `is_live` and `is_starting_soon` to the card component. +- Requires calculating "current time in meeting's timezone" using `Temporal` or + a date helper. +- Ensure the pulse animation is subtle and respects `prefers-reduced-motion`. + +--- + +### 8. Local Timezone Conversion + +**Current behavior:** Meetings show their native timezone (e.g., "7:00 PM America/Chicago"). +The "Your TZ" line is currently a placeholder and doesn't perform conversion. + +**Proposed change:** Automatically detect the member's browser timezone and +show the converted time if it differs from the meeting's native timezone. + +``` +🕐 7:00 PM CT (8:00 PM ET your time) +``` + +**Implementation notes:** +- Use `Intl.DateTimeFormat().resolvedOptions().timeZone` to get the user's TZ. +- If `user_tz !== meeting_tz`, perform the conversion. +- If the conversion results in a different day (e.g., late night ET vs early morning Europe), + prefix with "Tomorrow at..." or "Yesterday at...". + +--- + +### 9. Favorites / "My Meetings" + +**Current behavior:** Members scan the full list every time they want to find +their regular weekly meeting. + +**Proposed change:** Add a "Star" icon to every meeting card. +- Starring a meeting adds it to a `favorites` list stored in `$idaa_loc`. +- Favorited meetings are pinned to the top of the list by default, regardless + of other sort orders. +- Add a "Favorites" filter toggle in the query bar to show *only* starred meetings. + +**Implementation notes:** +- Store as an array of `event_id` strings in `$idaa_loc.recovery_meetings.favorites`. +- Update the `visible_event_obj_li` derived in `+page.svelte` to prioritize + these IDs in the sort logic. + +--- + +### 10. "Add to Calendar" (iCal / Google) + +**Current behavior:** Members must manually create calendar events if they +want reminders for recurring meetings. + +**Proposed change:** Add an "Add to Calendar" dropdown button on the meeting +detail page (and optionally the card). +- Generates a `.ics` file or a Google Calendar URL with the recurring rule + (e.g., "Every Wednesday at 7pm"). +- Includes the meeting name, description, and the Zoom/Jitsi link in the location field. + +**Implementation notes:** +- Use a helper to generate RFC 5545 `RRULE` strings from the `weekday_*` and + `recurring_pattern` fields. +- Include the `attend_url` in the calendar event description for one-tap join + from phone lock screens. + +--- + +### 11. Geographic Search for In-Person Meetings + +**Current behavior:** The only location filter is a binary "Physical" checkbox. +Members must use fulltext search (e.g., "Chicago") to find local meetings. + +**Proposed change:** Add a "City/State" search input or a map view. +- When `Physical` is checked, show a "Near [City, State]" input. +- Map view (optional): A toggle to switch from "List" to "Map" view, plotting + meetings on a map using their `location_address_json` coordinates. + +**Implementation notes:** +- The `event` table has `location_address_json` which often contains city/state. +- Simple implementation: a city-picker dropdown populated from the distinct + `location_address_json->>'$.city'` values in the current result set. + +--- + +### 12. Prominent "Join" button for virtual meetings + +**Current behavior:** On each meeting card, the Zoom or Jitsi join link is rendered +as a small `btn-sm` inside the content area, visually equivalent to other label/value +rows. The "Meeting Details" button at the top of the card is rendered *larger* than the +join link — meaning the primary action for a member who wants to attend a meeting right +now is visually subordinate to a navigation link. + +The copy-to-clipboard button for the join link is gated behind `$ae_loc.manager_access`, +so regular members have no easy way to share the link with a sponsee. + +**Proposed change:** For virtual meetings, elevate the join button to a full-width +prominent CTA inside the card header area, directly below the meeting name and badges: + +``` +┌──────────────────────────────────────────────────┐ +│ 📅 Monday Night IDAA Discussion 🖥 Virtual │ +│ │ +│ [ 🎥 Join Zoom Meeting ] ← full-width │ +│ [ 📋 Meeting Details ] │ +└──────────────────────────────────────────────────┘ +``` + +- The Join button uses `preset-filled` (solid) styling; Meeting Details uses + `preset-outlined` (hollow). This makes the action hierarchy visually clear. +- On mobile especially, a full-width join button is much easier to tap than a + small inline link buried inside label rows. +- Replace manager-only clipboard with a Web Share API button for all members on + virtual meetings: `navigator.share({ title, url })` on mobile triggers the native + OS share sheet. Fall back to clipboard copy on desktop (where `navigator.share` + is often unavailable). This lets members easily send a meeting link to a sponsee. +- Passcode, if present, moves to the Meeting Details page — exposing it in the + list view is unnecessary and clutters the card. + +**Implementation notes:** +- In `ae_idaa_comp__event_obj_li.svelte`, move the Zoom/Jitsi attend block from + the `event__content` section up into the `ae_options` div (line ~200), rendered + only when `idaa_event_obj?.virtual` is true and an attend URL exists. +- Keep the existing small label/link in `event__content` as a fallback for when + the prominent button is not shown (non-virtual meetings may still have a URL). +- Web Share: `{#if navigator?.share}` guard; wrap in a try/catch (user cancels + the share sheet throws `AbortError`). +- The Live Now / Starting Soon badges from item #7, when implemented, should also + interact with this button — e.g., a pulsing green border when the meeting is live. + +--- + +### 13. "Today's Meetings" section at the top of the list + +**Current behavior:** The meeting list shows all results sorted by the selected sort +order. To find a meeting happening today, a member must scan every card's "When" line +and mentally compare it to the current day and time. There is no at-a-glance view +of what's available right now or later today. + +This is distinct from the "Live Now" badge in item #7 (which marks individual cards +after they're already displayed in a long list). This is a dedicated section pinned +above the main results. + +**Proposed change:** Add a collapsible "Today" section at the very top of the results +list that shows only meetings scheduled on the current day of the week, sorted by +start time: + +``` +▼ Today — Sunday, May 17 3 meetings + ┌─────────────────────────────────────────────────┐ + │ Sunday Serenity Discussion 7:00 AM ET 🔴Live │ + │ IDAA Sunday Big Book 2:00 PM CT │ + │ Sunday Night IDAA 8:00 PM ET │ + └─────────────────────────────────────────────────┘ + +All Meetings (140) + ... +``` + +- The section is collapsed by default if it's empty (no meetings today). +- Meetings in the "Today" section also appear in the main list below — this is a + quick-access shortcut, not a filter. +- Past meetings (start time has already passed today) are dimmed but still shown; + a meeting may still be in progress. + +**Implementation notes:** +- Compute current day-of-week in the browser: `new Date().getDay()` → 0=Sunday, 6=Saturday. + Map to the `weekday_*` boolean fields on the event object (e.g., day 0 → `weekday_sunday`). +- Filter `visible_event_obj_li` (already computed in the list wrapper) for items where + the matching `weekday_*` field is truthy. Sort by `recurring_start_time`. +- Store collapse state in `$idaa_sess.recovery_meetings.today_section_expanded` (session + only; default true so it's visible on first load). +- Renders correctly in the Novi iframe since it's just a filtered sub-list of existing + data — no additional API calls needed. +- If item #7 (Live Now) is implemented, the "Today" section naturally becomes the host + for the live/starting-soon badges, since that's where members will look first. + +--- + +### 14. Data freshness indicator *(low priority — deprioritized)* + +**Note:** Meeting records change infrequently — once established, a meeting's schedule, +type, and contact info are typically stable for months or years. The occasional update is +usually minor wording. Surfacing a freshness indicator for data this static would add +visual noise with very little member benefit. The existing error state (item #4 in the +bug fix, distinct "Unable to load meetings") and the escape-hatch cache-reset button +already handle the reliability-concern case. This idea is recorded for completeness but +is not recommended for implementation. + +--- + +### 15. "Confirmed meetings only" default filter + +**Current behavior:** All meetings are shown by default, including those with +`status === 'unknown'` (not yet confirmed by IDAA Central Office). The "Not Confirmed +by IDAA" warning badge on those cards is alarming-looking but does nothing to prevent +unverified meetings from dominating the list. + +There is currently no filter to hide unconfirmed meetings. Members have no choice but +to see them all. + +**Business rationale:** IDAA staff want meeting chairs to submit their meeting info for +verification. Defaulting to "confirmed only" creates a natural incentive: unconfirmed +meetings disappear from the default member view, which encourages chairs to contact +IDAA staff and get their meeting verified. It also gives members a cleaner, higher- +confidence list by default — they're not seeing meetings that may be outdated or +inactive. + +**Proposed change:** Add a `qry__confirmed` filter field with three states: + +| Value | Behavior | +|-------|----------| +| `'confirmed_only'` (default) | Hide meetings where `status === 'unknown'` | +| `'all'` | Show all meetings, confirmed and unconfirmed | +| `'unconfirmed_only'` | Show only unconfirmed (admin/staff use) | + +The filter UI shows a simple toggle in the query bar: +``` +[✓ Confirmed Only] ← default, shown as active chip or checkbox +``` + +When the member switches to "All", the unconfirmed meetings appear with their warning +badge (see item #15 for making that badge useful on mobile). + +For trusted/admin users, the default should remain `'all'` so staff can see the full +picture without having to change a setting. + +**Implementation notes:** +- Add `qry__confirmed: 'confirmed_only' | 'all' | 'unconfirmed_only'` to + `$idaa_loc.recovery_meetings` defaults, defaulting to `'confirmed_only'`. + Trusted users default to `'all'`. +- Apply the filter in both the IDB fast path (`db_events.event.filter()`) and the + API revalidation secondary filter in `handle_search_refresh`. IDB: check + `ev.status !== 'unknown'` when `qry__confirmed === 'confirmed_only'`. API: same + post-fetch client-side filter. +- Pass `qry__confirmed` to `events_func.search__event` if the API supports a + `status` filter param; otherwise handle it client-side only. +- The `no_results_no_filters` derived (used for the escape-hatch button) should NOT + treat `qry__confirmed === 'confirmed_only'` as an active filter — it's the default + state, not a narrowing choice the member made. Only count it as a filter if the + member explicitly switched it to `'all'` or `'unconfirmed_only'`. +- Add a count badge to the toggle: "Confirmed Only (132 of 140)" so members can see + how many unconfirmed meetings exist without having to switch the filter. + +--- + +### 16. "Not Confirmed" status — inline explanation on mobile + +**Current behavior:** Meetings with `status === 'unknown'` show a warning badge: +`⚠ Not Confirmed by IDAA ⚠`. The badge has a `title` attribute with a full explanation +(~2 sentences). Title tooltips are invisible on mobile — tapping the badge does nothing. +Members on phones see a alarming-looking warning with no explanation of what it means +or what they should do. + +**Proposed change:** Make the badge tappable. On tap (or hover on desktop), show an +inline explanation panel directly below the badge: + +``` +⚠ Not Confirmed by IDAA [?] + + ↓ (on tap) + + This meeting has not been confirmed by IDAA Central Office. + Please reach out to the chair for current information. + If this meeting is active, email info@idaa.org to confirm it. + [✕ Close] +``` + +**Implementation notes:** +- Add a `$state show_unconfirmed_info = false` per card (scoped to the `{#each}` block). +- Replace the `title` attribute with an `onclick` toggle that sets `show_unconfirmed_info`. +- The explanation renders in a `{#if show_unconfirmed_info}` block directly below the + badge row — a simple div with a rounded border and the existing tooltip text. +- The `mailto:info@idaa.org` link in the explanation is already in the tooltip text; + making it a real clickable link here rather than plain text in a tooltip is a direct + improvement for mobile members who want to report a confirmed meeting. +- This pattern also applies to other `title`-only tooltips on the page if they appear. +- Note: with item #15 defaulting to "confirmed only", most members will never encounter + this badge unless they switch to the "All" view. The inline explanation is still worth + implementing for that audience, but the default filter reduces how often it's seen. + +--- + +## Notes + +- The fulltext search (`qry__fulltext_str`) searches against the `default_qry_str` + field, which is a server-side composite that already includes contact info, day-of-week + text, meeting type, location, and other metadata. The placeholder text in the search + input is accurate — it genuinely searches contacts and schedule information despite + those fields being stored in separate columns. +- All changes must render correctly inside the Novi iframe context (Bootstrap v3.4.1 + CSS conflicts — see `CLIENT__IDAA_and_customized_mods.md` for known issues). +- Mobile testing should cover Android Chrome specifically — the original "no meetings + found" bug disproportionately affected mobile users with intermittent connections. + diff --git a/documentation/TODO__Agents.md b/documentation/TODO__Agents.md index e3aae628..bf9c3dad 100644 --- a/documentation/TODO__Agents.md +++ b/documentation/TODO__Agents.md @@ -115,33 +115,40 @@ reactivity — only effects that actually read a changed field re-run. --- -### [Stores] IDB Content Version System (post June 10) -Scaffold added to `store_versions.ts` (`IDB_CONTENT_VERSIONS` constant) — values defined but -**not yet wired**. The mechanism mirrors `AE_LOC_VERSION` but targets Dexie table contents -rather than localStorage keys. +### [Stores] IDB Content Version System +Scaffolded in `store_versions.ts` (`IDB_CONTENT_VERSIONS` constant + `check_and_clear_idb_table()` +helper) and `core__idb_dexie.ts` (`check_and_clear_idb_tables()` batch helper). Mirrors +`AE_LOC_VERSION` but targets Dexie table contents rather than localStorage keys. -**Why:** `db_save_ae_obj_li__ae_obj` uses a `properties_to_save` whitelist. When that whitelist -changes (e.g. adding/removing a stored field), existing cached IDB records are stale but never -automatically cleared. Users see the old shape until a record is individually refreshed. +**Currently active:** `journals.journal_entry` (db_journals.ts), `events.event` (IDAA layout). +All other tables are defined but not yet wired. -**How it will work:** -- Each `db_*.ts` calls a helper (`core__idb_dexie.ts`) on open that checks a `_meta` IDB table -- If stored version ≠ `IDB_CONTENT_VERSIONS[module][table]`, clear the table + update `_meta` -- SWR repopulates from API on next access (same as any cold-start) +**Real-world impact:** Stale IDB records from a `properties_to_save` change were the root cause +of the IDAA Recovery Meetings "no meetings found" bug — a ~1-year unresolved issue (2025–2026). +Fixed 2026-05-16 by wiring `events.event` into the IDAA layout and bumping its version to 2. +See `BOOTSTRAP__AI_Agent_Quickstart.md` mistake #13 for the full postmortem. + +**How it works:** +- `check_and_clear_idb_table(db_table, 'module', 'table')` reads a localStorage key with the + expected version from `IDB_CONTENT_VERSIONS` +- On mismatch (or missing key), the Dexie table is cleared and the key is updated +- SWR repopulates from API on next access — no explicit reload needed +- Cost on version match: one `localStorage.getItem()` — effectively free - Bump a table's version in `IDB_CONTENT_VERSIONS` when `properties_to_save` changes shape **IDAA consideration:** IDAA tables are already cleared by `indexedDB.deleteDatabase()` on sign-out/auth failure in `(idaa)/+layout.svelte`. The content version check is a *complementary* deploy-time reset, not -a replacement. When wiring IDAA tables, ensure: (a) the check only runs on IDB open, not -mid-session; (b) the `_meta` table is included in the `deleteDatabase()` wipe scope. +a replacement. **Tasks:** - [x] Write `check_and_clear_idb_tables()` helper in `core__idb_dexie.ts` (2026-05-14) -- [x] Wire helper into `db_journals.ts` (pilot — `journal_entry: 2` clears stale content_md_html on first load) (2026-05-14) -- [ ] Roll out to `db_events.ts`, `db_core.ts` +- [x] Wire helper into `db_journals.ts` (pilot — `journal_entry: 2` cleared stale content_md_html) (2026-05-14) +- [x] Wire `events.event` into IDAA layout `(idaa)/+layout.svelte` + bump version to 2 (2026-05-16) +- [ ] Roll out to `db_events.ts` (module-wide: session, presenter, badge, device, location, file) +- [ ] Roll out to `db_core.ts` (site_domain, person, user) - [ ] Roll out to IDAA modules (`db_posts.ts`, `db_archives.ts`) — verify auth-wipe interaction first -- [ ] Update `store_versions.ts` comment from "NOT YET ACTIVE" to document the active mechanism +- [ ] Consolidate the two `check_and_clear_idb_table*` helpers (single-table in `store_versions.ts`, batch in `core__idb_dexie.ts`) ### [Stores] Refactor — Phase 2c (deferred) Phases 1, 2a, 2b are complete (see ✅ Completed below). One phase remaining: