- Update default qry__limit to 100 in idaa_loc - Add 75 to limit_steps in recovery meetings query component - Bump AE_IDAA_LOC_VERSION to 2 to apply changes to existing users - Update IDAA documentation and TODO__Agents.md with SQL optimization task - Mark implemented UI/UX ideas as done in documentation
570 lines
27 KiB
Markdown
570 lines
27 KiB
Markdown
# 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 — ✅ Implemented 2026-05-18
|
||
|
||
**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.
|
||
|
||
**Implemented:** `src/routes/idaa/(idaa)/recovery_meetings/+page.svelte`. `has_active_filters`
|
||
derived checks `qry__physical`, `qry__virtual`, `qry__type`, and `qry__fulltext_str`. Empty
|
||
state branches on `has_active_filters`: active filters → guided message + "Clear Filters"
|
||
button; no active filters → existing escape-hatch flow (timed "Refresh Meeting Cache" after
|
||
8 seconds).
|
||
|
||
---
|
||
|
||
### 2. Quick-filter chips below the search bar — ✅ Implemented 2026-05-18
|
||
|
||
**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`.
|
||
|
||
**Implemented:** `ae_idaa_comp__event_obj_qry.svelte`. Chips replaced the old
|
||
checkbox/radio/select UI entirely rather than layering on top. Two chip rows:
|
||
Row 1 — My Meetings (first), Virtual, In-Person. Row 2 — All / IDAA / Caduceus /
|
||
Family Recovery type chips. Cycling sort button replaces separate sort options
|
||
(see item below). Max Results uses a +/− stepper. Sort and max are in a third
|
||
row below the chips, inside the same `<form>` constraint.
|
||
|
||
---
|
||
|
||
### 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" — ✅ Implemented 2026-05-18
|
||
|
||
**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.
|
||
|
||
**Implemented:** Star toggle on the `[event_id]` detail page
|
||
(`src/routes/idaa/(idaa)/recovery_meetings/[event_id]/+page.svelte`).
|
||
"My Meetings" filter chip is first in the filter chip row on the list page.
|
||
**Implementation differs from proposal:** favorites stored server-side in a
|
||
`data_store` record (code: `idaa_meetings_favorites`) as a UUID-keyed JSON map
|
||
rather than in `$idaa_loc` — this means favorites persist across browsers and
|
||
devices without Novi write capability. Pinning favorites to the top of the list
|
||
was not implemented; the filter chip shows only favorites instead.
|
||
|
||
---
|
||
|
||
### 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.
|
||
|
||
---
|
||
|
||
---
|
||
|
||
### 17. Cycling sort button — ✅ Implemented 2026-05-18
|
||
|
||
**Problem:** Three separate sort chip buttons (Last Updated / Name A→Z / Name Z→A) took
|
||
too much horizontal space and caused layout bounce as the selected chip changed width.
|
||
|
||
**Implemented:** Single cycling button in `ae_idaa_comp__event_obj_qry.svelte`.
|
||
Clicking advances through `sort_modes` array (Last Updated → Name A→Z → Name Z→A → repeat)
|
||
using `$derived` index + `cycle_sort()` function. Button has `min-w-36` to prevent bounce.
|
||
Icon changes per mode (fa-clock / fa-sort-alpha-down / fa-sort-alpha-up-alt). A small
|
||
fa-redo icon indicates it's a cycling control.
|
||
|
||
---
|
||
|
||
### 18. Collapsible "Meeting Info" data store panel — ✅ Implemented 2026-05-18
|
||
|
||
**Problem:** The `Element_data_store` panel (code: `recovery_meetings_info`) displays
|
||
between the filter bar and the meeting results list. Once a member has read it, it
|
||
consumes vertical space on every page load and pushes results below the fold, especially
|
||
in the Novi iframe on mobile.
|
||
|
||
**Implemented:** Toggle button wrapping the `<Element_data_store>` in
|
||
`src/routes/idaa/(idaa)/recovery_meetings/+page.svelte`. Button shows
|
||
"Meeting Info" with a chevron (up = expanded, down = collapsed). Collapse state
|
||
persisted in `$idaa_loc.recovery_meetings.ds_info_collapsed` (localStorage) so the
|
||
user's preference survives page reloads. New field added to `idaa_local_data_struct`
|
||
in `ae_idaa_stores.ts` — no version bump needed (existing users without the field
|
||
get `undefined` which is falsy = expanded, the correct default).
|
||
|
||
---
|
||
|
||
## 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.
|
||
|