4 Commits

Author SHA1 Message Date
Scott Idem
17b549a75c docs/refactor: finalize V3 cleanup and archive badge config project
- Moved PROJECT__AE_Events_Badges_Config_Cleanup.md to archive.
- Updated PROJECT__Use_AE_API_V3_CRUD_upgrade.md with latest audit and migration status.
- Migrated ae_comp__event_presenter_form_agree.svelte to modern V3 update_ae_obj helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:04:07 -04:00
Scott Idem
3de01af1a1 docs: cleanup and archive agent TODO list
Archived completed May 2026 tasks and streamlined the active list to
focus on upcoming events and the final V3 API surgical cleanup.

- Created TODO__Agents__ARCHIVE_2026-05.md with completed items.
- Streamlined TODO__Agents.md for active show support (CMSC, Axonius).
- Added V3 CRUD migration tracking for core site and utility helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:02:25 -04:00
Scott Idem
518a450b91 docs(events): reorganize badges and leads documentation
Standardized documentation structure for Badges and Leads modules into
focused technical references and practical onsite guides.

- Refined MODULE__AE_Events_Badges.md (Core data integrity & sync logic)
- Renamed MODULE__AE_Events_Exhibitor_Leads.md to MODULE__AE_Events_Leads.md
- Renamed MODULE__AE_Events_Badges_Onsite.md to GUIDE__AE_Events_Badges_Onsite.md
- Expanded GUIDE__AE_Events_Onsite_Runbook.md with Badge and Leads sections.
- Maintained all critical business logic, including the 'Override Fields' pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:40:06 -04:00
Scott Idem
cb767ed115 docs(events): reorganize presentation and launcher documentation
Split the monolithic MODULE__AE_Events_PressMgmt_Launcher.md into focused,
granular modules to improve maintainability and onsite utility.

- Created MODULE__AE_Events_Presentation_Management.md (Back-office focus)
- Created MODULE__AE_Events_Launcher.md (Podium display focus)
- Created GUIDE__AE_Events_Onsite_Runbook.md (SRR and onsite workflows)
- Promoted PROJECT__AE_Events_Launcher_Native_integration.md to
  MODULE__AE_Events_Launcher_Native.md (Permanent technical reference)
- Renamed 'Press Mgmt' references to 'Presentation Management' for clarity.
- Removed redundant README.md in launcher route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:36:00 -04:00
14 changed files with 510 additions and 2160 deletions

View File

@@ -0,0 +1,124 @@
# Guide — Aether Events: Onsite Runbook
This guide covers the human-centric logistics and "In the Heat of the Moment" support for onsite event operations.
---
## Badge Printing
Aether badge printing uses the browser's native `window.print()` — no special software or print
server needed.
### Kiosk Station Setup
- **Browser:** Use **Chrome (Chromium)** for all kiosk stations.
- **Settings:** Set Margins to **None**. Enable **Background Graphics**.
- **Mode:** Use normal browser sessions (not Incognito) to allow PWA caching.
### Printer Reference: Zebra ZC10L (PVC)
- **Stock:** 3.5" × 5.5" PVC cards.
- **Orientation:** Cards face-up, landscape in the hopper.
- **Single-Sided:** Only the front face prints; the back section is hidden via CSS.
### Printing Workflow
1. **Search:** Find the attendee by name or QR scan in the Badges module.
2. **Review:** Open the print page and confirm the layout looks correct.
3. **Print:** Click **Print Badge**. `print_count` increments automatically.
4. **Handoff:** Verify the card print quality before handing it to the attendee.
---
## Exhibitor Leads (Lead Retrieval)
Exhibitors use a PWA (Progressive Web App) to scan badges and capture leads.
### Exhibitor Support Workflow
1. **Booth Lookup:** Help the exhibitor find their booth in the Leads landing page.
2. **Sign-In:** Assist with the **Shared Passcode** or individual **Licensed User** login.
3. **App Install:** Encourage them to "Add to Home Screen" (iOS) or click the Install button (Android/Chrome) for offline stability.
4. **Scanning Demo:** Show them the **Rapid Scan** mode. Remind them that attendees must have `allow_tracking = true` on their record to be scanned.
### Managing Licenses
- License counts are managed in the **Manage** tab (Admin or Shared Passcode only).
- If an exhibitor needs more staff slots, update the `license_max` in the Exhibit record.
---
## Speaker Ready Room (SRR)
... (rest of the file) ...
The SRR is the central hub for content management and presenter support.
### SRR Practice Stations
Stations mirror the session room setup exactly:
- Same Mac laptop model and adapter/dongle configuration as the podiums.
- Projector and screen (where possible).
- Launcher running in **Native** mode — ensures verification matches the podium experience.
### Staffing Roles
| Role | Access Level | Typical Tasks |
|---|---|---|
| **OSIT Staff** | `trusted_access` | Manage devices, monitor via VNC, deep troubleshooting. |
| **Client Staff** | `authenticated_access` | Upload files, view session lists, assist presenters. |
| **Presenter** | `authenticated_access` | Self-upload via QR link (if enabled). |
### SRR Workflow — Day-of-Show
1. **Check-in:** Staff looks up the presenter's session in Presentation Management.
2. **Upload:** File is uploaded to the presenter/session record.
3. **Verification:** Staff opens the file on a practice station to confirm rendering.
4. **Launcher Sync:** File propagates to the podium. Use **Force Sync Location** in the Launcher config if immediate full-room caching is needed.
5. **Proceed:** Presenter walks to the room; the podium kiosk already has the file cached.
---
## Onsite Operation (Managing Parallel Rooms)
### SRR Overview Page
The Pres Mgmt overview (`/events/[id]/pres_mgmt`) is the "Command Center":
- Monitor file status per session.
- Filter by location and time block to stay ahead of active sessions.
### Per-Room Monitoring
- Use **VNC or RustDesk** to monitor all podium screens in real time from the SRR.
- Confirm "Native Sync" status chip in the bottom-left of the Launcher is green/idle before sessions start.
### Session Transitions
- **Timing:** Ideally, sessions show/hide based on `datetime_start`.
- **Manual Control:** In looser schedules, use Launcher controls to manually select the current session.
---
## Pre-Show Checklist
### 12 Weeks Before
- [ ] Event created with correct dates and timezone.
- [ ] `mod_pres_mgmt_json` configured for client needs.
- [ ] Locations (rooms) created and named.
- [ ] Sessions created, assigned to locations, and timed.
- [ ] Launcher devices (`event_device`) registered with correct codes.
- [ ] Device-to-location assignments confirmed.
### Day Before (SRR Setup)
- [ ] Mac laptops at podiums booted; Electron app running.
- [ ] Each podium confirms it loaded the correct room's Launcher.
- [ ] SRR practice stations confirmed (matching hardware).
- [ ] Run **Force Sync Location** on all podiums to pre-cache all day-1 content.
- [ ] VNC/RustDesk connections established to all podiums.
### Day of Show
- [ ] Confirm all session times are accurate before the first block.
- [ ] Monitor SRR queue and verify every file on a practice station.
- [ ] Check VNC wall to ensure all podiums are online and synced.
---
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Session not in Launcher | Datetime wrong or Location unassigned. | Verify session metadata in Pres Mgmt. |
| File uploaded but missing | Polling lag or attached at wrong level. | Wait 30s; check if file is at Session vs Presenter level. |
| File opens slowly | Not in native cache yet. | Check "Native Sync" chip; use Force Sync in config. |
| File won't open | Corrupt upload or missing Mac codec. | Test on SRR station; convert or re-upload. |
| Drifted schedule | Room timing shifted. | Use Launcher controls to manually select the active session. |
| `lock_config` resets changes | Remote config is forced. | Edit the master `mod_pres_mgmt_json` in Event Settings. |
| Move laptop to new room | Hardware reassignment. | Update `location_id` in `event_device` record; restart Electron. |

View File

