docs: add IDAA client module doc, minor whitespace cleanup

- CLIENT__IDAA_and_customized_mods.md: New comprehensive doc covering IDAA
  architecture, all 4 submodules (Archives, BB, Recovery Meetings, Jitsi),
  Novi UUID auth system, permission levels, state stores, iframe integration,
  and testing requirements. Reverse-engineered from source 2026-02-26.
- MODULE__AE_Events_Badges.md: trailing whitespace only
- tests/README.md: blank line only
This commit is contained in:
Scott Idem
2026-02-26 18:50:20 -05:00
parent 8cb8195ecd
commit 911a427757
3 changed files with 354 additions and 1 deletions

View File

@@ -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=<url-encoded-email>
&full_name=<url-encoded-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

View File

@@ -378,7 +378,7 @@ Button has `data-testid="badge-print-btn"` and shows loading/done/error states w
### Print Workflow ### Print Workflow
1. **Pre-Print:** Check `print_count` to warn if already printed 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 3. **Post-Print:** Handled by `handle_print_badge()` — count + timestamps updated
4. **Audit:** Print history available for staff review 4. **Audit:** Print history available for staff review

View File

@@ -63,6 +63,7 @@ Help
* Aether development API: 'https://dev-api.oneskyit.com' * 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. 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 ### Core Modules
* Aether test/demo Account: '_XY7DXtc9MY' (1) "One Sky IT Demo" * Aether test/demo Account: '_XY7DXtc9MY' (1) "One Sky IT Demo"
* Aether test/demo Site: '92vkYC4fVEl' (12) "One Sky IT Demo" * Aether test/demo Site: '92vkYC4fVEl' (12) "One Sky IT Demo"