Proactively re-injects 'key' (site access key) and 'uuid' (Novi token) into 'Open Externally' and 'Copy Link' URLs on the Video Conferences page. This prevents authentication failures when members open meetings in a new browser tab after SvelteKit internal navigation has dropped the bootstrap parameters. Updated CLIENT__IDAA_and_customized_mods.md to document the requirement for these keys in breakout URLs.
877 lines
46 KiB
Markdown
877 lines
46 KiB
Markdown
# CLIENT: IDAA — International Doctors in Alcoholics Anonymous
|
||
|
||
**Client:** International Doctors in Alcoholics Anonymous (IDAA)
|
||
**Module Path:** `src/routes/idaa/`
|
||
**State Stores:** `src/lib/stores/ae_idaa_stores.ts`
|
||
**Last Updated:** 2026-05-18 (Default limit and stepper update)
|
||
|
||
---
|
||
|
||
## ⚠️ CRITICAL PRIVACY REQUIREMENT
|
||
|
||
**ALL IDAA content is PRIVATE. Authentication is required for ALL modules.**
|
||
|
||
IDAA serves a sensitive population — physicians in addiction recovery. Content exposure to the public is a **severe security failure** and a violation of member trust.
|
||
|
||
- A previous AI agent accidentally exposed IDAA Bulletin Board content publicly. This must never happen again.
|
||
- Every route, component, and API call in this module must enforce authentication.
|
||
- When in doubt: **it's private**.
|
||
|
||
**Required access level:** `trusted_access` or higher for all IDAA content.
|
||
|
||
---
|
||
|
||
## What IDAA Is
|
||
|
||
IDAA is a private membership organization for physicians in recovery. They use the Aether platform for:
|
||
- A private document archive (historical materials, meeting records)
|
||
- A members-only bulletin board (community posts and discussion)
|
||
- A searchable directory of in-person and virtual recovery meetings
|
||
- Video conferencing (Jitsi-based)
|
||
|
||
IDAA's Aether instance is embedded as an **iframe inside their existing Novi-powered website** (`idaa.org`). Novi is their external Association Management System (AMS) — it handles membership records and authentication. Aether receives the member context via URL parameters on iframe load.
|
||
|
||
### Breakout Links and Iframe Persistence
|
||
|
||
Members often need to open Jitsi meetings outside the Novi iframe (e.g., for full-screen features or on mobile). These are referred to as **Breakout Links**.
|
||
|
||
- **The Problem:** SvelteKit client-side navigation within the iframe often drops "bootstrap" query parameters like `?key=...` (site access key) and `?uuid=...` (Novi identity token).
|
||
- **The Requirement:** When a member breaks out of the iframe into a new browser tab, these keys **must** be present in the URL. Without them, the member will hit the site-domain gate or the IDAA auth gate and see "Access Denied."
|
||
- **The Solution:** The Video Conferences page uses a derived `breakout_url` that proactively re-injects the missing `key` (from `$ae_loc.allow_access`) and `uuid` (from `$idaa_loc.novi_uuid`) before generating the external link.
|
||
|
||
**Example Breakout URL:**
|
||
`https://client.oneskyit.com/idaa/video_conferences?uuid=...&key=...&room=...`
|
||
|
||
---
|
||
|
||
## Architecture: Composite Module
|
||
|
||
IDAA is **not a standalone module** — it is a **composition of three existing Aether modules**, access-gated and branded for the IDAA client.
|
||
|
||
| IDAA Feature | Aether Module Used | Library |
|
||
|---|---|---|
|
||
| Archives | Archives module | `src/lib/ae_archives/` |
|
||
| Bulletin Board (BB) | Posts module | `src/lib/ae_posts/` |
|
||
| Recovery Meetings | Events module (repurposed) | `src/lib/ae_events/` |
|
||
| Video Conferences | Jitsi (external embed) | External |
|
||
|
||
There is **no `src/lib/ae_idaa/`** library directory. IDAA-specific state and logic lives in `ae_idaa_stores.ts` and the route components only.
|
||
|
||
This design allows the IDAA module to be removed or updated without touching core modules.
|
||
|
||
---
|
||
|
||
## Route Structure
|
||
|
||
```
|
||
src/routes/idaa/
|
||
├── +layout.svelte # Root layout: Novi UUID extraction, iframe height sync
|
||
├── (idaa)/
|
||
│ ├── +layout.svelte # Access gate: blocks render if unauthorized; permission upgrade
|
||
│ ├── +page.svelte # IDAA dashboard — 3-module selector
|
||
│ ├── archives/ # Archives submodule
|
||
│ │ ├── +page.svelte # Archive list (LiveQuery)
|
||
│ │ └── [archive_id]/
|
||
│ │ ├── +page.svelte # Archive detail + content viewer
|
||
│ │ ├── ae_idaa_comp__archive_obj_id_view.svelte
|
||
│ │ ├── ae_idaa_comp__archive_obj_id_edit.svelte
|
||
│ │ ├── ae_idaa_comp__archive_content_obj_id_edit.svelte
|
||
│ │ └── ae_idaa_comp__modal_media_player.svelte
|
||
│ ├── bb/ # Bulletin Board (Posts) submodule
|
||
│ │ ├── +page.svelte # Post list (LiveQuery, archive-filtered)
|
||
│ │ └── [post_id]/
|
||
│ │ ├── +page.svelte # Post detail + comments
|
||
│ │ ├── ae_idaa_comp__post_obj_id_view.svelte
|
||
│ │ ├── ae_idaa_comp__post_obj_id_edit.svelte
|
||
│ │ └── ae_idaa_comp__post_comment_obj_id_edit.svelte
|
||
│ ├── recovery_meetings/ # Recovery Meetings (Events repurposed)
|
||
│ │ ├── +layout.ts # Layout loader (auth, stores)
|
||
│ │ ├── +layout.svelte # Layout wrapper
|
||
│ │ ├── +page.svelte # Meeting list + search filters
|
||
│ │ ├── ae_idaa_comp__event_obj_li_wrapper.svelte # List container/modal host
|
||
│ │ ├── ae_idaa_comp__event_obj_li.svelte # Individual list item card
|
||
│ │ ├── ae_idaa_comp__event_obj_qry.svelte # Query/filter bar
|
||
│ │ ├── ae_idaa_comp__event_obj_id_view.svelte # Meeting detail (read-only)
|
||
│ │ ├── ae_idaa_comp__event_obj_id_edit.svelte # Meeting edit form (active)
|
||
│ │ └── [event_id]/
|
||
│ │ ├── +page.svelte # Meeting detail page — renders view OR edit based on session flag
|
||
│ │ └── +page.ts
|
||
│ ├── video_conferences/ # Jitsi video conference integration
|
||
│ └── jitsi_reports/ # Jitsi meeting activity log report (trusted_access only)
|
||
```
|
||
|
||
> **Note:** Recovery Meetings has **two UI entry points**:
|
||
> 1. **Modal pattern** (primary list flow) — list, view, and edit components live at `recovery_meetings/`
|
||
> level, toggled via `$idaa_sess.recovery_meetings` session flags (`show__modal_view`, `show__modal_edit`).
|
||
> 2. **Direct page** (`[event_id]/+page.svelte`) — navigating to `/idaa/recovery_meetings/<id>` renders
|
||
> the same view/edit components gated by `$idaa_sess.recovery_meetings.edit__event_obj`.
|
||
>
|
||
> Both patterns use `ae_idaa_comp__event_obj_id_edit.svelte`. The edit form clears **both**
|
||
> `show__modal_edit` and `edit__event_obj` on save/cancel so it works correctly from either entry point.
|
||
|
||
---
|
||
|
||
## Authentication: Novi UUID System
|
||
|
||
IDAA members do not log in through Aether — they log in through Novi (idaa.org), and Novi passes their identity to the Aether iframe via URL parameters.
|
||
|
||
### URL Parameters (on iframe load)
|
||
```
|
||
?uuid=<36-char-uuid>
|
||
&iframe=true
|
||
&key=<site-access-key>
|
||
```
|
||
|
||
> **Security note (2026-03-09):** The iframe HTML files previously also passed `email` and `full_name`
|
||
> via URL params. These were unverifiable claims that could be spoofed via URL. They have been removed.
|
||
> The SvelteKit layout now verifies identity via the Aether server-side Novi proxy — the Novi API
|
||
> call originates from the server, not the member's browser.
|
||
> See "Iframe Integration" → "Novi UUID Verification Flow" below.
|
||
|
||
### Verification Flow (`(idaa)/+layout.svelte`)
|
||
|
||
When a `uuid` param is present in the URL, the layout performs an **async call to the Aether server-side endpoint** (`GET /v3/action/idaa/novi_member/{uuid}`), which proxies to Novi server-to-server:
|
||
|
||
1. The UUID actually exists in Novi's system (prevents fake/crafted UUIDs)
|
||
2. Gets verified name and email — these can't be forged via URL
|
||
3. Sets `$idaa_loc.novi_uuid`, `$idaa_loc.novi_email`, `$idaa_loc.novi_full_name`
|
||
4. Sets `$idaa_loc.novi_verified = true` on success
|
||
|
||
A `novi_verifying` UI state prevents the "Access Denied" screen from flashing during the API round-trip.
|
||
|
||
**All or nothing:** If the Novi API key is not configured on the site, or the verification call fails, access is denied. There is no URL-param fallback.
|
||
|
||
**Required `site_cfg_json` fields:**
|
||
```json
|
||
{
|
||
"novi_idaa_api_key": "Base64-encoded-key-from-Novi",
|
||
"novi_api_root_url": "https://www.idaa.org/api", // optional, this is the default
|
||
"novi_admin_li": ["uuid-1", "uuid-2"],
|
||
"novi_trusted_li": ["uuid-3", "uuid-4"],
|
||
"novi_idaa_group_guid_li": ["group-uuid"] // Jitsi moderators only
|
||
}
|
||
```
|
||
|
||
## Novi API Integration — How We Use It
|
||
|
||
This section documents the exact way Aether uses the Novi API for the IDAA integration so future maintainers can recreate the flow.
|
||
|
||
- **Purpose:** Verify a Novi-provided `uuid` received via iframe URL parameters, obtain a verified name/email from Novi, and upgrade Aether permissions for that session when appropriate.
|
||
|
||
- **All-or-nothing policy:** If the Novi API key is not configured or the verification call fails, the Novi-based access path is denied. The layout explicitly prevents child routes from rendering while verification is in-flight to avoid flashing "Access Denied".
|
||
|
||
- **Rate limits (Novi API):** 20 calls/second · 600 calls/minute · 100,000 calls/day. The Aether backend handles 429 responses; the frontend receives a `429` and retries once after 10 seconds. The 12-hour TTL cache on successful verification (Redis server-side + `$idaa_loc` client-side) prevents repeated calls during normal use. A `503` (Novi unreachable) is auto-retried once after 3 seconds before surfacing an error to the user.
|
||
|
||
### Verification Flow (implementation)
|
||
|
||
1. The IDAA iframe loads Aether pages with a `?uuid=<uuid>&iframe=true` param.
|
||
2. When the `uuid` param is present the IDAA layout calls the Aether server-side proxy:
|
||
|
||
```js
|
||
// simplified
|
||
fetch(`${aether_api_url}/v3/action/idaa/novi_member/${uuid}`, {
|
||
method: 'GET',
|
||
headers: {
|
||
'x-aether-api-key': api_key,
|
||
'x-account-id': account_id
|
||
}
|
||
})
|
||
// Aether calls Novi server-to-server; member's browser IP is never in the Novi call path.
|
||
```
|
||
|
||
3. On success (`200`), the layout reads `data.full_name` and `data.email` from the response and writes them to the IDAA store, marking verification success.
|
||
|
||
4. The layout then determines a target Novi permission level (`authenticated`, `trusted`, `administrator`) by checking configured UUID lists (`novi_trusted_li`, `novi_admin_li`) and upgrades the Aether session only if the Novi-derived level is higher than the current global level.
|
||
|
||
5. The layout also resets a few IDAA-specific query defaults (BB filters, etc.) to safe values after verification.
|
||
|
||
### Key `site_cfg_json` fields and where they are used
|
||
|
||
- **`novi_idaa_api_key`**: Base64-encoded Basic auth token provided by Novi. Used by the Aether **server** to authenticate against Novi — the frontend never touches the key itself. The frontend checks only for its *presence* in `site_cfg_json` as a guard meaning "IDAA is configured for this site". If missing, Novi-based access is denied.
|
||
|
||
- **`novi_api_root_url`**: Optional Novi API root (defaults to `https://www.idaa.org/api`). Read by the Aether server, not the frontend.
|
||
|
||
- **`novi_admin_li`**: Array of UUIDs treated as administrators for IDAA. Merged into `$idaa_loc.novi_admin_li` during layout initialization and used to set `administrator` level.
|
||
|
||
- **`novi_trusted_li`**: Array of UUIDs treated as trusted members. Merged into `$idaa_loc.novi_trusted_li` and used to set `trusted` level.
|
||
|
||
- **`novi_jitsi_mod_li` / `novi_idaa_group_guid_li`**: Lists used to map Jitsi moderator privileges and group GUIDs (where applicable).
|
||
|
||
- **`novi_bb_base_url`**: (optional) Base URL used to build links for Bulletin Board notification emails.
|
||
|
||
- **`jitsi_exclude_uuids`**: (optional) Array of Novi UUIDs to exclude from Jitsi Reports.
|
||
This is the canonical staff/test filter. UUIDs are matched case-insensitively against
|
||
`final_participants[].novi_uuid` when present. Example: `["uuid-1", "uuid-2"]`.
|
||
|
||
- **`jitsi_known_meetings`**: (optional) Array of meeting names / room names to keep in the report.
|
||
When this list is non-empty, only matching `room_name` values are shown. Matching is
|
||
case-insensitive.
|
||
|
||
- **Legacy fallback:** `jitsi_exclude_names` is still honored for older configs, but it should be
|
||
migrated to UUIDs.
|
||
|
||
- **Email config values** (`noreply_email`, `noreply_name`, `admin_email`, `admin_name`): used by functions that send notification emails (BB posts, comments, recovery meetings).
|
||
|
||
### Stores / runtime fields set by verification
|
||
|
||
- `$idaa_loc.novi_uuid` — the verified UUID
|
||
- `$idaa_loc.novi_email` — verified email (normalized)
|
||
- `$idaa_loc.novi_full_name` — display name built from Novi fields
|
||
- `$idaa_loc.novi_verified` — boolean flag indicating successful verification
|
||
- `$idaa_loc.novi_admin_li`, `$idaa_loc.novi_trusted_li` — merged lists from site config
|
||
|
||
These fields are read elsewhere in the IDAA UI to enable flows for verified users (for example: creating meetings, posting comments, or auto-populating contact info in notifications).
|
||
|
||
### Where in the codebase this runs (examples)
|
||
|
||
- The Novak UUID verification and permission-upgrade logic is implemented in the IDAA layout: [src/routes/idaa/(idaa)/+layout.svelte](src/routes/idaa/(idaa)/+layout.svelte).
|
||
- UI elements that permit actions for verified Novi users or trusted members check these values. Example: the "Create New Meeting" button allows creation when either the session has `trusted_access` or a `novi_uuid` is present — see [src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_qry.svelte](src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_qry.svelte).
|
||
|
||
### Security notes and operational guidance
|
||
|
||
- The previous implementation leaked `email` and `full_name` via URL params — this was removed because those values are unauthenticated and can be spoofed.
|
||
- The API key is sensitive — keep it only in site `cfg_json` and do not expose it in client-side code or public repositories. The key is read and used exclusively by the Aether backend; it is never sent to the browser.
|
||
- If Novi changes their customer API shape, update `app/methods/idaa_novi_verify_methods.py` in the backend (display name/email normalization) and this documentation.
|
||
|
||
If you need a compact checklist for re-creating this flow in another integration, ask and I will add a small runbook with exact request/response field mappings.
|
||
|
||
### ~~Planned: Server-Side Novi Verification~~ ✅ Implemented (2026-05-19)
|
||
|
||
**Problem solved:** The previous client-side Novi API call originated from the member's browser.
|
||
Hotel/conference WiFi, VPNs, corporate/hospital networks, and Cloudflare IP reputation filtering
|
||
could block these calls and produce false "Access Denied" for legitimate members.
|
||
|
||
**Solution implemented:** A FastAPI endpoint proxies the Novi call server-to-server
|
||
(Aether → Novi), with Redis caching. Members' browser IPs are no longer in the call path.
|
||
|
||
**Endpoint:** `GET /v3/action/idaa/novi_member/{uuid}`
|
||
- Standard Aether auth headers (`x-aether-api-key`, `x-account-id`)
|
||
- Server reads `novi_idaa_api_key` / `novi_api_root_url` from site `cfg_json`
|
||
- Redis cache: `idaa:novi_member:{uuid}` — 4-hour TTL, only 200s cached
|
||
- `404` results never cached (recently-joined members not incorrectly denied)
|
||
|
||
**Frontend:** `verify_novi_uuid()` in `(idaa)/+layout.svelte` now calls this endpoint with
|
||
standard Aether headers. The `novi_idaa_api_key` is still checked for presence in
|
||
`site_cfg_json` as a proxy for "is IDAA configured for this site" (server holds the key itself).
|
||
|
||
**Full API spec:** `GUIDE__AE_API_V3_for_Frontend.md` §12.
|
||
|
||
### Permission Levels (Ascending)
|
||
| Level | Condition | Access |
|
||
|---|---|---|
|
||
| Anonymous | No UUID, unrecognized UUID, or verification failure | No access |
|
||
| Authenticated | UUID verified against Novi API | View own content, limited actions |
|
||
| Trusted | Verified UUID in `novi_trusted_li` | Full member access to all IDAA content |
|
||
| Administrator | Verified UUID in `novi_admin_li` | Full access + edit/manage |
|
||
|
||
`novi_trusted_li` and `novi_admin_li` are managed in Aether site config (not in Novi directly).
|
||
|
||
## Identity Linkage: The Novi UUID Rule (Triple Linkage)
|
||
|
||
**CRITICAL ARCHITECTURAL STANDARD:**
|
||
All member-generated content in the IDAA module MUST be explicitly linked to the member's Novi UUID via the `external_person_id` field. This linkage is the primary mechanism for ownership, edit permissions, and auditing.
|
||
|
||
### 1. Mandatory at Creation
|
||
Linkage MUST happen at the moment of initial object creation (POST). Shell records created without an `external_person_id` are considered orphaned and may be inaccessible to the creator.
|
||
|
||
### 2. Triple Linkage Scope
|
||
The following objects require mandatory `external_person_id` linkage:
|
||
- **Recovery Meetings** (`ae_Event`)
|
||
- **Bulletin Board Posts** (`ae_Post`)
|
||
- **Post Comments** (`ae_PostComment`)
|
||
|
||
### 3. Implementation Patterns
|
||
- **Buttons:** Creation buttons (e.g., "Create New Meeting") must include `external_person_id: $idaa_loc.novi_uuid` in their initial `create_ae_obj` payload.
|
||
- **Edit Forms:** Edit components must provide robust fallbacks to `$idaa_loc.novi_uuid` for new or incomplete records, ensuring identity is captured even if the initial creation call was narrow.
|
||
- **Identity Sync:** Along with the UUID, `full_name` and `email` should also be synced from `$idaa_loc` to provide human-readable context in notifications and admin views.
|
||
- **Race Condition Defense:** `$idaa_loc` may be briefly null on mount before the store hydrates from localStorage. Creation buttons and edit submit handlers must scavenge identity directly from `localStorage.getItem('ae_idaa_loc')` as a fallback when the store value is missing.
|
||
|
||
### 4. Staff Editing Rules (IDAA Trusted/Admin Staff)
|
||
|
||
IDAA staff have their own Novi UUID. When they edit member content, their identity must **not** overwrite the member's `external_person_id`, `full_name`, or `email`.
|
||
|
||
| Content Type | `external_person_id` for staff | `full_name` / `email` for staff |
|
||
|---|---|---|
|
||
| BB Post | **Readonly** (unless `administrator_access`) — member's UUID preserved | Same — rendered from existing record, not staff identity |
|
||
| Post Comment | **Preserved** — form state initializes from existing record first | Same |
|
||
| Recovery Meeting | **Intentionally editable** for trusted staff — staff can reassign meeting ownership | Contact 1 renders from existing `contact_li_json[0]` first; staff identity only fills if blank |
|
||
|
||
The fallback to `$idaa_loc.novi_uuid` (the current user's UUID) only fires when the record has **no** existing `external_person_id`. For any record properly created after the 2026-04-07 triple-linkage enforcement, this fallback should never be reached.
|
||
|
||
### 5. Recovery Meetings — Contact 1 Convention
|
||
|
||
In 99% of cases, **Contact 1 should be the same person linked via `external_person_id`** — the IDAA member who owns and runs the meeting. These are two separate fields:
|
||
|
||
- `external_person_id` — the ownership/identity link (Novi UUID). Determines who may edit the meeting.
|
||
- `contact_li_json[0]` — the displayed contact info (name, email, phone). Shown to members searching for meetings.
|
||
|
||
They are expected to match but are set independently. Members unlock Contact 1 via confirm dialog if they need to list a different contact. Staff can edit both fields directly.
|
||
|
||
### Permission Upgrade Rule
|
||
```
|
||
// RULE: Only UPGRADE to Novi-based permissions, NEVER downgrade.
|
||
// If a user has a higher global Aether role (site manager, super),
|
||
// their global role is preserved and not overwritten by Novi auth.
|
||
```
|
||
|
||
This ensures that OSIT staff with `super` or `manager` roles retain full access regardless of Novi UUID status.
|
||
|
||
### Non-Novi Sign-in Paths (unaffected)
|
||
- **User/Pass or Auth Link:** No `uuid` in URL → layout Novi block does not run
|
||
- **Shared Passcode:** No `uuid` in URL → layout Novi block does not run
|
||
|
||
### Access Gate (`(idaa)/+layout.svelte`)
|
||
The inner layout blocks ALL rendering if the user is not authorized:
|
||
- `novi_verifying = true` → "Verifying identity..." spinner (message updates during retry)
|
||
- `verify_error_type === 'rate_limited'` → yellow "Identity Verification Unavailable" panel with:
|
||
- **Try Again** — calls `handle_verify_retry()` (respects retry_count, waits 10 s before re-calling Novi)
|
||
- **Clear Cache & Reload** — clears IDB + localStorage + sessionStorage, then reloads
|
||
- **Full Reset** — same clear but also navigates to `/` with `invalidateAll`
|
||
- `verify_error_type === 'api_error'` → same yellow panel (API returned non-2xx, not a rate limit)
|
||
- Verification failed or no UUID → "Access Denied" error page
|
||
- Access check runs before any child routes render
|
||
|
||
---
|
||
|
||
## Module 1: Archives
|
||
|
||
**Route:** `/idaa/archives/`
|
||
**Library:** `src/lib/ae_archives/`
|
||
**Types:** `ae_Archive`, `ae_ArchiveContent`
|
||
|
||
The Archives module stores IDAA historical content — meeting records, conference proceedings, historical documents, and media.
|
||
|
||
### Object Types
|
||
|
||
**Archive (Container)**
|
||
- Represents a collection (e.g., "2019 Conference Proceedings")
|
||
- Key fields: `name`, `description`, `original_datetime`, `original_location`, `archive_on`
|
||
- `archive_on` — date when this archive collection is auto-hidden (scheduled visibility control)
|
||
|
||
**ArchiveContent (Items)**
|
||
- Individual items within an archive
|
||
- Supports multiple content types: `'text'`, `'file'`, `'url'`, `'video'`
|
||
- Key fields: `archive_content_type`, `content_html`, `url`, `hosted_file_id`, `duration`
|
||
- Video/audio content has a dedicated media player component
|
||
|
||
### Database (Dexie)
|
||
```
|
||
db_archives.archive — Archive containers
|
||
db_archives.content — Archive content items (linked by archive_id)
|
||
```
|
||
|
||
### Demo / Test IDs
|
||
- Archive: `nAA2bHLv8RK` (id: 1) "One Sky Test Archive"
|
||
- Archive Content: `UjKzrk-GKu5` (id: 1) "Hosted File Test"
|
||
|
||
---
|
||
|
||
## Module 2: Bulletin Board (BB)
|
||
|
||
**Route:** `/idaa/bb/`
|
||
**Library:** `src/lib/ae_posts/`
|
||
**Types:** `ae_Post`, `ae_PostComment`
|
||
|
||
The BB is the IDAA members-only community discussion board. It is the **most sensitive module** — public exposure must never occur.
|
||
|
||
### Object Types
|
||
|
||
**Post (Thread)**
|
||
- Key fields: `title`, `content`, `anonymous`, `full_name`, `email`
|
||
- `archive_on` — date after which the post is hidden from all views
|
||
- `archive` — boolean flag for immediate archival
|
||
- `enable_comments` — controls whether replies are allowed
|
||
- `post_comment_count` — cached count of replies
|
||
|
||
**PostComment (Reply)**
|
||
- Key fields: `post_id`, `content`, `anonymous`, `full_name`, `email`
|
||
- Replies inherit the parent post's visibility rules
|
||
|
||
### Post Visibility / Archival Filter
|
||
Posts with `archive_on` set to a past date are **automatically hidden** from all queries. This is enforced at the component level via a LiveQuery filter:
|
||
|
||
```typescript
|
||
// This filter is REQUIRED — do not remove it
|
||
filter((x) => !x.archive_on || archiveDate > now)
|
||
```
|
||
|
||
Archived posts are soft-deleted — they remain in the database for audit purposes but are not shown to members.
|
||
|
||
Most recent first (sorted `updated_on DESC`).
|
||
|
||
### Database (Dexie)
|
||
```
|
||
db_posts.post — Posts (threads)
|
||
db_posts.comment — Post comments (linked by post_id)
|
||
```
|
||
|
||
---
|
||
|
||
## Module 3: Recovery Meetings
|
||
|
||
**Route:** `/idaa/recovery_meetings/`
|
||
**Library:** `src/lib/ae_events/` (standard Events module, repurposed)
|
||
**Types:** `ae_Event` (standard event type, filtered for meeting context)
|
||
|
||
Recovery Meetings reuses the Aether Events object to represent AA recovery meetings. These are NOT conferences — they are regular ongoing meetings (weekly, monthly, etc.) available to IDAA members.
|
||
|
||
### Search Filters
|
||
Members can filter meetings by:
|
||
- **Fulltext search** — name, location, day of week, contacts (debounced 250ms; uses SWR pattern)
|
||
- **Virtual** — online meetings (Zoom, Jitsi, other)
|
||
- **In-person** — physical location meetings
|
||
- **Meeting type** — IDAA / Caduceus / Family Recovery
|
||
- **My Meetings** — star toggle; shows only meetings the member has starred (favorites)
|
||
|
||
**Sort options:** Last Updated (default), Meeting Name A–Z, Meeting Name Z–A.
|
||
|
||
**Empty state behavior:**
|
||
- Zero results with active filters → "No meetings found for these filters" + "Clear all filters" button
|
||
- Zero results with no filters → bare message shown, then after 8s a "Refresh Meeting Cache" escape hatch appears (clears IDB and re-fetches from API — indicates a stale-cache problem, not a real empty set)
|
||
|
||
Search uses the standard Aether SWR pattern (IDB cache returned immediately, then API refreshes in background).
|
||
|
||
### Search Architecture — What Is and Isn't Searched
|
||
|
||
The fulltext search runs against the `default_qry_str` field (backend-computed STORED GENERATED
|
||
column, contains: `id_random`, type, name, description, timezone, recurring pattern/text,
|
||
location text, **contact name and email**).
|
||
|
||
**Contact names and emails ARE searchable via the API path.** `default_qry_str` includes
|
||
contact data, so the API `lk_qry` LIKE search on that field covers contacts automatically.
|
||
|
||
**IDB fast-path gap:** The local cache (Dexie) fast-path returns all cached meetings without
|
||
text filtering — users see the unfiltered list immediately, then the API result (with contacts
|
||
filtered) replaces it after the background refresh completes. The IDB path does not parse
|
||
`contact_li_json` for instant local text matching.
|
||
|
||
**Known history (2026-05-19):** Contact search appeared broken due to two issues now resolved:
|
||
1. The backend STORED GENERATED columns (`default_qry_str`, `contact_li_json_ext`) had stale
|
||
values; forced a rebuild via fake updates on each event record.
|
||
2. The recovery meetings page secondary filter was re-running text matching against response
|
||
fields — silently dropping results that matched only via `default_qry_str` (e.g. by contact
|
||
name, since that field may not appear in the response body). Fix: removed text re-filtering
|
||
from the secondary filter (type / physical / virtual OR-logic only).
|
||
|
||
**Remaining enhancement (tracked in TODO__Agents.md):**
|
||
- Add `contact_li_json_ext` to the IDB fast-path filter in `search__event()` and the recovery
|
||
meetings page so contact matches appear instantly from cache, not only after API refresh.
|
||
|
||
### My Meetings (Favorites)
|
||
|
||
Members can star meetings to build a personal "My Meetings" list. The star toggle appears:
|
||
- On each card in the meeting list (`ae_idaa_comp__event_obj_li.svelte`)
|
||
- On the meeting detail page nav bar (`[event_id]/+page.svelte`)
|
||
|
||
Favorites are stored in the `data_store` table (code: `idaa_meetings_favorites`, scoped to the
|
||
IDAA account). The record's `json` field holds `{ [novi_uuid]: [event_id, ...] }` — one shared
|
||
record per account containing all members' favorites. This means:
|
||
- Favorites persist across browsers and devices (server-side)
|
||
- Does **not** write to `ae_event` rows (avoiding the `ON UPDATE current_timestamp()` side effect)
|
||
- Known last-write-wins race condition if two members toggle simultaneously — acceptable for ~1000 members
|
||
- Pre-created DB records: ID 150 (`gaTKSVPagFj`, account_id=1, dev/demo), ID 151 (`knJh8zhyKT0`, account_id=13, live IDAA)
|
||
|
||
The star button uses inline styles (not `.btn`) to avoid Bootstrap v3 box-model overrides in the iframe.
|
||
|
||
### Edit Form — Sections and Key Fields
|
||
|
||
The edit form (`ae_idaa_comp__event_obj_id_edit.svelte`) is organized into these sections.
|
||
All fields map directly to the `ae_Event` object; none are IDAA-specific custom fields.
|
||
|
||
| Section | Key Fields |
|
||
| --- | --- |
|
||
| **General Information** | `name` (required), `description` (TipTap rich text), `type` (IDAA / Caduceus / Family Recovery) |
|
||
| **How to Attend** | `physical` (bool), `virtual` (bool) toggles; conditionally shows: |
|
||
| → Physical | `location_address_json` (name, line_1–3, city, state, postal, country), `location_text` (TipTap) |
|
||
| → Virtual | Platform toggle: **Zoom** (`attend_url_code` meeting ID, `attend_url_passcode`, `attend_json.zoom.passcode_enc`, `attend_json.zoom.domain`, `attend_json.zoom.full_url`), **Jitsi** (`attend_json.jitsi.*`), **Other** (`attend_url`, `attend_url_passcode`, `attend_phone`, `attend_phone_passcode`) |
|
||
| → Both | `attend_text` (TipTap — additional attendance instructions) |
|
||
| **Schedule** | `recurring_pattern` (weekly/every other week/monthly/other), `weekday_*` (Sun–Sat booleans), `timezone`, `recurring_start_time`, `recurring_end_time`, `recurring_text` (optional TipTap, auto-generated with `*gen*` prefix if blank) |
|
||
| **Contacts** | `external_person_id` (Novi UUID link), `contact_li_json[0]` (Contact 1: name, email, phone_mobile, phone_home, phone_office — name/email locked to Novi user by default), `contact_li_json[1]` (Contact 2: same fields, optional) |
|
||
| **Admin Options** | `status`, `hide`, `priority`, `sort`, `group`, `enable`, `notes` (TipTap) — **trusted_access only** |
|
||
|
||
**Rich text fields** all use `AE_Comp_Editor_TipTap` with separate `*_new_html` state variables
|
||
(not bound to `$idaa_slct.event_obj` directly) to track change state for the save-button logic.
|
||
|
||
**Zoom URL auto-generation:** Triggered by `$idaa_trig = 'update_zoom_full_url'`. An `$effect`
|
||
reconstructs `attend_json.zoom.full_url` from domain + meeting_id + passcode_enc whenever
|
||
the Meeting ID, Passcode, Encrypted Passcode, or Domain fields change.
|
||
|
||
**Recurring text auto-generation:** If `recurring_text` is blank or contains the `*gen*` prefix,
|
||
the submit handler generates a human-readable string (e.g., `*gen* weekly: Monday, Wednesday at 7:00 PM America/Chicago`).
|
||
Members can opt into a custom text via "Add More Details?" (admin/trusted only).
|
||
|
||
**Contact 1 lock:** Contact 1 name and email default to the logged-in Novi member's identity
|
||
(`$idaa_loc.novi_full_name`, `$idaa_loc.novi_email`). They are `readonly` unless the user
|
||
explicitly unlocks them via confirm dialog (or has administrator access).
|
||
|
||
### Jitsi Integration
|
||
Some virtual meetings are hosted via Jitsi. Members with a Jitsi moderator UUID (`novi_jitsi_mod_li`) have elevated permissions in video sessions.
|
||
|
||
### Edit Form — Implementation Notes (v2)
|
||
|
||
- The v2 edit form uses a `<style>` block with `@apply`. Tailwind v4 requires
|
||
`@reference "../../../../app.css";` at the top of any component `<style>` block that uses `@apply`.
|
||
- The country subdivision lookup list (`lu_country_subdivision_list`) contains duplicate entries —
|
||
specifically Puerto Rico (`PR`) has two rows with `code = '-'`. The `{#each}` key must use
|
||
the array index (`i`) rather than `sub.code` to avoid a Svelte `each_key_duplicate` error.
|
||
The duplicate entries are a **backend data quality issue** that should be cleaned up in the DB.
|
||
|
||
### Demo / Test IDs
|
||
No dedicated IDAA recovery meeting demo records — uses the standard Event demo record for dev:
|
||
- Event: `pjrcghqwert` (id: 1) "Demo One Sky IT Conference"
|
||
|
||
---
|
||
|
||
## Module 4: Video Conferences (Jitsi)
|
||
|
||
**Route:** `/idaa/video_conferences/`
|
||
|
||
Embeds Jitsi video conferences directly in the IDAA module. Separate from Recovery Meetings — this is for IDAA board meetings or special sessions, not regular AA meetings.
|
||
|
||
Moderation permissions are controlled by `novi_jitsi_mod_li` in the IDAA store.
|
||
|
||
---
|
||
|
||
## Module 5: Jitsi Reports
|
||
|
||
**Route:** `/idaa/jitsi_reports/`
|
||
**Access:** `trusted_access` or `novi_verified` — same gate as the rest of `(idaa)/`
|
||
**Data source:** `activity_log` table — `jitsi_meeting_event` and `jitsi_meeting_stats` log types
|
||
**Library function:** `qry__jitsi_report()` in `src/lib/ae_reports/reports_functions.ts`
|
||
|
||
An admin/staff reporting tool that aggregates raw Jitsi activity logs into human-readable meeting sessions. It is **not** a member-facing page — IDAA members do not see it.
|
||
|
||
**Reminder:** this page now filters staff by Novi UUID and can whitelist known meeting names from site config.
|
||
|
||
### View Modes
|
||
|
||
Two display modes, toggled via a button in the page header:
|
||
|
||
| Mode | Description |
|
||
| --- | --- |
|
||
| **Grouped by Room** (default) | One collapsible section per `room_name`. Each section contains a compact table: Date / Time / Duration / Attendees / Participant List. Mirrors the output of the offline Python script (`create_jitsi_report.py`). |
|
||
| **Flat List** | Original card-per-session accordion layout. Better for drilling into event timelines and raw participant lists. |
|
||
|
||
Both modes use the same filtered data set — switching views does not reset filters.
|
||
|
||
### Dark Mode / Surface Safety
|
||
|
||
The page now uses explicit page and row surfaces so dark mode does not collapse into white-on-white
|
||
text in either the regular app or the Novi iframe.
|
||
|
||
### Filters
|
||
|
||
| Filter | Default | Logic |
|
||
| --- | --- | --- |
|
||
| **Min. Participants** | 2 | Minimum `real_participant_count` to display a session. Used as the only size filter. |
|
||
| **Room Name** | edit mode only | Case-insensitive substring match against `room_name`. Hidden unless AE global edit mode is on. |
|
||
| **From / To** | last 60 days / today | Date range applied to `start_time`. "To" date includes the full end of day. |
|
||
|
||
A "Reset Filters" button appears whenever any filter is non-default.
|
||
|
||
In edit mode, two extra toggles appear:
|
||
- **Show excluded IDs** — temporarily include the UUIDs listed in `jitsi_exclude_uuids`
|
||
- **Show all meetings** — temporarily ignore `jitsi_known_meetings`
|
||
|
||
An "Active Exclusions" panel below the filter bar shows the currently applied Novi UUID exclusions
|
||
and known meeting-name whitelist values. Each list is collapsible so the page stays compact.
|
||
|
||
### Staff / Meeting Filtering
|
||
|
||
**Problem:** Staff/test accounts and one-off test rooms distort the reports.
|
||
|
||
**Site config keys:**
|
||
```json
|
||
{
|
||
"jitsi_exclude_uuids": ["uuid-1", "uuid-2"],
|
||
"jitsi_known_meetings": ["IDAA-BIPOC-Meeting", "IDAA-Sunday-Meeting"]
|
||
}
|
||
```
|
||
|
||
**How it works:**
|
||
|
||
1. The page reads `$ae_loc.site_cfg_json?.jitsi_exclude_uuids` and excludes matching participants by Novi UUID.
|
||
The UUID comes from the Jitsi log `url_params.uuid` field. `g_uuid` is the meeting/group UUID and is not used here.
|
||
2. If a participant record does not include a UUID in the activity log, it is left visible; UUIDs are used whenever available.
|
||
3. `real_participant_count = real_participants.length` drives filters, exports, and the per-meeting attendee count.
|
||
4. Room-level unique participant counts are computed from Novi UUIDs when present, with display-name fallback only for UUID-less records.
|
||
5. If `$ae_loc.site_cfg_json?.jitsi_known_meetings` is non-empty, only meetings whose `room_name` matches one of the listed names are shown.
|
||
6. The Room Name filter is only shown when global edit mode is enabled.
|
||
|
||
**Temporary stopgap:** the report also hides these staff display names through the same UUID-exclusion toggle until the long-term logging fix lands:
|
||
`Scott I.`, `Brie P.`, `Michelle V.`
|
||
|
||
**Note:** matching is case-insensitive on the stored `room_name` / meeting name.
|
||
|
||
### Summary Stats
|
||
|
||
Shown above the meeting list when data is loaded. Stats reflect the **filtered + exclusion-applied** view:
|
||
|
||
- **Meetings Shown** — count of sessions passing all filters
|
||
- **Total Participants** — sum of `real_participant_count` across all shown sessions
|
||
- **Avg Duration** — mean session duration (HH:MM:SS)
|
||
- **Total Duration** — sum of all session durations (HH:MM:SS)
|
||
|
||
In grouped view, each room header also shows its own subtotals (meeting count, unique participants by Novi UUID when available).
|
||
Each meeting instance keeps the full participant list visible; the **Copy names** button is edit-mode only so staff can grab the list for follow-up reports without exposing extra controls to normal viewers.
|
||
|
||
### Caching / Load Behavior
|
||
|
||
The page now reads cached `activity_log` rows from IndexedDB first, renders that result immediately,
|
||
then refreshes from the API in the background. That keeps the report usable even when the network
|
||
round-trip is slow.
|
||
|
||
Both the cache path and the API refresh now page through the matching activity-log set in
|
||
`created_on DESC` order with a 1000-row page size before building the report. That avoids the old
|
||
"first 500 rows" behavior that could hide newer sessions if the log table grew large.
|
||
|
||
The report page keeps the newest session first in both the flat list and the grouped-by-room view;
|
||
grouped room rows are also sorted newest session first within each room.
|
||
|
||
### Jitsi URL Builder
|
||
|
||
Collapsible panel, visible to `trusted_access` users only. Generates properly-formatted Jitsi meeting URLs for IDAA rooms. Component: `ae_idaa_comp__jitsi_url_builder.svelte`.
|
||
|
||
### Video Conferences → Reports Link
|
||
|
||
Trusted Access users now get a footer link on the Video Conferences page that jumps back to the Jitsi Reports page. It preserves the current iframe context so the staff workflow stays inside the Novi embed.
|
||
|
||
**Future idea:** make that link include a `room=` query param for the current meeting so Jitsi Reports can auto-filter to that meeting instance, and have Reset clear that param again.
|
||
|
||
### Export
|
||
|
||
CSV and JSON export buttons in the page header export the **currently filtered + exclusion-applied** data set.
|
||
|
||
### Room Name Fragmentation
|
||
|
||
The same logical meeting can appear as multiple rooms (e.g. `IDAA-BIPOC-Meeting`, `IDAA-BIPOC-Meeting-2026`, `IDAA-BIPOC-Meeting-March-31`) because the Jitsi URL builder appends a date suffix to generate unique per-session room names. In grouped view, these appear as separate groups. A future normalization pass (strip trailing date suffixes) could optionally merge them — not implemented yet.
|
||
|
||
### Data Flow
|
||
|
||
```text
|
||
activity_log table
|
||
└── qry__jitsi_report() # reports_functions.ts — fetches + aggregates by meeting_id
|
||
└── MeetingReport[] # { meeting_id, room_name, start_time, final_duration,
|
||
# final_participants, final_participant_count, events }
|
||
└── jitsi_reports/+page.svelte
|
||
├── apply exclusion list → real_participants / real_participant_count
|
||
├── apply filters → meetings_filtered
|
||
├── derive grouped view → Map<room_name, MeetingReport[]>
|
||
└── render flat or grouped
|
||
```
|
||
|
||
---
|
||
|
||
## State Management (`ae_idaa_stores.ts`)
|
||
|
||
Four stores manage all IDAA state:
|
||
|
||
### `idaa_loc` (localStorage — persistent across sessions)
|
||
Stores Novi auth context and per-submodule query settings:
|
||
```typescript
|
||
{
|
||
novi_uuid: string | null // Member UUID (set on verification success)
|
||
novi_email: string | null // Verified email from Novi API
|
||
novi_full_name: string | null // Verified name from Novi API
|
||
novi_verified: boolean // true after successful Novi API verification
|
||
novi_admin_li: string[] // Admin UUID list (from site config)
|
||
novi_trusted_li: string[] // Trusted member UUID list
|
||
novi_jitsi_mod_li: string[] // Jitsi moderator UUIDs
|
||
|
||
archives: { enabled, hidden, limit, offset, edit__archive_obj, edit__archive_content_obj }
|
||
bb: { enabled, hidden, limit, offset, edit__post_obj, edit__post_comment_obj,
|
||
qry__enabled, qry__hidden, qry__limit, qry__offset, qry__order_by, qry__order_by_li }
|
||
recovery_meetings: {
|
||
qry__enabled, qry__hidden, qry__limit, qry__offset,
|
||
qry__fulltext_str, qry__physical, qry__virtual, qry__type,
|
||
qry__order_by, qry__order_by_li,
|
||
qry__favorites_only, // true = show only starred meetings (My Meetings filter)
|
||
edit__event_obj // null or event_id string when edit form is open
|
||
}
|
||
}
|
||
```
|
||
|
||
### `idaa_sess` (in-memory only — resets on page load)
|
||
UI state per submodule:
|
||
```typescript
|
||
{
|
||
archives: { qry__status, show__modal_edit__archive_id, show__modal_view__archive_id,
|
||
show__modal_edit__archive_content_id, show__modal_view__archive_content_id, obj_changed }
|
||
bb: { qry__status, edit__post_obj, show__inline_edit__post_obj, show__modal_edit__post_id,
|
||
show__modal_view__post_id, obj_changed }
|
||
recovery_meetings: {
|
||
qry__status, // null | 'loading' | 'done' | 'error'
|
||
qry__fulltext_str, // session-only copy (separate from persisted loc copy)
|
||
search_version, // incremented to trigger a new search cycle
|
||
edit__event_obj, // null | event_id — controls edit form visibility
|
||
show__modal_edit, show__modal_view,
|
||
show__modal_edit__event_id, show__modal_view__event_id,
|
||
attend_platform, // 'Zoom' | 'Jitsi' | null — platform selected in virtual attend section
|
||
obj_changed
|
||
}
|
||
}
|
||
```
|
||
|
||
### `idaa_slct` (sessionStorage — selection tracking)
|
||
```typescript
|
||
{
|
||
event_id: string | null
|
||
archive_id: string | null
|
||
archive_content_id: string | null
|
||
post_id: string | null
|
||
post_comment_id: string | null
|
||
}
|
||
```
|
||
|
||
### `idaa_trig` / `idaa_prom`
|
||
Trigger flags and promise tracking for async operations (standard Aether pattern).
|
||
|
||
---
|
||
|
||
## Iframe Integration
|
||
|
||
The IDAA module is embedded in `idaa.org` via iframe. This requires:
|
||
|
||
1. **Height sync** — The root layout posts `message` events to the parent frame for dynamic height adjustment (content length varies)
|
||
2. **URL parameter auth** — Novi passes member context via query string on load
|
||
3. **No standard navigation** — Members navigate within the iframe; Aether's nav chrome is hidden or minimal in this context
|
||
|
||
### Novi UUID Verification Flow
|
||
|
||
**Iframe HTML files** (in `static/`): Pass only `uuid` to the iframe src — no Novi API calls in the browser:
|
||
```text
|
||
idaa_novi_iframe_archives.html
|
||
idaa_novi_iframe_bulletin_board.html
|
||
idaa_novi_iframe_recovery_meetings.html
|
||
idaa_novi_iframe_jitsi_meeting.html ← reference pattern (unchanged)
|
||
```
|
||
|
||
**SvelteKit layout** (`(idaa)/+layout.svelte`): Calls `GET /customers/{uuid}` on the Novi API using the `novi_idaa_api_key` from `site_cfg_json`. Sets verified name/email in `$idaa_loc` and grants permissions. Shows a "Verifying identity..." spinner during the async call.
|
||
|
||
**Jitsi page** (`video_conferences/+page.svelte`): Checks `$idaa_loc.novi_verified` in `fetch_novi_data()`. If the layout already verified the user, it reuses `$idaa_loc.novi_email` / `$idaa_loc.novi_full_name` and skips the duplicate member details API call. The group moderator check (`get_novi_group_moderators`) always runs — it is Jitsi-specific.
|
||
|
||
### ⚠️ Iframe CSS Conflicts (Bootstrap v3)
|
||
|
||
When `$ae_loc.iframe = true`, the root layout (`+layout.svelte`) injects two external stylesheets from Novi's CDN:
|
||
|
||
```text
|
||
https://assets-staging.noviams.com/novi-core-assets/css/fontawesome.css — safe, icon-only
|
||
https://assets-staging.noviams.com/novi-core-assets/css/c/idaa/idaa.css — Bootstrap v3.4.1 ⚠️
|
||
```
|
||
|
||
`idaa.css` is a full **Bootstrap v3.4.1** bundle. It applies global styles to bare HTML elements
|
||
(`input`, `select`, `textarea`, `h1–h6`) and commonly named classes (`.btn`, `.badge`, `.active`,
|
||
`.text-*`, `.bg-*`). These will compete with Tailwind v4 + Skeleton UI.
|
||
|
||
**Known consequences:**
|
||
- Bare form elements (`<input>`, `<select>`) receive Bootstrap's height/padding resets on top of Tailwind
|
||
- `.btn` class gets Bootstrap button colors, potentially overriding `preset-*` Skeleton classes
|
||
- `<section>` and heading elements may get unexpected margins/padding from Bootstrap's typography reset
|
||
- Class names `.field-input` and `.field-label` (used in the v2 edit form's scoped `<style>` block)
|
||
also exist in `idaa.css`'s date picker — Svelte's scoped attribute selector wins, but be aware
|
||
- In iframe widths near Tailwind `sm`, avoid hiding critical button labels behind breakpoint classes
|
||
and do not depend on color-only active states; Bootstrap's `.active`/button styling can make the
|
||
selected state nearly invisible unless the control uses an obvious fill/ring change plus
|
||
`aria-pressed`
|
||
|
||
**Mitigation:** The iframe CSS conflicts existed before v2 and are not new. The v2 form uses the
|
||
same Skeleton/Tailwind component classes as the rest of the app. Avoid using bare `<section>`,
|
||
`<article>`, or block-level HTML5 elements as style hooks; use `<div>` with explicit classes instead.
|
||
|
||
---
|
||
|
||
## Testing Requirements
|
||
|
||
### Auth Gate Tests Come First
|
||
**For every IDAA submodule, the first test written must be an authentication enforcement test.**
|
||
|
||
```typescript
|
||
// ✅ Required test pattern for each IDAA module
|
||
test('Archives - unauthenticated user cannot access content', async ({ page }) => {
|
||
// Inject localStorage WITHOUT trusted_access
|
||
// Navigate to /idaa/archives/
|
||
// Assert: access denied message shown, no archive content visible
|
||
});
|
||
|
||
test('Archives - trusted member can access content', async ({ page }) => {
|
||
// Inject localStorage WITH trusted_access + novi_uuid
|
||
// Navigate to /idaa/archives/
|
||
// Assert: archive list renders
|
||
});
|
||
```
|
||
|
||
### Privacy in Test Data
|
||
- Never use real member data in test fixtures
|
||
- Use canonical demo IDs from `tests/_helpers/env.ts` only
|
||
- Test names should document the privacy rule being enforced, not just the behavior
|
||
|
||
### Trusted Access State Injection
|
||
Tests that need authenticated IDAA access must set `trusted_access: true` and `novi_uuid` in the injected `ae_loc` localStorage:
|
||
```typescript
|
||
// In addInitScript or env helper
|
||
ae_loc.trusted_access = true;
|
||
ae_loc.idaa_loc = { novi_uuid: 'test-uuid-value', ... };
|
||
```
|
||
|
||
### Current Test Coverage (as of 2026-04-07)
|
||
| Module | State | Notes |
|
||
|---|---|---|
|
||
| Archives | ⚠️ Smoke only | `archive_content.test.ts` — no auth gate test |
|
||
| Bulletin Board | ❌ None | Priority — most sensitive module |
|
||
| Recovery Meetings | ✅ Substantial | `tests/idaa_recovery_meeting_edit.test.ts` — form render, field interactions, PATCH payload verification (all sections), real backend save, creation linkage (Novi UUID in POST body) |
|
||
| Video Conferences | ❌ None | Jitsi complexity, lower priority |
|
||
| Jitsi Reports | ❌ None | Admin-only tool; lower privacy risk than member modules |
|
||
|
||
**Pending:** BB Post and Post Comment creation linkage tests (pattern established in Recovery Meetings test).
|
||
|
||
---
|
||
|
||
## External Links (idaa.org)
|
||
- Archives: `https://www.idaa.org/idaa-archives`
|
||
- Bulletin Board: `https://www.idaa.org/idaa-bulletin-board`
|
||
- Meetings: `https://www.idaa.org/idaa-meetings`
|
||
|
||
---
|
||
|
||
## Related Documentation
|
||
- [AE API V3 for Frontend](./GUIDE__AE_API_V3_for_Frontend.md)
|
||
- [Development Guide](./GUIDE__Development.md)
|
||
- [Naming Conventions](./AE__Naming_Conventions.md)
|
||
- [Playwright Test README](../tests/README.md)
|
||
|
||
|
||
---
|
||
|
||
## IDAA Novi Groups and Moderators
|
||
|
||
### "IDAA Association Admins Group" = "409e91dc-f5a3-486c-a964-71b7d19e6841"
|
||
|
||
* Scott
|
||
* Michelle
|
||
* Brie
|
||
|
||
### "IDAA Couples Meeting" = "e9e162f0-3d03-4241-9682-340135ec3fb8"
|
||
|
||
* "Gregory X Boehm" "00ee764c-7559-496b-9d18-40d3e9092c0c"
|
||
* "Kee B. PARK" "24ab3297-bfce-473c-9311-4b31e3a8974f"
|
||
* "Laura Lander" "ac697456-61fe-4f7d-a8b8-d04866032320"
|
||
* "Nancy J Duff-Boehm" "5c7c09bc-4f23-432c-bfd9-87a66b548502"
|
||
* "Owen Lander" "9671a2c4-ff95-48c2-bcde-5c6eba95cded"
|
||
* "Susan Park" "4a9f94c5-d766-4808-ab76-117c9e43903a"
|
||
|
||
### "Student/Resident Meeting Moderators" "d76d2c00-962d-40f6-a2e8-ed9c85594d96"
|
||
|
||
* "Melissa Eve Valasky" "182d1db3-caa9-41bc-b04a-2facc6859aeb"
|
||
* "Steven L. Klein" "5724aad7-6d89-47e7-8943-966fd22911bd"
|
||
|
||
### "IDAA BIPOC Meeting" "873d3ad0-2605-4ccf-824c-638c16b2b9cf"
|
||
|
||
* "Paula Lynn Bailey-Walton" "68383ba2-0989-4860-9ea6-073f9698df67"
|
||
* "Tasha Hudson" "03d5408c-3c13-4c3a-a93f-49871f9050b1"
|
||
|
||
|
||
---
|
||
|
||
**Document Status:** ✅ Current
|
||
**Last Verified:** 2026-05-19 — Access Gate: documented new `verify_error_type` error-handling states and retry/reset UI; Search Architecture: corrected contact-search status (now works via `default_qry_str` in API path — two root causes fixed 2026-05-18/19); noted IDB fast-path gap as remaining enhancement
|