@@ -1,843 +1,106 @@
# MODULE: Aether Events — Badges
# Aether Events — Badges
**Module Path:** `src/routes/events/[event_id]/(badges)/badges/`
**API Module:** `src/lib/ae_events/ae_events__event_badge.ts`
**Database:** `db_events.badge` (Dexie IndexedDB table)
**Last Updated:** 2026-02-27 (rev 6)
**Related Docs:** `documentation/PROJECT__AE_Events_Badges_Review_Print.md` (implementation guide)
The Badges module manages event attendee records and their physical badge configurations. It supports multi-source imports, field protection for onsite edits, and multi-tier access control for self-service review.
---
## Overview
## Data Model & Hierarchy
The Badges module manages event attendee badges with support for:
- **External system imports as needed** (CSV/Excel, iMIS, Zoom, Novi, Impexium, Confex, Cvent, and others)
- **Field override protection** to prevent staff/attendee edits from being overwritten by automated syncs
- **Multi-tier access control** for field editing
- **QR code generation** for badge scanning
- **Print tracking** (count, first/last print datetime)
- **Advanced search and filtering**
- **HTML rendering** in display fields for rich text formatting
- **Accessibility features** (text enlargement toggle)
### Core Objects
- **Event Badge** (`event_badge`): The attendee record containing name, title, affiliations, and tracking flags.
- **Badge Template** (`event_badge_template`): The visual and structural configuration for printing (branding, layout, QR placement).
### Relationships
- **Badge → Event:** Many-to-one.
- **Badge → Template:** Many-to-one (via `event_badge_template_id`).
- **Badge → Person:** Optional link to core Aether Person record for unified profiles.
---
## Critical Design Pattern: Override Fields
### Purpose
The `*_override` fields pattern protects data from being overwritten during scheduled cron syncs from external systems. This is essential because:
1. Staff may need to correct imported data
2. Attendees may be allowed to self-update certain fields (e.g., preferred name, pronouns)
3. External systems often have outdated or incorrect data
4. Changes should persist across multiple sync cycles
The `*_override` fields pattern (established in 2018) protects data from being overwritten during scheduled cron syncs from external systems (iMIS, Novi, etc.). This ensures that staff corrections or attendee self-updates persist across multiple sync cycles.
### How It Works
1. **Import:** External systems populate **REGULAR** fields only.
2. **Display Logic:** The UI displays the `*_override` field if it has a value; otherwise, it falls back to the regular field.
3. **HTML Rendering:** Certain display fields (Name, Title, Affiliations, Location) support HTML markup for rich text formatting (bold, italics, line breaks) on the physical badge.
**Import Behavior:**
```
External System → Aether API → Populates REGULAR fields only
(never touches *_override fields)
```
### Standard Override Pairs
**Display Behavior:**
```
UI Display Logic:
1. IF `*_override` field has value → USE IT (highest priority)
2. ELSE IF regular field has value → USE IT (fallback)
3. ELSE → Display placeholder/empty
```
**HTML Rendering (implemented 2026-02-27):**
Certain fields support HTML markup for rich text formatting. When viewing (not editing), these fields use Svelte's `{@html}` directive to render the markup:
- `full_name` / `full_name_override`
- `professional_title` / `professional_title_override`
- `affiliations` / `affiliations_override`
- `location` / `location_override`
This allows for formatting like:
- Bold/italic: `<b>Dr.</b> Jane Smith` or `<i>Chief Medical Officer</i>`
- Line breaks: `Hospital Name<br>Department Name`
- Special characters and entities
**Example — Full Name:**
```typescript
// API imports from iMIS
badge.given_name = "Robert"
badge.family_name = "Smith"
badge.full_name = "Robert Smith" // Auto-computed
// Staff edits to preferred name with HTML
badge.full_name_override = "<b>Bob</b> Smith"
// Display in UI (review form)
{@html badge.full_name_override || badge.full_name || "— no name —"}
// Result: **Bob** Smith (bold rendered)
// Edit mode shows raw HTML
<input bind:value={editable_full_name_override} />
// Shows: <b>Bob</b> Smith (editable as text)
// Next cron sync from iMIS
// ✅ badge.full_name updated to "Robert J. Smith" (middle initial added)
// ✅ badge.full_name_override remains "<b>Bob</b> Smith" (PROTECTED)
// ✅ Display still shows **Bob** Smith (bold rendered)
```
### Override Fields
| Regular Field | Override Field | Purpose | Editable By | HTML Rendering |
|---|---|---|---|---|
| `pronouns` | `pronouns_override` | Preferred pronouns | Staff, Attendee | No |
| `professional_title` | `professional_title_override` | Job title display | Staff, Attendee | ✅ Yes |
| `full_name` | `full_name_override` | Preferred name display | Staff, Attendee | ✅ Yes |
| `affiliations` | `affiliations_override` | Organization display | Staff, Attendee | ✅ Yes |
| `phone` | `phone_override` | Phone number | Staff, Attendee | No |
| `email` | `email_override` | Contact email override | Staff only | No |
| `location` | `location_override` | City/State/Country display | Staff, Attendee | ✅ Yes |
| `badge_type` | `badge_type_override` | Badge category label text | Staff only | No |
| `badge_type_code` | `badge_type_code_override` | Badge access level code | Staff only | No |
| `registration_type` | `registration_type_override` | Registration category label text | Staff only | No |
| `registration_type_code` | `registration_type_code_override` | Registration category code | Staff only | No |
> **Note:** `phone`, `phone_override`, `pronouns_override`, `registration_type`, `registration_type_code`, `registration_type_override`, `registration_type_code_override` may need to be confirmed against the DB schema via `ae_describe event_badge` and added to `properties_to_save` in `ae_events__event_badge.ts` if not already present.
### Sync Safety Rules
**Automated Sync (Cron Jobs):**
- ✅ CAN update: All regular fields (`given_name`, `family_name`, `email`, `affiliations`, etc.)
- ❌ CANNOT update: Any `*_override` field
- ❌ CANNOT delete: Any `*_override` value
**Manual Staff Edit:**
- ✅ CAN update: Any field (including overrides)
- ✅ CAN clear: Override fields (reverts to regular field)
**Attendee Self-Service Edit:**
- ✅ CAN update: Only specific override fields (per event config)
- ✅ CAN clear: Their own override fields
- ❌ CANNOT edit: Regular fields, badge_type, email_override
| Regular Field | Override Field | Editable By | HTML? |
|---|---|---|---|
| `full_name` | `full_name_override` | Staff, Attendee | ✅ |
| `professional_title` | `professional_title_override` | Staff, Attendee | ✅ |
| `affiliations` | `affiliations_override` | Staff, Attendee | ✅ |
| `location` | `location_override` | Staff, Attendee | ✅ |
| `email` | `email_override` | Staff Only | No |
| `badge_type` | `badge_type_override` | Staff Only | No |
---
## External System Integration
### Supported Import Sources
- **iMIS** (Association Management)
- **Zoom** (Virtual event registration)
- **Novi AMS** (Association Management)
- **Impexium** (Association Management)
- **Confex** (Event abstract management)
- **Cvent** (Event registration)
- **Custom CSV/Excel** imports
Aether acts as a **Pull-Only** consumer for registration data. It does not push changes back to external systems, maintaining them as the source of truth for base registration while Aether handles the "Onsite Truth."
### Data Flow Direction
```
External Systems ─────────> Aether
(READ ONLY) (WRITE + DISPLAY)
```
**Important:** Aether is **pull-only** — does not push changes back to external systems. This prevents sync conflicts and maintains external systems as the source of truth for base data.
### Sync Behavior
- **Frequency:** Scheduled cron jobs (typically hourly, daily, or on-demand)
- **Method:** Full sync or incremental (depends on external system API)
- **Conflict Resolution:** Override fields always win
**Pseudocode:**
```python
def sync_badge_from_external(external_badge_data, existing_badge):
# Update regular fields from external source
existing_badge.given_name = external_badge_data.first_name
existing_badge.family_name = external_badge_data.last_name
existing_badge.email = external_badge_data.email
existing_badge.affiliations = external_badge_data.organization
existing_badge.badge_type_code = external_badge_data.registration_type
# NEVER TOUCH OVERRIDE FIELDS
# existing_badge.full_name_override ← PROTECTED
# existing_badge.affiliations_override ← PROTECTED
# existing_badge.email_override ← PROTECTED
return existing_badge
```
### Supported Sources
- **iMIS**, **Novi AMS**, **Impexium** (Associations)
- **Zoom**, **Cvent** (Registrations)
- **Confex** (Abstracts/Presenters)
- **Custom CSV/Excel**
---
## Access Control & Edit Permissions
## Access Control & Permissions
### Access Levels (Ascending)
1. **Anonymous** — No access to badges
2. **Public** View public event info only (no badge access)
3. **Authenticated** — View own badge, limited self-edit
4. **Trusted** — Search all badges, view all, edit own
5. **Administrator** — Full CRUD, bulk operations, override any field
6. **Manager** — All administrator + event configuration
7. **Super** — All manager + cross-event operations
| Level | Access |
|---|---|
| **Authenticated** | View own badge, limited self-edit (overrides only). |
| **Trusted** | Search all badges, view all, reprint existing badges. |
| **Administrator** | Full CRUD, bulk operations, override any field. |
| **Manager** | All Admin + Event/Template configuration. |
### Current Implementation (v3) — 2026-02-27
#### Badge Search Results Visibility
| Access Level | Sees |
| --- | --- |
| Below Trusted (incl. anonymous) | Only badges where `print_count < 1` and not hidden |
| Trusted, not Edit Mode | Only badges where `print_count < 1` and not hidden |
| Trusted + Edit Mode | All badges where `hide === false` (including already-printed) |
#### Print Button Behavior (per result row)
| Access Level | Print Action |
| --- | --- |
| Below Trusted | No print action — name shown with User icon, non-interactive |
| Trusted, `print_count < 1` | Clickable link → `/print` page, Printer icon |
| Trusted, `print_count >= 1`, not Edit Mode | Disabled (already printed safety lock), shows `Nx` count |
| Trusted, `print_count >= 1`, Edit Mode | Clickable reprint — shows `Nx` count badge next to icon |
Print count displayed as `[Printer][2×] Name` when `print_count >= 1`.
#### Review Area Buttons (per result row, up to 3 buttons total)
| Button | Visible To | Behavior |
| --- | --- | --- |
| Email Review Link | All users | Placeholder `alert()` — will trigger email API |
| Review Link (clipboard) | Trusted + Edit Mode only | Copies `/review` URL to clipboard; shows `Copied!` feedback |
| *(direct Review link)* | *(future)* | *(not yet implemented as separate nav button)* |
#### Badge Edit Form (`ae_comp__badge_obj_view.svelte`)
**Currently editable fields (local `edit_mode_active`, not global `edit_mode`):**
```typescript
editable_full_name_override: string | null
editable_professional_title_override: string | null
editable_affiliations_override: string | null // textarea
editable_location_override: string | null
editable_allow_tracking: boolean | null
editable_email: string | null
editable_badge_type_code: string | null
```
- Save button → `handle_save_changes()` — only changed fields sent to API
- Cancel button → `handle_cancel_changes()` — reverts to IDB values
- **IMPORTANT:** This component must NEVER write to `$ae_loc.edit_mode` — it uses its own local `edit_mode_active` flag only. (Bug fixed 2026-02-27)
#### Badge Review Form (`ae_comp__badge_review_form.svelte`)
Form-based review (NOT a badge render). Used by the `/review` page.
**Props:**
- `can_edit_fields: string[]` prop controls which fields are editable per user level
- `['*']` = administrator (all fields)
- `is_staff: boolean` prop shows/hides the staff-only fields
- Fields show "(overridden)" label when an override value differs from the base field
**Features (implemented 2026-02-27):**
- **HTML Rendering**: `full_name_override`, `professional_title_override`, `affiliations_override`, and `location_override` fields render HTML markup using `{@html}` directive when viewing (not when editing)
- **Accessibility**: Text enlargement toggle button switches between text-2xl (normal) and text-4xl (enlarged) for improved readability
- **Help Modal**: Flowbite Modal component with 6 help sections (Reviewing Badge, Editing Info, Accessibility, QR Code, Lead Scanning, Assistance)
- **QR Code Display**: Generates QR code using `core_func.js_generate_qr_code()` with badge ID, supports hover zoom and click-to-expand
- **Print Status**: Shows print count, first print datetime, and last print datetime at top of form
- **Local Edit Mode**: Independent `local_edit_active` state (never writes to `$ae_loc.edit_mode`)
- **Save/Cancel**: Only changed fields sent to API; revert button for override fields
**Editable Fields:**
- Pronouns, Full Name, Professional Title, Affiliations, Phone, Location (all with override support)
- Allow Tracking checkbox (lead scanning permission)
- Staff-only: Email, Badge Type, Registration Type, Hide, Priority, Notes
- Staff-only: Options (`other_1_code` through `other_8_code`) and Tickets (`ticket_1_code` through `ticket_8_code`)
- Agree to Terms & Conditions checkbox (attendee-visible when in can_edit_fields)
#### Badge Review Page — Header Buttons (implemented 2026-02-27)
| Button | Visible To | Behavior |
| --- | --- | --- |
| Back → Search (ArrowLeft) | Staff (`has_staff_access`) only | `<a href="/events/{id}/badges">` |
| Print (Printer icon) | Trusted+, not printed OR Trusted+Edit if printed | `<a href="/print">`, shows `Nx` count if reprinting |
| Copy Link (clipboard) | Trusted + Edit Mode only | Copies review URL to clipboard; `Copied!` feedback for 2s |
| Email Link (Mail icon) | All if not printed; Trusted+Edit if printed | Placeholder `alert()` — email API pending |
#### Badge Print Page — Header Buttons (implemented 2026-02-27)
| Button | Visible To | Behavior |
| --- | --- | --- |
| Back → Search (ArrowLeft) | Always (when badge loaded) | `<a href="/events/{id}/badges">` |
| Print Now (Printer icon) | Trusted+, not printed OR Trusted+Edit if printed | Calls `window.print()` directly (convenience duplicate); print count tracked by component button |
| Review (Eye icon) | Trusted + Edit Mode only | `<a href="/review">` nav link |
| Email Link (Mail icon) | All if not printed; Trusted+Edit if printed | Placeholder `alert()` — email API pending |
#### Badge Review Page — Display Sections (implemented 2026-02-27)
The review form (`ae_comp__badge_review_form.svelte`) displays:
1. **Print status** ✅ — print count + first/last print timestamps (read-only, hidden if never printed)
2. **QR Code** ✅ — the attendee's badge QR code for scanning at the badge kiosk (for automatic badge search + print flow). Generated using `core_func.js_generate_qr_code()` with `obj_type: 'event_badge'` and badge ID. Supports hover zoom overlay and click-to-expand.
3. **Editable Fields** ✅ — all fields with access-level gating, override support, and HTML rendering for display fields
4. **Options** ✅ (`other_1_code` through `other_8_code`) — Staff: editable text inputs; Attendees: shown as `[✓] Option X` checkmark display only when value exists
5. **Tickets** ✅ (`ticket_1_code` through `ticket_8_code`) — Staff: editable text inputs; Attendees: shown as `[✓] Ticket X` checkmark display only when value exists
6. **Accessibility Toggle** ✅ — Font size enlargement button in sticky header (text-2xl ↔ text-4xl)
7. **Help Modal** ✅ — Attendee guidance modal with 6 sections explaining the review process, editing, QR codes, and lead scanning
#### Default Field Permissions (hardcoded for now — Axonius first show, mid-April 2026)
These are hardcoded in `review/+page.svelte` pending connection to `mod_badges_json.edit_permissions`.
**Attendee (passcode-authenticated / anonymous with link):**
```typescript
[
'pronouns_override',
'full_name_override',
'professional_title_override',
'affiliations_override',
'phone_override',
'location_override',
'allow_tracking', // Exhibitor Leads opt-in
'agree_to_tc', // Terms & Conditions placeholder
]
```
**Trusted Staff and above:**
```typescript
[
'pronouns_override',
'full_name_override',
'professional_title_override',
'affiliations_override',
'email_override',
'phone_override',
'location_override',
'badge_type_code_override', // + badge_type_override (text label)
'registration_type_code_override', // + registration_type_override (text label)
'option_1' ... 'option_8', // i.e. other_1_code ... other_8_code
'ticket_1_code' ... 'ticket_8_code',
'allow_tracking',
'agree_to_tc',
'hide',
'priority',
'notes',
]
```
**Administrator**`can_edit_fields = ['*']` (all fields)
**Badge type options (hardcoded for now):** `member`, `non-member`, `guest`, `exhibitor`, `staff`, `test`
(In future: read from Event Badge Template's configured list)
**Registration type options:** Same list as badge type for now — identical select options.
#### Future: Per-Event Configuration
`event.mod_badges_json.edit_permissions` — placeholder settings UI exists in
`ae_comp__event_settings_badges_form.svelte`. Review page uses hardcoded defaults for now.
The settings form and review page are not yet connected.
```json
{
"authenticated": {
"can_edit": ["pronouns_override", "full_name_override", "professional_title_override", "affiliations_override", "phone_override", "location_override", "allow_tracking", "agree_to_tc"]
},
"trusted": {
"can_edit": ["*attendee_fields", "email_override", "badge_type_code_override", "registration_type_code_override", "option_x", "ticket_x_code", "allow_tracking", "agree_to_tc", "hide", "priority", "notes"]
}
}
```
### Attendee Self-Service (`/review`)
Attendees can access their own record via a passcode-gated link (typically `?passcode=...`). This allows them to verify their info and provide preferred name/title overrides before printing.
---
## Search & Filter Capabilities
### Search Component
**File:** `ae_comp__badge_search.svelte`
### Multi-Word Search Fix (2026-02-26)
Fulltext search now correctly handles multi-word queries by splitting on whitespace and applying AND logic per word:
```typescript
// "scott idem" → LIKE '%scott%' AND LIKE '%idem%'
// Previously: LIKE '%scott idem%' (failed to match)
const words = qry.split(/\s+/).filter(w => w.length > 0);
for (const word of words) {
search_query.and.push({ field: 'default_qry_str', op: 'like', value: `%${word}%` });
}
```
**Committed:** dc0f3066
### Available Filters
**Fulltext Search** (All Users)
- Searches: `default_qry_str` database field
- Includes: Name, email, external IDs
- Type: `LIKE %query%` (case-insensitive)
- Trigger: Enter key or 3+ characters typed
**Advanced Filters** (Trusted Access & Above)
```typescript
// Badge Type Filter
badge_type_code: 'current_member' | 'inactive_member' | 'ex_all' | 'staff' | etc.
// Note: Badge types are defined per Event and Event Badge Template in database table records.
// Common types include: member, nonmember, guest, exhibitor, staff
// This is a work in progress - types vary by event configuration.
// Print Status Filter
qry_printed_status: 'all' | 'printed' | 'not_printed'
// Affiliations Search
qry_affiliations: string // Separate filter for organization search
// Sort Options
qry_sort_order:
- 'name_asc' / 'name_desc'
- 'updated_desc' / 'updated_asc'
- 'print_count_desc'
- 'print_first_desc' / 'print_last_desc'
- 'badge_type_asc'
- 'affiliations_asc'
```
### QR Scan Search
- Scans badge QR code
- Extracts badge ID
- Auto-fills search with ID
- Jumps to badge detail view
### Search Implementation Pattern
**File:** `badges/+page.svelte` (Lines 117-365)
**Strategy:** Standardized Reactive Search Pattern (Aether UI V3)
1. **Isolate dependencies** into stable `$derived` object
2. **Debounced effect** (300ms) triggers search
3. **Fast Path:** Search IDB first (if not `remote_first`)
4. **Revalidate:** API request updates IDB
5. **LiveQuery:** UI auto-updates from IDB changes
**Search API:** `events_func.search__event_badge()`
```typescript
await search__event_badge({
api_cfg: $ae_api,
event_id: event_id,
fulltext_search_qry_str: qry_str || null,
type_code: type_code || null,
printed_status: printed_status,
affiliations_qry_str: aff_str || null,
order_by_li: order_by_li,
limit: 150,
log_lvl: 0
})
```
---
## Badge Display Logic
### Name Display Priority
```typescript
// Component: ae_comp__badge_obj_li.svelte (Lines 113-121)
if (event_badge_obj?.full_name_override)
display: full_name_override
else if (event_badge_obj?.full_name)
display: full_name
else
display: given_name + ' ' + family_name
```
### Badge View Page
**Route:** `/events/[event_id]/badges/[badge_id]`
**Components:**
- `+page.svelte` — Container with LiveQuery for badge data
- `ae_comp__badge_obj_view.svelte` — Full badge display + edit UI
**LiveQueries:**
```typescript
lq__event_badge_obj = liveQuery(() => db_events.badge.get(event_badge_id))
lq__event_badge_template_obj = liveQuery(() =>
db_events.badge_template.get(badge.event_badge_template_id)
)
```
**Loading States:**
- `is_loading_idb` — Waiting for initial IDB lookup
- If badge not found → "Badge Not Found" error with reload button
- Loader spinner while fetching
---
## Badge Templates
### Purpose
Badge templates define the visual layout and content structure for printed badges:
- Header images/logos
- Field positions and font sizes
- QR code placement
- Ticket/option indicator display
- WiFi credentials display
### Template Selection
Each badge references an `event_badge_template_id`. The template controls:
- Layout (front/back)
- Branding elements
- Which fields to show
- Field formatting rules
### Template Loading
Templates are loaded alongside badges via `inc_template` parameter:
```typescript
load_ae_obj_id__event_badge({
event_badge_id: badge_id,
inc_template: true // Also loads template
})
```
- **Fulltext Search:** Matches against a consolidated `default_qry_str` (Name, email, IDs).
- **Multi-Word Logic:** Queries like "Scott Idem" are split and treated as `LIKE %Scott% AND LIKE %Idem%`.
- **QR Scan Search:** Scanning an attendee's QR code (from a confirmation email or old badge) immediately jumps to their record.
- **Advanced Filters:** Filter by Badge Type, Printed Status, or Affiliations (Staff only).
---
## Print Tracking
### Print Fields
```typescript
print_count: number // Increments each print
print_first_datetime: string // ISO datetime of first print
print_last_datetime: string // ISO datetime of most recent print
```
Aether tracks the lifecycle of every physical badge to prevent unauthorized reprints and monitor kiosk activity.
### Print Button (Implemented 2026-02-26)
The `handle_print_badge()` function in `ae_comp__badge_obj_view.svelte` increments the count and records timestamps:
```typescript
async function handle_print_badge() {
const now = new Date().toISOString();
const current_print_count = $lq__event_badge_obj.print_count ?? 0;
const data_to_update = {
print_count: current_print_count + 1,
print_last_datetime: now
};
if (current_print_count === 0) {
data_to_update.print_first_datetime = now; // Only set on first print
}
await events_func.update_ae_obj__event_badge({ ... });
}
```
| Field | Purpose |
|---|---|
| `print_count` | Increments on every "Print Badge" action. |
| `print_first_datetime` | Timestamp of the very first print. |
| `print_last_datetime` | Timestamp of the most recent print. |
Button has `data-testid="badge-print-btn"` and shows loading/done/error states with icon feedback.
### Print Workflow
1. **Pre-Print:** Badge print page (`/print`) shows "Already printed N times" warning in screen-only header if `print_count >= 1`
2. **Record:** `handle_print_badge()` updates `print_count`, `print_last_datetime`, and `print_first_datetime` (first print only) via API before printing
3. **Print:** `window.print()` — standard browser print dialog, wired and working (2026-02-27)
4. **Redirect:** After 1 second, `goto(/events/{id}/badges)` returns to search
5. **Audit:** `print_first_datetime` and `print_last_datetime` visible in Edit Mode debug row
**Browser vs Electron:** Badge printing does NOT require the Electron native app. The standard browser print dialog works well across Chrome, Chromium, and Firefox. The Electron native app is specialized for the **Events Pres Mgmt Launcher only** and should not be assumed available for badge stations.
> **Operational Note:** Reprints triggered via the Edit Mode shortcut do not increment the count; only the formal "Print Badge" workflow does.
---
## Database Schema
## Route Map (Badges)
### IndexedDB Table: `badge`
**File:** `src/lib/ae_events/db_events.ts` (Lines 841-852)
**Indexed Fields:**
```typescript
badge: `
event_badge_id, id,
event_id,
full_name, full_name_override, email, email_override,
affiliations, affiliations_override,
badge_type, badge_type_code, badge_type_code_override, badge_type_override,
external_event_id, external_id, external_person_id,
default_qry_str,
alert,
tmp_sort_1, tmp_sort_2,
print_count, print_first_datetime, print_last_datetime,
enable, hide, priority, sort, group, notes, created_on, updated_on
`
```
### Saved Properties
**File:** `ae_events__event_badge.ts` (Lines 495-563)
**Complete field list** (67 fields total):
- Identity: `id`, `event_badge_id`, `event_id`, `event_badge_template_id`
- Name: `pronouns`, `informal_name`, `title_names`, `given_name`, `middle_name`, `family_name`, `designations`
- Professional: `professional_title`, `professional_title_override`
- Display: `full_name`, `full_name_override`
- Organization: `affiliations`, `affiliations_override`
- Contact: `email`, `email_override`
- Address: `address_line_1`, `address_line_2`, `address_line_3`, `city`, `country_subdivision_code`, `state_province`, `state_province_abb`, `postal_code`, `country_alpha_2_code`, `country`, `full_address`
- Location: `location`, `location_override`
- Classification: `badge_type`, `badge_type_code`, `badge_type_override`, `badge_type_code_override`
- External: `external_event_id`, `external_id`, `external_person_id`
- Search: `query_str`, `default_qry_str`
- System: `alert`, `enable`, `hide`, `priority`, `sort`, `group`, `notes`, `created_on`, `updated_on`
- Print: `print_count`, `print_first_datetime`, `print_last_datetime`
- Sorting: `tmp_sort_1`, `tmp_sort_2`
- Person Link: `person_external_id`, `person_external_sys_id`, `person_given_name`, `person_family_name`, `person_full_name`, `person_professional_title`, `person_affiliations`, `person_primary_email`, `person_passcode`
---
## API Functions
### CRUD Operations
**File:** `src/lib/ae_events/ae_events__event_badge.ts`
```typescript
// Load single badge
load_ae_obj_id__event_badge({ event_badge_id, event_id, inc_template })
// Load badge list
load_ae_obj_li__event_badge({ event_id, view, limit, order_by_li })
// Search badges (V3 API)
search__event_badge({
event_id,
fulltext_search_qry_str,
type_code,
printed_status,
affiliations_qry_str,
order_by_li
})
// Create badge
create_ae_obj__event_badge({ event_id, data_kv })
// Update badge
update_ae_obj__event_badge({ event_badge_id, event_id, data_kv })
// Delete badge
delete_ae_obj_id__event_badge({ event_badge_id, event_id, method })
```
### Field Processing
**Function:** `process_ae_obj__event_badge_props()`
**Processing Steps:**
1. Map `*_random` fields to clean names (`event_badge_id_random``event_badge_id`)
2. Set primary `id` field from `event_badge_id`
3. Ensure `event_id` is set (from function parameter if missing)
4. Calculate `tmp_sort_1` and `tmp_sort_2` for efficient sorting
5. Return processed objects
**Critical Fix (2026-02-26):** All CRUD functions now return **processed** data (matches IDB cache) instead of raw API responses. This ensures consistency between function return values and cached data.
---
## Component Architecture
### Route Structure
```
/events/[event_id]/(badges)/badges/
├── +layout.svelte # Layout wrapper (minimal)
├── +page.svelte # Badge list + search
├── ae_comp__badge_search.svelte # Search form + filters
├── ae_comp__badge_obj_li.svelte # Badge list display (results)
├── ae_comp__badge_create_form.svelte # (Not actively used)
├── ae_comp__badge_upload_form.svelte # Bulk CSV upload
└── [badge_id]/
├── ae_comp__badge_obj_view.svelte # Badge rendering + staff edit + print button
├── ae_comp__badge_review_form.svelte # Form-based field review/edit (attendee + staff)
├── print/
│ ├── +page.ts # Non-blocking badge loader (inc_template: true)
│ └── +page.svelte # Print-focused page — screen header + badge render
└── review/
├── +page.ts # Non-blocking badge loader (inc_template: false)
└── +page.svelte # Passcode-gated review page
```
> **Note:** The old `[badge_id]/+page.svelte` placeholder was removed (2026-02-27). The name link in the search results list now goes directly to `/print`.
#### Badge Print Page (`/print`)
- Screen-only header (`print:hidden`): "Back to Search" link + "Already printed N times" warning
- Badge rendered via `ae_comp__badge_obj_view` with `is_review_mode={false}`
- Print button inside `ae_comp__badge_obj_view` handles count update → `window.print()` → redirect to search
- Page `<title>` includes badge name + event name
#### Badge Review Page (`/review`)
- Passcode-gated for attendees — URL `?passcode=...` matched against `badge.person_passcode`
- **Note:** `person_passcode` field is not yet in the DB (as of 2026-02-27). Review page accessible to staff via `trusted_access` without a passcode.
- Access hierarchy (checked in order):
1. Administrator → full access (`can_edit_fields = ['*']`)
2. Trusted Staff → staff field set
3. Attendee with valid passcode → attendee field set
4. No access → passcode entry form shown
- Uses `ae_comp__badge_review_form.svelte` (NOT badge render)
- "Back to Search" link shown for staff only
### Key Components
**Badge List Page** (`+page.svelte`)
- **LiveQuery:** Reactive badge list from IDB
- **Search Pattern:** Debounced search with fast path + revalidation
- **ID List:** `event_badge_id_li` drives LiveQuery
- **Loading State:** Shows spinner when `search_status === 'loading'`
**Badge Search** (`ae_comp__badge_search.svelte`)
- **Form Mode:** Toggle between search form and QR scanner
- **Filters:** Badge type, print status, affiliations, sort order (trusted+ only)
- **Fulltext:** Name/email search (all users)
- **QR Scan:** Integrated QR scanner for badge ID lookup
**Badge List Display** (`ae_comp__badge_obj_li.svelte`)
- **Visibility Filter:** Respects `hide` flag (trusted+ sees all)
- **Display Logic:** Override → regular → fallback pattern
- **Print Indicator:** Green checkmark badge shows `print_count`
- **Metadata:** ID, created/updated timestamps (edit mode only)
**Badge Detail View** (`ae_comp__badge_obj_view.svelte`)
- **Edit Mode:** Activated by edit button (or `#review` URL hash for future self-service)
- **Condition:** Renders only when BOTH `$lq__event_badge_obj` AND `$lq__event_badge_template_obj` are non-null
- **Form Binding:** Direct `bind:value` on editable fields
- **Dynamic Sizing:** Font size adjusts based on text length
- **Print Preview:** Full badge layout with template
- **Save Handler:** Only sends changed fields to API
- **`data-testid` attributes:** `badge-edit-btn`, `badge-save-btn`, `badge-cancel-btn`, `badge-print-btn`, `badge-professional-title-input` — use these in tests
---
## Testing Status
### Current Test Coverage
- ✅ Badge list loads (all 6 data integrity tests passing)
- ✅ Badge template list loads and displays
- ✅ Badge template form renders and populates correctly
- ✅ Badge template values persist in edit form
- ✅ Electron bridge compatibility (graceful degradation in browser)
- ✅ Badge field processor handles missing optional fields
- ✅ Badge type filter tests
- ✅ Badge template relationship tests
-**Attendee workflow test** — navigate → edit professional title → print → return (d1ded2d4)
### Key Test Lessons Learned
**Search API path is FLAT, not nested.** `search_ae_obj` builds `/v3/crud/{obj_type}/search` — always flat regardless of the parent relationship. Mocks must match this:
```typescript
// CORRECT — flat path
url.includes('/v3/crud/event_badge/search') && method === 'POST'
// WRONG — nested path, mock will never fire
url.includes(`/v3/crud/event/${event_id}/event_badge/search`) && method === 'POST'
```
**List API (GET) is also FLAT with query params.** `get_ae_obj_li` builds `/v3/crud/{obj_type}/?for_obj_id=...` — always flat. Mocks must check `url.includes('/v3/crud/event_badge_template/') && url.includes('for_obj_id')`.
**CSS `input[value*=...]` selectors don't work with Svelte bind:value.** The CSS selector checks the HTML *attribute*; Svelte's `bind:value` sets the DOM *property* only. In Playwright tests, use `page.getByLabel()` or `locator.inputValue()` instead.
**Dexie requires `_random` ID fields.** Badge objects saved to IDB must include:
```typescript
event_badge_id_random: string // Must be present or Dexie skips the object
id_random: string // Also checked
// Error: "Object is missing a valid ID for table 'badge'"
```
All API mock responses in tests need these fields.
**Badge view requires both badge AND template.** `ae_comp__badge_obj_view.svelte` wraps everything in `{#if $lq__event_badge_obj && $lq__event_badge_template_obj}` — if the template isn't loaded, edit/print buttons and the badge itself don't render. Tests must mock the badge template endpoint.
**Badge GET endpoint (single object):** `/v3/crud/event_badge/{id}` (NOT nested under event). Matches `api.get_ae_obj()` which uses the flat path.
**Badge PATCH endpoint (update):** `/v3/crud/event/${event_id}/event_badge/${badge_id}` (nested under event). Matches `api.patch_ae_obj()` which uses the nested path.
**Use `data-testid` for test selectors.** Key buttons have targets: `badge-edit-btn`, `badge-save-btn`, `badge-cancel-btn`, `badge-print-btn`, `badge-professional-title-input`.
### Remaining Test Issues
None — all current badge tests passing as of 2026-02-26 (f5e98b8c).
---
## Known Issues & Future Enhancements
### Known Issues
1. **Session Cold-Start:** Potential race condition on first load (same as pres mgmt module)
2. **Type Definitions:** Some pre-existing TypeScript errors on external package types (not introduced by badge work)
3. **`person_passcode` not in DB:** Attendee-gated review URL (`?passcode=...`) cannot function until this field is added to the `event_badge` schema. The review page falls back to passcode entry form for non-staff.
4. **Print page CSS:** Badge print rendering and `@page` print styles not yet fine-tuned — expected to need work
5. **`mod_badges_json.edit_permissions` not connected:** Settings UI exists but review page uses hardcoded field defaults
### Implemented (2026-02-27)
-`window.print()` wired to print button (records count first, then prints, then redirects)
- ✅ Dedicated `/print` page — replaces old `[badge_id]/+page.svelte` placeholder
- ✅ Dedicated `/review` page — passcode-gated, access-tiered
-`ae_comp__badge_review_form.svelte` — stub created, full form fields pending
- ✅ Badge search results visibility rules (unprinted-only for non-edit, all for trusted+edit)
- ✅ Badge list: 4 action buttons per row (Print, Review nav, Copy Link, Email Link) — all Lucide icons
- ✅ Print page: 3 action buttons in header (Print Now, Review nav, Email Link) — all Lucide icons
- ✅ Review page: 3 action buttons in header (Print nav, Copy Link, Email Link) — all Lucide icons
- ✅ Print button: not shown when already printed (unless Edit Mode)
- ✅ Print count shown as `Nx` badge next to printer icon
- ✅ Email obscuring for non-trusted users
- ✅ Email Review Link button (placeholder alert — email API pending)
- ✅ Direct Review Link clipboard copy (trusted + Edit Mode only)
- ✅ Fixed: components no longer write to `$ae_loc.edit_mode`
- ✅ Settings UI for `edit_permissions` per event (`ae_comp__event_settings_badges_form.svelte`)
- ✅ All badge module icons converted to Lucide (Font Awesome removed from badge routes)
### Recently Completed (2026-02-27)
-**Badge Review Form**`ae_comp__badge_review_form.svelte` fully implemented (fields, QR, save/cancel, options/tickets, accessibility, help modal)
-**Print font size controls (v1)** — Screen-only `[]/[+]/[↺]` panel on print page; 4 px props added to `ae_comp__badge_obj_view.svelte`; auto-sizing unchanged when props absent
-**Bug fix**`default_authenticated_fields` / `default_trusted_fields` in `review/+page.svelte` corrected (wrong field names caused silent save drops)
### Still Needed — HIGH PRIORITY (first show: April 2026)
### Still Needed — MEDIUM PRIORITY
1. **Email API for review links:** `send_review_email()` is a placeholder `alert()`. Needs actual email send endpoint.
2. **`person_passcode` DB field:** Add to `event_badge` schema to enable attendee-gated review URLs.
3. **Connect `edit_permissions` config:** Read `mod_badges_json.edit_permissions` in review page instead of hardcoded defaults.
4. **Print page CSS / `@page` styles:** Badge rendering, sizing, and print-specific stylesheet.
### Still Needed — FUTURE / LOW PRIORITY
1. **Batch Operations:** Bulk update, bulk print, bulk export
2. **Audit Log:** Track who edited which fields and when
3. **Photo Badges:** Support badge photo upload and display
4. **Real-Time Sync:** WebSocket updates for multi-device badge printing stations
---
## Development Guidelines
### Adding New Override Fields
1. Add `{field}_override` to database schema
2. Add to `properties_to_save` array in `ae_events__event_badge.ts`
3. Update display logic to check override first
4. Add to editable fields in `ae_comp__badge_obj_view.svelte`
5. Update access control config
6. Document in this file
### Testing Override Fields
```typescript
// Simulate external sync
badge.given_name = "External Value"
// User edits
badge.given_name_override = "User Value"
// Next sync (should NOT change override)
badge.given_name = "Updated External Value"
// Display should still show "User Value"
assert(display === badge.given_name_override)
```
### Debugging Search Issues
```typescript
// Enable search logging
log_lvl: 2
// Check search params object
console.log('Search params:', search_params)
// Verify API request
console.log('API request:', { event_id, fulltext_search_qry_str, type_code })
// Check returned IDs
console.log('Badge IDs:', event_badge_id_li)
// Verify IDB contents
db_events.badge.toArray().then(console.log)
```
| URL | Purpose |
|---|---|
| `/events/[id]/badges` | Main search and attendee list. |
| `/events/[id]/badges/templates` | Badge template management. |
| `/events/[id]/badges/[id]/print` | The actual print-ready render page. |
| `/events/[id]/badges/[id]/review` | Attendee-facing self-service form. |
---
## Related Documentation
- [AE API V3 for Frontend](./GUIDE__AE_API_V3_for_Frontend.md)
- [Development Guide](./GUIDE__Development.md)
- [Events Launcher Native Integration](./PROJECT__AE_Events_Launcher_Native_integration.md)
- [Naming Conventions](./AE__Naming_Conventions.md)
---
**Document Status:** 🔄 In Progress
**Last Verified:** 2026-02-27 (rev 5 — field permissions spec added, header buttons implemented, review form fields pending)
**Verified Against:** Code as of 2026-02-27 (branch ae_app_3x_llm)
👉 **[MODULE__AE_Events_Badge_Templates.md](./MODULE__AE_Events_Badge_Templates.md)** (Technical reference for layouts)
👉 **[GUIDE__AE_Events_Badges_Onsite.md](./GUIDE__AE_Events_Badges_Onsite.md)** (Hardware & station setup)
👉 **[GUIDE__AE_Events_Onsite_Runbook.md](./GUIDE__AE_Events_Onsite_Runbook.md)** (Onsite operational checklists)

View File

@@ -0,0 +1,79 @@
# Aether Events — Launcher (Podium Display)
The Launcher module provides the podium display interface that runs on each session room's kiosk machine. It is designed to work in standard browsers but is optimized for the **Aether Desktop (Electron)** native shell.
---
## Operational Modes
| Mode | Use Case | File Handling |
|---|---|---|
| **Default** | Browser on any machine | Files downloaded on demand via browser. |
| **Onsite** | Browser on event network | Faster polling; browser-managed files. |
| **Native** | Electron app on podium Mac | Background pre-cache; atomic file handover. |
For production onsite use, **Native mode on Mac laptops** is the target. The Electron
app pre-caches all session files in the background so presentations open instantly without
a network round-trip at the moment of launch.
---
## Launcher Display Views
| View | Shown When |
|---|---|
| **Session view** | Active session with session-level files. |
| **Presentation view** | Active session with named presentations. |
| **Presenter view** | Presentation selected; shows presenter bio/photo. |
| **Poster/group view** | Special layout for poster sessions. |
| **Screensaver** | No active session; idle state. |
---
## Sync Engine & File Handling
### Background Sync (File Warming)
When a user navigates to a session in the Launcher UI, the background engine automatically warms the cache for that specific session by downloading all associated files.
### Force Sync Location
To ensure full room readiness (e.g., during SRR setup or overnight), operators can trigger a **Force Sync Location** via the configuration menu. This performs a recursive fetch of all sessions, presentations, and presenters for the room and queues every file for the day for download.
### Download Priority & Room Readiness
To ensure the podium is ready for the day's first sessions, the Launcher sync engine uses a 4-tier chronological sorting priority:
1. **Global Assets:** Event and Location level files (branding, walk-in slides) are cached first.
2. **Session Schedule:** Files for the earliest sessions in the room are prioritized.
3. **Presentation Order:** Within a session block, speakers are prioritized by their scheduled start time.
4. **First-In Fairness:** When times are equal, older uploads are prioritized over late revisions (respecting on-time presenters).
### Native File Opening (Safe Handover)
1. Verify SHA-256 hash in permanent cache.
2. Atomic copy to system `[tmp]` directory.
3. Rename to original filename (e.g., `Abstract_101.pptx`).
4. OS opens the file via a **Launch Profile** (AppleScript or Shell command).
---
## Device & Native Integration
Each Launcher kiosk is registered as an `event_device` record in Aether. The technical specifications for the Electron bridge, hashed cache protocol, and hardware actuators are documented in:
👉 **[MODULE__AE_Events_Launcher_Native.md](./MODULE__AE_Events_Launcher_Native.md)**
---
## Route Map (Display)
| URL | Purpose |
|---|---|
| `/events/[id]/launcher` | Launcher home — select location |
| `/events/[id]/launcher/[location_id]` | Launcher display for a specific room |
---
## Access Levels
| Feature | Minimum Access |
|---|---|
| View Launcher display | `authenticated_access` |
| Manual session selection | `trusted_access` |
| Advanced Config / Sync Control | `trusted_access` (via Configuration Drawer) |

View File

@@ -1,7 +1,7 @@
# Aether Events Launcher: Native Electron Integration
# Aether Events Launcher: Native Integration
> **Status:** Operational / Phase 5 Implementation
> **Last Updated:** 2026-05-11
> **Status:** Operational / Permanent Reference
> **Last Updated:** 2026-05-21 (Reorganized)
> **Primary Platform:** macOS (Darwin)
> **Fallback Platform:** Linux / Windows

View File

@@ -0,0 +1,139 @@
# Aether Events — Presentation Management
The Presentation Management module handles the full lifecycle of conference content: sessions, presentations, presenters, presentation files, and room/location assignments. It serves as the "Back Office" interface for event staff.
---
## Data Model
### Object Hierarchy
```text
Event
├── Event File (walk-in/out, hold slides for the whole event)
├── Location (physical room — assigned to Sessions, not the other way around)
├── Track (optional grouping; rarely used)
└── Session (time block; Location assigned here, but may be unset initially)
├── Session File (moderator slides, group/hold slides for this session)
└── Presentation (a talk within the session; must belong to exactly one Session)
└── Presenter (belongs to exactly one Presentation)
└── Presenter File (their slides/materials — the common case)
```
> **Import note:** When program data is initially imported (sessions, presentations,
> presenters), Locations are often not assigned to Sessions yet — rooms may not be
> finalized or the venue's room list may not be set up in Aether. Location assignment
> typically happens as a separate step once the room list is confirmed.
### Relationships
- **Session → Location:** Many-to-one. A Session is assigned to one Location; a Location
hosts many Sessions across the event timeline. Location may be null initially.
- **Presentation → Session:** Many-to-one. A Presentation belongs to exactly one Session.
A Session can have many Presentations (or none, for session-only setups).
- **Presenter → Presentation:** Many-to-one. A Presenter belongs to exactly one Presentation.
Optionally linked to an `event_person_id` for cross-referencing the person record.
- **Event File:** Can be attached at any level — Presenter, Presentation, Session,
Location, or Event. See the File Attachment Levels table.
### File Attachment Levels
Files (`event_file`) can be attached at five levels:
| Level | When Used | Typical Content |
|---|---|---|
| **Presenter** | 99% of the time for individual speakers | Their PowerPoint/PDF/video |
| **Session** | Moderator slides; group/hold content for a specific session | "Session 3 — Group Discussion.pptx" |
| **Location** | Walk-in/out or hold slides for a room across all sessions | Looped PPTX playing between sessions |
| **Event** | Walk-in/out or hold slides used everywhere | Looped PPTX; branding overlay |
| **Presentation** | File attached to the presentation record itself (less common) | Varies |
### Key Objects
| Object | Table | Purpose |
|---|---|---|
| Session | `event_session` | Time block; Location and datetime range assigned here |
| Location | `event_location` | Physical room |
| Presentation | `event_presentation` | A talk within a session; belongs to exactly one Session |
| Presenter | `event_presenter` | Person linked to exactly one Presentation |
| Event Person | `event_person` | Person record within the event context |
| Event File | `event_file` | Uploaded file; attached at Presenter, Presentation, Session, Location, or Event level |
---
## Client Setup Variation
There are no rigid "modes" — events are configured with as much or as little structure
as needed. The platform handles the full range:
**Minimal setup (BGH):**
Sessions have room and time info. No Presentations or Presenters defined.
Staff upload files directly at the session or location level onsite.
**Mid-range setup:**
Sessions defined with named Presentations. Presenters may or may not be tracked.
Mix of pre-uploaded and onsite files. QR codes may be used for quick session/presenter lookup.
**Full setup (LCI):**
Sessions, Presentations, Presenters all defined and managed. External ID labeling
(e.g., "LCI Member ID"). Agreement tracking for presenters. Files managed per-presenter.
The config that drives this is `event.mod_pres_mgmt_json` — see the Configuration section.
---
## Configuration — `mod_pres_mgmt_json`
The event's Presentation Management behavior is controlled by `event.mod_pres_mgmt_json`.
### Convention
| Prefix | Default state | Meaning |
|---|---|---|
| `hide__` | `false` = visible | Feature is ON by default; set `true` to suppress |
| `show__` | `false` = hidden | Feature is OFF by default; set `true` to enable |
### Common Config Keys
| Key | Default | Notes |
|---|---|---|
| `lock_config` | `false` | `true` = force remote→local sync; prevents user overrides of local config |
| `hide__session_code` | `false` | Hide session code column/field |
| `hide__session_description` | `false` | Hide session description field |
| `hide__session_location` | `false` | Hide location field on session view |
| `hide__session_datetime` | `false` | Hide datetime fields |
| `hide__presentation_code` | `false` | Hide presentation code |
| `hide__presenter_code` | `false` | Hide presenter code |
| `hide__location_code` | `false` | Hide location code |
| `show__launcher_link` | `false` | Show direct Launcher link in session view |
| `show__session_qr` | `false` | Show QR code for session (SRR lookup) |
| `show__presenter_qr` | `false` | Show QR code for presenter (SRR lookup) |
| `label__person_external_id` | `null` | Override label for external ID field (e.g., `"Member ID"`) |
| `label__session_poc_name` | `null` | Override label for session POC (e.g., `"Champion"`) |
| `file_purpose_option_kv` | `{}` | Key-value map of file purpose options (e.g., `{"ppt": "PowerPoint", "pdf": "PDF"}`) |
---
## Route Map (Administration)
| URL | Purpose |
|---|---|
| `/events/[id]/pres_mgmt` | Overview — sessions list, search, filter by location |
| `/events/[id]/pres_mgmt/config` | Config editor (admin only) |
| `/events/[id]/session/[session_id]` | Session detail — files, presentations, timing, alert |
| `/events/[id]/presenter/[presenter_id]` | Presenter detail — bio, files, agreement, alert |
| `/events/[id]/location/[location_id]` | Location detail — session schedule for this room, alert |
| `/events/[id]/locations` | All locations list |
| `/events/[id]/reports` | Reports — sessions, presenters, files |
---
## Access Levels
| Feature | Minimum Access |
|---|---|
| View pres_mgmt overview | `authenticated_access` |
| Upload files | `authenticated_access` |
| Edit sessions / presentations | `trusted_access` |
| Edit config | `administrator_access` + `edit_mode` |
| Device management | `administrator_access` |

View File

@@ -1,508 +0,0 @@
# Aether Events — Presentation Management & Launcher
Notes on setup, workflow, configuration, and onsite operation for the Events Presentation
Management module and the companion Launcher (podium display) system.
---
## Overview
The Presentation Management (Pres Mgmt) module handles the full lifecycle of conference
content: sessions, presentations, presenters, presentation files, and room/location
assignments. The Launcher module provides the podium display interface that runs on each
session room's kiosk machine.
These two modules are deployed together — Pres Mgmt is the back office, Launcher is the
front-of-house display. Every client show is at least slightly customized. Some clients
have extensive presenter/presentation data; others just have sessions and files. The
platform is flexible enough to handle the full range.
**Reference clients (current/repeat):**
- **BGH** (Business Group on Health) — most basic setup; session-only, no named Presenters
- **LCI** (Lean Construction Institute) — most complex current setup
- **AAPOR**, **ASCM**, **CMSC** — other active/repeat clients
**Module paths:**
- Pres Mgmt: `/events/[event_id]/pres_mgmt`
- Launcher: `/events/[event_id]/launcher`
**Key source directories:**
- `src/routes/events/[event_id]/(pres_mgmt)/`
- `src/routes/events/[event_id]/(launcher)/`
- `src/lib/ae_events/` — data types and API functions for all event objects
---
## Data Model
### Object Hierarchy
```text
Event
├── Event File (walk-in/out, hold slides for the whole event)
├── Location (physical room — assigned to Sessions, not the other way around)
├── Track (optional grouping; rarely used — see note below)
└── Session (time block; Location assigned here, but may be unset initially)
├── Session File (moderator slides, group/hold slides for this session)
└── Presentation (a talk within the session; must belong to exactly one Session)
└── Presenter (belongs to exactly one Presentation)
└── Presenter File (their slides/materials — the common case)
```
> **Import note:** When program data is initially imported (sessions, presentations,
> presenters), Locations are often not assigned to Sessions yet — rooms may not be
> finalized or the venue's room list may not be set up in Aether. Location assignment
> typically happens as a separate step once the room list is confirmed.
### Relationships
- **Session → Location:** Many-to-one. A Session is assigned to one Location; a Location
hosts many Sessions across the event timeline. Location may be null initially.
- **Presentation → Session:** Many-to-one. A Presentation belongs to exactly one Session.
A Session can have many Presentations (or none, for session-only setups).
- **Presenter → Presentation:** Many-to-one. A Presenter belongs to exactly one Presentation.
Optionally linked to an `event_person_id` for cross-referencing the person record.
- **Event File:** Can be attached at any level — Presenter, Presentation, Session,
Location, or Event. See the File Attachment Levels table.
### File Attachment Levels
Files (`event_file`) can be attached at five levels:
| Level | When Used | Typical Content |
|---|---|---|
| **Presenter** | 99% of the time for individual speakers | Their PowerPoint/PDF/video |
| **Session** | Moderator slides; group/hold content for a specific session | "Session 3 — Group Discussion.pptx" |
| **Location** | Walk-in/out or hold slides for a room across all sessions | Looped PPTX playing between sessions |
| **Event** | Walk-in/out or hold slides used everywhere | Looped PPTX; branding overlay |
| **Presentation** | File attached to the presentation record itself (less common) | Varies |
### Key Objects
| Object | Table | Purpose |
|---|---|---|
| Session | `event_session` | Time block; Location and datetime range assigned here |
| Location | `event_location` | Physical room; the Launcher's primary unit of display |
| Presentation | `event_presentation` | A talk within a session; belongs to exactly one Session |
| Presenter | `event_presenter` | Person linked to exactly one Presentation; optionally linked to `event_person` |
| Event Person | `event_person` | Person record within the event context |
| Event File | `event_file` | Uploaded file; attached at Presenter, Presentation, Session, Location, or Event level |
| Event Device | `event_device` | Registered Launcher kiosk (Electron native instance) |
| Event Track | `event_track` | Optional content grouping (see note below) |
### Event Tracks
The API supports Event Tracks — an optional grouping layer above Sessions. Used twice
historically; could have been omitted both times. Tracks may become genuinely useful for
larger events running many parallel Locations where thematic grouping helps navigation.
Not in active use currently and not wired into the standard Pres Mgmt UI workflow.
### Session → Location
The Launcher's primary display unit is the Location. It shows the active Session for that
Location based on `datetime_start` / `datetime_end` or manual selection. A Location hosts
many Sessions over the event's run; typically only one is active at a time.
---
## Client Setup Variation
There are no rigid "modes" — events are configured with as much or as little structure
as needed. The platform handles the full range:
**Minimal setup (BGH):**
Sessions have room and time info. No Presentations or Presenters defined.
Staff upload files directly at the session or location level onsite.
**Mid-range setup:**
Sessions defined with named Presentations. Presenters may or may not be tracked.
Mix of pre-uploaded and onsite files. QR codes may be used for quick session/presenter lookup.
**Full setup (LCI):**
Sessions, Presentations, Presenters all defined and managed. External ID labeling
(e.g., "LCI Member ID"). Agreement tracking for presenters. Files managed per-presenter.
Launcher linked from Pres Mgmt views.
The config that drives this is `event.mod_pres_mgmt_json` — see the Configuration section.
---
## Speaker Ready Room (SRR)
The Speaker Ready Room is a dedicated space where presenters check in and staff manage
content before it goes live in the session rooms. Setup varies by client:
- **Small/private:** Only a few client staff and OSIT. Not open to presenters at large.
- **Open SRR:** Open to all presenters as long as sessions are running. People come and go
all day — reviewing silently, editing with a group, practicing at a station.
### SRR Practice Stations
Stations mirror the session room setup exactly:
- Same Mac laptop model and adapter/dongle configuration as the podiums
- Projector and screen (same as session rooms where possible)
- Launcher running in Native (Electron) mode — cached files open immediately
- Full dry-run capability: load their file, start the deck, confirm everything works
### Remote Monitoring
SRR staff typically monitor the session room Launchers in real time via **VNC or RustDesk**.
This lets one person watch multiple podium displays simultaneously without being in each room.
### QR Codes (Session and Presenter)
QR codes are available for Sessions and Presenters and have been useful onsite for quick
lookups — scanning a code takes staff directly to the session or presenter record.
Whether to enable this depends on the SRR flow for each show. It gets toggled on or off
per event via config.
### SRR Staffing Roles
| Role | Access Level | Typical Tasks |
|---|---|---|
| OSIT Staff | `trusted_access` or higher | Upload files, edit sessions/presentations, manage devices, monitor via VNC |
| Client Staff | `authenticated_access` | Upload files, view session list |
| Presenter (self-service) | `authenticated_access` (if enabled) | Upload their own files via QR link |
### SRR Workflow — Day-of-Show
1. **Presenter checks in** — staff looks up their session(s) in Pres Mgmt
2. **File upload** — staff or presenter uploads file to the correct presenter/session record
3. **File verification** — staff opens the file on a practice station to confirm it renders
4. **Launcher Sync** — file appears in the Launcher within the next polling cycle. Staff can use the **Force Sync Location** button in the Launcher config to immediately queue all files for the room's schedule.
5. **Presenter proceeds to room** — podium kiosk already has the file cached.
---
## File Upload Workflows
### Pre-Show (Remote / Staff Ahead of Time)
Files can be uploaded anytime before the event via the Pres Mgmt web UI:
1. Navigate to the presenter, session, or appropriate level
2. Use the file upload panel (drag & drop or browse)
3. File is stored server-side and immediately available to the Launcher
Some clients enable presenter self-upload via a direct link (requires `authenticated_access`).
Controlled per-event via config.
### Day Before — SRR Setup
For higher-volume shows, the SRR opens the day before the event:
- Pre-uploaded files are already loaded and can be verified
- Early-arriving presenters check in; staff upload their files
- Electron Launcher instances on podium Macs begin pre-caching files overnight
- Problems (corrupt files, wrong format, wrong codec) surface with time to fix them
### Live Onsite Upload
For late arrivals and last-minute changes:
1. Presenter arrives at SRR (or sends file via USB/email to staff)
2. Staff uploads via Pres Mgmt web UI
3. File propagates to Launcher within one polling cycle (~30 seconds on fast networks)
4. VNC or RustDesk confirms the podium received the file before the presenter walks in
---
## Onsite Operation — Managing 412 Parallel Rooms
### Overview Page
The Pres Mgmt overview (`/events/[event_id]/pres_mgmt`) shows:
- All sessions, filterable by location and time
- File status per session
- Quick links to each session's file management
For events with multiple parallel rooms, filtering by location and time block is essential
for SRR staff staying on top of what's active right now.
### Per-Room Workflow
Each room/location has its own Launcher display:
- `/events/[event_id]/launcher` → select location → Launcher for that room
- The Launcher shows the active session based on the current time or manual selection
- VNC/RustDesk gives SRR staff a real-time view of all podiums simultaneously
### Session Display Timing
Ideally, sessions would automatically show and hide based on `datetime_start` /
`datetime_end` — appearing a configurable number of minutes before the session starts
and disappearing after it ends. This is a planned/desired behavior. In practice:
- Some clients run tight schedules and could rely on time-based transitions
- Others drift significantly from the published schedule; time-based auto-advance
would cause more problems than it solves
- Currently, session transitions can be managed manually via Launcher controls
> **TODO (future):** Configurable `show_before_minutes` / `hide_after_minutes` per event
> so well-run shows can automate transitions while looser shows stay manual.
### Device (Laptop) Assignment
Each Launcher kiosk Mac is registered as an `event_device` and typically assigned to one
Location for the duration of the event. However, laptops do get moved:
- Venues add or lose rooms as spaces are reconfigured
- A session room may open for one day only
- Devices can be reassigned to a different Location in the `event_device` record as needed
The Electron app reads its location assignment from the API at startup, so reassigning a
device takes effect on the next launch (or app restart).
---
## Alert Fields
Sessions, Presenters, and Locations each have alert fields that can display a visible
notice in the Pres Mgmt UI and/or the Launcher.
Useful for:
- "Presenter requested no recording"
- "Room change — moved to Hall B"
- "File not received — follow up"
- "AV note: needs confidence monitor"
> **Status:** Alert fields exist but the implementation and display behavior needs review
> and cleanup. Not a blocking issue for BGH next week — revisit for a future show.
---
## Launcher Module
### Operational Modes
| Mode | Use Case | File Handling |
|---|---|---|
| **Default** | Browser on any machine | Files downloaded on demand |
| **Onsite** | Browser on event network | Faster polling; browser-managed files |
| **Native** | Electron app on dedicated podium Mac | Background pre-cache; atomic file handover |
For production onsite use, **Native mode on Mac laptops** is the target. The Electron
app pre-caches all session files in the background so presentations open instantly without
a network round-trip at the moment of launch.
### Download Priority & Room Readiness
To ensure the podium is ready for the day's first sessions, the Launcher sync engine uses a
4-tier chronological sorting priority:
1. **Global Assets:** Event and Location level files (branding, walk-in slides) are cached first.
2. **Session Schedule:** Files for the earliest sessions in the room are prioritized.
3. **Presentation Order:** Within a session block, speakers are prioritized by their scheduled start time.
4. **First-In Fairness:** When times are equal, older uploads are prioritized over late revisions.
Operators can manually trigger a full pre-sync of all location materials via the **Force Sync Location** button in the Launcher configuration (Sync Engine section).
### Native Mode — Electron App
- **Repo:** `~/OSIT_dev/aether_app_native_electron/`
- **Platform:** macOS (primary); Linux/Windows as fallback
- **Seed config:** `seed.json` (Device ID + API key) — loaded at startup
- **File cache:** `~/Library/Caches/OSIT/file_cache/` (hashed by SHA-256)
- **Doc:** `documentation/PROJECT__AE_Events_Launcher_Native_integration.md`
The Electron app zero-configs itself:
1. Reads `seed.json` → gets device code
2. Calls Aether API → pulls device config and location assignment
3. Navigates directly to the Launcher for that location
4. Begins pre-caching session files in the background
### Launcher Display Views
| View | Shown When |
|---|---|
| Session view | Active session with session-level files |
| Presentation view | Active session with named presentations |
| Presenter view | Presentation selected; shows presenter bio/photo |
| Poster/group view | Special layout for poster sessions |
| Screensaver | No active session; idle state |
### File Opening (Native Mode) — Safe Handover
1. Verify SHA-256 hash in permanent cache
2. Atomic copy to system `[tmp]` directory
3. Rename to original filename (e.g., `Abstract_101.pptx`)
4. OS opens the file (Keynote, PowerPoint, Preview, etc.)
**Configurable launch behavior:** The file-open behavior is driven by a Launch Profile, not
just a command string. Profiles are stored per file extension in
`event_device.data_json.launch_profiles` (device-level config) or
`event.launcher.launch_profiles` (event-level fallback). The built-in Svelte defaults are the
final fallback and are documented below. A profile can choose the app, display mode, open
command, and post-open automation. The resolved native template uses `{{path}}` as the file
path placeholder; AppleScript or `shell:` prefixed commands are both supported. No Electron
rebuild is required to change how files open — edit config in Aether and it applies
immediately. See `PROJECT__AE_Events_Launcher_Native_integration.md` Section 8.
### Built-In Default Launch Profiles
These are the initial built-in defaults shipped with the Launcher. They are the Svelte-side
fallbacks used when neither device config nor event config defines a profile for the file
extension. Each canonical profile can have multiple extension aliases. `post_delay_ms` is part
of the profile object, so a device-specific `launch_profiles[profile].post_delay_ms` override can
tune it later without changing the profile table.
| Profile name | Extension aliases | Default app | Display mode | Post delay | Notes |
|---|---|---|---|---|---|
| `powerpoint_mac_extend` | `pptx`, `ppt` | Microsoft PowerPoint for macOS | `extend` | `1000ms` | Open in the presentation app and extend to an external display if one is present. |
| `keynote_mac_extend` | `key` | Keynote | `extend` | `1000ms` | Keynote slideshow on the external display if available. Post-script polls `count of documents > 0` before starting — handles slow file load on cold start. |
| `libreoffice_mac_extend` | `odp` | LibreOffice for macOS | `extend` | `1000ms` | LibreOffice Impress for OpenDocument presentations. |
| `acrobat_mac_mirror` | `pdf` | Adobe Acrobat for macOS | `mirror` | `1000ms` | PDF handout / deck view uses mirrored display. |
| `vlc_mirror` | `mp4`, `mkv`, `mp3`, `m4v`, `m4a`, `webm`, `wav`, `aac`, `flac`, `mov`, `mpeg`, `avi`, `flv`, `ogg`, `ogv`, `wmv` | VLC for macOS | `mirror` | `1000ms` | Media playback is mirrored so the room sees the same output as the operator. Uses `--no-play-and-exit` so playback ends on the last frame instead of closing video output. |
| `powerpoint_win_extend` | `pptxwin`, `pptwin` | PowerPoint for Windows (Parallels) | `extend` | `1500ms` | Windows PowerPoint profile for Parallels-based rooms. Higher base delay to account for Parallels VM startup latency. |
| `libreoffice_win_extend` | `odpwin` | LibreOffice for Windows | `extend` | `1500ms` | Windows LibreOffice profile for Parallels-based rooms. |
| `acrobat_win_mirror` | `pdfwin` | Adobe Acrobat for Windows (Parallels) | `mirror` | `1500ms` | Windows PDF profile for mirrored display rooms. |
| `url_web` | `url` | Browser / Event File web presentation | `extend` | `n/a` | Web-based presentations are handled as Event File URLs rather than cached local files. |
> **Post-open automation timing:** All profiles with a `post_script` use a polling loop rather than a fixed sleep — the AppleScript waits up to ~7.5 s for the target process to become frontmost before firing the automation keystroke. The `post_delay_ms` value is a minimum baseline before polling begins. Overridable per profile via `launch_profiles[profile].post_delay_ms` in `event_device.data_json`.
Versioning is handled automatically: when a presenter uploads an updated file, the new
hash is cached separately and the old one remains intact.
---
## Configuration — `mod_pres_mgmt_json`
The event's Pres Mgmt behavior is controlled by `event.mod_pres_mgmt_json`.
> **Note:** The config schema is being cleaned up — see
> `documentation/PROJECT__AE_Events_PressMgmt_Config_Cleanup.md` for the canonical
> `PressMgmtRemoteCfg` interface and naming conventions.
### Convention
| Prefix | Default state | Meaning |
|---|---|---|
| `hide__` | `false` = visible | Feature is ON by default; set `true` to suppress |
| `show__` | `false` = hidden | Feature is OFF by default; set `true` to enable |
### Common Config Keys
| Key | Default | Notes |
|---|---|---|
| `lock_config` | `false` | `true` = force remote→local sync; prevents user overrides of local config |
| `hide__session_code` | `false` | Hide session code column/field |
| `hide__session_description` | `false` | Hide session description field |
| `hide__session_location` | `false` | Hide location field on session view |
| `hide__session_datetime` | `false` | Hide datetime fields |
| `hide__presentation_code` | `false` | Hide presentation code |
| `hide__presenter_code` | `false` | Hide presenter code |
| `hide__location_code` | `false` | Hide location code |
| `show__launcher_link` | `false` | Show direct Launcher link in session view |
| `show__session_qr` | `false` | Show QR code for session (SRR lookup) |
| `show__presenter_qr` | `false` | Show QR code for presenter (SRR lookup) |
| `label__person_external_id` | `null` | Override label for external ID field (e.g., `"Member ID"`) |
| `label__session_poc_name` | `null` | Override label for session POC (e.g., `"Champion"`) |
| `file_purpose_option_kv` | `{}` | Key-value map of file purpose options (e.g., `{"ppt": "PowerPoint", "pdf": "PDF"}`) |
### Per-Show Config Examples
**BGH (session-only, minimal; no named Presentations or Presenters):**
```json
{
"lock_config": false,
"hide__presentation_code": true,
"hide__presenter_code": true
}
```
**LCI (full setup, member ID label, Launcher link enabled):**
```json
{
"lock_config": true,
"label__person_external_id": "LCI Member ID",
"show__launcher_link": true
}
```
> Admin must currently edit `mod_pres_mgmt_json` directly in the DB or via the event
> settings page. A proper Config UI is planned — see `PROJECT__AE_Events_PressMgmt_Config_Cleanup.md`.
---
## Route Map
| URL | Purpose |
|---|---|
| `/events/[id]/pres_mgmt` | Overview — sessions list, search, filter by location |
| `/events/[id]/pres_mgmt/config` | Config editor (admin only) |
| `/events/[id]/session/[session_id]` | Session detail — files, presentations, timing, alert |
| `/events/[id]/presenter/[presenter_id]` | Presenter detail — bio, files, agreement, alert |
| `/events/[id]/location/[location_id]` | Location detail — session schedule for this room, alert |
| `/events/[id]/locations` | All locations list |
| `/events/[id]/reports` | Reports — sessions, presenters, files |
| `/events/[id]/launcher` | Launcher home — select location |
| `/events/[id]/launcher/[location_id]` | Launcher display for a specific room |
---
## Device Management
Each Electron kiosk is registered as an `event_device` record:
- `code` — matches the device's `seed.json` code
- `name` — human-readable (e.g., "Ballroom A Podium")
- `data_json.location_id` — the `event_location_id` this device is assigned to
Devices can be managed in Pres Mgmt (`/events/[id]/device/device`). Location reassignment
takes effect on the next Electron app launch.
---
## Access Levels
| Feature | Minimum Access |
|---|---|
| View pres_mgmt overview | `authenticated_access` |
| Upload files | `authenticated_access` |
| Edit sessions / presentations | `trusted_access` |
| Edit config | `administrator_access` + `edit_mode` |
| View Launcher display | `authenticated_access` |
| Manual session selection in Launcher | `trusted_access` |
| Device management | `administrator_access` |
---
## Pre-Show Checklist
### 12 Weeks Before
- [ ] Event created in Aether with correct dates
- [ ] `mod_pres_mgmt_json` configured for this client's needs
- [ ] Locations (rooms) created and named
- [ ] Sessions created, assigned to locations, datetime ranges set
- [ ] If using Presentations/Presenters: records imported or entered
- [ ] File purpose options configured in `file_purpose_option_kv`
- [ ] Launcher devices registered (`event_device` records with correct codes)
- [ ] Device-to-location assignments confirmed
- [ ] Decide: QR codes for Sessions / Presenters needed? Enable/disable in config
### Day Before (SRR Setup)
- [ ] Mac laptops at podiums booted and Electron app running
- [ ] Each podium confirms it loaded the correct location's Launcher
- [ ] SRR practice stations confirmed — projector, same Mac/dongle setup as session rooms
- [ ] Pre-loaded files verified in Launcher (open at least one per room to test Safe Handover)
- [ ] SRR staff briefed on upload workflow and VNC/RustDesk monitoring setup
- [ ] VNC/RustDesk connections established to all podium displays
### Day of Show
- [ ] Confirm all session times in Aether are accurate before first session
- [ ] Monitor SRR queue — upload files as presenters check in
- [ ] Verify each file opens on a practice station before the presenter walks to their room
- [ ] Monitor podium displays via VNC/RustDesk — flag any stuck or offline devices
---
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Session not showing in Launcher | Session datetime wrong or location not assigned | Verify session location and datetime range |
| File uploaded but not in Launcher | Polling cycle lag; or file attached at wrong level | Wait one cycle; check that file is attached to session/location (not just a presenter record) if using session-only setup |
| Electron app shows wrong location | Device code mismatch or stale device config | Re-check `event_device` record; restart Electron app |
| File opens slowly at podium | Not in native cache yet | Check background sync in Launcher; pre-cache may not have completed |
| File won't open | Corrupt upload, wrong format, or missing codec on Mac | Test on SRR practice station; re-upload or convert |
| Session out of sync with schedule | Timing drifted; manual advance needed | Use Launcher controls to manually select the current session |
| Alert field not showing | Alert fields need implementation review | Known — lower priority than active operations |
| `lock_config: true` resets local changes | Expected behavior — remote config wins | Change the remote config in `mod_pres_mgmt_json` |
| Device needs to move to different room | Location reassigned mid-event | Update `data_json.location_id` on `event_device` record; restart Electron app on that machine |

View File

@@ -1,7 +1,7 @@
# Project: CRUD V3 Final Migration
> **Status:** Active / In Progress
> **Last Updated:** 2026-01-20
> **Status:** 🟡 Surgical Cleanup (90% Complete — Events Module Fully Migrated)
> **Last Updated:** 2026-05-21
> **Goal:** Eliminate all dependency on legacy API wrappers (`create_ae_obj_crud`, `get_ae_obj_id_crud`, etc.) and ensure 100% adoption of the V3 Standard (`/v3/crud/...`).
---
@@ -21,23 +21,23 @@ While the **Journals** and **Identity (User/Account)** modules have been success
The following files have been identified as using legacy CRUD wrappers.
### 🔴 High Priority: Events Module
The entire `ae_events` library is heavily dependent on legacy `v2` list and `v1` CRUD wrappers.
- [ ] `src/lib/ae_events/ae_events__event_session.ts`
- [ ] `src/lib/ae_events/ae_events__event_presenter.ts`
- [ ] `src/lib/ae_events/ae_events__event_presentation.ts`
- [ ] `src/lib/ae_events/ae_events__event_location.ts`
- [ ] `src/lib/ae_events/ae_events__event_badge_template.ts`
- [ ] `src/lib/ae_events/ae_events__event_device.ts`
- [x] `src/lib/ae_events/ae_events__event_session.ts` (Migrated 2026-01-30)
- [x] `src/lib/ae_events/ae_events__event_presenter.ts` (Migrated 2026-01-30)
- [x] `src/lib/ae_events/ae_events__event_presentation.ts` (Migrated 2026-01-30)
- [x] `src/lib/ae_events/ae_events__event_location.ts` (Migrated 2026-01-30)
- [x] `src/lib/ae_events/ae_events__event_badge_template.ts` (Migrated 2026-01-30)
- [x] `src/lib/ae_events/ae_events__event_device.ts` (Migrated 2026-01-30)
- [x] `src/lib/ae_events/ae_events__exhibit.ts` (Migrated 2026-01-28)
- [ ] `src/lib/ae_events/ae_events__event_file.ts`
- [x] `src/lib/ae_events/ae_events__event_file.ts` (Migrated 2026-01-30)
### 🟠 Medium Priority: Core & Sponsorships
Legacy patterns persisting in core logic and config modules.
- [ ] `src/lib/ae_sponsorships/ae_sponsorships_functions.ts`
- [ ] `src/lib/ae_core/core__hosted_files.ts` (Uses `get_ae_obj_id_crud`)
- [ ] `src/lib/ae_core/core__site.ts` (Uses `get_ae_obj_id_crud`)
- [x] `src/lib/ae_core/core__hosted_files.ts` (Migrated 2026-01-20)
- [x] `src/lib/ae_core/core__site.ts` (Migrated 2026-01-26)
- [ ] `src/lib/ae_core/core__site_domain.ts` (STILL USES `get_ae_obj_id_crud` for bootstrap)
- [ ] `src/lib/ae_core/ae_core_functions.ts` (STILL USES `get_ae_obj_id_crud` / `update_ae_obj_id_crud`)
- [ ] `src/lib/ae_core/core__country_subdivisions.ts`
- [ ] `src/lib/ae_core/core__time_zones.ts`
- [ ] `src/lib/ae_core/core__countries.ts`
@@ -48,9 +48,10 @@ Specific UI components that make direct API calls instead of using store functio
- [ ] `src/lib/elements/element_data_store.svelte` (Direct `create_ae_obj_crud`)
- [x] `src/lib/elements/element_data_store_v2.svelte`
- [ ] `src/routes/events/[event_id]/event_page_menu.svelte`
- [ ] `src/routes/events/[event_id]/(pres_mgmt)/session/ae_comp__event_session_alert.svelte`
- [x] `src/routes/events/[event_id]/(pres_mgmt)/session/ae_comp__event_session_alert.svelte` (Migrated to `update_ae_obj`)
- [ ] `src/routes/events/ae_comp__event_session_obj_li.svelte`
- [ ] `src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_id_edit.svelte`
- [ ] `src/routes/events/[event_id]/(pres_mgmt)/presenter/[presenter_id]/ae_comp__event_presenter_form_agree.svelte` (STILL USES `update_ae_obj_id_crud`)
---

View File

@@ -2,556 +2,91 @@
> Use this file to track steps for complex features or bug fixes.
> **Status:** Stable — ongoing development.
## 🔴 CMSC Charlotte — May 27 (Presentation Management)
**Drive down:** May 25 | **Setup:** May 26 morning | **Show:** May 27+
**[Electron/Launcher] Clean up presentation file launch profiles** — BGH show revealed issues
with the profiles used to open/launch presentation files. Architecture decision: move launch
logic to the Svelte side so it can be changed without an Electron rebuild. Electron becomes a
thin OS primitive layer; all business logic lives in Svelte and device config.
Why this matters: the profile map is policy, while the native template is the exact runtime
command. Keeping those separate avoids a second hidden source of truth and keeps Electron from
guessing defaults.
**Electron groundwork (2026-05-11) — DONE:**
- [x] `run_osascript` hardened — temp `.scpt` file approach; handles multi-line + special chars
- [x] `native:copy-from-cache-to-temp` primitive added — copy to tmp, caller decides launch
- [x] `native:launch-from-cache` executes a provided `native_template` string — AppleScript or `shell:` prefix; no Electron-side fallback
- [x] `get_launch_profile()` in `launcher_file_cont.svelte` reads from device config then event config; resolves to a `native_template` string and passes it to `launch_from_cache`
- [x] Built-in Launcher defaults refactored into canonical profile names plus extension aliases (`ae_launcher__default_launch_profiles.ts`)
- [x] Device-level Launch Timing section added under Launcher Configuration → Device, with per-profile `launch_profiles[profile].post_delay_ms` overrides
- [x] **URL file launch support (2026-05-13)**`event_file.extension = 'URL'` (or filename starting with `http://`/`https://`) is treated as a non-downloaded URL. Background sync skips URL files so they are never treated as cacheable hosted files. Shared `AE_Comp_Hosted_Files_Download_Button` now hard-bypasses the download path for URL records. In native mode, `handle_open_file()` routes to `native.open_external({ url, app: 'chrome' })` with `'default'` fallback. Health section crash fixed (guarded `native_device` against undefined in preview/edit mode).
- [x] **[Launcher/Electron] Display mirroring auto-detection (2026-05-20)** — `native:set-display-layout` rewrote to auto-detect displays via `displayplacer list` when no `configStr` provided. Old code silently returned `{ success: false }` (swallowed by `.catch(() => {})` in relay). Now parses `displayplacer list` output, extracts quoted display strings, builds correct mirror/extend commands. Manual `configStr` still takes priority when provided.
**Svelte-side migration — remaining before May 26:**
- [x] **[Launcher] Built-in Svelte default profiles (2026-05-11)** — canonical profile constants live in `ae_launcher__default_launch_profiles.ts` with extension aliases and a `resolve_launch_profile()` 3-step fallback (device config → event config → built-in defaults). Covers macOS (`pptx`, `ppt`, `key`, `odp`, `pdf`), media (`mp4`, `mkv`, `mp3`, etc.), Windows/Parallels variants, and URL path.
- [ ] **[Launcher] Composable open flow** — refactor `handle_open_file()` to use
`copy_from_cache_to_temp` + `run_osascript` / `run_cmd` directly instead of the all-in-one
`launch_from_cache`. Finer error handling at each step (verify copy succeeded before
attempting script; surface failure clearly in UI).
- [x] **[Launcher] Error handling + fallback (2026-05-14)** — post-script failure is non-fatal: surfaces `'fallback'` status with error detail (file already open). If `open_cmd` itself fails, falls back to `open_local_file_v2` (OS default); surfaces combined error detail if that also fails. UI renders `'open'` / `'fallback'` / `'error'` states distinctly.
- [ ] **[Launcher] Slide control scripts in Svelte config** — `control_presentation` AppleScript
one-liners are hardcoded in Electron (`shell_handlers.ts` lines 149159). The Svelte side
(`launcher_cfg_native_os.svelte`) already calls `native.control_presentation({ app, action })`
with next/prev/start/stop buttons wired and working. The Electron IPC call is stable enough
for current shows.
**Remaining (post June 10):** Move scripts to device config (`data_json.control_scripts`) or
Svelte constants so behavior can be changed without an Electron rebuild. Replace
`native.control_presentation()` call with `native.run_osascript(script)` using the
config-resolved script string. The `run_osascript` primitive is already in place; this is
purely a wiring/config task. Low priority — hardcoded scripts work fine for PowerPoint and
Keynote for now.
- [ ] **[Launcher] `kill_processes` target list in config** — The `kill_processes` IPC primitive
exists in Electron and is exposed via `electron_relay.ts`, but **is never called from anywhere
in Svelte** — no routes, no config components, no file launch flow. No UI exists for it yet.
**What needs doing (post June 10):**
- Decide where the "kill on cleanup" action lives: end of `handle_open_file()` (auto), or a
manual "Kill Apps" button in Native OS config, or both.
- Process names should come from device config (`data_json.kill_process_li`) with a built-in
fallback list (e.g. `['Microsoft PowerPoint', 'Keynote', 'LibreOffice Impress']`).
- The Native OS config section (`launcher_cfg_native_os.svelte`) is the natural home for a
manual kill button; auto-cleanup on file open would go in `launcher_file_cont.svelte`.
**Device tab length note:** The Device tab now has 6 collapsible sections (Sync Timers, Health,
Native OS, Wallpaper, Launch Timing, Updates). Consider moving Launch Timing + a future Kill
Apps control into a dedicated **"Presentation"** section or a fourth tab to keep Device focused
on machine/OS concerns rather than per-app launch behavior.
- [x] **[Launcher] Launcher config UI — launch_profiles editor (2026-05-14)** — Launch Timing section in `launcher_cfg_launch_timing.svelte` exposes per-profile `post_delay_ms` overrides with Save/Reset per row. PATCHes `event_device.other_json.launcher.launch_profiles` via V3 CRUD. Wired into `launcher_cfg.svelte`. Shown under Technical Mode (`$ae_loc.edit_mode`).
- [ ] **[Launcher] End-to-end test on macOS** — test pptx and key opens on a real podium Mac
before May 26 setup day. Verify: file copies to tmp correctly, script fires, app opens in
slideshow mode, error fallback works.
`launch_from_cache`. Finer error handling at each step.
- [ ] **[Launcher] Slide control scripts in Svelte config** — Move AppleScript one-liners from
Electron to device config or Svelte constants.
- [ ] **[Launcher] `kill_processes` target list in config** — Implement UI for manual "Kill Apps"
button and auto-cleanup on file open.
- [ ] **[Launcher] End-to-end test on macOS** — test pptx and key opens on a real podium Mac.
- [ ] **[Launcher/Electron] Wallpaper stops applying after several changes (post-CMSC)** —
After setting the wallpaper 35 times in a session, macOS silently ignores further `set desktop
picture` calls even though the SvelteKit side reports "Saved & Applied ✓". Restore Default
(`restore_macos_default_wallpaper`) immediately unblocks it; closing/reopening Electron does
not. **Workaround:** use Restore Default, then re-apply. **Root cause:** macOS caches the
current wallpaper path and skips the AppleScript call when the downloaded file lands at the
same temp path. **Fix (post-CMSC):** in the Electron `set_wallpaper` handler
(`aether_app_native_electron`), append a timestamp or random suffix to the temp filename on
every download so macOS always sees a new path (e.g. `wallpaper_1748123456.jpg` instead of
`wallpaper.jpg`).
Append timestamp/random suffix to temp filename so macOS always sees a new path.
- [ ] **[Launcher/Electron] Wallpaper drift after display hotplug (post-CMSC)** —
In live setups, wallpaper can randomly reset when projectors/displays are unplugged or
reattached. Add a resilient strategy that avoids unnecessary churn when wallpaper is already
correct.
**Options to evaluate:**
- periodic reconciliation loop with backoff/jitter and a cheap "is wallpaper already correct?"
check before applying
- event-driven reapply when display topology changes (new display detected), leveraging the
updated Electron display library
- hybrid: event-driven first, periodic safety check as fallback
**Goal:** keep podium/projector wallpaper stable without repeatedly setting wallpaper when no
change is needed.
- [ ] **[Launcher/Electron] `run_cmd`/`run_cmd_sync` — phantom `return_stdout` param (low priority)** —
`electron_relay.ts` passes `return_stdout` in the args object, but both IPC handlers ignore it
(stdout is always returned). Effectively a no-op, but creates a misleading API contract. Fix:
remove the param from relay calls, or honor it in the handlers. No behavioral impact currently.
- [ ] **[Launcher/Electron] `power_control` — sudo requirement not surfaced in UI (low priority)** —
`shutdown` and `reboot` require `sudo` on Linux. macOS works without it; Launcher only targets
macOS so this is a non-issue in production. If Linux podiums are ever deployed, `power_control`
shutdown/reboot will fail silently with a permissions error. No fix needed before CMSC.
- [ ] **[Launcher/Electron] `manage_recording` — aperture binary path on packaged builds** —
Handler resolves the aperture binary via a dev-relative path. Needs verification that the path
resolves correctly inside a packaged `.app` bundle (`app.asar` / `resources/`). Not blocking
for CMSC (recording not part of CMSC workflow). Verify on next packaged build test.
Add resilient reconciliation loop or event-driven reapply on topology change.
---
## 🔴 Axonius DC — June 9 (Badge Printing)
**Setup/Registration:** June 8 | **Show:** June 9
- [ ] **[Badges] Epson C3500 fanfold badge layout** — Axonius is using Epson C3500 printers
with fanfold (continuous) badge stock. Create/configure a fanfold badge layout compatible
with the C3500 format. Must be ready before the June 8 setup/registration day.
- [ ] **[Badges] Epson C3500 fanfold badge layout** — Create/configure a fanfold badge layout
compatible with the Epson C3500 continuous stock format.
---
## 🚧 Upcoming High Priority
## 🚧 V3 CRUD Migration (Surgical Cleanup)
Finalizing the 100% adoption of V3 Standard endpoints and retirement of legacy wrappers.
### ~~[IDAA] Random "Access Denied" — Root Cause Review & Fixes~~ ✅ Resolved (2026-05-19)
All known root causes fixed across 10+ commits to `src/routes/idaa/(idaa)/+layout.svelte`.
Deploying as of 2026-05-19. Monitor for further member reports.
#### All fixes applied
- [x] **Server-side Novi verification migrated (key fix for hotel/VPN/Cloudflare-filtered networks)**
`verify_novi_uuid()` now calls `GET /v3/action/idaa/novi_member/{uuid}` through the Aether
backend (server-to-server, Redis-cached 4h) instead of making a browser-to-Novi call.
Eliminates false Access Denied for members on hotel/conference WiFi, VPNs, and corporate
networks where the member's IP was rejected by Novi/Cloudflare.
Frontend: `(idaa)/+layout.svelte` | Backend: FastAPI `aether_api_fastapi` | Docs: `GUIDE__AE_API_V3_for_Frontend.md` §12, `CLIENT__IDAA_and_customized_mods.md`
- [x] Novi TTL extended to **12 hours** (5 min → 45 min → 12 h) — covers a full conference day
- [x] Access Denied on iframe reload (sessionStorage URL preservation) — `2855e091f`
- [x] TTL cache bypassed when `$ae_loc` auth flags reset — `2855e091f`
- [x] "Verification Unavailable" screen distinct from "Access Denied" — `2855e091f`
- [x] "Try Again" without page reload (`retry_count` pattern) — `2855e091f`
- [x] 12 s AbortController hard timeout on Novi fetch — `e921ca973`
- [x] Network/AbortError gets 3 s grace + one retry — `e921ca973`
- [x] Clear Cache & Reload added to Access Denied state (iframe mode) — `2855e091f`
- [x] `VERIFY_TIMEOUT_MS` 8 s → 35 s (was firing mid-retry, causing premature Reset clicks) — `53fd5e7de`
- [x] `sessionStorage` try-catch (iOS Safari Private Browsing throws on access) — `53fd5e7de`
- [x] Appshell stores guarded behind `account_id``8850db89c`
- [x] Recovery meetings over-filtering bug (API `default_qry_str`) — `76e21b08f`
- [x] A→Z sort in recovery meetings API revalidation path — `c0386f27b`
- [x] `events.event` IDB content version bump (stale cache) — previous commit
#### Root layout SWR verified safe:
The root `+layout.ts` builds `ae_loc_init` as a plain site-config object (no `authenticated_access`,
`trusted_access`, or `access_type` fields). The root layout sync effect
`$ae_loc = { ...current_loc, ...ae_acct.loc }` therefore cannot overwrite Novi-set auth flags.
Confirmed safe — this is NOT a cause of Access Denied.
#### Remaining architectural note:
The long-term fix for the coarse `$ae_loc` reactivity (Svelte 4 store) causing Effect 2 to
re-run on unrelated writes is tracked under **[Stores] Svelte 4 → Svelte 5 State Migration**
below. The TTL + `verify_in_flight` guards are the current mitigation.
- [x] **[Badges] Presenter Agreement Form** — migrated to `update_ae_obj` (2026-05-21)
- [ ] **[Core] Site Domain Bootstrap Refactor** — `load_ae_obj_by_fqdn__site_domain` in
`core__site_domain.ts` still uses legacy ID-lookup-by-FQDN. Refactor to use V3
`api.search_ae_obj` with fqdn filter per integration guide.
- [ ] **[Core] Legacy Utility Helpers** — Refactor `ae_core_functions.ts` to use V3 helpers.
- [ ] **[Cleanup] Delete Legacy Wrappers** — Once all callsites are migrated, remove
`src/lib/ae_api/api_get__crud_obj_id.ts` and the legacy exports from `api.ts`.
---
### ~~[IDAA] Server-side Novi verification — 503 not auto-retried~~ ✅ Fixed (2026-05-20)
## 🚧 High Priority Workstreams
---
### [API] GET/POST retry hardening — differentiate timeout aborts vs intentional aborts
**Status:** ✅ Completed (2026-05-21)
Recent API helper fixes restored retry/backoff for transient network `TypeError` failures.
Timeout-triggered aborts are now handled separately from intentional/user aborts so the
retry loop behavior is correct.
**Decision (for now):** Keep the global default timeout at **20s**.
**Implemented:**
- GET/POST now explicitly distinguish abort class in helper code:
- **Intentional abort** (navigation/unmount/caller cancel): fail fast, no retry
- **Timeout abort** (helper timer): retryable via existing retry loop
- Timeout classification added with per-attempt timeout flag (not `AbortError` name-only logic).
- Backoff behavior retained for retryable failures (`2s -> 4s -> 6s -> 8s`, cap 8s).
- Existing fail-fast class retained for 400/401/403/422, with auth-expired store signaling on 401/403.
- Validation done:
- `npx svelte-check` clean
- API Playwright tests updated/fixed and passing (`v3_api_security.modern`, `v3_api_nested_crud`)
**Timeout policy improvement (class-based):**
- Keep **20s default** as baseline.
- Add request classes with explicit timeout selection at callsites/wrappers (not random per-page values):
- fast CRUD/read/search: ~20s baseline
- medium actions: higher bounded timeout
- heavy actions (uploads, exports, ffmpeg/video clip): explicit long timeout already required
- Centralize the class mapping so timeout intent is clear and audit-friendly.
**Primary files:**
- `src/lib/ae_api/api_get_object.ts`
- `src/lib/ae_api/api_post_object.ts`
- Wrapper callsites in `src/lib/ae_api/` and legacy bridge points in `src/lib/api/api.ts`
**Acceptance criteria:**
- Timeout-aborted requests retry according to retry_count/backoff policy.
- User/navigation aborts still fail fast with no retry.
- No regression on 400/401/403/422 fail-fast handling.
- Existing long-running flows that already set explicit timeouts (uploads/video tools/exports)
continue to function without behavior regressions.
---
### [API] PATCH/DELETE retry hardening — parity with GET/POST
**Status:** ✅ Completed (2026-05-21)
PATCH and DELETE now implement the same retry-classification model used in GET/POST,
including timeout abort separation and capped retry backoff.
**Implemented:**
- PATCH:
- Per-attempt timeout controller with explicit timeout-abort flag.
- Retries timeout/network transient failures only.
- Intentional caller aborts fail fast (no retry).
- Fail-fast retained for 400/401/403/422.
- Backoff capped at `2s -> 4s -> 6s -> 8s`.
- DELETE:
- Same timeout-vs-intentional abort separation.
- Same retry class for timeout/network transient failures.
- Same caller-abort fail-fast behavior.
- Explicit fail-fast for 400/401/403/422.
- Backoff capped at `2s -> 4s -> 6s -> 8s`.
**Mutation safety note:**
- PATCH/DELETE can have ambiguous commit state on timeout. Current policy is conservative:
retries target obvious transient failure class (timeout/network), while caller aborts remain
fail-fast to avoid duplicate side effects during navigation/unmount flows.
**Primary files:**
- `src/lib/ae_api/api_patch_object.ts`
- `src/lib/ae_api/api_delete_object.ts`
**Acceptance criteria:**
- ✅ PATCH and DELETE timeout-aborts retry under capped backoff.
- ✅ Caller/navigation aborts do not retry.
- ✅ No regression for 400/401/403/422 fail-fast behavior.
-`npx svelte-check` clean, API-focused Playwright tests remained green during rollout.
**Additional fixes found during review pass (2026-05-21, commit ea765d8ad):**
- PATCH + DELETE: default timeout lowered from 60s → 20s to match GET/POST. No callers set
explicit timeouts; 60s × 5 retries = 5-minute worst case before giving up.
- DELETE: added `ae_auth_error` import and session-expired banner on 401/403. All other
files (GET/POST/PATCH) trigger the banner; DELETE was missing it, causing stale-session
deletes to silently return false with no user-visible feedback.
---
### [Testing] V3 API performance probe (basic stress rounds)
**Status:** ✅ Completed baseline harness (2026-05-21)
Implemented a gated Playwright probe for quick repeated list-query timing against live V3 endpoints.
**Files:**
- `tests/v3_api_latency_probe.test.ts`
- `tests/README.md` (run/tuning docs)
**Current capabilities:**
- Measures rounds for event sessions, journal entries, and user lists.
- Writes per-run JSON + Markdown reports to `tests/results/`.
- Optional anomaly thresholds for error-rate / p95 / empty-row detection.
---
### [Launcher/VLC] Linux playback — fullscreen + pause-on-end not working
**Status:** Mac ✅ working perfectly; Linux 🚧 deferred for later investigation
**Date discovered:** 2026-05-20
macOS VLC profile (direct binary path) successfully:
- Opens VLC with the media file
- Plays + pauses on the last frame (instead of returning to playlist)
- Fullscreen toggle works (Cmd+F via AppleScript post-script)
Linux VLC command (`vlc --no-play-and-exit --play-and-pause "{{path}}"`) currently:
- Does NOT go fullscreen
- Does NOT pause on the last frame (plays through, returns to playlist)
**Current state:** Both macOS and Linux commands in `ae_launcher__default_launch_profiles.ts`.
macOS is the primary venue deployment platform; Linux support is nice-to-have.
**Investigation needed:** Determine if the VLC flags are being interpreted on Linux,
or if there's a launcher execution layer issue (e.g. `shell:` prefix handling).
File: `src/lib/ae_events/ae_launcher__default_launch_profiles.ts``make_vlc_mirror_linux_profile()`.
---
### [Stores] Svelte 4 → Svelte 5 State Migration (prerequisite for Phase 2c)
The app uses `svelte-persisted-store` (Svelte 4 store contract) for all core persisted state
(`ae_loc`, `idaa_loc`, `ae_api`, `ae_sess`, etc.). In Svelte 5 `$effect`, reading **any field**
of a Svelte 4 store subscribes to the **entire store** — coarse-grained reactivity. This is the
root cause of the IDAA Novi re-auth bug (2026-03-30): unrelated `$ae_loc` writes (e.g. iframe
height, SWR cfg reload) triggered the Novi verification effect repeatedly.
Migration target: replace `svelte-persisted-store` with Svelte 5 `$state`-based persistence
(e.g. `runed` `PersistedState`, or a lightweight custom wrapper). This gives fine-grained
reactivity — only effects that actually read a changed field re-run.
**Phased approach (do NOT do all at once):**
### [Stores] Svelte 4 → Svelte 5 State Migration
The app uses `svelte-persisted-store` (coarse reactivity). Migration target: replace with Svelte 5
`$state`-based persistence for fine-grained updates.
- [ ] **Phase A — Project plan + wrapper decision:** Write `PROJECT__Stores_Svelte5_Migration.md`.
Decide: `runed` library vs. custom `$state` + localStorage wrapper. Audit all store consumers.
Identify stores in priority order. Estimate blast radius per store.
- [ ] **Phase B — Core auth stores (highest impact):** `ae_loc`, `idaa_loc`.
- [ ] **Phase C — Remaining persisted stores:** `ae_api`, `ae_events_stores`.
- [ ] **Phase D — Non-persisted writable stores:** `ae_sess`, `slct`, `ae_snip`, etc.
- [ ] **Phase B — Core auth stores (highest impact, start here):**
- `ae_loc` (persisted) — auth flags, site cfg, UI state; ~471 consumer sites across 150+ files
- `idaa_loc` (persisted) — Novi auth, IDAA query prefs
These two cause the most reactive noise. Migrating them also unlocks Phase 2c (separate `ae_auth`
store) since the callsite sweep is now required anyway.
### [Stores] IDB Content Version System
- [x] Write `check_and_clear_idb_tables()` helper.
- [x] Wire helper into `db_journals.ts` and IDAA layout.
- [ ] Roll out to `db_events.ts` (module-wide: session, presenter, badge, etc.).
- [ ] Roll out to `db_core.ts` (site_domain, person, user).
- [ ] **Phase C — Remaining persisted stores:**
- `ae_api` (persisted) — API config / JWT
- `ae_events_stores` persisted entries (badges, launcher, leads, pres_mgmt loc stores)
### [TypeScript] svelte-check hidden errors
- [ ] **[flowbite-svelte] `ModalProps.children` — 31 errors across 26 files.**
Replace `children` prop binding with Svelte snippet syntax.
- [ ] **Phase D — Non-persisted writable stores:**
- `ae_sess`, `idaa_sess`, `slct`, `slct_trigger`, `ae_auth_error`, `ae_trig`, `ae_snip`, etc.
- Lower urgency (no localStorage churn), but fine-grained reactivity still beneficial.
- [ ] **Phase E — Phase 2c (unblocked after B):** Split `ae_loc` into `ae_auth` + `ae_app`
(see entry below — ~471 callsites, but sweep is cheap once already touching every consumer).
**Project plan doc needed:** Yes — scope is app-wide. Do NOT start Phase B without Phase A.
### [Journals] Journal Entry Config follow-ups
- [ ] **[Journals] Entry passcode secondary auth** — implement `passcode_hash` comparison.
- [ ] **[Journals] Summary AI shortcut** — add button to modal.
---
### [Stores] IDB Content Version System
Scaffolded in `store_versions.ts` (`IDB_CONTENT_VERSIONS` constant + `check_and_clear_idb_table()`
helper) and `core__idb_dexie.ts` (`check_and_clear_idb_tables()` batch helper). Mirrors
`AE_LOC_VERSION` but targets Dexie table contents rather than localStorage keys.
**Currently active:** `journals.journal_entry` (db_journals.ts), `events.event` (IDAA layout).
All other tables are defined but not yet wired.
**Real-world impact:** Stale IDB records from a `properties_to_save` change were the root cause
of the IDAA Recovery Meetings "no meetings found" bug — a ~1-year unresolved issue (20252026).
Fixed 2026-05-16 by wiring `events.event` into the IDAA layout and bumping its version to 2.
See `BOOTSTRAP__AI_Agent_Quickstart.md` mistake #13 for the full postmortem.
**How it works:**
- `check_and_clear_idb_table(db_table, 'module', 'table')` reads a localStorage key with the
expected version from `IDB_CONTENT_VERSIONS`
- On mismatch (or missing key), the Dexie table is cleared and the key is updated
- SWR repopulates from API on next access — no explicit reload needed
- Cost on version match: one `localStorage.getItem()` — effectively free
- Bump a table's version in `IDB_CONTENT_VERSIONS` when `properties_to_save` changes shape
**IDAA consideration:**
IDAA tables are already cleared by `indexedDB.deleteDatabase()` on sign-out/auth failure in
`(idaa)/+layout.svelte`. The content version check is a *complementary* deploy-time reset, not
a replacement.
**Tasks:**
- [x] Write `check_and_clear_idb_tables()` helper in `core__idb_dexie.ts` (2026-05-14)
- [x] Wire helper into `db_journals.ts` (pilot — `journal_entry: 2` cleared stale content_md_html) (2026-05-14)
- [x] Wire `events.event` into IDAA layout `(idaa)/+layout.svelte` + bump version to 2 (2026-05-16)
- [ ] Roll out to `db_events.ts` (module-wide: session, presenter, badge, device, location, file)
- [ ] Roll out to `db_core.ts` (site_domain, person, user)
- [ ] Roll out to IDAA modules (`db_posts.ts`, `db_archives.ts`) — verify auth-wipe interaction first
- [ ] Consolidate the two `check_and_clear_idb_table*` helpers (single-table in `store_versions.ts`, batch in `core__idb_dexie.ts`)
### [Stores] Refactor — Phase 2c (deferred)
Phases 1, 2a, 2b are complete (see ✅ Completed below). One phase remaining:
- [ ] **Phase 2c — Actual separate stores (`ae_auth`, `ae_app`):** Requires touching ~471
`$ae_loc.*` auth-field read sites across 150+ files. Deferred until a Svelte runes migration
of the store layer itself (touching every component anyway makes the callsite sweep cheap).
### [TypeScript] svelte-check hidden errors — discovered 2026-03-27
**HOW WE FOUND THIS:** The `@lucide/svelte` 0.577.0 update (2026-03-10) dropped `class` from
`IconProps`. Fixing it required a `declare module '@lucide/svelte'` augmentation. That
augmentation was mistakenly placed in `app.d.ts`, which is a *script-context* declaration file
(no `export {}`). In that context, `declare module` is an **ambient replacement**, not a merge —
it wiped all icon exports from svelte-check's view, surfacing 1368 previously hidden errors.
Once moved to `src/lucide-augment.d.ts` (a proper module file with `export {}`), the masking
lifted and the real pre-existing errors became visible.
**Lesson:** A broken ambient declaration can silently hide unrelated errors. If svelte-check
suddenly jumps to 0 errors, verify it's not because a bad `.d.ts` replaced a package's types.
**Current state (2026-03-31):** 32 errors, 0 warnings — all `ModalProps.children`.
- [ ] **[flowbite-svelte] `ModalProps.children` — 31 errors across 26 files.** The flowbite-svelte
`Modal` component API changed; `children` is no longer a direct prop (now Svelte snippet-based).
Affected files span journals, pres_mgmt, events/settings, and IDAA archives.
Run `npx svelte-check 2>&1 | grep ModalProps` to get the current list.
Fix pattern: replace `children` prop binding with Svelte snippet syntax per flowbite-svelte docs.
### [Journals] Journal Entry Config follow-ups
- [ ] **[Journals] Visibility / audience toggle contrast** — the flag buttons need a clearer
selected state in both light and dark mode.
- [ ] **[Journals] Footer button style** — the actual `Done` button should read like a real button,
not a seamless footer spacer.
- [ ] **[Journals] Entry passcode secondary auth** — `passcode_hash` stores a hash; compare the
entered passcode hash to the stored hash, gate entry loading, and honor the TTL-based access
window. This is secondary entry auth, not a plain-text passcode field.
- [ ] **[Journals] Summary AI shortcut** — add an AI summarize button next to Entry Details
Summary so staff can generate a summary directly from the modal.
- [ ] **[Journals] Archive On sizing** — constrain the Archive On control to a reasonable width
instead of letting it expand to full width.
- [ ] **[Journals] Archive On behavior** — define what Archive On actually means and wire the
behavior; it is currently just a UI field with no live effect.
- [x] **[IDAA] Do not cache IDAA data in IDB when access is denied (2026-04-19, audited 2026-04-28)**
Full audit confirmed all protection layers are in place. No code changes required.
- All `+page.ts` / `+layout.ts` under `src/routes/idaa/` are clean — no SWR loads run before auth resolves.
- All `$effect` SWR calls in IDAA `+page.svelte` files are gated on `$idaa_loc.novi_verified || $ae_loc.trusted_access`.
- `(idaa)/+layout.svelte` purges `db_posts`, `db_archives`, `db_events` on auth failure, no-UUID/no-session, and inconsistent state.
- `sign_out()` calls `indexedDB.deleteDatabase()` on all IDAA databases.
- API 401/403 responses fail-fast in `api_get_object.ts` (throw before any IDB write).
- `idaa_trig` is in-memory `writable()` only — cannot carry stale trigger state across sessions.
- `$effect` auth guards in IDAA page components are reactivity guards (prevent spurious SWR calls on coarse `$ae_loc` writes), NOT auth-bypass guards. SvelteKit layout hierarchy already prevents child components from mounting when `(idaa)/+layout.svelte` blocks rendering.
- Doc: SvelteKit layout hierarchy security model captured in `GUIDE__SvelteKit2_Svelte5_DexieJS.md` and `BOOTSTRAP__AI_Agent_Quickstart.md` (Mistake #7).
- [ ] **[IDAA] IDB fast-path contact search — Recovery Meetings (2026-04-08, updated 2026-05-19)**
**API path is now working**`default_qry_str` already includes contact name/email.
Two bugs were fixed 2026-05-19: (1) STORED GENERATED columns had stale values; forced
rebuild via fake updates. (2) Frontend secondary filter was re-checking text against
response fields, silently dropping API results that matched only via `default_qry_str`.
**Remaining gap — IDB fast-path only:** The local cache fast-path returns all cached meetings
without text filtering; users see the unfiltered list first, then the API-filtered result
replaces it. To make contact matches appear instantly from cache:
- `src/lib/ae_events/ae_events__event.ts` → fast-path filter in `search__event()`: parse
`contact_li_json` and include contact names/emails in the local text match.
- `src/routes/idaa/(idaa)/recovery_meetings/+page.svelte` fast-path display: same filter.
Backend enhancement (`contact_li_json_ext` whitelist) is not required for this — the IDB
records already store `contact_li_json` raw JSON which can be parsed client-side.
## 🧪 Testing & Optimization
- [ ] **[IDAA] IDB fast-path contact search** — parse `contact_li_json` in `search__event()`.
- [ ] **[IDAA] Optimize Recovery Meetings SQL VIEW and indexes.**
The current search query can be taxing on the server. With ~150 active meetings, the view
logic and supporting indexes need a performance review to ensure fast responses as the
database grows. (Requested 2026-05-18)
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage** in all other event search pages.
- [ ] **[Launcher/VLC] Linux playback investigation** — fullscreen + pause-on-end flags.
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage in other event search pages.**
The backend was updated 2026-03-31 to expose `default_qry_str` in API responses.
Frontend fix applied to Recovery Meetings (`+page.svelte` + `properties_to_save`).
Check all other event search pages that use `db_events.event.filter()` or a secondary
post-API text filter — they may have the same mismatch (local searches `name`/`description`
only while server uses `default_qry_str`). Start with: any route under `/events/` or `/idaa/`
that has a full-text search input.
---
### [IDAA] Jitsi config editor + live site fix
- [ ] **Fix live site (id=17) `jitsi_token_endpoint` pointing to dev-api:** DB has
`https://dev-api.oneskyit.com/api/jitsi_token` for both site 10 and site 17 (IDAA live).
Need to update site 17 in **production** to `https://api.oneskyit.com/api/jitsi_token`.
SQL: `UPDATE site SET cfg_json = JSON_SET(cfg_json, '$.jitsi_token_endpoint', 'https://api.oneskyit.com/api/jitsi_token') WHERE id = 17;`
## ⚙️ DevOps & Backend
- [ ] **Add IDAA Jitsi config editor UI** to the jitsi_reports page (administrator_access only),
alongside the existing Jitsi URL Builder section. Should allow editing key fields in
`site_cfg_json` without needing phpMyAdmin:
- `jitsi_token_endpoint` — the JWT signing endpoint (needs to point to prod)
- Jitsi domain default (currently hardcoded as `jitsi.dgrzone.com` fallback in the page)
- `novi_jitsi_mod_li` — list of Novi UUIDs who get moderator privileges
Read from `$ae_loc.site_cfg_json`, PATCH the site record via V3 CRUD
(`PATCH /v3/crud/site/{id}/`), reload `$ae_loc.site_cfg_json` on save so it takes
effect without re-login.
- [ ] **[Backend] Re-add `Access-Control-Allow-Private-Network: true` CORS header.**
- [ ] **[DevOps] Nginx caching** — Investigate `index.html` cache-pickup issues.
- [ ] **[DevOps] Simplify Dockerfile env file selection** — Use plain `.env` instead of `BUILD_MODE`.
### [IDAA] Jitsi Reports still incomplete
- [x] **Finish Jitsi Reports filters** — added Novi UUID exclusion plus meeting-name whitelist
filtering, with room-level unique counts based on Novi UUID when present. (2026-05-06)
---
### ~~[PWA] Service worker ignoring `chrome-extension://` requests~~ ✅ Fixed (2026-05-14)
Guard added to `src/service-worker.js` fetch handler: `if (!event.request.url.startsWith('http')) return;`
Also skips cross-origin requests entirely (origin check). No console errors from extension URLs.
### [CSS] Global placeholder text color — too dark in light mode
Placeholder text inherits full input text color in light mode (Tailwind CSS default), making
placeholders indistinguishable from filled-in values. Most visible in badge print controls
where placeholders show the actual badge value (e.g. "John Smith").
Workaround: scoped `::placeholder` rule added to `ae_comp__badge_print_controls.svelte`
(gray-400 light / gray-500 dark) — `commit 7733ef8`.
**Long-term fix:** Add a global rule to the main CSS (e.g. `src/app.css` or a theme file):
```css
::placeholder {
color: #9ca3af; /* gray-400 */
opacity: 1; /* overrides Firefox's 0.54 default */
}
.dark ::placeholder {
color: #6b7280; /* gray-500 */
}
```
Once the global rule is in place, remove the scoped workaround from the badge controls.
### [Backend/DevOps] Re-add `Access-Control-Allow-Private-Network: true` CORS header
Chrome's Private Network Access (PNA) policy blocks public-origin iframes from fetching
private-network addresses. Symptom: when `dev-api.oneskyit.com` resolves to a LAN IP
(testing from home), Chrome blocks the site domain lookup → ghost account → `site_cfg_json`
never loads → `novi_idaa_api_key` is null → IDAA Novi verifier spins forever → timeout banner.
Firefox unaffected. Production unaffected (public IPs only).
- [ ] **Re-add PNA header to API CORS config**`dev-api` Nginx or FastAPI CORS middleware
must respond with `Access-Control-Allow-Private-Network: true` when Chrome sends
`Access-Control-Request-Private-Network: true` in the preflight. This was fixed ~1 month
ago and regressed. Check Nginx site config and FastAPI `CORSMiddleware` settings.
Low urgency (dev-only, Firefox workaround available), but blocks home-network iframe testing.
### [DevOps] Nginx caching — app version pickup issue
- [ ] **Investigate Nginx reverse proxy caching so users pick up new app deploys.**
Some users are not getting the updated app after a deploy. SvelteKit hashes JS/CSS bundle
filenames (cache-busting is automatic), but if Nginx or the browser caches `index.html`
itself, users get stale HTML pointing to old (possibly deleted) hashed assets.
**Check:**
- `proxy_cache` / `expires` directives in Nginx config for the SvelteKit app location
- Actual `Cache-Control` header on `/``curl -I https://<host>/` to verify
- Any service worker registration that might add another caching layer
**Expected fix:** Serve `index.html` with `Cache-Control: no-cache` (or `no-store`).
Hashed static assets (`/_app/immutable/`) can stay aggressively cached (`max-age=31536000,
immutable`). Root page must always be fresh so users pick up the new bundle references.
**Task ID:** #182928308 (shared Kanban)
### [DevOps] Remaining deployment items
- [ ] **Simplify Dockerfile env file selection** — Currently the Dockerfile uses a `BUILD_MODE` arg to
select between `.env.dev`, `.env.test`, `.env.prod` during the Docker build. This is unnecessary
complexity: each server (test Linode, prod Linode, workstation) only ever runs one environment, so
there will only ever be one env file present in that server's app directory.
**The fix:** Each server's app dir (`/srv/apps/test_aether_app_sveltekit/`, etc.) should have a
plain `.env` file (gitignored, placed manually during server setup). The Dockerfile should just
`COPY . .` and `cp .env .env.runtime` unconditionally — no `if prod / elif test / else dev`
branching for env file selection.
**What this changes:**
- `aether_app_sveltekit/Dockerfile` — remove the `BUILD_MODE`-driven `cp` block; always use `.env`
- Each Linode app dir gets a plain `.env` instead of `.env.test` / `.env.prod`
- Workstation keeps `.env.local` (for `npm run dev`) and `.env.dev` (for `build:docker:dev`) —
those stay as-is since they legitimately coexist locally
- `BUILD_MODE` arg can stay if needed for other build differences; just stop using it to pick the env file
- Update `.gitignore` in sveltekit to un-ignore `.env.test` / remove stale entries if desired
Low risk but unnecessary churn — defer until after the next active show.
- [ ] **Branch strategy cleanup:** All environments (test, prod, bak) currently pull from the same
branches. `deploy.sh` defaults are `ae_app_3x_llm` / `development` — acceptable for now but
should establish proper branch separation (e.g. `main`/`master` for prod).
- [ ] **Tier 2 deploy (Gitea webhook):** Push-triggered deploys via Gitea webhook → listener on
Linode → `deploy.sh`. Deferred until Gitea usage is more established.
### [Files] `db_events.file.clear()` on upload clears all cached files (2026-04-22)
In `ae_comp__event_files_upload.svelte` line 114, `db_events.file.clear()` wipes the entire
`file` Dexie table, not just files for the current session/presenter. Normally harmless (the
reload right after repopulates), but if multiple sessions' file lists are open simultaneously
they'd briefly flash empty. Low priority — only noticeable in multi-panel workflows.
### [General]
- **Input Field Audit:** Several input fields are missing `name`/`id` attributes or `data-testid`. Known examples: badge override fields in `ae_comp__badge_obj_view.svelte`; template name input in `ae_comp__badge_template_form.svelte`. Matters for: accessibility, autofill, label associations, and test targeting. (For tests, use `getByLabel()` rather than `input[value*=...]` which only checks the HTML attribute, not the Svelte-bound DOM property.)
## ✅ Completed (2026-04)
## ✅ Completed (archived)
See the full completed history in:
[documentation/archive/TODO__Agents__ARCHIVE_2026-03.md](documentation/archive/TODO__Agents__ARCHIVE_2026-03.md)
[documentation/archive/TODO__Agents__ARCHIVE_2026-04.md](documentation/archive/TODO__Agents__ARCHIVE_2026-04.md)
[documentation/archive/TODO__Agents__ARCHIVE_2026-05.md](documentation/archive/TODO__Agents__ARCHIVE_2026-05.md)

View File

@@ -1,326 +1,54 @@
# Frontend Agent Task List
> Use this file to track steps for complex features or bug fixes.
> **Status:** Stable — ongoing development.
# Frontend Agent Task List (Archived May 2026)
## ✅ Completed (2026-05)
## 🔴 BGH Conference — April 21 (Must Fix Before Event)
### [API] GET/POST retry hardening — differentiate timeout aborts vs intentional aborts
**Status:** ✅ Completed (2026-05-21)
- GET/POST now explicitly distinguish abort class in helper code.
- Timeout-triggered aborts are retryable via existing retry loop; intentional aborts fail fast.
- Backoff behavior retained (`2s -> 4s -> 6s -> 8s`).
- Validation done via Playwright tests.
- [x] **[Locations] Event Locations list does not auto-load** — added `+page.ts` to trigger
`load_ae_obj_li__event_location` on page load. Also fixed session query using stale
`event_location_id_random` index (should be `event_location_id`). (2026-04-19)
### [API] PATCH/DELETE retry hardening — parity with GET/POST
**Status:** ✅ Completed (2026-05-21)
- PATCH and DELETE now implement the same retry-classification model used in GET/POST.
- Added explicit fail-fast for 400/401/403/422.
- DELETE now triggers the session-expired banner on 401/403.
- [x] **[Files] Warn/error on `.ppt`/`.doc` upload** — warning rows shown per-file in upload table;
non-trusted users are fully blocked (`file_list_status = 'blocked_legacy'`); trusted users see
warnings but can still upload. Covers `.ppt`, `.doc` (block) and other legacy exts (warn-only).
(2026-04-19)
### [Testing] V3 API performance probe (basic stress rounds)
**Status:** ✅ Completed baseline harness (2026-05-21)
- Implemented a gated Playwright probe for quick repeated list-query timing against live V3 endpoints.
- Writes reports to `tests/results/`.
- [x] **[Files] Hide internal-purpose files from Launcher by default** — renamed `hide_draft` prop
to `show_internal_purpose_files` in `launcher_file_cont.svelte`; logic flipped so `false` (the
default) hides files with `file_purpose == 'outline'`, `'draft'`, or `'admin'`. Store key renamed
from `hide_content__draft_files` (inverted, misleading) to `show_content__internal_files: false`
(show-on-opt-in, consistent with all other `show_content__*` flags). Updated across all 8 Launcher
templates that pass this prop. (2026-04-19, revised 2026-04-20)
### [IDAA] Random "Access Denied" — Root Cause Review & Fixes
**Status:** ✅ Resolved (2026-05-19)
- Server-side Novi verification migrated to V3 action endpoint.
- Extended Novi TTL to 12 hours.
- Hardened retry and timeout logic in `+layout.svelte`.
- [x] **[Launcher] Remove duplicate session API call on session select** — `menu_session_list.svelte`
was calling `load_ae_obj_id__event_session` directly AND then `goto()` triggered `+page.ts` which
also called it — two concurrent calls per session click. Removed the direct call entirely;
`+page.ts` is now the sole owner of session data loading. `goto()` promise assigned to
`ae_promises.slct__event_session_id` to drive the existing `{#await}` spinner. (2026-04-20)
### [IDAA] Server-side Novi verification — 503 not auto-retried
**Status:** ✅ Fixed (2026-05-20)
- [ ] **[Electron/Launcher] Deploy + test Aether Native Electron app on Mac laptops** — build,
deploy, and verify on onsite Mac laptops. Additional testing of cache/launch flow still needed
before April 21.
- [x] **[Pres Mgmt] POC column shown in "Sessions at this Location"** — wired
`hide__session_poc={!pres_mgmt_loc.current.show__session_li_poc_field}` in
`ae_comp__event_location_obj_li.svelte`; also set `hide__session_location={true}` since
location is implicit in that context. (2026-04-19)
---
## 🔴 CMSC Charlotte — May 27 (Presentation Management)
**Drive down:** May 25 | **Setup:** May 26 morning | **Show:** May 27+
- [ ] **[Electron/Launcher] Clean up presentation file launch scripts** — BGH show revealed
issues with the scripts used to open/launch presentation files through the Electron Launcher.
Audit and improve the launch/open flow (script reliability, error handling, file path resolution).
Must be tested and ready before May 26 setup day.
---
## 🔴 Axonius DC — June 9 (Badge Printing)
**Setup/Registration:** June 8 | **Show:** June 9
- [ ] **[Badges] Epson C3500 fanfold badge layout** — Axonius is using Epson C3500 printers
with fanfold (continuous) badge stock. Create/configure a fanfold badge layout compatible
with the C3500 format. Must be ready before the June 8 setup/registration day.
---
## 🚧 Upcoming High Priority
### [Stores] Svelte 4 → Svelte 5 State Migration (prerequisite for Phase 2c)
The app uses `svelte-persisted-store` (Svelte 4 store contract) for all core persisted state
(`ae_loc`, `idaa_loc`, `ae_api`, `ae_sess`, etc.). In Svelte 5 `$effect`, reading **any field**
of a Svelte 4 store subscribes to the **entire store** — coarse-grained reactivity. This is the
root cause of the IDAA Novi re-auth bug (2026-03-30): unrelated `$ae_loc` writes (e.g. iframe
height, SWR cfg reload) triggered the Novi verification effect repeatedly.
Migration target: replace `svelte-persisted-store` with Svelte 5 `$state`-based persistence
(e.g. `runed` `PersistedState`, or a lightweight custom wrapper). This gives fine-grained
reactivity — only effects that actually read a changed field re-run.
**Phased approach (do NOT do all at once):**
- [ ] **Phase A — Project plan + wrapper decision:** Write `PROJECT__Stores_Svelte5_Migration.md`.
Decide: `runed` library vs. custom `$state` + localStorage wrapper. Audit all store consumers.
Identify stores in priority order. Estimate blast radius per store.
- [ ] **Phase B — Core auth stores (highest impact, start here):**
- `ae_loc` (persisted) — auth flags, site cfg, UI state; ~471 consumer sites across 150+ files
- `idaa_loc` (persisted) — Novi auth, IDAA query prefs
These two cause the most reactive noise. Migrating them also unlocks Phase 2c (separate `ae_auth`
store) since the callsite sweep is now required anyway.
- [ ] **Phase C — Remaining persisted stores:**
- `ae_api` (persisted) — API config / JWT
- `ae_events_stores` persisted entries (badges, launcher, leads, pres_mgmt loc stores)
- [ ] **Phase D — Non-persisted writable stores:**
- `ae_sess`, `idaa_sess`, `slct`, `slct_trigger`, `ae_auth_error`, `ae_trig`, `ae_snip`, etc.
- Lower urgency (no localStorage churn), but fine-grained reactivity still beneficial.
- [ ] **Phase E — Phase 2c (unblocked after B):** Split `ae_loc` into `ae_auth` + `ae_app`
(see entry below — ~471 callsites, but sweep is cheap once already touching every consumer).
**Project plan doc needed:** Yes — scope is app-wide. Do NOT start Phase B without Phase A.
---
### [Stores] Refactor — Phase 2c (deferred)
Phases 1, 2a, 2b are complete (see ✅ Completed below). One phase remaining:
- [ ] **Phase 2c — Actual separate stores (`ae_auth`, `ae_app`):** Requires touching ~471
`$ae_loc.*` auth-field read sites across 150+ files. Deferred until a Svelte runes migration
of the store layer itself (touching every component anyway makes the callsite sweep cheap).
### [Backend] Join event_location_id onto event_presenter API view
The `event_presenter` object currently has `event_session_id` but not `event_location_id`.
When navigating from the Presenter View to the Launcher, the frontend has to do a secondary
session lookup to discover the location (magic redirect in launcher base `+page.svelte`).
Joining `event_session.event_location_id` into the presenter view/response would let the
frontend pass the location directly in the Launcher URL without the extra lookup.
- [x] Backend: added `event_location_id` (and `event_location_id_random`) to the `event_presenter` view or API response (2026-04-09)
- [x] Frontend: updated `ae_EventPresenter` type and `properties_to_save`; now pass as `events__launcher_id` in `presenter_page_menu.svelte` (2026-04-09)
### [TypeScript] svelte-check hidden errors — discovered 2026-03-27
**HOW WE FOUND THIS:** The `@lucide/svelte` 0.577.0 update (2026-03-10) dropped `class` from
`IconProps`. Fixing it required a `declare module '@lucide/svelte'` augmentation. That
augmentation was mistakenly placed in `app.d.ts`, which is a *script-context* declaration file
(no `export {}`). In that context, `declare module` is an **ambient replacement**, not a merge —
it wiped all icon exports from svelte-check's view, surfacing 1368 previously hidden errors.
Once moved to `src/lucide-augment.d.ts` (a proper module file with `export {}`), the masking
lifted and the real pre-existing errors became visible.
**Lesson:** A broken ambient declaration can silently hide unrelated errors. If svelte-check
suddenly jumps to 0 errors, verify it's not because a bad `.d.ts` replaced a package's types.
**Current state (2026-03-31):** 32 errors, 0 warnings — all `ModalProps.children`.
- [ ] **[flowbite-svelte] `ModalProps.children` — 31 errors across 26 files.** The flowbite-svelte
`Modal` component API changed; `children` is no longer a direct prop (now Svelte snippet-based).
Affected files span journals, pres_mgmt, events/settings, and IDAA archives.
Run `npx svelte-check 2>&1 | grep ModalProps` to get the current list.
Fix pattern: replace `children` prop binding with Svelte snippet syntax per flowbite-svelte docs.
### [Journals] Journal Entry Config follow-ups
- [ ] **[Journals] Visibility / audience toggle contrast** — the flag buttons need a clearer
selected state in both light and dark mode.
- [ ] **[Journals] Footer button style** — the actual `Done` button should read like a real button,
not a seamless footer spacer.
- [ ] **[Journals] Entry passcode secondary auth** — `passcode_hash` stores a hash; compare the
entered passcode hash to the stored hash, gate entry loading, and honor the TTL-based access
window. This is secondary entry auth, not a plain-text passcode field.
- [ ] **[Journals] Summary AI shortcut** — add an AI summarize button next to Entry Details
Summary so staff can generate a summary directly from the modal.
- [ ] **[Journals] Archive On sizing** — constrain the Archive On control to a reasonable width
instead of letting it expand to full width.
- [ ] **[Journals] Archive On behavior** — define what Archive On actually means and wire the
behavior; it is currently just a UI field with no live effect.
- [x] **[IDAA] Do not cache IDAA data in IDB when access is denied (2026-04-19, audited 2026-04-28)**
Full audit confirmed all protection layers are in place. No code changes required.
- All `+page.ts` / `+layout.ts` under `src/routes/idaa/` are clean — no SWR loads run before auth resolves.
- All `$effect` SWR calls in IDAA `+page.svelte` files are gated on `$idaa_loc.novi_verified || $ae_loc.trusted_access`.
- `(idaa)/+layout.svelte` purges `db_posts`, `db_archives`, `db_events` on auth failure, no-UUID/no-session, and inconsistent state.
- `sign_out()` calls `indexedDB.deleteDatabase()` on all IDAA databases.
- API 401/403 responses fail-fast in `api_get_object.ts` (throw before any IDB write).
- `idaa_trig` is in-memory `writable()` only — cannot carry stale trigger state across sessions.
- `$effect` auth guards in IDAA page components are reactivity guards (prevent spurious SWR calls on coarse `$ae_loc` writes), NOT auth-bypass guards. SvelteKit layout hierarchy already prevents child components from mounting when `(idaa)/+layout.svelte` blocks rendering.
- Doc: SvelteKit layout hierarchy security model captured in `GUIDE__SvelteKit2_Svelte5_DexieJS.md` and `BOOTSTRAP__AI_Agent_Quickstart.md` (Mistake #7).
- [ ] **[IDAA] Make `contact_li_json_ext` searchable — Recovery Meeting contact search (2026-04-08)**
Members cannot search for meetings by contact name or email. `contact_li_json` data is not
included in `default_qry_str` and MariaDB cannot substring-search a JSON longtext directly.
The `event` table already has `contact_li_json_ext` (STORED GENERATED, indexed) to work around this.
**Backend (blocked on this first):** Add `contact_li_json_ext` to the searchable fields
whitelist for the `event` object type — likely a one-line change in `ae_obj_types_def.py`
or the event object definition. Message sent to backend agent 2026-04-08.
**Frontend (after backend ships):**
- `src/lib/ae_events/ae_events__event.ts``search__event()`: add `contact_li_json_ext`
as an OR condition alongside `default_qry_str` when `qry_str` is present.
- `src/routes/idaa/(idaa)/recovery_meetings/+page.svelte` fast-path IDB filter: parse
`contact_li_json` and include contact names/emails in the local text match check.
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage in other event search pages.**
The backend was updated 2026-03-31 to expose `default_qry_str` in API responses.
Frontend fix applied to Recovery Meetings (`+page.svelte` + `properties_to_save`).
Check all other event search pages that use `db_events.event.filter()` or a secondary
post-API text filter — they may have the same mismatch (local searches `name`/`description`
only while server uses `default_qry_str`). Start with: any route under `/events/` or `/idaa/`
that has a full-text search input.
### [IDAA] Jitsi config editor + live site fix
- [ ] **Fix live site (id=17) `jitsi_token_endpoint` pointing to dev-api:** DB has
`https://dev-api.oneskyit.com/api/jitsi_token` for both site 10 and site 17 (IDAA live).
Need to update site 17 in **production** to `https://api.oneskyit.com/api/jitsi_token`.
SQL: `UPDATE site SET cfg_json = JSON_SET(cfg_json, '$.jitsi_token_endpoint', 'https://api.oneskyit.com/api/jitsi_token') WHERE id = 17;`
- [ ] **Add IDAA Jitsi config editor UI** to the jitsi_reports page (administrator_access only),
alongside the existing Jitsi URL Builder section. Should allow editing key fields in
`site_cfg_json` without needing phpMyAdmin:
- `jitsi_token_endpoint` — the JWT signing endpoint (needs to point to prod)
- Jitsi domain default (currently hardcoded as `jitsi.dgrzone.com` fallback in the page)
- `novi_jitsi_mod_li` — list of Novi UUIDs who get moderator privileges
Read from `$ae_loc.site_cfg_json`, PATCH the site record via V3 CRUD
(`PATCH /v3/crud/site/{id}/`), reload `$ae_loc.site_cfg_json` on save so it takes
effect without re-login.
### [IDAA] Jitsi Reports still incomplete
- [x] **Finish Jitsi Reports filters** — added Novi UUID exclusion plus meeting-name whitelist
filtering, with room-level unique counts based on Novi UUID when present. (2026-05-06)
### [IDAA] Jitsi Reports filters
**Status:** ✅ Finished (2026-05-06)
- Added Novi UUID exclusion plus meeting-name whitelist filtering.
### [PWA] Service worker ignoring `chrome-extension://` requests
Browser console shows repeated errors:
```text
TypeError: Failed to execute 'put' on 'Cache': Request scheme 'chrome-extension' is unsupported
```
The service worker's fetch/install handler is trying to cache requests with `chrome-extension://`
URLs (injected by browser extensions), which the Cache API rejects. Fix: filter out non-`http`/`https`
requests before attempting to cache. In the service worker fetch handler, add a guard:
```js
if (!event.request.url.startsWith('http')) return; // skip chrome-extension:// etc.
```
Locate in `static/service-worker.js` or the Vite PWA plugin config. Low severity — doesn't break
functionality, but pollutes the console and may cause unhandled promise rejections.
**Status:** ✅ Fixed (2026-05-14)
- Added guard to filter out non-http/https requests before Attempting to cache.
### [CSS] Global placeholder text color — too dark in light mode
Placeholder text inherits full input text color in light mode (Tailwind CSS default), making
placeholders indistinguishable from filled-in values. Most visible in badge print controls
where placeholders show the actual badge value (e.g. "John Smith").
### [Electron/Launcher] Display mirroring auto-detection
**Status:** ✅ Completed (2026-05-20)
- `native:set-display-layout` now auto-detects displays via `displayplacer list`.
Workaround: scoped `::placeholder` rule added to `ae_comp__badge_print_controls.svelte`
(gray-400 light / gray-500 dark) — `commit 7733ef8`.
### [Launcher] Force Sync Location
**Status:** ✅ Completed (2026-05-21)
- Implemented manual trigger and background engine logic to pre-cache all location files.
**Long-term fix:** Add a global rule to the main CSS (e.g. `src/app.css` or a theme file):
```css
::placeholder {
color: #9ca3af; /* gray-400 */
opacity: 1; /* overrides Firefox's 0.54 default */
}
.dark ::placeholder {
color: #6b7280; /* gray-500 */
}
```
Once the global rule is in place, remove the scoped workaround from the badge controls.
### [Launcher] Chronological Download Priority
**Status:** ✅ Completed (2026-05-21)
- Refactored download queue to prioritize Event Assets > Early Sessions > Presentation Order > Created Date.
### [Backend/DevOps] Re-add `Access-Control-Allow-Private-Network: true` CORS header
Chrome's Private Network Access (PNA) policy blocks public-origin iframes from fetching
private-network addresses. Symptom: when `dev-api.oneskyit.com` resolves to a LAN IP
(testing from home), Chrome blocks the site domain lookup → ghost account → `site_cfg_json`
never loads → `novi_idaa_api_key` is null → IDAA Novi verifier spins forever → timeout banner.
Firefox unaffected. Production unaffected (public IPs only).
- [ ] **Re-add PNA header to API CORS config**`dev-api` Nginx or FastAPI CORS middleware
must respond with `Access-Control-Allow-Private-Network: true` when Chrome sends
`Access-Control-Request-Private-Network: true` in the preflight. This was fixed ~1 month
ago and regressed. Check Nginx site config and FastAPI `CORSMiddleware` settings.
Low urgency (dev-only, Firefox workaround available), but blocks home-network iframe testing.
### [DevOps] Remaining deployment items
- [ ] **Simplify Dockerfile env file selection** — Currently the Dockerfile uses a `BUILD_MODE` arg to
select between `.env.dev`, `.env.test`, `.env.prod` during the Docker build. This is unnecessary
complexity: each server (test Linode, prod Linode, workstation) only ever runs one environment, so
there will only ever be one env file present in that server's app directory.
**The fix:** Each server's app dir (`/srv/apps/test_aether_app_sveltekit/`, etc.) should have a
plain `.env` file (gitignored, placed manually during server setup). The Dockerfile should just
`COPY . .` and `cp .env .env.runtime` unconditionally — no `if prod / elif test / else dev`
branching for env file selection.
**What this changes:**
- `aether_app_sveltekit/Dockerfile` — remove the `BUILD_MODE`-driven `cp` block; always use `.env`
- Each Linode app dir gets a plain `.env` instead of `.env.test` / `.env.prod`
- Workstation keeps `.env.local` (for `npm run dev`) and `.env.dev` (for `build:docker:dev`) —
those stay as-is since they legitimately coexist locally
- `BUILD_MODE` arg can stay if needed for other build differences; just stop using it to pick the env file
- Update `.gitignore` in sveltekit to un-ignore `.env.test` / remove stale entries if desired
**Do not touch before the April 21 show.** Low risk but unnecessary churn right before an event.
- [ ] **Branch strategy cleanup:** All environments (test, prod, bak) currently pull from the same
branches. `deploy.sh` defaults are `ae_app_3x_llm` / `development` — acceptable for now but
should establish proper branch separation (e.g. `main`/`master` for prod).
- [ ] **Tier 2 deploy (Gitea webhook):** Push-triggered deploys via Gitea webhook → listener on
Linode → `deploy.sh`. Deferred until Gitea usage is more established.
### [Files] Download button — wrong ID used in `handle_click()` (2026-04-22)
`ae_comp__hosted_files_download_button.svelte` resolves `file_id` for the download call as
`hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id`. When called from
Manage Files with an `event_file_obj`, `hosted_file_obj.id` = `event_file_id` (set by
`_process_generic_props` via the `_random` strip logic), so the chain stops at the wrong value.
The download call goes to `/v3/action/hosted_file/{event_file_id}/download` instead of using the
correct `hosted_file_id`. May work if the backend accepts event_file_id at that endpoint —
needs live verification.
**Status (2026-04-22):** Tested — downloads ARE working despite the wrong ID. The backend
V3 action endpoint appears to silently accept `event_file_id` at the `hosted_file` download
path (or maps between the two). Working by accident, not by design. Needs proper fix before
it breaks — if the backend ever tightens that endpoint, all Manage Files downloads will 404.
**Fix:** In `handle_click()` and both `$effect` blocks and the `content` snippet, replace:
```ts
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
```
with:
```ts
const file_id = hosted_file_obj?.hosted_file_id ?? hosted_file_id;
```
The direct-download `<a>` path is unaffected (already uses `event_file_id` → correct endpoint).
### [Files] `db_events.file.clear()` on upload clears all cached files (2026-04-22)
In `ae_comp__event_files_upload.svelte` line 114, `db_events.file.clear()` wipes the entire
`file` Dexie table, not just files for the current session/presenter. Normally harmless (the
reload right after repopulates), but if multiple sessions' file lists are open simultaneously
they'd briefly flash empty. Low priority — only noticeable in multi-panel workflows.
### [General]
- **Input Field Audit:** Several input fields are missing `name`/`id` attributes or `data-testid`. Known examples: badge override fields in `ae_comp__badge_obj_view.svelte`; template name input in `ae_comp__badge_template_form.svelte`. Matters for: accessibility, autofill, label associations, and test targeting. (For tests, use `getByLabel()` rather than `input[value*=...]` which only checks the HTML attribute, not the Svelte-bound DOM property.)
## ✅ Completed (2026-04)
## ✅ Completed (archived)
See the full completed history in:
[documentation/archive/TODO__Agents__ARCHIVE_2026-03.md](documentation/archive/TODO__Agents__ARCHIVE_2026-03.md)
[documentation/archive/TODO__Agents__ARCHIVE_2026-04.md](documentation/archive/TODO__Agents__ARCHIVE_2026-04.md)
### [Launcher] Error handling + fallback
**Status:** ✅ Completed (2026-05-14)
- Post-script failure surfaces 'fallback' status; `open_cmd` failure falls back to OS default.

View File

@@ -1,9 +0,0 @@
# Aether (AE) Event Launcher Module (v3)
This directory contains the files for the new Event Launcher module (v3). Detailed documentation to follow.
This should be able to run in 3 modes:
- Default - What most users will see for demo purposes
- Onsite - This is what is set onsite, usually just in the Speaker Ready Room
- Native App - This is for the native app version of Aether and used on the MacBook laptops in each of the session rooms

View File

@@ -127,13 +127,11 @@ async function handle_update__event_presenter({
console.log('*** handle_update__event_presenter() ***');
ae_promises.update__event_presenter_obj = api
.update_ae_obj_id_crud({
.update_ae_obj({
api_cfg: $ae_api,
obj_type: obj_type,
obj_id: obj_id,
fields: data,
key: $ae_api.api_crud_super_key,
return_obj: true,
log_lvl: log_lvl
})
.then(async function (update__obj_result) {