# 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-03-09 (Novi UUID verification upgrade)

---

## ⚠️ 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.

---

## 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/              # External Jitsi reporting
```

> **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 fetches verified identity directly from the Novi API.
> 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 Novi API call** to verify:

1. The UUID actually exists in Novi's system (prevents fake/crafted UUIDs)
2. Gets verified name and email directly from Novi — 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, 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 layout handles 429 responses with a 10-second flat backoff and one retry. If the retry also returns 429, access is denied and a "Reload / Retry" button is shown. The 5-minute TTL cache on successful verification prevents repeated calls during normal use.

### 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 performs an authenticated GET against the Novi customers endpoint:

```js
// simplified
fetch(`${api_root_url}/customers/${uuid}`, {
  method: 'GET',
  headers: { 'Authorization': `Basic ${api_key}` }
})
```

3. On success the layout uses the returned JSON to build a display name and normalized email, then writes these values to the IDAA store and marks 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. Required for the verification request. Accessed in code as `$ae_loc.site_cfg_json.novi_idaa_api_key` and passed in the `Authorization: Basic <key>` header. If missing, Novi-based access is denied.

- **`novi_api_root_url`**: Optional API root (defaults to `https://www.idaa.org/api`). Used to form the verification URL.

- **`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.

- **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 config and do not expose it in client-side code or public repositories.
- The verification request uses Basic auth with the provided `novi_idaa_api_key` (already Base64-encoded by Novi) — treat the token like a password.
- If Novi changes their customer API shape, update the layout parsing (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.

### 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
- 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
- **Physical** — in-person meetings
- **Virtual** — online meetings (Zoom, Google Meet, etc.)
- **Meeting type** — specific meeting format categories

Search is debounced (250ms) and uses the standard Aether SWR pattern.

### Search Architecture — What Is and Isn't Searched

The fulltext search runs against the `default_qry_str` field (backend-computed, contains:
`id_random`, type, name, description, timezone, recurring pattern/text, location text).

**Contact names and emails are NOT currently searchable.** The `contact_li_json` field is a
JSON longtext — MariaDB cannot efficiently substring-search it directly. The backend already
has a `contact_li_json_ext` (STORED GENERATED, indexed) column to work around this, but it
has not yet been added to the searchable fields whitelist in the API.

**Pending fix (tracked in TODO__Agents.md, 2026-04-08):**
- Backend: add `contact_li_json_ext` to the event object searchable fields whitelist
- Frontend: add `contact_li_json_ext` as an OR condition in `search__event()`, and update
  the local IDB fast-path filter to parse `contact_li_json` for instant cache results

### 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.

---

## 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 }
  recovery_meetings: { qry__fulltext_str, qry__physical, qry__virtual, qry__type, qry__limit, edit__event_obj }
}
```

### `idaa_sess` (sessionStorage — cleared on tab close)
UI state per submodule:
```typescript
{
  archives: { qry__status, show__modal_edit__archive_id, show__modal_view__archive_id, obj_changed }
  bb: { qry__status, show__modal_edit__post_id, show__modal_view__post_id, obj_changed }
  recovery_meetings: { qry__status, show__modal_edit, show__modal_view, attend_platform, 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

**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 |

**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-04-07 — updated for Novi UUID triple-linkage enforcement, staff editing rules, Contact 1 convention, test coverage
