diff --git a/documentation/CLIENT__IDAA_and_customized_mods.md b/documentation/CLIENT__IDAA_and_customized_mods.md new file mode 100644 index 00000000..f08d5cfc --- /dev/null +++ b/documentation/CLIENT__IDAA_and_customized_mods.md @@ -0,0 +1,352 @@ +# 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-02-26 + +--- + +## ⚠️ 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) +│ │ ├── +page.svelte # Meeting list + search filters +│ │ └── [event_id]/ +│ │ ├── +page.svelte # Meeting detail +│ │ ├── ae_idaa_comp__event_obj_id_view.svelte +│ │ └── ae_idaa_comp__event_obj_id_edit.svelte +│ └── video_conferences/ # Jitsi video conference integration +└── jitsi_reports/ # External Jitsi reporting +``` + +--- + +## 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> +&email= +&full_name= +&iframe=true +``` + +### Permission Levels (Ascending) +| Level | Condition | Access | +|---|---|---| +| Anonymous | No UUID or unrecognized | No access | +| Authenticated | Valid UUID (36 chars) | View own content, limited actions | +| Trusted | UUID in `novi_trusted_li` | Full member access to all IDAA content | +| Administrator | 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). + +### 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. + +### Access Gate (`(idaa)/+layout.svelte`) +The inner layout blocks ALL rendering if the user is not authorized: +- Anonymous → "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. + +### 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. + +### 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 from Novi + novi_email: string | null + novi_full_name: string | null + 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 + +--- + +## 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-02-26) +| Module | State | Notes | +|---|---|---| +| Archives | ⚠️ Smoke only | `archive_content.test.ts` — no auth gate test | +| Bulletin Board | ❌ None | Priority — most sensitive module | +| Recovery Meetings | ❌ None | — | +| Video Conferences | ❌ None | Jitsi complexity, lower priority | + +--- + +## 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) + +--- + +**Document Status:** ✅ Complete (initial) +**Last Verified:** 2026-02-26 — reverse-engineered from source code diff --git a/documentation/MODULE__AE_Events_Badges.md b/documentation/MODULE__AE_Events_Badges.md index 57a9da0d..fc0aa850 100644 --- a/documentation/MODULE__AE_Events_Badges.md +++ b/documentation/MODULE__AE_Events_Badges.md @@ -378,7 +378,7 @@ Button has `data-testid="badge-print-btn"` and shows loading/done/error states w ### Print Workflow 1. **Pre-Print:** Check `print_count` to warn if already printed -2. **Print:** `window.print()` — standard browser print dialog. Works well in Chrome, Chromium, and Firefox. Chrome is the recommended browser for onsite badge printing (most stable in production). +2. **Print:** `window.print()` — standard browser print dialog. Works well in Chrome, Chromium, and Firefox. Chrome is the recommended browser for onsite badge printing (most stable in production). 3. **Post-Print:** Handled by `handle_print_badge()` — count + timestamps updated 4. **Audit:** Print history available for staff review diff --git a/tests/README.md b/tests/README.md index 13bea2bf..7a3d112e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -63,6 +63,7 @@ Help * Aether development API: 'https://dev-api.oneskyit.com' These are IDs for records that we can use for testing. Please do not delete them. They are also used for demo purposes with clients. + ### Core Modules * Aether test/demo Account: '_XY7DXtc9MY' (1) "One Sky IT Demo" * Aether test/demo Site: '92vkYC4fVEl' (12) "One Sky IT Demo"