Compare commits
4 Commits
86201f0fc1
...
17b549a75c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17b549a75c | ||
|
|
3de01af1a1 | ||
|
|
518a450b91 | ||
|
|
cb767ed115 |
124
documentation/GUIDE__AE_Events_Onsite_Runbook.md
Normal file
124
documentation/GUIDE__AE_Events_Onsite_Runbook.md
Normal 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
|
||||
|
||||
### 1–2 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. |
|
||||
@@ -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)
|
||||
|
||||
79
documentation/MODULE__AE_Events_Launcher.md
Normal file
79
documentation/MODULE__AE_Events_Launcher.md
Normal 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) |
|
||||
@@ -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
|
||||
|
||||
139
documentation/MODULE__AE_Events_Presentation_Management.md
Normal file
139
documentation/MODULE__AE_Events_Presentation_Management.md
Normal 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` |
|
||||
@@ -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 4–12 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
|
||||
|
||||
### 1–2 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 |
|
||||
@@ -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`)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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 149–159). 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 3–5 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 (2025–2026).
|
||||
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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user