Compare commits
160 Commits
cfc5d237c7
...
ae_app_3x_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03b3c84921 | ||
|
|
e89c982022 | ||
|
|
c6ef729c55 | ||
|
|
fd7ccd7ecc | ||
|
|
7831179970 | ||
|
|
75e7ca541a | ||
|
|
e6fb4b289f | ||
|
|
1e3f541a39 | ||
|
|
e966261324 | ||
|
|
67d2607da1 | ||
|
|
7192cbc0af | ||
|
|
a227c6aaa7 | ||
|
|
e05602b87b | ||
|
|
10f7f04fbc | ||
|
|
94bdeb9a26 | ||
|
|
080ad06a45 | ||
|
|
45f8bb5e58 | ||
|
|
3085d1dc63 | ||
|
|
7fc073053b | ||
|
|
582b43da34 | ||
|
|
98e31f1528 | ||
|
|
573d20e574 | ||
|
|
83c9b9fd4f | ||
|
|
27c775d816 | ||
|
|
5823f18161 | ||
|
|
94e4fad061 | ||
|
|
9a1ba02b59 | ||
|
|
05841350fe | ||
|
|
a5beff4aa8 | ||
|
|
246d4f8ef3 | ||
|
|
666b54bd36 | ||
|
|
89c1decf8d | ||
|
|
b9d70b616f | ||
|
|
e8a49562a9 | ||
|
|
e909c34874 | ||
|
|
48bc52899f | ||
|
|
6b122a065e | ||
|
|
1b81b8873c | ||
|
|
4f74cf1353 | ||
|
|
6dc6be9926 | ||
|
|
97c4c1cd6b | ||
|
|
b6481a3507 | ||
|
|
7b45b548e4 | ||
|
|
a1057fd776 | ||
|
|
d0286f7868 | ||
|
|
1c541cd090 | ||
|
|
868b4017f2 | ||
|
|
3122725610 | ||
|
|
6e04145514 | ||
|
|
b8ceed69d0 | ||
|
|
71aacb6346 | ||
|
|
04f3b82d59 | ||
|
|
cc04411d23 | ||
|
|
55f3e3a5a4 | ||
|
|
955d28d9c5 | ||
|
|
7c5cf53106 | ||
|
|
24b52b8027 | ||
|
|
6b3fb36926 | ||
|
|
5cad150b0a | ||
|
|
8a41f02f0d | ||
|
|
88ab5b27d4 | ||
|
|
b623557795 | ||
|
|
7d2b30b7ce | ||
|
|
c9b0acfa06 | ||
|
|
29a24812f4 | ||
|
|
70fda25c95 | ||
|
|
8f815b7033 | ||
|
|
ba4a0dc828 | ||
|
|
35c1324824 | ||
|
|
1a53a20995 | ||
|
|
04c2042060 | ||
|
|
4831f4b81b | ||
|
|
7bf76bf766 | ||
|
|
3ae5b30c37 | ||
|
|
b04202ecec | ||
|
|
84c4a2aa43 | ||
|
|
399f98ce8e | ||
|
|
f5ccd2e3cf | ||
|
|
94a3cb0644 | ||
|
|
9d904446d4 | ||
|
|
b45a27481a | ||
|
|
26ab5dda75 | ||
|
|
0511d9591f | ||
|
|
59fc7cabc6 | ||
|
|
41b352bc0a | ||
|
|
a4fed750fa | ||
|
|
74e0f752a6 | ||
|
|
be3e56eece | ||
|
|
e2d3c5a822 | ||
|
|
1b6aeb5b02 | ||
|
|
04018a27ed | ||
|
|
4292aebc56 | ||
|
|
3466d6552c | ||
|
|
b7969bc46e | ||
|
|
99b8eb0b5e | ||
|
|
9e361eae9b | ||
|
|
e735d0c213 | ||
|
|
d05cc63459 | ||
|
|
ac17417f3c | ||
|
|
3773758eb5 | ||
|
|
60bdd2fdba | ||
|
|
72c8f9b502 | ||
|
|
1de87b6c5f | ||
|
|
84a9d0fffc | ||
|
|
87084f0f71 | ||
|
|
de048a084b | ||
|
|
ee79e33a2a | ||
|
|
a5243fa820 | ||
|
|
5fce149808 | ||
|
|
a74effa6ff | ||
|
|
33d48e7e78 | ||
|
|
65e48c764e | ||
|
|
f6c950abdf | ||
|
|
3d6f9035c8 | ||
|
|
535efd9c4b | ||
|
|
4a39ca1468 | ||
|
|
182a066d38 | ||
|
|
35fed53e2a | ||
|
|
322abc2691 | ||
|
|
cfaf687717 | ||
|
|
e7620a1c06 | ||
|
|
e4e2174c97 | ||
|
|
d3bf314c62 | ||
|
|
d32355a1a2 | ||
|
|
213eabd8c1 | ||
|
|
872291b0a0 | ||
|
|
25d17841e4 | ||
|
|
6282fb167f | ||
|
|
a90572bcb8 | ||
|
|
194c89f6d1 | ||
|
|
469729ce22 | ||
|
|
d1f5d0e2fd | ||
|
|
9c83567430 | ||
|
|
b4d0d82141 | ||
|
|
15bfe6d5d6 | ||
|
|
dddf4b6170 | ||
|
|
587b815446 | ||
|
|
ca51a82dae | ||
|
|
a38320c7f5 | ||
|
|
c76fb8f2b5 | ||
|
|
a26ea8b49c | ||
|
|
21fad1a698 | ||
|
|
33e9eeef78 | ||
|
|
172ea994c7 | ||
|
|
17b549a75c | ||
|
|
3de01af1a1 | ||
|
|
518a450b91 | ||
|
|
cb767ed115 | ||
|
|
86201f0fc1 | ||
|
|
60e3fc539e | ||
|
|
b3029a4d27 | ||
|
|
ea765d8ad2 | ||
|
|
db5acdd30a | ||
|
|
a000e07647 | ||
|
|
7f9368589a | ||
|
|
55d3d49595 | ||
|
|
f5cf1ef398 | ||
|
|
d5d552a029 | ||
|
|
689bb326cb | ||
|
|
e6db2b4d6a |
15
.ae_brief
15
.ae_brief
@@ -1,22 +1,15 @@
|
|||||||
# Aether Project Brief: aether_app_sveltekit
|
# Aether Project Brief: aether_app_sveltekit
|
||||||
**Last Updated:** 2026-02-09 22:03:56
|
**Last Updated:** 2026-05-21 22:25:05
|
||||||
**Current Agent:** mcp_agent
|
**Current Agent:** mcp_agent
|
||||||
|
|
||||||
## 🛠️ What I Just Did
|
## 🛠️ What I Just Did
|
||||||
Addressed multiple Svelte compiler warnings:
|
Implemented "Force Sync Location" feature. Optimized file download order with a 4-tier chronological sort (Global > Session > Presentation > Creation Date). Added UI button for onsite operators. Updated project documentation. Verified with npm run check.
|
||||||
1. Converted ~30 decorative labels to spans (a11y).
|
|
||||||
2. Applied Svelte 5 untrack() pattern to initial state from props.
|
|
||||||
3. Fixed CSS scoping for TipTap editor.
|
|
||||||
4. Added rel="noopener noreferrer" to external links.
|
|
||||||
5. Commited changes in two atomic batches.
|
|
||||||
|
|
||||||
## 🚧 Current Blockers
|
## 🚧 Current Blockers
|
||||||
None. Remaining svelte-check warnings (219) require more granular ID/for linking in complex forms.
|
None.
|
||||||
|
|
||||||
## ➡️ Exact Next Steps
|
## ➡️ Exact Next Steps
|
||||||
1. Granular fix for remaining 68 label/ID associations in address/person forms.
|
User to review changes. Ready for onsite testing/deployment.
|
||||||
2. Systematic application of untrack() for remaining state-from-props warnings.
|
|
||||||
3. Clean up unused TipTap CSS selectors identified by svelte-check.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
*Generated by ae_brief*
|
*Generated by ae_brief*
|
||||||
|
|||||||
33
conductor/fix-idaa-breakout-links.md
Normal file
33
conductor/fix-idaa-breakout-links.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Plan: Fix IDAA Jitsi Breakout Links
|
||||||
|
|
||||||
|
IDAA Jitsi meetings are embedded in an iframe on the `idaa.org` website. To allow members to "break out" of the iframe (for a better experience on mobile or to use full-tab features), the app provides an "Open Meeting Externally" link.
|
||||||
|
|
||||||
|
Currently, this link is generated from `$page.url.href`, which often lacks the `key` (site access key) and `uuid` (Novi identity token) required for Aether's authentication gate, especially if the user has navigated internally within SvelteKit.
|
||||||
|
|
||||||
|
## 1. Objective
|
||||||
|
Ensure all "Breakout" and "Copy Link" actions on the IDAA Video Conferences page include the necessary `key` and `uuid` parameters.
|
||||||
|
|
||||||
|
## 2. Implementation Steps
|
||||||
|
|
||||||
|
### Step 1: Update `src/routes/idaa/(idaa)/video_conferences/+page.svelte`
|
||||||
|
- Create a reactive `breakout_url` derived from `$page.url.href`.
|
||||||
|
- In the derivation logic:
|
||||||
|
- Instantiate a `new URL`.
|
||||||
|
- Check if `key` is present in `searchParams`. If missing, pull from `$ae_loc.allow_access` or `$ae_loc.site_access_key`.
|
||||||
|
- Check if `uuid` is present in `searchParams`. If missing, pull from `$idaa_loc.novi_uuid`.
|
||||||
|
- Return the resulting `href`.
|
||||||
|
- Update the following UI elements to use `breakout_url`:
|
||||||
|
- `copy_meeting_link` function (uses `navigator.clipboard.writeText`).
|
||||||
|
- "Open in New Tab" anchor tag (`href`).
|
||||||
|
- "Copy Link" fallback textarea (`value`).
|
||||||
|
- "Copy Break-out Link" in the Jitsi Tools panel (`value`).
|
||||||
|
|
||||||
|
### Step 2: Update `documentation/CLIENT__IDAA_and_customized_mods.md`
|
||||||
|
- Add a note in the "Authentication: Novi UUID System" or "Iframe Integration" section about the requirement for `key` and `uuid` in breakout links.
|
||||||
|
- Document that the frontend now automatically re-injects these for external links to ensure persistent session access outside the iframe.
|
||||||
|
|
||||||
|
## 3. Verification
|
||||||
|
- Manually verify the logic:
|
||||||
|
- If `key` and `uuid` are already in the URL (e.g., initial load), the derived URL should remain unchanged (or correctly deduplicated).
|
||||||
|
- If they are missing (e.g., after navigating from another IDAA page), they should be added to the generated link.
|
||||||
|
- Run `npx svelte-check` to ensure no syntax regressions.
|
||||||
53
conductor/launcher-ux-refinement.md
Normal file
53
conductor/launcher-ux-refinement.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Plan: Launcher Config UX Refinement (Cohesion & Stability)
|
||||||
|
|
||||||
|
The goal of this plan is to address the visual "bouncing", layering overload, and the misplaced close button in the new Launcher configuration modal.
|
||||||
|
|
||||||
|
## 1. Dimensional Stability
|
||||||
|
- **Problem:** Switching tabs causes the modal to resize vertically and horizontally, leading to a "bouncing" feel.
|
||||||
|
- **Solution:**
|
||||||
|
- Set a fixed height for the `Launcher_cfg` container (e.g., `h-[750px]`).
|
||||||
|
- Use `overflow-y-auto` only for the right-hand content pane.
|
||||||
|
- Ensure the sidebar has a stable width.
|
||||||
|
|
||||||
|
## 2. Visual Hierarchy & Layering
|
||||||
|
- **Problem:** Too many nested backgrounds (Page > Launcher > Modal > Inner Pane > Section Pane > Section Content).
|
||||||
|
- **Solution:**
|
||||||
|
- Flatten the background of the main content pane.
|
||||||
|
- Simplify `Launcher_Cfg_Section.svelte`:
|
||||||
|
- Remove `shadow-xl` from individual sections.
|
||||||
|
- Use subtler borders instead of strong "preset-outlined" colors.
|
||||||
|
- Remove the secondary background (`bg-white/5`) from the section content area.
|
||||||
|
- Standardize on a single, clean surface color for the right-hand pane.
|
||||||
|
|
||||||
|
## 3. The "Centered Close Button" Bug
|
||||||
|
- **Problem:** A close button is appearing in the middle of the screen.
|
||||||
|
- **Investigation:**
|
||||||
|
- Check for absolute-positioned elements in `Launcher_cfg.svelte` or `+layout.svelte`.
|
||||||
|
- Verify if Flowbite's `Modal` default close button is clashing with internal buttons.
|
||||||
|
- **Solution:**
|
||||||
|
- Consolidate all "Close" actions.
|
||||||
|
- Use the Modal's built-in top-right close button (if available) or a single, well-positioned button in the sidebar.
|
||||||
|
|
||||||
|
## 4. Implementation Steps
|
||||||
|
|
||||||
|
### Step 1: Update `Launcher_cfg.svelte`
|
||||||
|
- Set stable dimensions: `h-[750px] max-h-[90vh] w-[1000px] max-w-[95vw]`.
|
||||||
|
- Remove internal shadows and borders that conflict with the Modal container.
|
||||||
|
- Clean up the sidebar "Close" button.
|
||||||
|
|
||||||
|
### Step 2: Update `Launcher_cfg_section.svelte`
|
||||||
|
- Simplify the styling to reduce visual clutter.
|
||||||
|
- Remove `shadow-xl`.
|
||||||
|
- Use consistent padding and margins.
|
||||||
|
|
||||||
|
### Step 3: Update `+layout.svelte`
|
||||||
|
- Ensure the `Modal` is configured for a stable, large view without default padding issues.
|
||||||
|
- Verify the `modal_cfg_open` logic.
|
||||||
|
|
||||||
|
### Step 4: Add `Launcher_cfg_field.svelte` (Helper)
|
||||||
|
- Implement a unified field helper to standardize Label/Description/Input layouts across all tabs.
|
||||||
|
|
||||||
|
## 5. Verification
|
||||||
|
- Toggle between all 7 tabs. Verify zero layout shift (height/width remains constant).
|
||||||
|
- Check appearance in Light and Dark modes.
|
||||||
|
- Verify "Technical Mode" transitions.
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Aether Project Architecture
|
# Aether Project Architecture
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
This document outlines the overall architecture and key technologies used in the Aether SvelteKit frontend project.
|
This document outlines the overall architecture and key technologies used in the Aether SvelteKit frontend project.
|
||||||
|
|
||||||
## 1. Project Overview
|
## 1. Project Overview
|
||||||
@@ -20,7 +22,7 @@ The Aether project is a Svelte and SvelteKit based application, utilizing Tailwi
|
|||||||
- TipTap (`element_editor_tiptap.svelte`) — WYSIWYG rich-text for content fields (IDAA, Journals, Leads notes)
|
- TipTap (`element_editor_tiptap.svelte`) — WYSIWYG rich-text for content fields (IDAA, Journals, Leads notes)
|
||||||
- **Icons:** Lucide Icons (SVG Icons)
|
- **Icons:** Lucide Icons (SVG Icons)
|
||||||
- **Markdown Parsing:** `marked` library
|
- **Markdown Parsing:** `marked` library
|
||||||
- **State Management:** Svelte stores, potentially with `liveQuery` from Dexie for reactive IndexedDB interactions.
|
- **State Management:** Svelte 5 runes plus Dexie `liveQuery`. Events persisted state uses `runed` `PersistedState`; core and IDAA still contain legacy coarse-grained persisted stores pending migration.
|
||||||
|
|
||||||
### 2.1. Journals as the Canonical Frontend Pattern
|
### 2.1. Journals as the Canonical Frontend Pattern
|
||||||
|
|
||||||
@@ -78,8 +80,8 @@ Used for client-side persistence of various application states and configuration
|
|||||||
|
|
||||||
Used for more structured client-side data storage, often for caching and offline capabilities.
|
Used for more structured client-side data storage, often for caching and offline capabilities.
|
||||||
|
|
||||||
- `ae_core_db`: Core database instance.
|
- `db_core`: Core database instance.
|
||||||
- `<module>`: Module-specific database instances.
|
- `db_<module>`: Module-specific database instances (for example, `db_events` and `db_journals`).
|
||||||
- `<custom>`: Custom module-specific database instances (none currently defined).
|
- `<custom>`: Custom module-specific database instances (none currently defined).
|
||||||
|
|
||||||
## 5. Data Sorting
|
## 5. Data Sorting
|
||||||
@@ -97,9 +99,9 @@ A set of standardized field names and types are used across Aether objects.
|
|||||||
|
|
||||||
These fields are expected to be present in most Aether objects.
|
These fields are expected to be present in most Aether objects.
|
||||||
|
|
||||||
- `id`: Primary key for an object (internal use, often a UUID).
|
- `<object_type>_id`: Canonical randomized string ID returned by V3 (for example, `person_id`). Use this for URLs, relationships, and Dexie indexed lookups.
|
||||||
- `id_random`: Randomly generated ID for an object (often used for external exposure or URL parameters).
|
- `id`: Generic randomized string alias when returned by V3; do not assume it is a DB autonumber.
|
||||||
- `<object_type>_id_random`: Specific random ID for an object (e.g., `person_id_random`).
|
- `id_random` / `<object_type>_id_random`: Legacy aliases. Do not introduce new usage.
|
||||||
- `code`: Short, unique identifier.
|
- `code`: Short, unique identifier.
|
||||||
- `name`: Display name.
|
- `name`: Display name.
|
||||||
- `enable`: Boolean for active/inactive status.
|
- `enable`: Boolean for active/inactive status.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Aether Project Naming Conventions
|
# Aether Project Naming Conventions
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
## 1. General Principles
|
## 1. General Principles
|
||||||
|
|
||||||
- **Clarity:** Names should clearly convey their purpose and meaning.
|
- **Clarity:** Names should clearly convey their purpose and meaning.
|
||||||
@@ -11,9 +13,10 @@
|
|||||||
|
|
||||||
- **Logic/Service Files:** `ae_<module>__<concept>.ts` (e.g., `ae_core__account.ts`, `ae_events__event.ts`)
|
- **Logic/Service Files:** `ae_<module>__<concept>.ts` (e.g., `ae_core__account.ts`, `ae_events__event.ts`)
|
||||||
- **Database Definition Files:** `db_<module>.ts` (e.g., `db_core.ts`, `db_journals.ts`)
|
- **Database Definition Files:** `db_<module>.ts` (e.g., `db_core.ts`, `db_journals.ts`)
|
||||||
- **Svelte Store Files:** `ae_<module>_stores.ts` (e.g., `ae_core_stores.ts`, `ae_journals_stores.ts`)
|
- **Svelte Store Files:** Follow existing module names. Svelte 5 `PersistedState` files use a `.svelte.ts` suffix and are imported via the `.svelte` module path (for example, `ae_events_stores__badges.svelte.ts`).
|
||||||
- **Svelte Components:**
|
- **Svelte Components:**
|
||||||
- **Module-specific components:** `ae_comp__<module>__<component_name>.svelte` (e.g., `ae_comp__events__event_card.svelte`)
|
- **Route-level components:** `ae_comp__<component_name>.svelte`.
|
||||||
|
- **Module-specific components:** `ae_<module>_comp__<component_name>.svelte` (for example, `ae_events_comp__session_list.svelte`).
|
||||||
- **Generic/reusable components:** `element_<component_name>.svelte` (e.g., `element_input_file.svelte`, `element_qr_scanner_v2.svelte`)
|
- **Generic/reusable components:** `element_<component_name>.svelte` (e.g., `element_input_file.svelte`, `element_qr_scanner_v2.svelte`)
|
||||||
- **SvelteKit Routes:** Follow SvelteKit's standard routing conventions (e.g., `+page.svelte`, `+layout.svelte`, `[id]/+page.svelte`).
|
- **SvelteKit Routes:** Follow SvelteKit's standard routing conventions (e.g., `+page.svelte`, `+layout.svelte`, `[id]/+page.svelte`).
|
||||||
- **CSS Files:** `ae-<module>-<purpose>.css` (e.g., `ae-c-idaa-light.css`, `ae-osit-default.css`)
|
- **CSS Files:** `ae-<module>-<purpose>.css` (e.g., `ae-c-idaa-light.css`, `ae-osit-default.css`)
|
||||||
@@ -37,9 +40,9 @@
|
|||||||
|
|
||||||
- **Singularity:** Use singular nouns for objects and properties (e.g., `example.id`, not `examples.id`).
|
- **Singularity:** Use singular nouns for objects and properties (e.g., `example.id`, not `examples.id`).
|
||||||
- **IDs:**
|
- **IDs:**
|
||||||
- `id`: Primary key for an object (internal use, often a UUID).
|
- `<object_type>_id`: Canonical randomized string ID returned by V3 (for example, `person_id`).
|
||||||
- `<object_type>_id`: Specific ID for an object (e.g., `person_id`).
|
- `id`: Generic randomized string alias when V3 returns one; never assume it is an integer autonumber.
|
||||||
- `<object_type>_id_random`: Randomly generated ID for an object (often used for external exposure or URL parameters).
|
- `<object_type>_id_random`: Legacy alias; do not introduce new usage.
|
||||||
- `account_id`, `site_id`, `user_id`, etc.: Foreign keys.
|
- `account_id`, `site_id`, `user_id`, etc.: Foreign keys.
|
||||||
- **Common Properties:**
|
- **Common Properties:**
|
||||||
- `code`: Short, unique identifier.
|
- `code`: Short, unique identifier.
|
||||||
@@ -88,6 +91,6 @@
|
|||||||
- `<module>` (extended modules)
|
- `<module>` (extended modules)
|
||||||
- `<custom>` (custom modules)
|
- `<custom>` (custom modules)
|
||||||
- **IndexedDB:**
|
- **IndexedDB:**
|
||||||
- `ae_core_db`
|
- `db_core`
|
||||||
- `<module>`
|
- `db_<module>` (for example, `db_events`, `db_journals`)
|
||||||
- `<custom>`
|
- `<custom>`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Aether — Permissions and Security
|
# Aether — Permissions and Security
|
||||||
|
|
||||||
**Last updated:** 2026-02-27
|
**Last Updated:** 2026-06-12
|
||||||
**Source of truth:** `src/lib/ae_utils/ae_utils__perm_checks.ts`, `src/lib/stores/ae_stores.ts`
|
**Source of truth:** `src/lib/ae_utils/ae_utils__perm_checks.ts`, `src/lib/stores/ae_stores.ts`
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -76,15 +76,18 @@ $ae_loc.adv_mode // boolean — advanced mode toggle
|
|||||||
| AE Username + Password | `trusted` and above | Staff with AE accounts |
|
| AE Username + Password | `trusted` and above | Staff with AE accounts |
|
||||||
| Novi UUID | `authenticated` | IDAA members (Novi membership system) |
|
| Novi UUID | `authenticated` | IDAA members (Novi membership system) |
|
||||||
|
|
||||||
Passcodes are stored per-level in `$ae_loc.site_access_code_kv`:
|
### Site Passcode Security Warning
|
||||||
```typescript
|
|
||||||
site_access_code_kv: {
|
The current frontend receives every site passcode in `access_code_kv_json`, copies the map into
|
||||||
administrator: null, // highest passcode tier
|
persisted `$ae_loc.site_access_code_kv`, and compares entered passcodes locally. Verbose logging
|
||||||
trusted: null, // onsite staff passcode
|
can also expose the complete map. This is a known active security gap, not the target design.
|
||||||
public: 'public1980', // example
|
|
||||||
authenticated: 'auth1980'
|
Do not add new consumers of `site_access_code_kv`, log passcodes, or treat persisted
|
||||||
}
|
`access_type` as durable proof of authentication. The target flow verifies passcodes through
|
||||||
```
|
`/authenticate_passcode`, stores a signed JWT with a role-specific TTL, and removes passcodes from
|
||||||
|
the public bootstrap response and client state.
|
||||||
|
|
||||||
|
See `documentation/PROJECT__AE_Site_Passcode_Security.md` for the active migration plan.
|
||||||
|
|
||||||
### `x-no-account-id` — Narrow Transport Exception
|
### `x-no-account-id` — Narrow Transport Exception
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
# Aether SvelteKit — AI Agent Bootstrap / Quickstart
|
# Aether SvelteKit — AI Agent Bootstrap / Quickstart
|
||||||
|
> **Doc Owner:** Frontend platform maintainers (OSIT) + active coding agents
|
||||||
|
> **Review Trigger:** Update when module architecture, critical rules, or primary doc paths change.
|
||||||
> **Read this first.** This doc is the fast path to being productive on this project.
|
> **Read this first.** This doc is the fast path to being productive on this project.
|
||||||
> It covers the rules, patterns, and gotchas that matter most.
|
> It covers the rules, patterns, and gotchas that matter most.
|
||||||
> Deep dives are in the linked docs at the bottom.
|
> Deep dives are in the linked docs at the bottom.
|
||||||
@@ -29,7 +31,7 @@ running in Docker. The frontend talks to it exclusively via the V3 REST API.
|
|||||||
| Editors | CodeMirror 6 (primary), Edra/TipTap (secondary) |
|
| Editors | CodeMirror 6 (primary), Edra/TipTap (secondary) |
|
||||||
| Native | Electron app for onsite launcher (`src/lib/electron/electron_relay.ts`) |
|
| Native | Electron app for onsite launcher (`src/lib/electron/electron_relay.ts`) |
|
||||||
| Backend | FastAPI + MariaDB, V3 API (`/v3/crud/`, `/v3/lookup/`) |
|
| Backend | FastAPI + MariaDB, V3 API (`/v3/crud/`, `/v3/lookup/`) |
|
||||||
| Auth | Custom headers: `x-aether-api-key` + `x-account-id`; JWT Bearer is auto-injected when a session exists |
|
| Auth | Custom headers only: `x-aether-api-key` + `x-account-id` — **NOT** Bearer tokens |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -142,19 +144,31 @@ onDestroy(() => cleanup());
|
|||||||
- Use `$state()` for local component state with no external binding.
|
- Use `$state()` for local component state with no external binding.
|
||||||
|
|
||||||
### Store reactivity trap (important for `$effect`)
|
### Store reactivity trap (important for `$effect`)
|
||||||
The app uses `svelte-persisted-store` (Svelte 4 contract) for `$ae_loc`, `$ae_api`,
|
The app has two kinds of persisted stores — know which you're reading:
|
||||||
`$ae_sess`, etc. In Svelte 5 `$effect`, reading **any field** of a Svelte 4 store
|
|
||||||
subscribes to the **entire store**. This means unrelated writes to `$ae_loc`
|
|
||||||
(e.g. iframe height, SWR reload) will re-trigger your effect. Be conservative about
|
|
||||||
what you read from these stores inside `$effect` blocks. See `PROJECT__Stores_Svelte5_Migration.md`
|
|
||||||
for the long-term fix plan.
|
|
||||||
|
|
||||||
For search pages specifically, this usually means:
|
**Svelte 4 `svelte-persisted-store` (coarse reactivity) — still used for:**
|
||||||
|
- `$ae_loc`, `$ae_sess`, `$ae_api` (global app state)
|
||||||
|
- `$idaa_loc`, `$idaa_sess` (IDAA module)
|
||||||
|
|
||||||
|
In Svelte 5 `$effect`, reading **any field** of these stores subscribes to the **entire store**.
|
||||||
|
Unrelated writes to `$ae_loc` (e.g. iframe height, SWR reload) will re-trigger your effect.
|
||||||
|
Be conservative about what you read from these stores inside `$effect` blocks.
|
||||||
|
|
||||||
|
**Svelte 5 `PersistedState` (fine-grained reactivity) — Events module stores:**
|
||||||
|
- `badges_loc`, `leads_loc`, `pres_mgmt_loc`, `launcher_loc`, `events_auth_loc`
|
||||||
|
|
||||||
|
These use `runed`'s `PersistedState`. Access via `.current` (no `$` sigil):
|
||||||
|
`badges_loc.current.field`. Writing one field only re-triggers effects that read that field.
|
||||||
|
Import from the `.svelte` extension: `import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte'`.
|
||||||
|
|
||||||
|
For search pages using the coarse stores, this usually means:
|
||||||
- keep true user preferences in persisted local state
|
- keep true user preferences in persisted local state
|
||||||
- keep transient triggers, loading flags, and last-executed search keys in session state when possible
|
- keep transient triggers, loading flags, and last-executed search keys in session state when possible
|
||||||
- let the page effect schedule the search, but put the duplicate-execution guard inside the search executor so page-load auto-search still runs after hydration
|
- let the page effect schedule the search, but put the duplicate-execution guard inside the search executor so page-load auto-search still runs after hydration
|
||||||
- if the search text or filters are mirrored from localStorage on mount, expect that mount-time writes can re-trigger the effect unless the executor has its own guard
|
- if the search text or filters are mirrored from localStorage on mount, expect that mount-time writes can re-trigger the effect unless the executor has its own guard
|
||||||
|
|
||||||
|
See `PROJECT__Stores_Svelte5_Migration.md` for migration status and the pattern to follow when migrating remaining stores.
|
||||||
|
|
||||||
### `{#await}` blocks
|
### `{#await}` blocks
|
||||||
```svelte
|
```svelte
|
||||||
{#await somePromise}
|
{#await somePromise}
|
||||||
@@ -271,108 +285,29 @@ When building anything new, model it after Journals.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Mistakes Agents Have Made on This Project
|
## 7. Common Mistakes (Reference)
|
||||||
|
|
||||||
These are real incidents — know them before you start.
|
The full, curated mistake catalog now lives in
|
||||||
|
`documentation/REFERENCE__Common_Agent_Mistakes.md`.
|
||||||
|
|
||||||
1. **IDAA BB exposed publicly** — an agent removed an auth guard from the bulletin board
|
Read this section first, then open the reference doc when your task touches one of these areas:
|
||||||
route. All IDAA content must be behind authentication. Always check route guards when
|
|
||||||
touching `/idaa/` routes.
|
|
||||||
|
|
||||||
2. **`event_file_id` in PATCH body (400 error)** — including the object ID in `data_kv`
|
1. **Security/Auth** — private route guards, account scoping, and pre-gate data load risks.
|
||||||
when calling `update_ae_obj__*`. The V3 API tries to `SET event_file_id = ...` which
|
2. **V3 API payloads** — object ID in URL, `data_kv` field-only PATCH payloads.
|
||||||
fails because it's a view alias, not a DB column. See Section 2 above.
|
3. **Dexie/IDB behavior** — `.get()` primary key trap, stale cache/version mismatches, broad result clipping.
|
||||||
|
4. **Svelte 5 reactivity** — coarse-store `$effect` loops and `$`-sigil misuse on plain props.
|
||||||
|
5. **Sorting and search correctness** — `tmp_sort_*` comparator direction and Dexie sorting caveats.
|
||||||
|
6. **Network reliability** — retry classification in `api_*_object.ts` and timeout behavior.
|
||||||
|
7. **JSON field safety** — `*_json` null reads/writes and wrapper serialization behavior.
|
||||||
|
8. **Service worker rollout behavior** — stale-tab symptoms, activation expectations, and trade-offs.
|
||||||
|
|
||||||
3. **Bad `.d.ts` declaration silently hid 1368 errors** — a `declare module` in `app.d.ts`
|
The reference doc also includes a short archive list for older, less-relevant historical incidents.
|
||||||
(a script-context file) replaced the entire `@lucide/svelte` type exports instead of
|
|
||||||
merging. `svelte-check` showed 0 errors, masking real problems. If `svelte-check`
|
|
||||||
suddenly drops to 0 errors, verify it's not because a bad declaration wiped a module.
|
|
||||||
|
|
||||||
4. **Coarse store reactivity loop** — an `$effect` that read `$ae_loc.some_field` was
|
|
||||||
re-triggering repeatedly because unrelated writes to `$ae_loc` (e.g. SWR config reload)
|
|
||||||
fired the effect. In Svelte 5, any read of a Svelte 4 store inside `$effect` subscribes
|
|
||||||
to the whole store. Scope what you read carefully.
|
|
||||||
|
|
||||||
5. **`file_purpose == 'admin'` not hidden in Launcher** — the `hide_draft` prop hid
|
|
||||||
`outline` and `draft` files but not `admin` files. Gaps like this happen when a new
|
|
||||||
enum value is added to a field without auditing all the places that filter on it.
|
|
||||||
|
|
||||||
6. **Deleting files with `rm`** — always move to `~/tmp/agents_trash`. A deleted file may
|
|
||||||
contain context that's not recoverable from git if it was gitignored.
|
|
||||||
|
|
||||||
8. **Dexie `.get()` with a string object ID returns `undefined`** — Dexie `.get(value)`
|
|
||||||
looks up by the table's **primary key**, which is `id` (the first schema field). The V3
|
|
||||||
API never returns `id`, so it is always `undefined` in stored records. Passing a string
|
|
||||||
object ID (e.g. `person_id`) to `.get()` will silently return nothing. Always use
|
|
||||||
`.where('person_id').equals(person_id).first()` instead. This has caused liveQuery
|
|
||||||
blocks to always produce `undefined` even when the record exists in Dexie.
|
|
||||||
|
|
||||||
9. **Treating `$effect` blocks as auth bypass risks** — a `$effect` inside a child
|
|
||||||
component cannot bypass a parent `+layout.svelte` auth gate. Children only mount if
|
|
||||||
the parent calls `{@render children?.()}`. Adding redundant auth guards to `$effect`
|
|
||||||
blocks that can only run after the parent gate already passed is unnecessary — and
|
|
||||||
misleads future readers into thinking the parent gate is not sufficient on its own.
|
|
||||||
The **real** pre-gate risk is `+page.ts` / `+layout.ts`: universal load functions run
|
|
||||||
before any layout mounts and also fire during SvelteKit link prefetch. Keep those files
|
|
||||||
clean of data loads in private modules. See `GUIDE__SvelteKit2_Svelte5_DexieJS.md` →
|
|
||||||
"SvelteKit Layout Hierarchy: Security and Execution Order" for the full explanation.
|
|
||||||
|
|
||||||
10. **Using query `key` as a proxy for bypass stripped `x-account-id`** — this caused
|
|
||||||
valid account-scoped requests to lose account context and 403. `key` can be a valid
|
|
||||||
endpoint/business param, but it is not equivalent to `x-no-account-id: bypass`. Keep
|
|
||||||
`x-no-account-id` usage narrow and temporary; do not expand it without a documented
|
|
||||||
allowlist case.
|
|
||||||
|
|
||||||
11. **Pre-stringifying `*_json` fields before passing to API wrappers** — the API wrappers
|
|
||||||
(`api_post__crud_obj.ts` for V3, `api.ts` for legacy CRUD) automatically serialize any
|
|
||||||
field ending in `_json` (e.g. `cfg_json`, `data_json`). Pass these as plain JS objects.
|
|
||||||
Pre-stringifying with `JSON.stringify()` before calling the wrapper will double-encode
|
|
||||||
the value in the legacy path (stringify sees a string and escapes it), and is at best
|
|
||||||
redundant on the V3 path. Both paths now pretty-print with 2-space indent.
|
|
||||||
See `GUIDE__AE_API_V3_for_Frontend.md` → section 3C for the full explanation.
|
|
||||||
|
|
||||||
12. **Broad Dexie result windows get silently clipped** — if a broad "All" view shows fewer
|
|
||||||
rows than a narrower filter, check for a page-level limit or an API revalidation step
|
|
||||||
replacing the local IDB result set. For empty text searches, the full local result set
|
|
||||||
should drive the display; server refreshes should update cache, not shrink visibility.
|
|
||||||
|
|
||||||
13. **Not bumping `IDB_CONTENT_VERSIONS` when changing `properties_to_save`** — this caused
|
|
||||||
the IDAA Recovery Meetings "no meetings found" bug for approximately one year (2025–2026).
|
|
||||||
|
|
||||||
**What happened:** A deploy changed `properties_to_save` in `ae_events__event.ts`, but no
|
|
||||||
one bumped `IDB_CONTENT_VERSIONS.events.event` in `store_versions.ts`. Existing users kept
|
|
||||||
the old stale event records in IndexedDB indefinitely. On the Recovery Meetings page, the
|
|
||||||
fast path (IDB search) returned those stale records, which all failed the `account_id`
|
|
||||||
filter and returned 0 results. The API call then either errored silently or was filtered
|
|
||||||
to 0 by the secondary client-side filter. Critically, the error state and the genuinely
|
|
||||||
empty state showed the **same** "No meetings found" message — users and staff had no
|
|
||||||
indication a failure had occurred. The manual Full Reset (via the `?` help panel) always
|
|
||||||
fixed it, but no one knew why it worked, making the root cause impossible to track down.
|
|
||||||
|
|
||||||
**The fix (2026-05-16):** `check_and_clear_idb_table()` in `store_versions.ts` is now
|
|
||||||
wired in `src/routes/idaa/(idaa)/+layout.svelte` for `db_events.event`. On a version
|
|
||||||
match it costs one localStorage read. On a mismatch it silently clears the table; the
|
|
||||||
SWR pattern then repopulates from the API on next load.
|
|
||||||
|
|
||||||
**The rule going forward:**
|
|
||||||
- When you change `properties_to_save` in any `ae_events__*.ts` file (or any other
|
|
||||||
object file) in a way that makes existing cached records stale — fields added, removed,
|
|
||||||
renamed, or where a computed field's behavior changes — **bump the matching entry in
|
|
||||||
`IDB_CONTENT_VERSIONS` in `src/lib/stores/store_versions.ts`**.
|
|
||||||
- If the table is not yet wired, wire it first (see the wiring instructions in the
|
|
||||||
`IDB_CONTENT_VERSIONS` comment block in `store_versions.ts`).
|
|
||||||
- Currently wired: `events.event`. All other tables are not yet wired.
|
|
||||||
|
|
||||||
**Also:** Never show the same UI message for both a failed API call and a genuinely empty
|
|
||||||
result. Always distinguish `qry__status === 'error'` from `qry__status === 'done'` with
|
|
||||||
0 results in your templates. Silent failures look like data problems and are extremely
|
|
||||||
difficult to diagnose.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Source Layout (Quick Reference)
|
## 8. Source Layout (Quick Reference)
|
||||||
|
|
||||||
```
|
```text
|
||||||
src/lib/
|
src/lib/
|
||||||
ae_api/ — API helpers (V3 preferred)
|
ae_api/ — API helpers (V3 preferred)
|
||||||
ae_core/ — Account, User, Person, Site, hosted files
|
ae_core/ — Account, User, Person, Site, hosted files
|
||||||
@@ -404,12 +339,23 @@ Start here, then go deeper as needed:
|
|||||||
| What you need | Read |
|
| What you need | Read |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Active tasks + known bugs | `documentation/TODO__Agents.md` ← always first |
|
| Active tasks + known bugs | `documentation/TODO__Agents.md` ← always first |
|
||||||
|
| Documentation index | `documentation/README__Docs_Index.md` |
|
||||||
| Dev workflow + commit rules | `documentation/GUIDE__Development.md` |
|
| Dev workflow + commit rules | `documentation/GUIDE__Development.md` |
|
||||||
| V3 API reference | `documentation/GUIDE__AE_API_V3_for_Frontend.md` |
|
| V3 API reference | `documentation/GUIDE__AE_API_V3_for_Frontend.md` |
|
||||||
|
| WebSockets / real-time updates | `documentation/GUIDE__AE_API_V3_for_Frontend_websockets.md` |
|
||||||
| Dexie / liveQuery patterns | `documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md` |
|
| Dexie / liveQuery patterns | `documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md` |
|
||||||
|
| Common mistakes reference | `documentation/REFERENCE__Common_Agent_Mistakes.md` |
|
||||||
| Svelte 5 patterns + pitfalls | `documentation/GEMINI__Svelte_and_Me.md` |
|
| Svelte 5 patterns + pitfalls | `documentation/GEMINI__Svelte_and_Me.md` |
|
||||||
| Permissions + auth levels | `documentation/AE__Permissions_and_Security.md` |
|
| Permissions + auth levels | `documentation/AE__Permissions_and_Security.md` |
|
||||||
| Electron / native launcher | `documentation/PROJECT__AE_Events_Launcher_Native_integration.md` |
|
| Electron / native launcher | `documentation/MODULE__AE_Events_Launcher_Native.md` |
|
||||||
| Store migration plan | `documentation/PROJECT__Stores_Svelte5_Migration.md` |
|
| Store migration plan | `documentation/PROJECT__Stores_Svelte5_Migration.md` |
|
||||||
| Exhibitor Leads module | `documentation/MODULE__AE_Events_Exhibitor_Leads.md` |
|
| Journals module overview | `documentation/MODULE__AE_Journals.md` |
|
||||||
|
| Journals settings map | `documentation/MODULE__AE_Journals_Config_Map.md` |
|
||||||
|
| Exhibitor Leads module | `documentation/MODULE__AE_Events_Leads.md` |
|
||||||
|
| Presentation Management module | `documentation/MODULE__AE_Events_Presentation_Management.md` |
|
||||||
|
| IDAA client architecture | `documentation/CLIENT__IDAA_and_customized_mods.md` |
|
||||||
|
| IDAA Archives module | `documentation/MODULE__AE_IDAA_Archives.md` |
|
||||||
|
| IDAA Bulletin Board module | `documentation/MODULE__AE_IDAA_Bulletin_Board.md` |
|
||||||
|
| IDAA Recovery Meetings module | `documentation/MODULE__AE_IDAA_Recovery_Meetings.md` |
|
||||||
|
| IDAA Video Conferences module | `documentation/MODULE__AE_IDAA_Video_Conferences.md` |
|
||||||
| Naming conventions | `documentation/AE__Naming_Conventions.md` |
|
| Naming conventions | `documentation/AE__Naming_Conventions.md` |
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ IDAA is a private membership organization for physicians in recovery. They use t
|
|||||||
|
|
||||||
IDAA's Aether instance is embedded as an **iframe inside their existing Novi-powered website** (`idaa.org`). Novi is their external Association Management System (AMS) — it handles membership records and authentication. Aether receives the member context via URL parameters on iframe load.
|
IDAA's Aether instance is embedded as an **iframe inside their existing Novi-powered website** (`idaa.org`). Novi is their external Association Management System (AMS) — it handles membership records and authentication. Aether receives the member context via URL parameters on iframe load.
|
||||||
|
|
||||||
|
### Breakout Links and Iframe Persistence
|
||||||
|
|
||||||
|
Members often need to open Jitsi meetings outside the Novi iframe (e.g., for full-screen features or on mobile). These are referred to as **Breakout Links**.
|
||||||
|
|
||||||
|
- **The Problem:** SvelteKit client-side navigation within the iframe often drops "bootstrap" query parameters like `?key=...` (site access key) and `?uuid=...` (Novi identity token).
|
||||||
|
- **The Requirement:** When a member breaks out of the iframe into a new browser tab, these keys **must** be present in the URL. Without them, the member will hit the site-domain gate or the IDAA auth gate and see "Access Denied."
|
||||||
|
- **The Solution:** The Video Conferences page uses a derived `breakout_url` that proactively re-injects the missing `key` (from `$ae_loc.allow_access`) and `uuid` (from `$idaa_loc.novi_uuid`) before generating the external link.
|
||||||
|
|
||||||
|
**Example Breakout URL:**
|
||||||
|
`https://client.oneskyit.com/idaa/video_conferences?uuid=...&key=...&room=...`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Architecture: Composite Module
|
## Architecture: Composite Module
|
||||||
@@ -446,6 +457,32 @@ filtered) replaces it after the background refresh completes. The IDB path does
|
|||||||
- Add `contact_li_json_ext` to the IDB fast-path filter in `search__event()` and the recovery
|
- Add `contact_li_json_ext` to the IDB fast-path filter in `search__event()` and the recovery
|
||||||
meetings page so contact matches appear instantly from cache, not only after API refresh.
|
meetings page so contact matches appear instantly from cache, not only after API refresh.
|
||||||
|
|
||||||
|
### Sort Encoding — Events Use Legacy (Not `build_tmp_sort`)
|
||||||
|
|
||||||
|
`ae_events__event.ts` builds `tmp_sort_1` with the **legacy encoding**: `priority ? 1 : 0`
|
||||||
|
(priority=true → `'1'`). This is the **opposite** of `build_tmp_sort` (priority=true → `'0'`).
|
||||||
|
|
||||||
|
| Module | Encoding | Correct comparator |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ae_events__event.ts` (Recovery Meetings) | Legacy: `priority=true→'1'` | **Descending** `b.localeCompare(a)` |
|
||||||
|
| `ae_events__event_session.ts` | Legacy: `priority=true→'1'` | **Descending** `b.localeCompare(a)` |
|
||||||
|
| `ae_events__event_presentation.ts` | `build_tmp_sort` (overrides legacy in `specific_processor`) | **Ascending** `a.localeCompare(b)` |
|
||||||
|
| Journals, Posts, Archives | `build_tmp_sort` | **Ascending** `a.localeCompare(b)` |
|
||||||
|
|
||||||
|
**Do not apply the `build_tmp_sort` ascending rule to raw event or session sorts** until
|
||||||
|
`ae_events__event.ts` is migrated (tracked in TODO__Agents.md under IDB Sort rollout).
|
||||||
|
|
||||||
|
### Search Trigger — Use `$slct.account_id`, Not `$ae_loc.account_id`
|
||||||
|
|
||||||
|
The recovery meetings search `$effect` gates on `$slct.account_id` (set only by the bootstrap
|
||||||
|
Sync Effect, non-persisted). Do NOT change this back to `$ae_loc.account_id`.
|
||||||
|
|
||||||
|
**Why:** `$ae_loc` is a persisted store that hydrates from localStorage on page load. Its
|
||||||
|
`account_id` may be stale from a previous session (e.g., a dev/demo account_id left behind).
|
||||||
|
Using it as the gate fires the API call with the wrong account before bootstrap has run,
|
||||||
|
producing either a 403 or wrong-account data. `$slct.account_id` is null until bootstrap
|
||||||
|
sets it — a reliable gate. See mistake #14 in `BOOTSTRAP__AI_Agent_Quickstart.md`.
|
||||||
|
|
||||||
### My Meetings (Favorites)
|
### My Meetings (Favorites)
|
||||||
|
|
||||||
Members can star meetings to build a personal "My Meetings" list. The star toggle appears:
|
Members can star meetings to build a personal "My Meetings" list. The star toggle appears:
|
||||||
@@ -862,4 +899,4 @@ ae_loc.idaa_loc = { novi_uuid: 'test-uuid-value', ... };
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Document Status:** ✅ Current
|
**Document Status:** ✅ Current
|
||||||
**Last Verified:** 2026-05-19 — Access Gate: documented new `verify_error_type` error-handling states and retry/reset UI; Search Architecture: corrected contact-search status (now works via `default_qry_str` in API path — two root causes fixed 2026-05-18/19); noted IDB fast-path gap as remaining enhancement
|
**Last Verified:** 2026-06-03 — Recovery Meetings: documented legacy `tmp_sort_1` encoding for events (requires descending sort, not ascending); documented `$slct.account_id` gate pattern for search trigger; noted service worker `skipWaiting`/`clients.claim` requirement for long-lived IDAA iframe sessions (root cause of user-reported loading failures that could not be reproduced in dev)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Aether API V3 Frontend Integration Guide (Svelte/TypeScript)
|
# Aether API V3 Frontend Integration Guide (Svelte/TypeScript)
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
This guide defines the standards for interacting with the **Aether API V3 CRUD** and **Action** endpoints.
|
This guide defines the standards for interacting with the **Aether API V3 CRUD** and **Action** endpoints.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -96,6 +98,26 @@ The primary way to retrieve data.
|
|||||||
* **Endpoint:** `POST /v3/crud/{obj_type}/search`
|
* **Endpoint:** `POST /v3/crud/{obj_type}/search`
|
||||||
* **Security:** Automatically filters results to only show records belonging to your `x-account-id`. If no account context is provided, it will return **0 records** for private objects.
|
* **Security:** Automatically filters results to only show records belonging to your `x-account-id`. If no account context is provided, it will return **0 records** for private objects.
|
||||||
|
|
||||||
|
#### Sorting with `order_by_li`
|
||||||
|
|
||||||
|
Pass a JSON object as the `order_by_li` query parameter to sort results:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ?order_by_li={"filename":"ASC","created_on":"DESC"}
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
order_by_li: JSON.stringify({ filename: 'ASC', created_on: 'DESC' })
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **`order_by_li` only accepts columns from the raw base table** — not view-only join columns.
|
||||||
|
>
|
||||||
|
> Some object types (e.g. `event_file`) have enriched views that JOIN other tables to expose convenience fields like `event_presenter_family_name`. These are available in search results when using `?view=alt`, but they **cannot** be used in `order_by_li`. Attempting to sort by them silently drops those sort keys (the query proceeds without them).
|
||||||
|
>
|
||||||
|
> If you need to sort by a joined field, sort client-side on the returned list.
|
||||||
|
>
|
||||||
|
> **Columns safe to sort on for `event_file`:** any field in the `event_file` table itself — `filename`, `title`, `extension`, `created_on`, `updated_on`, `sort`, `enable`, etc.
|
||||||
|
|
||||||
### C. POST Create / PATCH Update
|
### C. POST Create / PATCH Update
|
||||||
Modify data in the system.
|
Modify data in the system.
|
||||||
* **Endpoints:**
|
* **Endpoints:**
|
||||||
@@ -266,7 +288,7 @@ When seeding new lookup data (e.g., adding timezones in bulk):
|
|||||||
|
|
||||||
## 5. Event File Data Retrieval (Hosted Files)
|
## 5. Event File Data Retrieval (Hosted Files)
|
||||||
|
|
||||||
Every Event File (`event_file`) **must** have a linked Hosted File (`hosted_file`). The Hosted File itself is a metadata record for binary content (files), which is accessed via separate Action endpoints (e.g., `/v3/action/hosted_file/download`). This API endpoint provides metadata about the associated hosted file. To retrieve this additional metadata:
|
Every Event File (`event_file`) **must** have a linked Hosted File (`hosted_file`). The Hosted File is a metadata record for binary content, accessed via dedicated Action endpoints. To download an event file use `/v3/action/event_file/{event_file_id}/download` — not the hosted_file endpoint directly (each endpoint only accepts its own ID type). To retrieve hosted file metadata alongside an event file record:
|
||||||
|
|
||||||
* **Endpoint:** `GET /v3/crud/event_file/{event_file_id}`
|
* **Endpoint:** `GET /v3/crud/event_file/{event_file_id}`
|
||||||
* **Query Parameter:** Add `inc_hosted_file=true`
|
* **Query Parameter:** Add `inc_hosted_file=true`
|
||||||
@@ -281,6 +303,48 @@ Every Event File (`event_file`) **must** have a linked Hosted File (`hosted_file
|
|||||||
* `hosted_file_size` (string - in bytes)
|
* `hosted_file_size` (string - in bytes)
|
||||||
2. **Nested Hosted File Object:** A full `hosted_file` object will be nested under the `hosted_file` key. This object (`Hosted_File_Base` model) will contain all its standard fields, including `id` (random string ID), `hash_sha256`, `content_type`, `size`, etc.
|
2. **Nested Hosted File Object:** A full `hosted_file` object will be nested under the `hosted_file` key. This object (`Hosted_File_Base` model) will contain all its standard fields, including `id` (random string ID), `hash_sha256`, `content_type`, `size`, etc.
|
||||||
|
|
||||||
|
### Direct Download Links (Shareable / External)
|
||||||
|
|
||||||
|
Event files can be downloaded without standard auth headers using one of two bypass mechanisms. This is useful for generating shareable links for staff or external recipients.
|
||||||
|
|
||||||
|
- **Method:** `GET`
|
||||||
|
- **Path:** `/v3/action/event_file/{event_file_id}/download`
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> **Breaking change (2026-06-10):** This endpoint now requires an `event_file_id`. Previously it accepted `hosted_file_id` or `archive_content_id` and resolved the chain automatically — that cross-resolution has been removed. Pass the correct ID type for the endpoint you are calling. If you were routing downloads through `/v3/action/hosted_file/{hosted_file_id}/download` as a workaround, switch to this endpoint using `event_file_id`. *(Remove this note after ~2026-06-24.)*
|
||||||
|
|
||||||
|
#### Auth bypass options
|
||||||
|
|
||||||
|
| Query param | Value | When to use |
|
||||||
|
|---|---|---|
|
||||||
|
| `?key=<account_id_random>` | Any valid account random ID | Staff sharing within a known account context |
|
||||||
|
| `?site_key=<site_access_key>` | The site's `access_key` value | Public or semi-public distribution tied to a specific site |
|
||||||
|
|
||||||
|
Either param replaces the need for `x-aether-api-key` / `x-account-id` headers, so the URL is self-contained and works in a plain browser tab or `<a href>` link.
|
||||||
|
|
||||||
|
#### Optional params
|
||||||
|
|
||||||
|
| Query param | Description |
|
||||||
|
|---|---|
|
||||||
|
| `filename` | Override the download filename (min 4 chars). Useful for giving files clean display names. |
|
||||||
|
|
||||||
|
#### Building a shareable link
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Build a self-contained download URL for staff/external use
|
||||||
|
function makeDownloadUrl(eventFileId: string, accountId: string, displayName?: string): string {
|
||||||
|
const base = `https://dev-api.oneskyit.com/v3/action/event_file/${eventFileId}/download`;
|
||||||
|
const params = new URLSearchParams({ key: accountId });
|
||||||
|
if (displayName) params.set('filename', displayName);
|
||||||
|
return `${base}?${params}`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The endpoint supports byte-range requests (`Range` header), so it works correctly for in-browser media streaming as well as direct file downloads.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The `?key=` bypass verifies only that the account ID exists — it does not confirm the file belongs to that account. It is appropriate for internal staff tools. For publicly distributed links, prefer `?site_key=` which ties access to a specific site's configured key.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Hosted File Actions: Convert & Clip (Frontend Notes)
|
## 6. Hosted File Actions: Convert & Clip (Frontend Notes)
|
||||||
@@ -301,18 +365,16 @@ These helper endpoints let the frontend request small server-side transformation
|
|||||||
- Required query params: `link_to_type`, `link_to_id`, `start_time`, `end_time` (format `HH:MM:SS`)
|
- Required query params: `link_to_type`, `link_to_id`, `start_time`, `end_time` (format `HH:MM:SS`)
|
||||||
- Optional query params: `filename_no_ext` (defaults to `automated_hosted_file_clip_video`), `reencode` (bool), `scale_down` (bool)
|
- Optional query params: `filename_no_ext` (defaults to `automated_hosted_file_clip_video`), `reencode` (bool), `scale_down` (bool)
|
||||||
- Auth: standard V3 headers
|
- Auth: standard V3 headers
|
||||||
- Behavior: extracts a clip using `ffmpeg` and saves it as a new `hosted_file`. Defaults to stream-copying to be fast; set `reencode=true` to force H.264 or `scale_down=true` to resize. Returns 400 on failure.
|
- Behavior: extracts a clip using `ffmpeg` and saves it as a new `hosted_file`.
|
||||||
- Behavior: extracts a clip using `ffmpeg` and saves it as a new `hosted_file`.
|
- Defaults to stream-copying (fast); set `reencode=true` to force H.264 or `scale_down=true` to resize.
|
||||||
- Defaults to stream-copying to be fast; set `reencode=true` to force H.264 or `scale_down=true` to resize.
|
- Add `?background=true` to schedule the clip asynchronously — returns `202 Accepted` immediately; poll the `hosted_file` record for completion.
|
||||||
- For longer-running clips you can schedule the job in the background by adding `?background=true`. When scheduled the API returns `202 Accepted` and the clip runs asynchronously on the server; check the returned `hosted_file` record later via the standard V3 `hosted_file` endpoints.
|
- Returns 400 on synchronous failure; 202 when scheduled successfully.
|
||||||
- Returns 400 on synchronous failure; returns 202 when scheduled successfully.
|
|
||||||
|
|
||||||
Frontend guidance:
|
Frontend guidance:
|
||||||
|
|
||||||
- Call these routes with the same `link_to_type` / `link_to_id` you plan to associate the resulting hosted_file with — the server resolves random IDs for you.
|
- Call these routes with the same `link_to_type` / `link_to_id` you plan to associate the resulting hosted_file with — the server resolves random IDs for you.
|
||||||
- After a successful response, use the V3 `hosted_file` action endpoints (download/delete) to manage or retrieve the new file.
|
- After a successful response, use the V3 `hosted_file` action endpoints (download/delete) to manage or retrieve the new file.
|
||||||
- These endpoints run synchronously and can take time for large inputs; for heavy or batch workloads use a queued job pattern instead.
|
- Prefer `?background=true` for large inputs to avoid request timeouts. For heavy or batch workloads use a queued job pattern instead.
|
||||||
- These endpoints may take time for large inputs. Prefer using `?background=true` to schedule work and receive a `202 Accepted` response for async processing. For heavy or batch workloads use a queued job pattern instead.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -673,19 +735,19 @@ Verifies a Novi AMS member UUID by proxying the Novi API call through the Aether
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `404` | UUID not found in Novi, or Novi returned 200 with no identity data (empty-member anti-pattern — member may have just joined) | Treat as denied / not a member |
|
| `404` | UUID not found in Novi, or Novi returned 200 with no identity data (empty-member anti-pattern — member may have just joined) | Treat as denied / not a member |
|
||||||
| `429` | Novi rate limit hit | Surface as `'rate_limited'`; advise retry |
|
| `429` | Novi rate limit hit | Surface as `'rate_limited'`; advise retry |
|
||||||
| `503` | Novi unreachable or Novi 5xx error | Auto-retry once after 3s; if retry also fails, surface as `'api_error'` |
|
| `503` | Novi unreachable or Novi 5xx error | Surface as `'api_error'`; advise retry |
|
||||||
|
|
||||||
### Migration from direct Novi call — ✅ Complete (2026-05-19)
|
### Migration from direct Novi call
|
||||||
|
|
||||||
`+layout.svelte:verify_novi_uuid()` now calls this endpoint instead of Novi directly. Response code mapping (for reference):
|
The frontend's `+layout.svelte:verify_novi_uuid()` currently calls Novi directly from the browser. Replace that `fetch()` with this endpoint. Response code mapping:
|
||||||
|
|
||||||
| Direct Novi result | This endpoint returns | Frontend behavior |
|
| Direct Novi result | This endpoint returns | Frontend state |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `200` with identity data | `200` | `verified` |
|
| `200` with identity data | `200` | `verified` |
|
||||||
| `200` with no identity data | `404` | `denied` |
|
| `200` with no identity data | `404` | `denied` |
|
||||||
| `404` | `404` | `denied` |
|
| `404` | `404` | `denied` |
|
||||||
| `429` | `429` | Auto-retry after 10s; `'rate_limited'` if retry fails |
|
| `429` | `429` | `'rate_limited'` |
|
||||||
| Network error / Novi 5xx | `503` | Auto-retry after 3s; `'api_error'` if retry fails |
|
| Network error / Novi 5xx | `503` | `'api_error'` |
|
||||||
|
|
||||||
### Caching
|
### Caching
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Aether API V3 WebSocket Integration Guide
|
# Aether API V3 WebSocket Integration Guide
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
This guide explains how to implement real-time communication using the **Aether API V3 WebSocket** protocol. V3 introduces granular routing, strict message schemas, and improved multi-tenant isolation compared to previous versions.
|
This guide explains how to implement real-time communication using the **Aether API V3 WebSocket** protocol. V3 introduces granular routing, strict message schemas, and improved multi-tenant isolation compared to previous versions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
# Aether Events — Onsite Badge Printing
|
# Aether Events — Onsite Badge Printing
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
Notes on setup, process, hardware, and browser behavior for onsite badge printing at events.
|
Notes on setup, process, hardware, and browser behavior for onsite badge printing at events.
|
||||||
|
|
||||||
|
For cross-module onsite operations (SRR, launcher monitoring, exhibitor leads support), see:
|
||||||
|
`documentation/GUIDE__AE_Events_Onsite_Runbook.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@@ -157,26 +162,58 @@ This layout hides `.badge_back` in `@media print` — only the front face prints
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Epson — Fan-Fold / Label Printer
|
### Epson ColorWorks C3500 — Fan-Fold Label Printer
|
||||||
|
|
||||||
**Status:** Not yet tested. Section to be filled in after testing.
|
**Card stock:** 4" × 6" fan-fold paper label stock
|
||||||
|
**Layout code:** `badge_4x6_fanfold`
|
||||||
|
**Status:** Configured. First live use: Axonius Adapt DC — June 9, 2026.
|
||||||
|
|
||||||
Common Epson models used for fan-fold name badge stock: TM-T88 series, C3500, LX series.
|
The C3500 is a color inkjet label printer — it prints continuous fan-fold paper stock,
|
||||||
Fan-fold stock is typically 4" × 3" or 4" × 6" paper labels.
|
not individual cards. Badges are separated along the perforation after printing.
|
||||||
|
|
||||||
|
#### Physical Setup
|
||||||
|
|
||||||
|
- Connect via USB or Ethernet
|
||||||
|
- Load 4" × 6" fan-fold stock per Epson instructions
|
||||||
|
- The C3500 is single-sided — only the front face prints. Back section is suppressed in CSS.
|
||||||
|
- The badge has a lanyard hole punch: 5/8" × 1/8", centered, 1/4" from the top.
|
||||||
|
Most fan-fold stock for badge use includes a pre-punched lanyard slot — verify stock matches.
|
||||||
|
|
||||||
|
#### Driver
|
||||||
|
|
||||||
|
- Epson ColorWorks C3500 CUPS driver available from epson.com (ColorWorks section)
|
||||||
|
- On Linux/CUPS: install the provided PPD and add the printer at `http://localhost:631`
|
||||||
|
- Set default paper size to **4" × 6"** in CUPS
|
||||||
|
- Print a test page from CUPS before going live
|
||||||
|
|
||||||
|
#### Chrome Print Settings (C3500)
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
|---|---|
|
||||||
|
| Destination | Epson C3500 (CUPS name) |
|
||||||
|
| Paper size | 4 × 6 in (set in CUPS driver) |
|
||||||
|
| Margins | **None** |
|
||||||
|
| Background graphics | On |
|
||||||
|
| Pages | 1 (single-sided) |
|
||||||
|
|
||||||
#### CSS Layout
|
#### CSS Layout
|
||||||
|
|
||||||
Fan-fold badges would use a layout sized to the specific label stock.
|
The C3500 uses the `badge_4x6_fanfold` layout. CSS file:
|
||||||
A new CSS layout file will need to be created per stock size if not already present.
|
`src/lib/ae_events/badges/css/badge_layout_epson_4x6_fanfold.css`
|
||||||
Naming convention: `badge_layout_epson_[model]_[size].css`
|
|
||||||
|
|
||||||
#### Setup Notes
|
Created 2026-05-15 for Axonius Adapt DC. Key specs:
|
||||||
|
- `badge_front` 4" × 6", portrait orientation
|
||||||
*(To be filled in after testing — cover: driver source, CUPS setup, paper size, Chrome settings)*
|
- `badge_header` max-height 1.5in
|
||||||
|
- Lanyard hole: 5/8" × 1/8", centered, 1/4" from top
|
||||||
|
- `@page { size: 4in 6in; margin: 0; }` set in the print page dynamically
|
||||||
|
- `.badge_back` suppressed in `@media print` (single-sided)
|
||||||
|
|
||||||
#### Known Behaviors
|
#### Known Behaviors
|
||||||
|
|
||||||
*(To be filled in after testing)*
|
- Same Chrome margin rules apply: **Margins → None** prevents URL/date header clipping
|
||||||
|
- Firefox honors `@page { size: 4in 6in }` for PDF proofing — use it to verify layout
|
||||||
|
- Fan-fold stock separates along the perforation — no cutting needed, but verify the
|
||||||
|
perforation lands outside the badge content area
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
138
documentation/GUIDE__AE_Events_Onsite_Runbook.md
Normal file
138
documentation/GUIDE__AE_Events_Onsite_Runbook.md
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# Guide — Aether Events: Onsite Runbook
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
This runbook keeps badge guidance concise for onsite flow. For detailed printer/browser setup,
|
||||||
|
driver notes, and troubleshooting matrix, use:
|
||||||
|
`documentation/GUIDE__AE_Events_Badges_Onsite.md`.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
- **Layout code:** `badge_3.5x5.5_pvc`
|
||||||
|
|
||||||
|
### Printer Reference: Epson ColorWorks C3500 (Fan-Fold)
|
||||||
|
- **Stock:** 4" × 6" fan-fold paper label stock.
|
||||||
|
- **Single-Sided:** Only the front face prints; the back section is hidden via CSS.
|
||||||
|
- **Layout code:** `badge_4x6_fanfold`
|
||||||
|
- **Lanyard hole:** Pre-punched 5/8" × 1/8" slot at top center — verify stock matches.
|
||||||
|
- **First live use:** Axonius Adapt DC, June 9, 2026.
|
||||||
|
|
||||||
|
### 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,8 +1,10 @@
|
|||||||
# Aether UI — Design System Style Guidelines
|
# Aether UI — Design System Style Guidelines
|
||||||
> **Version:** 1.2 (2026-03-20)
|
> **Version:** 1.2 (2026-03-20)
|
||||||
|
> **Last Updated:** 2026-03-20
|
||||||
> **Author:** One Sky IT / Scott Idem
|
> **Author:** One Sky IT / Scott Idem
|
||||||
> **Scope:** All Aether SvelteKit frontend components
|
> **Scope:** All Aether SvelteKit frontend components
|
||||||
> **Related:** `AE__UI_Component_Patterns.md`, `ae-firefly.css`, `documentation/AE__Components.md`
|
> **Related:** `ae-firefly.css`, `documentation/MODULE__AE_Journals.md`, `documentation/MODULE__AE_Events_Presentation_Management.md`
|
||||||
|
> **Historical implementation log:** `documentation/archive/PROJECT__AE_Style_Review_2026-03.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Aether Development SOP (Frontend)
|
# Aether Development SOP (Frontend)
|
||||||
> **Version:** 1.2 (2026-03-17)
|
> **Version:** 1.2 (2026-03-17)
|
||||||
|
> **Last Updated:** 2026-03-17
|
||||||
> **Location:** documentation/GUIDE__Development.md
|
> **Location:** documentation/GUIDE__Development.md
|
||||||
|
|
||||||
## 1. Verification (The "Test-First" Mandate)
|
## 1. Verification (The "Test-First" Mandate)
|
||||||
@@ -50,7 +51,7 @@ You are not working in a vacuum. Coordinate with the Backend Agent via MCP tools
|
|||||||
| `documentation/GEMINI__Svelte_and_Me.md` | Svelte 5 runes patterns |
|
| `documentation/GEMINI__Svelte_and_Me.md` | Svelte 5 runes patterns |
|
||||||
| `documentation/AE__Architecture.md` | System architecture overview |
|
| `documentation/AE__Architecture.md` | System architecture overview |
|
||||||
| `documentation/AE__Naming_Conventions.md` | Naming rules |
|
| `documentation/AE__Naming_Conventions.md` | Naming rules |
|
||||||
| `documentation/PROJECT__AE_Events_Launcher_Native_integration.md` | Electron/Launcher reference |
|
| `documentation/MODULE__AE_Events_Launcher_Native.md` | Electron/Launcher reference |
|
||||||
| `tests/README.md` | Playwright test guide — shared helpers, hard-won lessons, demo IDs |
|
| `tests/README.md` | Playwright test guide — shared helpers, hard-won lessons, demo IDs |
|
||||||
|
|
||||||
## 6. Inline Field Editing — `element_ae_obj_field_editor`
|
## 6. Inline Field Editing — `element_ae_obj_field_editor`
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
# AE Docker CI Cache Policy (recommendation)
|
# Aether Docker CI Cache Policy
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
Purpose
|
Purpose
|
||||||
- Provide a straightforward policy to keep build caches useful but bounded.
|
- Provide a straightforward policy to keep build caches useful but bounded.
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Stability Patterns for liveQuery + Svelte 5
|
# Stability Patterns for liveQuery + Svelte 5
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
Dexie's `liveQuery` works well with Svelte 5 runes, but the combination requires a few stable patterns so queries don't get recreated unintentionally and components render correctly on a "cold start" (empty IndexedDB).
|
Dexie's `liveQuery` works well with Svelte 5 runes, but the combination requires a few stable patterns so queries don't get recreated unintentionally and components render correctly on a "cold start" (empty IndexedDB).
|
||||||
- Keep the observable instance stable: wrap `liveQuery` in a stable `$derived` so the observable isn't recreated on every render. Recreate the `liveQuery` only when explicit dependencies change (IDs, filters, or search keys).
|
- Keep the observable instance stable: wrap `liveQuery` in a stable `$derived` so the observable isn't recreated on every render. Recreate the `liveQuery` only when explicit dependencies change (IDs, filters, or search keys).
|
||||||
|
|
||||||
@@ -25,13 +27,54 @@ let lq__obj = $derived(
|
|||||||
|
|
||||||
- Cold start (IDB empty) + non-blocking API writes: If you mount a component before data is written to IDB, `liveQuery` may run against an empty DB. The API write will populate IDB later, but sometimes a chain of dependent queries (e.g., presentations -> presenters) won't all rerun in the order you expect. The symptoms you described — session shows after one refresh, presenters only after a second — are consistent with either (a) queries recreated in the wrong order or (b) dependent store values being set only after some subscriptions are already created.
|
- Cold start (IDB empty) + non-blocking API writes: If you mount a component before data is written to IDB, `liveQuery` may run against an empty DB. The API write will populate IDB later, but sometimes a chain of dependent queries (e.g., presentations -> presenters) won't all rerun in the order you expect. The symptoms you described — session shows after one refresh, presenters only after a second — are consistent with either (a) queries recreated in the wrong order or (b) dependent store values being set only after some subscriptions are already created.
|
||||||
|
|
||||||
|
### Bootstrap Race: Account-scoped Loads Before `account_id` Is Set (2026-06)
|
||||||
|
|
||||||
|
Account-scoped `liveQuery` triggers can fire before `+layout.svelte`'s bootstrap Sync Effect
|
||||||
|
has propagated the real `account_id`. Two failure modes:
|
||||||
|
|
||||||
|
1. **IDB empty:** fetch runs with `account_id = null`. The `localStorage` scavenge in
|
||||||
|
`api_get_object.ts` reads the stale value from a previous session — possibly a different
|
||||||
|
account — and caches that wrong record into IDB.
|
||||||
|
2. **IDB has a stale record:** `liveQuery` returns a cached record from a different account as
|
||||||
|
a valid hit, so the trigger condition (`!entry`) is never true and the correct record is
|
||||||
|
never fetched.
|
||||||
|
|
||||||
|
**Rule:** Gate any trigger `$effect` that loads account-scoped data on `$slct.account_id`,
|
||||||
|
not `$ae_loc.account_id`. `$slct` is a plain writable store (not persisted), initialized to
|
||||||
|
`null` and set _only_ by the bootstrap Sync Effect. `$ae_loc` is a persisted store that
|
||||||
|
hydrates from `localStorage` before effects run and may carry a stale `account_id`.
|
||||||
|
|
||||||
|
Also treat a non-null, non-matching `account_id` in an IDB record as a cache miss:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
$effect(() => {
|
||||||
|
const account_id = $slct.account_id; // null until bootstrap Sync Effect runs
|
||||||
|
const api_ready = !!$ae_api?.base_url;
|
||||||
|
const entry = $lq__obj as SomeType | null | undefined;
|
||||||
|
|
||||||
|
if (!browser || !account_id || !api_ready) return;
|
||||||
|
|
||||||
|
// null account_id on a record = global/shared fallback — still a valid hit.
|
||||||
|
const entry_is_stale_account =
|
||||||
|
entry !== undefined && entry !== null &&
|
||||||
|
entry.account_id !== null &&
|
||||||
|
entry.account_id !== account_id;
|
||||||
|
|
||||||
|
if (!entry || entry_is_stale_account) {
|
||||||
|
trigger = 'load...';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
See `BOOTSTRAP__AI_Agent_Quickstart.md` → Section 7, entry 14 for the full incident writeup.
|
||||||
|
|
||||||
### Critical Discovery (2026-02-26): The "try_cache: false" Bug
|
### Critical Discovery (2026-02-26): The "try_cache: false" Bug
|
||||||
|
|
||||||
**Symptom:** Nested data (e.g., Session → Presentations → Presenters) requires multiple manual refreshes to display on cold-start, even when using blocking loads.
|
**Symptom:** Nested data (e.g., Session → Presentations → Presenters) requires multiple manual refreshes to display on cold-start, even when using blocking loads.
|
||||||
|
|
||||||
**Root Cause:** Two interconnected issues in nested data loaders:
|
**Root Cause:** Two interconnected issues in nested data loaders:
|
||||||
1. **Disabled caching in nested loads**: Parent loads were passing `try_cache: false` to child loads, meaning presentations and presenters were fetched from API but **never written to IndexedDB**.
|
1. **Disabled caching in nested loads**: Parent loads were passing `try_cache: false` to child loads, meaning presentations and presenters were fetched from API but **never written to IndexedDB**.
|
||||||
2. **Missing microtask yields**: Even when caching was enabled, components would mount and subscribe to liveQuery *before* IndexedDB writes completed, causing race conditions.
|
2. **Missing microtask yields**: Even when caching was enabled, components would mount and subscribe to liveQuery _before_ IndexedDB writes completed, causing race conditions.
|
||||||
|
|
||||||
**Example of the Bug:**
|
**Example of the Bug:**
|
||||||
```typescript
|
```typescript
|
||||||
@@ -89,6 +132,99 @@ $effect(() => {
|
|||||||
|
|
||||||
- When you have chains (presentations depend on session; presenters depend on presentation.person_id), make the dependent liveQuery explicitly wait for the upstream ID and log inside each query to verify the order — adding a small `await Promise.resolve()` or `await 0` inside the `liveQuery` is sometimes useful during debugging to ensure the JS microtask queue has a chance to settle after DB writes.
|
- When you have chains (presentations depend on session; presenters depend on presentation.person_id), make the dependent liveQuery explicitly wait for the upstream ID and log inside each query to verify the order — adding a small `await Promise.resolve()` or `await 0` inside the `liveQuery` is sometimes useful during debugging to ensure the JS microtask queue has a chance to settle after DB writes.
|
||||||
|
|
||||||
|
## IDB Sort: `build_tmp_sort` Pattern (2026-05)
|
||||||
|
|
||||||
|
All Aether objects support `priority`, `sort`, `group`, and `name` fields. Rather than sorting in JS after a Dexie query (which requires `.reverse()` hacks and duplicated logic), pre-compute up to three `tmp_sort_*` string fields during the processing pipeline and store them in Dexie. Then `.sortBy('tmp_sort_2')` does the right thing in one call, with no `.reverse()`.
|
||||||
|
|
||||||
|
**Utility:** `src/lib/ae_core/core__idb_sort.ts` — `build_tmp_sort()`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
|
||||||
|
|
||||||
|
// Inside specific_processor callback:
|
||||||
|
const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({
|
||||||
|
prefix: [obj.group ?? '0'], // always first
|
||||||
|
priority: obj.priority, // boolean; true→'0' so ASC sorts it first
|
||||||
|
sort: obj.sort, // zero-padded to 8 chars
|
||||||
|
fields_1: [...], // module-specific tier-1 fields
|
||||||
|
fields_2: [...], // tier-2 fields (tmp_sort_2 = base + tier-1 + tier-2)
|
||||||
|
fields_3: [...] // tier-3 fields
|
||||||
|
});
|
||||||
|
obj.tmp_sort_1 = tmp_sort_1;
|
||||||
|
obj.tmp_sort_2 = tmp_sort_2;
|
||||||
|
obj.tmp_sort_3 = tmp_sort_3;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sort chain convention:** `group → priority DESC → sort ASC → [module-specific] → name`
|
||||||
|
|
||||||
|
**Priority encoding:** `priority ? '0' : '1'` — inverted so that `priority=true` sorts first in ascending order. This means:
|
||||||
|
- **Dexie `.sortBy('tmp_sort_*')`** — always call without `.reverse()` before it (Dexie ignores collection-level `.reverse()` when using `.sortBy()`). If descending is needed for non-tmp_sort fields, call `.reverse()` on the resulting array after `await`.
|
||||||
|
- **JS `.sort()` comparators** — use **ascending** `a.localeCompare(b)`, NOT `b.localeCompare(a)`. Using descending flips the priority encoding and puts `priority=false` items first.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ✅ Correct — ascending; priority=true ('0') sorts before priority=false ('1')
|
||||||
|
list.sort((a, b) => (a.tmp_sort_1 ?? '').localeCompare(b.tmp_sort_1 ?? ''));
|
||||||
|
|
||||||
|
// ❌ Wrong — descending inverts the encoding; priority=false ('1') sorts first
|
||||||
|
list.sort((a, b) => (b.tmp_sort_1 ?? '').localeCompare(a.tmp_sort_1 ?? ''));
|
||||||
|
```
|
||||||
|
|
||||||
|
**Modules using `build_tmp_sort`:**
|
||||||
|
- `ae_events__event_presentation.ts` — `tmp_sort_1/2`: group → priority → sort → start_datetime → code → name
|
||||||
|
- `ae_events__event.ts` — `tmp_sort_1/2/3`: group → priority → sort → name → updated_on (used by IDAA recovery meetings)
|
||||||
|
- `ae_journals__journal.ts` — `tmp_sort_1/2/3`: group → priority → sort → name → updated_on
|
||||||
|
- `ae_journals__journal_entry.ts` — same chain as journal
|
||||||
|
|
||||||
|
**Legacy encoding (not yet migrated to `build_tmp_sort`):** `ae_posts__post.ts`, `ae_posts__post_comment.ts`, `ae_archives__archive.ts`, `ae_archives__archive_content.ts`, `ae_sponsorships_functions.ts` use the opposite encoding (`priority ? '1' : '0'`, designed for descending sort). Their current route consumers sort by date/name so there is no visible priority bug today, but they must be migrated before any route starts sorting by `tmp_sort_*`. See `TODO__Agents.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `$derived.by` Dependency Capture for Extra Filter State
|
||||||
|
|
||||||
|
When a `liveQuery` has a SCENARIO 2 fallback (broad search with no IDs), it may run before the debounced search fast path populates `event_session_id_li`. If that fallback doesn't apply the same visibility filter as the fast path, hidden items will briefly appear then disappear ("blink").
|
||||||
|
|
||||||
|
**Fix:** capture the filter flag as a `$derived.by` dependency in the outer closure so Svelte recreates the liveQuery instance whenever it changes — SCENARIO 2 then uses the correct filter from first render.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
let lq__event_session_obj_li = $derived.by(() => {
|
||||||
|
const ids = event_session_id_li; // drives SCENARIO 1 vs 2
|
||||||
|
const event_id = $events_slct?.event_id;
|
||||||
|
const qry_hidden = pres_mgmt_loc.current.qry_hidden; // extra dependency
|
||||||
|
|
||||||
|
return liveQuery(async () => {
|
||||||
|
// SCENARIO 1 — specific IDs (fast path or API result)
|
||||||
|
if (Array.isArray(ids) && ids.length > 0) {
|
||||||
|
const results = await db.session.bulkGet(ids);
|
||||||
|
return results.filter(Boolean);
|
||||||
|
}
|
||||||
|
// SCENARIO 2 — broad fallback, uses captured qry_hidden
|
||||||
|
if (event_id && !someFilter) {
|
||||||
|
const all = await db.session.where('event_id').equals(event_id).sortBy('name');
|
||||||
|
return all.filter((s: any) => {
|
||||||
|
if (qry_hidden === 'not_hidden') return !s.hide;
|
||||||
|
if (qry_hidden === 'hidden') return !!s.hide;
|
||||||
|
return true; // 'all'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key rule:** anything read inside `$derived.by()`'s outer closure (but outside the `liveQuery` callback) becomes a Svelte reactive dependency. Changes to it recreate the liveQuery. Use this to synchronize filter flags that Dexie doesn't track.
|
||||||
|
|
||||||
|
**Also fix the API call:** use the snapshot value from `params` (captured at debounce time) rather than the live store, so rapid toggling doesn't create a mismatch between fast path and API results:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Bad — uses live store value, can race if user toggles during pending call:
|
||||||
|
hidden: pres_mgmt_loc.current.qry_hidden ?? 'not_hidden'
|
||||||
|
|
||||||
|
// Good — uses snapshot captured when handle_search_refresh was called:
|
||||||
|
hidden: params.qry_hidden ?? 'not_hidden'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Practical Patterns from Aether (Journals & Events & IDAA Recovery Meetings)
|
## Practical Patterns from Aether (Journals & Events & IDAA Recovery Meetings)
|
||||||
- Journals: The journaling pages use SWR-style background refreshes but reliably render because either (a) the page `+page.ts` blocks to populate DB for critical views, or (b) components accept `data.initial_*` fallback values until `liveQuery` emits. This hybrid approach avoids the "refresh twice" problem while keeping navigation snappy.
|
- Journals: The journaling pages use SWR-style background refreshes but reliably render because either (a) the page `+page.ts` blocks to populate DB for critical views, or (b) components accept `data.initial_*` fallback values until `liveQuery` emits. This hybrid approach avoids the "refresh twice" problem while keeping navigation snappy.
|
||||||
- Journals broad views: if text search is empty, let the local IDB result set drive the visible list. The API can revalidate the cache in the background, but it should not replace a broad "All" view with a limited slice that hides valid rows.
|
- Journals broad views: if text search is empty, let the local IDB result set drive the visible list. The API can revalidate the cache in the background, but it should not replace a broad "All" view with a limited slice that hides valid rows.
|
||||||
@@ -244,7 +380,7 @@ The `createLiveQueryStore` function creates a readable store that automatically
|
|||||||
|
|
||||||
## SvelteKit Layout Hierarchy: Security and Execution Order
|
## SvelteKit Layout Hierarchy: Security and Execution Order
|
||||||
|
|
||||||
Understanding *when* SvelteKit code runs is critical for private-data modules like IDAA.
|
Understanding _when_ SvelteKit code runs is critical for private-data modules like IDAA.
|
||||||
|
|
||||||
### Execution order on any navigation
|
### Execution order on any navigation
|
||||||
|
|
||||||
@@ -280,7 +416,7 @@ future readers into thinking the parent gate alone is not sufficient.
|
|||||||
|
|
||||||
### Where the actual pre-gate risk lives: `+page.ts` / `+layout.ts`
|
### Where the actual pre-gate risk lives: `+page.ts` / `+layout.ts`
|
||||||
|
|
||||||
Universal load functions run *before* components mount and *before* layout effects
|
Universal load functions run _before_ components mount and _before_ layout effects
|
||||||
execute. They also fire during SvelteKit link prefetch — triggered by the user
|
execute. They also fire during SvelteKit link prefetch — triggered by the user
|
||||||
hovering a link, even if they never navigate. This makes them unsafe for private data:
|
hovering a link, even if they never navigate. This makes them unsafe for private data:
|
||||||
|
|
||||||
@@ -364,7 +500,7 @@ If you must use non-blocking loads, you must pass the initial data to the compon
|
|||||||
|
|
||||||
## The `untrack()` Reactive-Tracking Trap
|
## The `untrack()` Reactive-Tracking Trap
|
||||||
|
|
||||||
`untrack()` is used inside `$effect` to read reactive values without registering them as tracked dependencies of that effect. This is correct for most "read-once" values (params, IDs) where you don't want the effect re-running on every change. But it has a silent failure mode: if a value you *need* to re-read is consumed inside `untrack()`, the effect becomes a one-shot and never retries when that value changes.
|
`untrack()` is used inside `$effect` to read reactive values without registering them as tracked dependencies of that effect. This is correct for most "read-once" values (params, IDs) where you don't want the effect re-running on every change. But it has a silent failure mode: if a value you _need_ to re-read is consumed inside `untrack()`, the effect becomes a one-shot and never retries when that value changes.
|
||||||
|
|
||||||
### Symptom
|
### Symptom
|
||||||
|
|
||||||
@@ -428,7 +564,7 @@ Before wrapping a store read in `untrack()`, ask: **"Do I need this effect to re
|
|||||||
Svelte 5's `bind:` directive is more restrictive than previous versions. You can only bind to a simple **Identifier** or **MemberExpression**.
|
Svelte 5's `bind:` directive is more restrictive than previous versions. You can only bind to a simple **Identifier** or **MemberExpression**.
|
||||||
|
|
||||||
**❌ Invalid Pattern (Causes Compile Error):**
|
**❌ Invalid Pattern (Causes Compile Error):**
|
||||||
Attempting to normalize a value *inside* the binding will fail.
|
Attempting to normalize a value _inside_ the binding will fail.
|
||||||
```svelte
|
```svelte
|
||||||
<!-- Error: Can only bind to an Identifier or MemberExpression -->
|
<!-- Error: Can only bind to an Identifier or MemberExpression -->
|
||||||
<Launcher_menu bind:slct__event_session_id={$events_slct.event_session_id || null} />
|
<Launcher_menu bind:slct__event_session_id={$events_slct.event_session_id || null} />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
**Module Path:** `src/routes/events/[event_id]/(badges)/templates/`
|
**Module Path:** `src/routes/events/[event_id]/(badges)/templates/`
|
||||||
**API Module:** `src/lib/ae_events/ae_events__event_badge_template.ts`
|
**API Module:** `src/lib/ae_events/ae_events__event_badge_template.ts`
|
||||||
**Database Table:** `event_badge_template`
|
**Database Table:** `event_badge_template`
|
||||||
**Last Updated:** 2026-03-02
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -114,17 +114,19 @@ corresponding `ticket_N_text` on the template provides the HTML rendered on the
|
|||||||
| `priority`, `sort`, `group` | int/str | Standard AE sort fields |
|
| `priority`, `sort`, `group` | int/str | Standard AE sort fields |
|
||||||
| `notes` | str | Internal notes |
|
| `notes` | str | Internal notes |
|
||||||
|
|
||||||
### New Field (pending backend addition)
|
### Duplex / Single-Sided
|
||||||
| Field | Type | Notes |
|
| Field | Type | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `duplex` | bool | **Planned** — when `false`, back section is hidden from print (`@media print`) |
|
| `duplex` | bool | When `false`, back section is hidden from print (`@media print`) |
|
||||||
|
|
||||||
The `duplex` field controls whether the back-of-badge section renders during printing.
|
The `duplex` field controls whether the back-of-badge section renders during printing.
|
||||||
When `false` (single-sided), `badge_back` gets `print:hidden` applied so only the front
|
When `false` (single-sided), `badge_back` gets `print:hidden` applied so only the front
|
||||||
prints. The back section still displays on screen for configuration reference.
|
prints. The back section still displays on screen for configuration reference.
|
||||||
|
|
||||||
The first event using this system (Axonius, NYC, mid-April 2026) uses single-sided PVC
|
`duplex` is in `properties_to_save` and `show_badge_back` is derived from it in
|
||||||
cards on a Zebra ZC10L — `duplex` will be `false` for that event's templates.
|
`ae_comp__badge_obj_view.svelte`. (Verified 2026-03-18)
|
||||||
|
|
||||||
|
Axonius events use `duplex = false` — single-sided printing only.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -155,7 +157,7 @@ The print page (`print/+page.svelte`) or the badge view should conditionally add
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
```
|
```
|
||||||
|
|
||||||
This is not yet implemented — tracked as a pending Phase 1 item.
|
This is implemented — `style_href` loads via `<svelte:head>` in `print/+page.svelte` and is included in `properties_to_save`. (Verified 2026-03-18)
|
||||||
|
|
||||||
### CSS Scope
|
### CSS Scope
|
||||||
|
|
||||||
@@ -192,6 +194,128 @@ wrapper so multiple layouts can coexist in the bundle without conflict.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## cfg_json Reference
|
||||||
|
|
||||||
|
All keys are optional. Unknown keys are preserved on save (forward-compatible). Managed via the template form's **Advanced** and **Header & Branding** sections, or directly in phpMyAdmin.
|
||||||
|
|
||||||
|
### Visibility
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `hide_badge_header` | bool | `false` | Hides the entire header section (image + logo/text fallback). Auto-true when `background_image_path` is set, unless explicitly overridden. |
|
||||||
|
| `hide_badge_footer` | bool | `false` | Hides the badge type footer stripe. |
|
||||||
|
| `hide_title` | bool | `false` | Suppresses the professional title field on the badge front. |
|
||||||
|
| `hide_affiliations` | bool | `false` | Suppresses the affiliations field. |
|
||||||
|
| `hide_location` | bool | `false` | Suppresses the location field. |
|
||||||
|
|
||||||
|
### QR Codes
|
||||||
|
|
||||||
|
These keys override the top-level DB fields (`show_qr_front`, `show_qr_back`) when present. Prefer setting them here rather than the top-level fields.
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `show_qr_front` | bool | `false` | Show attendee QR on badge front. |
|
||||||
|
| `show_qr_back` | bool | `true` | Show attendee QR (+ ID text) on badge back. |
|
||||||
|
|
||||||
|
### Text Alignment
|
||||||
|
|
||||||
|
Stored under a nested `align` object.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"align": { "name": "left", "title": "left", "affiliations": "left", "location": "center" }
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Values | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `align.name` | `left` \| `center` \| `right` \| `justify` | `center` |
|
||||||
|
| `align.title` | `left` \| `center` \| `right` \| `justify` | `center` |
|
||||||
|
| `align.affiliations` | `left` \| `center` \| `right` \| `justify` | `center` |
|
||||||
|
| `align.location` | `left` \| `center` \| `right` \| `justify` | `center` |
|
||||||
|
|
||||||
|
QR alignment stored under `qr_alignment`:
|
||||||
|
|
||||||
|
| Key | Values | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `qr_alignment.front` | `left` \| `center` \| `right` | `center` |
|
||||||
|
| `qr_alignment.back` | `left` \| `center` \| `right` \| `justify` | `center` |
|
||||||
|
|
||||||
|
### Header Image
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `header_margin_top` | CSS length | `2rem` | Vertical offset of the header image. Negative = shift up. e.g. `"-0.25in"`, `"1rem"`. |
|
||||||
|
| `header_border_color` | hex color | none | Bottom border drawn below the header div. **Empty = no border.** e.g. `"#FE6111"`. |
|
||||||
|
| `header_border_width` | CSS length | `2px` | Thickness of the header bottom border. Only applied when `header_border_color` is set. |
|
||||||
|
| `header_padding_bottom` | CSS length | none | Space between the header image and the bottom border line. e.g. `"1.45in"`. |
|
||||||
|
|
||||||
|
### Appearance
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `body_text_color` | hex color | `#000000` | Inline color applied to all badge body text. |
|
||||||
|
| `bleed` | CSS length | none | Extends background image past card edges on all sides. Prevents white borders on printers that clip slightly inside the card. e.g. `"0.125in"`, `"3mm"`. |
|
||||||
|
|
||||||
|
### Text Zone Heights (`fit_heights`)
|
||||||
|
|
||||||
|
Per-layout height overrides for the auto-scaling text zones. Set any subset — unset keys fall back to the layout default. Useful when `background_image_path` is set and the designed zones don't align with code defaults.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"fit_heights": { "grp_name_title": "1.8in", "name": "1.4in" }
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Notes |
|
||||||
|
|---|---|
|
||||||
|
| `grp_name_title` | Height of the name+title container |
|
||||||
|
| `grp_name_title_flex` | Flex distribution: `around` \| `between` \| `even` \| `center` \| `start` \| `end` |
|
||||||
|
| `name` | Height of the name text zone |
|
||||||
|
| `title` | Height of the title text zone |
|
||||||
|
| `grp_aff_loc` | Height of the affiliations+location container |
|
||||||
|
| `grp_aff_loc_flex` | Flex distribution (same values as above) |
|
||||||
|
| `affiliations` | Height of the affiliations text zone |
|
||||||
|
| `location` | Height of the location text zone |
|
||||||
|
|
||||||
|
### Punch-Out Hole Markers (`punch_holes`)
|
||||||
|
|
||||||
|
Enables X overlays at the physical badge clip slot positions. Slots are pre-perforated on the badge stock — the markers print on the badge so attendees know where to push them out.
|
||||||
|
|
||||||
|
**Slot dimensions:** 5/8″ wide × 1/8″ tall, 1/4″ from top edge, 3/8″ from left/right edges. Center slot is horizontally centered.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"punch_holes": { "left": true, "right": true, "center": false }
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Default | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `punch_holes.left` | `false` | Left clip slot marker |
|
||||||
|
| `punch_holes.right` | `false` | Right clip slot marker |
|
||||||
|
| `punch_holes.center` | `false` | Center clip slot marker (less common) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Controls Panel (`controls_cfg`)
|
||||||
|
|
||||||
|
Controls which fields appear in the print controls panel for non-trusted users, and which fields authenticated users may edit. Trusted + Edit Mode always sees and can edit all fields regardless of this config.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"controls_cfg": {
|
||||||
|
"shown": ["name", "title", "affiliations"],
|
||||||
|
"auth_editable": ["title", "affiliations", "location"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Type | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `controls_cfg.shown` | `string[]` | `["name", "title", "affiliations", "location"]` |
|
||||||
|
| `controls_cfg.auth_editable` | `string[]` | `["title", "affiliations", "location", "allow_tracking", "pronouns"]` |
|
||||||
|
|
||||||
|
Valid field keys: `name`, `title`, `affiliations`, `location`, `pronouns`, `allow_tracking`.
|
||||||
|
|
||||||
|
This config applies to the onsite print controls. Remote review currently uses
|
||||||
|
`event.mod_badges_json.edit_permissions` instead. Consolidating or defining precedence between
|
||||||
|
these two permission sources is tracked in `documentation/TODO__Agents.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Template-Derived Features (component behavior)
|
## Template-Derived Features (component behavior)
|
||||||
|
|
||||||
### badge_type_list → badge type select
|
### badge_type_list → badge type select
|
||||||
@@ -218,12 +342,12 @@ The `properties_to_save` array in `ae_events__event_badge_template.ts` controls
|
|||||||
gets cached locally. Current state — fields **NOT** in properties_to_save that exist
|
gets cached locally. Current state — fields **NOT** in properties_to_save that exist
|
||||||
in DB and may be needed:
|
in DB and may be needed:
|
||||||
|
|
||||||
- `style_href` — needed once external CSS is wired via `<svelte:head>`
|
|
||||||
- `passcode` — not needed client-side
|
- `passcode` — not needed client-side
|
||||||
- `footer_title`, `footer_left`, `footer_right` — not needed (legacy)
|
- `footer_title`, `footer_left`, `footer_right` — not needed (legacy)
|
||||||
- `header_background`, `footer_background` — not needed (legacy)
|
- `header_background`, `footer_background` — not needed (legacy)
|
||||||
- `script_src` — do not add; this field should not be used
|
- `script_src` — do not add; this field should not be used
|
||||||
- `duplex` — **add when backend adds the field**
|
|
||||||
|
`duplex` is already saved to IDB and drives single-sided rendering.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -360,6 +484,8 @@ Firefox users can use "Save to PDF" directly — it just works.
|
|||||||
- [x] Add `duplex` to `properties_to_save` — done. (2026-03-18 verified)
|
- [x] Add `duplex` to `properties_to_save` — done. (2026-03-18 verified)
|
||||||
- [x] Add `duplex`-driven suppression to `badge_back` section — done in `ae_comp__badge_obj_view.svelte`; `show_badge_back` derived from `duplex` field.
|
- [x] Add `duplex`-driven suppression to `badge_back` section — done in `ae_comp__badge_obj_view.svelte`; `show_badge_back` derived from `duplex` field.
|
||||||
- [x] `badge_4x6_fanfold` layout CSS created (`badge_layout_epson_4x6_fanfold.css`), imported in badge component, `@page 4in 6in` wired in print page. (2026-05-15)
|
- [x] `badge_4x6_fanfold` layout CSS created (`badge_layout_epson_4x6_fanfold.css`), imported in badge component, `@page 4in 6in` wired in print page. (2026-05-15)
|
||||||
|
- [x] Template form expanded — `layout`, `style_href`, `badge_type_list`, `duplex`, and all `cfg_json` keys now editable via the form. (2026-06-04)
|
||||||
|
- [x] `cfg_json.header_margin_top`, `header_border_color`, `header_border_width`, `header_padding_bottom` added — header image position and bottom border are fully configurable without a code deploy. (2026-06-04)
|
||||||
|
- [ ] Wire `badge_type_list` from the template into the badge search filter — currently the search form uses a hardcoded list. See `ae_comp__badge_search.svelte` TODO comment.
|
||||||
- [ ] `badge_4x5_fanfold` layout CSS exists but is stale (not used in 2+ years) — review against actual hardware before next use.
|
- [ ] `badge_4x5_fanfold` layout CSS exists but is stale (not used in 2+ years) — review against actual hardware before next use.
|
||||||
- [ ] Remove dead `exhibitor_info` / `presenter_info` / `staff_info` / `vip_info` / `vote_info` `{#if}` blocks from `ae_comp__badge_obj_view.svelte` (if they were carried over from v1)
|
- [ ] Remove dead `exhibitor_info` / `presenter_info` / `staff_info` / `vip_info` / `vote_info` `{#if}` blocks from `ae_comp__badge_obj_view.svelte` (if they were carried over from v1)
|
||||||
- [ ] Improve `ae_comp__badge_template_form.svelte` to edit all relevant fields (currently minimal)
|
|
||||||
|
|||||||
@@ -1,843 +1,150 @@
|
|||||||
# MODULE: Aether Events — Badges
|
# Aether Events — Badges
|
||||||
|
|
||||||
**Module Path:** `src/routes/events/[event_id]/(badges)/badges/`
|
**Last Updated:** 2026-06-12
|
||||||
**API Module:** `src/lib/ae_events/ae_events__event_badge.ts`
|
|
||||||
**Database:** `db_events.badge` (Dexie IndexedDB table)
|
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.
|
||||||
**Last Updated:** 2026-02-27 (rev 6)
|
|
||||||
**Related Docs:** `documentation/PROJECT__AE_Events_Badges_Review_Print.md` (implementation guide)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
## Data Model & Hierarchy
|
||||||
|
|
||||||
The Badges module manages event attendee badges with support for:
|
### Core Objects
|
||||||
- **External system imports as needed** (CSV/Excel, iMIS, Zoom, Novi, Impexium, Confex, Cvent, and others)
|
- **Event Badge** (`event_badge`): The attendee record containing name, title, affiliations, and tracking flags.
|
||||||
- **Field override protection** to prevent staff/attendee edits from being overwritten by automated syncs
|
- **Badge Template** (`event_badge_template`): The visual and structural configuration for printing (branding, layout, QR placement).
|
||||||
- **Multi-tier access control** for field editing
|
|
||||||
- **QR code generation** for badge scanning
|
### Relationships
|
||||||
- **Print tracking** (count, first/last print datetime)
|
- **Badge → Event:** Many-to-one.
|
||||||
- **Advanced search and filtering**
|
- **Badge → Template:** Many-to-one (via `event_badge_template_id`).
|
||||||
- **HTML rendering** in display fields for rich text formatting
|
- **Badge → Person:** Optional link to core Aether Person record for unified profiles.
|
||||||
- **Accessibility features** (text enlargement toggle)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Critical Design Pattern: Override Fields
|
## Critical Design Pattern: Override Fields
|
||||||
|
|
||||||
### Purpose
|
### Purpose
|
||||||
The `*_override` fields pattern protects data from being overwritten during scheduled cron syncs from external systems. This is essential because:
|
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.
|
||||||
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
|
|
||||||
|
|
||||||
### How It Works
|
### 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:**
|
### Standard Override Pairs
|
||||||
```
|
|
||||||
External System → Aether API → Populates REGULAR fields only
|
|
||||||
(never touches *_override fields)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Display Behavior:**
|
| Regular Field | Override Field | Editable By | HTML? |
|
||||||
```
|
|---|---|---|---|
|
||||||
UI Display Logic:
|
| `full_name` | `full_name_override` | Staff, Attendee | ✅ |
|
||||||
1. IF `*_override` field has value → USE IT (highest priority)
|
| `professional_title` | `professional_title_override` | Staff, Attendee | ✅ |
|
||||||
2. ELSE IF regular field has value → USE IT (fallback)
|
| `affiliations` | `affiliations_override` | Staff, Attendee | ✅ |
|
||||||
3. ELSE → Display placeholder/empty
|
| `location` | `location_override` | Staff, Attendee | ✅ |
|
||||||
```
|
| `email` | `email_override` | Staff Only | No |
|
||||||
|
| `badge_type` | `badge_type_override` | Staff Only | No |
|
||||||
**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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## External System Integration
|
## External System Integration
|
||||||
|
|
||||||
### Supported Import Sources
|
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."
|
||||||
- **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
|
|
||||||
|
|
||||||
### Data Flow Direction
|
### Supported Sources
|
||||||
```
|
- **iMIS**, **Novi AMS**, **Impexium** (Associations)
|
||||||
External Systems ─────────> Aether
|
- **Zoom**, **Cvent** (Registrations)
|
||||||
(READ ONLY) (WRITE + DISPLAY)
|
- **Confex** (Abstracts/Presenters)
|
||||||
```
|
- **Custom CSV/Excel**
|
||||||
|
|
||||||
**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
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Access Control & Edit Permissions
|
## Access Control & Permissions
|
||||||
|
|
||||||
### Access Levels (Ascending)
|
| Level | Access |
|
||||||
1. **Anonymous** — No access to badges
|
|---|---|
|
||||||
2. **Public** — View public event info only (no badge access)
|
| **Public kiosk** | View badge and perform the first print; cannot edit fields without authenticated access. |
|
||||||
3. **Authenticated** — View own badge, limited self-edit
|
| **Authenticated** | Edit fields allowed by the active permission config. |
|
||||||
4. **Trusted** — Search all badges, view all, edit own
|
| **Trusted** | Search all badges, view all, and correct names; reprint requires global Edit Mode. |
|
||||||
5. **Administrator** — Full CRUD, bulk operations, override any field
|
| **Administrator** | Full CRUD, bulk operations, and override access. |
|
||||||
6. **Manager** — All administrator + event configuration
|
| **Manager** | All Administrator capabilities plus Event/Template configuration. |
|
||||||
7. **Super** — All manager + cross-event operations
|
|
||||||
|
|
||||||
### Current Implementation (v3) — 2026-02-27
|
### Attendee Self-Service (`/review`)
|
||||||
|
Attendees can access their own record via a passcode-gated link (typically `?passcode=...`).
|
||||||
|
Editable fields come from `event.mod_badges_json.edit_permissions`, with module defaults as fallback.
|
||||||
|
|
||||||
#### Badge Search Results Visibility
|
### Onsite Kiosk (`/print`)
|
||||||
|
The print controls update the badge preview live. Authenticated field editing is controlled by the
|
||||||
|
badge template's `cfg_json.controls_cfg` (`shown` and `auth_editable`). Trusted + global Edit Mode
|
||||||
|
overrides the template config and exposes all controls. This differs from the review page's
|
||||||
|
event-level permission source; consolidation is an active follow-up.
|
||||||
|
|
||||||
| Access Level | Sees |
|
### Review-Link Email
|
||||||
| --- | --- |
|
Email Link actions are placeholders and do not currently send mail. When delivery is implemented,
|
||||||
| Below Trusted (incl. anonymous) | Only badges where `print_count < 1` and not hidden |
|
it must use the imported `event_badge.email` address, never attendee-editable `email_override`.
|
||||||
| 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"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Search & Filter Capabilities
|
## Search & Filter Capabilities
|
||||||
|
|
||||||
### Search Component
|
- **Fulltext Search:** Matches against a consolidated `default_qry_str` (Name, email, IDs).
|
||||||
**File:** `ae_comp__badge_search.svelte`
|
- **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 (Trusted + Edit Mode):** Badge Type, Printed Status, Affiliations, Sort Order.
|
||||||
|
|
||||||
### Multi-Word Search Fix (2026-02-26)
|
### Visibility Filter (Trusted + Edit Mode)
|
||||||
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
|
Three-option select controlling which records are shown:
|
||||||
|
|
||||||
**Fulltext Search** (All Users)
|
| Option | Who can set it | Effect |
|
||||||
- Searches: `default_qry_str` database field
|
| --- | --- | --- |
|
||||||
- Includes: Name, email, external IDs
|
| **Default** | Any | Hides hidden and disabled badges |
|
||||||
- Type: `LIKE %query%` (case-insensitive)
|
| **Show Hidden** | Trusted | Shows hidden badges alongside normal ones |
|
||||||
- Trigger: Enter key or 3+ characters typed
|
| **Show Disabled + Hidden** | Manager only | Shows all records regardless of enable/hide flags |
|
||||||
|
|
||||||
**Advanced Filters** (Trusted Access & Above)
|
### Result Limit Stepper (Edit Mode)
|
||||||
```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
|
Controls the maximum number of results returned. Only visible in edit mode.
|
||||||
qry_printed_status: 'all' | 'printed' | 'not_printed'
|
|
||||||
|
|
||||||
// Affiliations Search
|
| Access Level | Range | Step |
|
||||||
qry_affiliations: string // Separate filter for organization search
|
| --- | --- | --- |
|
||||||
|
| Below Trusted | Fixed 25 | — |
|
||||||
|
| Trusted | 25 – 250 | 25 |
|
||||||
|
| Manager+ | 25 – 2550 | 25 up to 250, then 100 |
|
||||||
|
|
||||||
// Sort Options
|
### Badge Type Filter — Known Limitation
|
||||||
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
|
The badge type dropdown in the search form uses a **hardcoded list**, not the template's `badge_type_list`. This means the codes shown in the filter may not match the codes used by the current event's template. This is a known gap — the fix requires passing the template object into the search component. Until resolved, staff can still search by name/email and filter results manually.
|
||||||
- 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
|
## Print Rendering and Tracking
|
||||||
|
|
||||||
### Name Display Priority
|
- The canonical badge render uses binary-search text fitting for name, title, affiliations, and location.
|
||||||
```typescript
|
- Template `show_qr_front`/`show_qr_back` settings control QR placement.
|
||||||
// Component: ae_comp__badge_obj_li.svelte (Lines 113-121)
|
- Template `style_href` loads event-specific CSS on the print page.
|
||||||
if (event_badge_obj?.full_name_override)
|
- Template `duplex = false` suppresses the badge back for single-sided stock.
|
||||||
display: full_name_override
|
- Chromium PDF proofing requires margins set to None; physical printer paper size remains driver-controlled.
|
||||||
else if (event_badge_obj?.full_name)
|
|
||||||
display: full_name
|
|
||||||
else
|
|
||||||
display: given_name + ' ' + family_name
|
|
||||||
```
|
|
||||||
|
|
||||||
### Badge View Page
|
Aether tracks the lifecycle of every physical badge to prevent unauthorized reprints and monitor kiosk activity.
|
||||||
**Route:** `/events/[event_id]/badges/[badge_id]`
|
|
||||||
|
|
||||||
**Components:**
|
| Field | Purpose |
|
||||||
- `+page.svelte` — Container with LiveQuery for badge data
|
|---|---|
|
||||||
- `ae_comp__badge_obj_view.svelte` — Full badge display + edit UI
|
| `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. |
|
||||||
|
|
||||||
**LiveQueries:**
|
> **Operational Note:** Reprints triggered via the Edit Mode shortcut do not increment the count; only the formal "Print Badge" workflow does.
|
||||||
```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
|
## Route Map (Badges)
|
||||||
|
|
||||||
### Purpose
|
| URL | Purpose |
|
||||||
Badge templates define the visual layout and content structure for printed badges:
|
|---|---|
|
||||||
- Header images/logos
|
| `/events/[id]/badges` | Main search and attendee list. |
|
||||||
- Field positions and font sizes
|
| `/events/[id]/badges/templates` | Badge template management. |
|
||||||
- QR code placement
|
| `/events/[id]/badges/[id]/print` | The actual print-ready render page. |
|
||||||
- Ticket/option indicator display
|
| `/events/[id]/badges/[id]/review` | Attendee-facing self-service form. |
|
||||||
- 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
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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({ ... });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Database Schema
|
|
||||||
|
|
||||||
### 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)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
- [AE API V3 for Frontend](./GUIDE__AE_API_V3_for_Frontend.md)
|
👉 **[MODULE__AE_Events_Badge_Templates.md](./MODULE__AE_Events_Badge_Templates.md)** (Technical reference for layouts)
|
||||||
- [Development Guide](./GUIDE__Development.md)
|
👉 **[GUIDE__AE_Events_Badges_Onsite.md](./GUIDE__AE_Events_Badges_Onsite.md)** (Hardware & station setup)
|
||||||
- [Events Launcher Native Integration](./PROJECT__AE_Events_Launcher_Native_integration.md)
|
👉 **[GUIDE__AE_Events_Onsite_Runbook.md](./GUIDE__AE_Events_Onsite_Runbook.md)** (Onsite operational checklists)
|
||||||
- [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)
|
|
||||||
|
|||||||
81
documentation/MODULE__AE_Events_Launcher.md
Normal file
81
documentation/MODULE__AE_Events_Launcher.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Aether Events — Launcher (Podium Display)
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
|
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) |
|
||||||
95
documentation/MODULE__AE_Events_Launcher_Config_Menu.md
Normal file
95
documentation/MODULE__AE_Events_Launcher_Config_Menu.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Aether Events — Launcher Configuration Menu (Inventory)
|
||||||
|
|
||||||
|
> **Status:** Current Reference (v3.0)
|
||||||
|
> **Last Updated:** 2026-06-12
|
||||||
|
> **Location:** `src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte`
|
||||||
|
|
||||||
|
This document provides a detailed inventory of the Launcher's configuration menu settings as of May 2026. This serves as the baseline for the v3.1 reorganization into a modal-based tabbed interface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. UI Architecture & Visibility
|
||||||
|
|
||||||
|
The configuration menu currently resides in a slide-out **Drawer** (sidebar).
|
||||||
|
|
||||||
|
### 1.1 Visibility Modes
|
||||||
|
- **Standard Mode:** Default view for onsite operators. Hides advanced technical and destructive controls.
|
||||||
|
- **Technical Mode (`$ae_loc.edit_mode`):** Toggled via a subtle pencil icon. Reveals advanced diagnostic fields, manual overrides, and debug tools.
|
||||||
|
- **Native Mode (`$ae_loc.is_native`):** Automatically detected when running in the Electron shell. Shows OS-level controls (Filesystem, Power, Apps).
|
||||||
|
|
||||||
|
### 1.2 Section Expansion Logic
|
||||||
|
- **`collapsed`**: Content hidden.
|
||||||
|
- **`auto`**: Expanded by default; collapses when another "auto" section opens.
|
||||||
|
- **`pinned`**: Remains expanded regardless of other interactions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Menu Inventory (Tabbed View)
|
||||||
|
|
||||||
|
### Tab 1: Setup (Onsite Operator Focus)
|
||||||
|
|
||||||
|
| Section | Feature | Technical Mode Only |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Display & App Modes** | Session Mode Preset (Oral vs Poster Kiosk) | |
|
||||||
|
| | Operational Env (Web / App / Onsite) | |
|
||||||
|
| | Interface Visibility (Hide Header/Menu/Footer/Times) | |
|
||||||
|
| | Clock Format (12/24 hour) | |
|
||||||
|
| | WebSocket Debugger Toggle | Yes |
|
||||||
|
| | Poster Modal Title Toggle | Yes |
|
||||||
|
| | Native Test Mode (Simulation) | Yes |
|
||||||
|
| **Remote Controller** | WS Connection Status Badge | |
|
||||||
|
| | Controller Strategy (Local / Remote / Local Push) | |
|
||||||
|
| | Connect / Disconnect Action | |
|
||||||
|
| | Group Reload (WS trigger) | |
|
||||||
|
| | Channel Group Code (Locked/Unlockable) | Yes |
|
||||||
|
| **Poster Screen Saver** | Idle Timeout Summary | |
|
||||||
|
| | Timer Overrides (Idle / Cycle / Loop) | Yes |
|
||||||
|
|
||||||
|
### Tab 2: Device (Technical & Native Focus)
|
||||||
|
|
||||||
|
| Section | Feature | Technical Mode Only |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Sync Engine & Timers** | Pause / Resume Sync | |
|
||||||
|
| | Force Sync Location (Recursive fetch) | |
|
||||||
|
| | Polling Periods (Event/Device/Loc/Sess/Pres/Presenter) | Yes |
|
||||||
|
| | Cache Hash Prefix Length (1-3 chars) | Yes |
|
||||||
|
| **System & Sync Health** | CPU & RAM Usage Gauges | |
|
||||||
|
| | Heartbeat Status & Timestamp | |
|
||||||
|
| | Sync Progress (Cached vs Total) | |
|
||||||
|
| | Active Sync Filename (Animated) | |
|
||||||
|
| | Hostname & IP List | Yes |
|
||||||
|
| | Raw Device JSON Inspector | Yes |
|
||||||
|
| **Native OS Management** | Open Cache / Temp Folders | |
|
||||||
|
| | Window Control (Maximize / Kiosk) | |
|
||||||
|
| | Display Mode (Extend / Mirror) | |
|
||||||
|
| | Presentation Remote (Prev/Start/Stop/Next) | |
|
||||||
|
| | Reset Wallpaper (Site Header) | Yes |
|
||||||
|
| | Kill Presentation Apps (PowerPoint/Keynote/etc) | Yes |
|
||||||
|
| | Power Actions (Reboot / Shutdown) | Yes |
|
||||||
|
| | Manual Terminal Command Entry | Yes |
|
||||||
|
| **Wallpaper** | Primary Display URL Preset/Input | |
|
||||||
|
| | External Display URL Preset/Input | |
|
||||||
|
| | Save & Apply Wallpaper | |
|
||||||
|
| | Restore macOS Default | |
|
||||||
|
| **Launch Timing** | Per-Profile Post-Open Delay (ms) Overrides | Yes |
|
||||||
|
| **Application Updates** | Update Source (File / URL) | Yes |
|
||||||
|
| | Check for Updates | |
|
||||||
|
| | Install & Relaunch | |
|
||||||
|
|
||||||
|
### Tab 3: Dev (Technical/Developer Focus)
|
||||||
|
|
||||||
|
| Section | Feature | Technical Mode Only |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Local Reset & Actions** | Maintenance Select (Wipe IDB / LocalStorage) | Yes |
|
||||||
|
| | Global Sys Menu Toggle | Yes |
|
||||||
|
| | Global Debug Menu Toggle | Yes |
|
||||||
|
| | Cache .tmp Cleanup (Native Only) | Yes |
|
||||||
|
| | API Endpoint & Account ID Summary | Yes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Global Actions (Footer)
|
||||||
|
|
||||||
|
- **Close:** Dismisses the configuration menu.
|
||||||
|
- **Reload:** Performs a full browser `location.reload()`.
|
||||||
|
- **Debug Panel:** Opens the raw state inspector (Technical Mode Only).
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Aether Events Launcher: Native Electron Integration
|
# Aether Events — Launcher: Native Integration
|
||||||
|
|
||||||
> **Status:** Operational / Phase 5 Implementation
|
> **Status:** Operational / Permanent Reference
|
||||||
> **Last Updated:** 2026-05-11
|
> **Last Updated:** 2026-05-21 (Reorganized)
|
||||||
> **Primary Platform:** macOS (Darwin)
|
> **Primary Platform:** macOS (Darwin)
|
||||||
> **Fallback Platform:** Linux / Windows
|
> **Fallback Platform:** Linux / Windows
|
||||||
|
|
||||||
@@ -72,10 +72,15 @@ Files are stored persistently using their SHA-256 hash to prevent filename colli
|
|||||||
- **Filename:** `{hash}.file`
|
- **Filename:** `{hash}.file`
|
||||||
|
|
||||||
### 4.2 Background Sync (File Warming)
|
### 4.2 Background Sync (File Warming)
|
||||||
When a user navigates to a session in the Launcher UI, the `LauncherBackgroundSync` component:
|
When a user navigates to a session in the Launcher UI, the `LauncherBackgroundSync` component warms the cache for that specific session. To ensure full room readiness, a **Force Sync Location** trigger is available in the configuration UI.
|
||||||
1. Extracts all `event_file_id` values for that session.
|
|
||||||
2. Checks the native cache via `aetherNative.check_cache`.
|
1. **Metadata Fetch:** The system fetches all sessions, presentations, and presenters for the current location into the local database (Dexie).
|
||||||
3. Triggers background downloads for missing files via `aetherNative.download_to_cache`.
|
2. **Chronological Priority:** Missing files are added to the download queue and sorted to prioritize the event schedule:
|
||||||
|
- **Tier 1: Global Assets** — Event and Location level files (virtual time 0).
|
||||||
|
- **Tier 2: Session Schedule** — Earliest sessions are prioritized first.
|
||||||
|
- **Tier 3: Presentation Order** — Within a session, speakers are prioritized by their start time.
|
||||||
|
- **Tier 4: Integrity & Fairness** — Tie-breakers use `created_on` (oldest first) to ensure on-time uploads are cached before last-minute revisions.
|
||||||
|
3. **Download:** Triggers background downloads via `aetherNative.download_to_cache` sequentially to preserve network bandwidth and ensure file integrity.
|
||||||
|
|
||||||
### 4.3 Safe Handover (Launch Sequence)
|
### 4.3 Safe Handover (Launch Sequence)
|
||||||
When a user clicks "Open", the system follows a non-destructive sequence:
|
When a user clicks "Open", the system follows a non-destructive sequence:
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Aether Events — Exhibitor Leads Module (v3)
|
# Aether Events — Exhibitor Leads Module (v3)
|
||||||
|
|
||||||
**Status:** Implemented and ready for demo. Core lead capture flow works end-to-end.
|
**Status:** Implemented and ready for demo. Core lead capture flow works end-to-end.
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
**Platform:** PWA only — mobile-first, offline-capable.
|
**Platform:** PWA only — mobile-first, offline-capable.
|
||||||
**Target users:** Conference exhibitors scanning attendee badges at their booths.
|
**Target users:** Conference exhibitors scanning attendee badges at their booths.
|
||||||
|
|
||||||
141
documentation/MODULE__AE_Events_Presentation_Management.md
Normal file
141
documentation/MODULE__AE_Events_Presentation_Management.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# Aether Events — Presentation Management
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
|
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,496 +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
|
|
||||||
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.
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
39
documentation/MODULE__AE_IDAA_Archives.md
Normal file
39
documentation/MODULE__AE_IDAA_Archives.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Aether IDAA — Archives Module
|
||||||
|
|
||||||
|
**Status:** Active (private)
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
**Routes:** `src/routes/idaa/(idaa)/archives/`
|
||||||
|
**Underlying library:** `src/lib/ae_archives/`
|
||||||
|
|
||||||
|
IDAA Archives provides authenticated access to archival documents and media for the IDAA community.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Responsibilities
|
||||||
|
|
||||||
|
- List, view, and edit archive records (permission-gated).
|
||||||
|
- Upload and manage archive content files.
|
||||||
|
- Render media/content viewers for archived assets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Requirements
|
||||||
|
|
||||||
|
- All IDAA archive content is private.
|
||||||
|
- Auth guard must remain enforced for all archive routes and child views.
|
||||||
|
- Do not add pre-gate data loading in universal `+page.ts`/`+layout.ts` paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Route Map
|
||||||
|
|
||||||
|
- `/idaa/archives`
|
||||||
|
- `/idaa/archives/[archive_id]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Docs
|
||||||
|
|
||||||
|
- `documentation/CLIENT__IDAA_and_customized_mods.md`
|
||||||
|
- `documentation/AE__Permissions_and_Security.md`
|
||||||
|
- `documentation/REFERENCE__Common_Agent_Mistakes.md`
|
||||||
39
documentation/MODULE__AE_IDAA_Bulletin_Board.md
Normal file
39
documentation/MODULE__AE_IDAA_Bulletin_Board.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Aether IDAA — Bulletin Board Module
|
||||||
|
|
||||||
|
**Status:** Active (private)
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
**Routes:** `src/routes/idaa/(idaa)/bb/`
|
||||||
|
**Underlying library:** `src/lib/ae_posts/`
|
||||||
|
|
||||||
|
The IDAA Bulletin Board (BB) is a private community posting and comment system for authenticated IDAA users.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Responsibilities
|
||||||
|
|
||||||
|
- Post list and post detail flows.
|
||||||
|
- Comment create/edit workflows.
|
||||||
|
- Priority/visibility and sort behavior aligned with IDAA privacy and moderation rules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Requirements
|
||||||
|
|
||||||
|
- All BB routes are private/authenticated.
|
||||||
|
- Do not weaken layout-level auth gating.
|
||||||
|
- Treat any public exposure of BB data as Sev-1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Route Map
|
||||||
|
|
||||||
|
- `/idaa/bb`
|
||||||
|
- `/idaa/bb/[post_id]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Docs
|
||||||
|
|
||||||
|
- `documentation/CLIENT__IDAA_and_customized_mods.md`
|
||||||
|
- `documentation/AE__Permissions_and_Security.md`
|
||||||
|
- `documentation/REFERENCE__Common_Agent_Mistakes.md`
|
||||||
41
documentation/MODULE__AE_IDAA_Recovery_Meetings.md
Normal file
41
documentation/MODULE__AE_IDAA_Recovery_Meetings.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Aether IDAA — Recovery Meetings Module
|
||||||
|
|
||||||
|
**Status:** Active (private)
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
**Routes:** `src/routes/idaa/(idaa)/recovery_meetings/`
|
||||||
|
**Underlying library:** `src/lib/ae_events/` (repurposed)
|
||||||
|
|
||||||
|
Recovery Meetings adapts Events module primitives for IDAA meeting discovery, filtering, viewing, and trusted editing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Responsibilities
|
||||||
|
|
||||||
|
- Meeting search/list/detail flows (IDB-first with API revalidation).
|
||||||
|
- Filter and sort controls for member workflows.
|
||||||
|
- Trusted/staff edit flows for meeting records.
|
||||||
|
- Modal and direct-page detail/edit entry paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security and Data Integrity
|
||||||
|
|
||||||
|
- Module is private/authenticated.
|
||||||
|
- Keep error state distinct from empty-result state.
|
||||||
|
- When persisted object shape changes, update `IDB_CONTENT_VERSIONS` wiring/version.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Route Map
|
||||||
|
|
||||||
|
- `/idaa/recovery_meetings`
|
||||||
|
- `/idaa/recovery_meetings/[event_id]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Docs
|
||||||
|
|
||||||
|
- `documentation/CLIENT__IDAA_and_customized_mods.md`
|
||||||
|
- `documentation/PROJECT__IDAA_Stores_Svelte5_Migration_2026.md`
|
||||||
|
- `documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md`
|
||||||
|
- `documentation/REFERENCE__Common_Agent_Mistakes.md`
|
||||||
35
documentation/MODULE__AE_IDAA_Video_Conferences.md
Normal file
35
documentation/MODULE__AE_IDAA_Video_Conferences.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Aether IDAA — Video Conferences Module
|
||||||
|
|
||||||
|
**Status:** Active (private)
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
**Routes:** `src/routes/idaa/(idaa)/video_conferences/`
|
||||||
|
|
||||||
|
Video Conferences provides IDAA’s Jitsi meeting access experience within the IDAA private module.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Responsibilities
|
||||||
|
|
||||||
|
- Render conference links and meeting access UX.
|
||||||
|
- Support breakout links from Novi iframe context.
|
||||||
|
- Preserve required URL bootstrap parameters when generating breakout URLs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Breakout Link Requirement
|
||||||
|
|
||||||
|
When opening outside the iframe, ensure required keys (for example site key and Novi identity UUID) remain in the URL so users do not hit access denial flows.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Requirements
|
||||||
|
|
||||||
|
- Module is private/authenticated.
|
||||||
|
- Avoid exposing conference route details publicly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Docs
|
||||||
|
|
||||||
|
- `documentation/CLIENT__IDAA_and_customized_mods.md`
|
||||||
|
- `documentation/AE__Permissions_and_Security.md`
|
||||||
81
documentation/MODULE__AE_Journals.md
Normal file
81
documentation/MODULE__AE_Journals.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Aether Journals — Module Overview
|
||||||
|
|
||||||
|
**Status:** Active module
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
**Library:** `src/lib/ae_journals/`
|
||||||
|
**Routes:** `src/routes/journals/`
|
||||||
|
|
||||||
|
This module manages private personal journals and journal entries with offline-first behavior and Svelte 5 runes patterns.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Responsibilities
|
||||||
|
|
||||||
|
- Journal and journal-entry CRUD via V3 API wrappers.
|
||||||
|
- Dexie-backed local cache with liveQuery-driven UI updates.
|
||||||
|
- Private/passcode-aware access behavior and client-side content encryption.
|
||||||
|
- Quick Add, Append/Prepend, import/export, and entry auto-save workflows.
|
||||||
|
- Tabbed module, journal, and entry configuration modals.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Data Objects
|
||||||
|
|
||||||
|
- `journal`
|
||||||
|
- `journal_entry`
|
||||||
|
|
||||||
|
Common fields and behavior follow Aether object conventions (`code`, `name`, `enable`, `hide`, `priority`, `sort`, `cfg_json`, `data_json`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage and Reactivity
|
||||||
|
|
||||||
|
- Local cache: Dexie tables in journals DB layer.
|
||||||
|
- UI reactivity: Svelte 5 runes (`$state`, `$derived`, `$effect`) plus liveQuery wrappers (`lq__*`, `lqw__*`).
|
||||||
|
- Persisted module settings: see config map.
|
||||||
|
|
||||||
|
Related config map:
|
||||||
|
- `documentation/MODULE__AE_Journals_Config_Map.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implemented Entry Workflows
|
||||||
|
|
||||||
|
- Quick Add creates a plaintext note in a selected journal without opening the full editor.
|
||||||
|
- Append/Prepend injects timestamped content into an existing entry.
|
||||||
|
- Bulk import creates entries from parsed files; export supports centralized templates.
|
||||||
|
- Entry edits support debounced auto-save when `journals_loc.entry.auto_save` is enabled.
|
||||||
|
- Full entry saves encrypt `content` into `content_encrypted` when the entry's `private`
|
||||||
|
flag is enabled; disabling `private` clears encrypted content/history fields.
|
||||||
|
- The non-reactive `decrypt_journal_entry()` helper isolates decryption from Svelte effects.
|
||||||
|
- Entry configuration exposes Actions, Metadata, Security, and JSON views. Trusted users
|
||||||
|
can Remove (disable); managers and administrators can hard Delete.
|
||||||
|
|
||||||
|
## Current Security Limitations
|
||||||
|
|
||||||
|
- `passcode_hash` is editable but is not compared as secondary authentication before
|
||||||
|
decryption. This remains an active task.
|
||||||
|
- Quick Add explicitly creates entries with `private: false`; import creates plaintext
|
||||||
|
content without setting encryption fields. These paths do not currently offer E2EE.
|
||||||
|
- Successful decryption currently logs a short plaintext preview to the browser console.
|
||||||
|
Removal is tracked as an active privacy fix.
|
||||||
|
- Outbound email sharing is not implemented and requires a product/security decision
|
||||||
|
because journal content is private.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Access and Privacy
|
||||||
|
|
||||||
|
Journals contain private personal data. The Journals layout renders module content only when
|
||||||
|
the user has `user_id`, `person_id`, and `trusted_access`. Treat all journal and journal-entry
|
||||||
|
routes, API responses, decrypted state, logs, exports, and future sharing features as private.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Docs
|
||||||
|
|
||||||
|
- `documentation/archive/PROJECT__AE_UI_Journals_Module_Update_2026.md`
|
||||||
|
- `documentation/TODO__Agents.md`
|
||||||
|
- `documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md`
|
||||||
|
- `documentation/GUIDE__AE_API_V3_for_Frontend.md`
|
||||||
|
- `documentation/BOOTSTRAP__AI_Agent_Quickstart.md`
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Aether Journals: Configuration & Settings Map
|
# Aether Journals: Configuration & Settings Map
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
This document tracks all available settings across the three levels of the Journals module.
|
This document tracks all available settings across the three levels of the Journals module.
|
||||||
|
|
||||||
## 1. Module Level (Global)
|
## 1. Module Level (Global)
|
||||||
@@ -51,9 +53,23 @@ This document tracks all available settings across the three levels of the Journ
|
|||||||
| `sort` | integer | Manual sort order weight. | Manual (Done) |
|
| `sort` | integer | Manual sort order weight. | Manual (Done) |
|
||||||
| `archive_on` | datetime | Scheduled date for automatic archiving. | Manual (Done) |
|
| `archive_on` | datetime | Scheduled date for automatic archiving. | Manual (Done) |
|
||||||
| `private` | boolean | Trigger for E2EE (Encryption). | Manual (Done) |
|
| `private` | boolean | Trigger for E2EE (Encryption). | Manual (Done) |
|
||||||
|
| `content_encrypted` | encrypted string | Encrypted entry content written during a full save when `private` is enabled. | Generated on save |
|
||||||
|
| `history_encrypted` | encrypted string | Encrypted entry history when history encryption is available. | Generated on save |
|
||||||
|
| `passcode_hash` | string | Entry-level secondary-auth field; comparison logic is not yet implemented. | Manual (Done) |
|
||||||
| `alert` | boolean | Trigger for visual "Alert" state. | Manual (Done) |
|
| `alert` | boolean | Trigger for visual "Alert" state. | Manual (Done) |
|
||||||
| `group` | string | Grouping key for the list view. | Manual (JSON only) |
|
| `group` | string | Grouping key for the list view. | Manual (JSON only) |
|
||||||
|
|
||||||
|
## Encryption Behavior and Gaps
|
||||||
|
|
||||||
|
1. Full entry saves combine the journal `passcode` and `private_passcode` to encrypt
|
||||||
|
plaintext content when the entry's `private` flag is enabled.
|
||||||
|
2. Decryption prefers a passcode typed in the current session, then falls back to the
|
||||||
|
journal `private_passcode`; the journal `passcode` is combined with that private key.
|
||||||
|
3. `passcode_hash` secondary-auth comparison is pending and must not be described as enforced.
|
||||||
|
4. Quick Add currently forces `private: false`, and bulk import creates plaintext entries
|
||||||
|
without encryption fields. Use the full editor to enable encryption until those workflows
|
||||||
|
are updated.
|
||||||
|
|
||||||
## 📐 Data Normalization Rules
|
## 📐 Data Normalization Rules
|
||||||
To prevent infinite reactivity loops and trivial save cycles, the following normalizations are applied before comparison:
|
To prevent infinite reactivity loops and trivial save cycles, the following normalizations are applied before comparison:
|
||||||
1. **Strings:** Trimmed and `null` treated as `""`.
|
1. **Strings:** Trimmed and `null` treated as `""`.
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
# Project: Pres Mgmt Config Cleanup & Config UI
|
# Project: Pres Mgmt Config Cleanup & Config UI
|
||||||
|
|
||||||
**Status:** Planning / Ready to Execute
|
**Status:** 🟡 ~70% Complete — Core Working, Cleanup Pass Needed
|
||||||
**Priority:** High (BGH conference in ~2 weeks; only one active event using pres_mgmt)
|
**Priority:** Medium (core features functional; remaining work is deprecation + consistency)
|
||||||
**Created:** 2026-04-02
|
**Created:** 2026-04-02
|
||||||
|
**Last Updated:** 2026-06-12 (regression audit)
|
||||||
**Related:** `TODO__Agents.md`, `PROJECT__Stores_Svelte5_Migration.md`
|
**Related:** `TODO__Agents.md`, `PROJECT__Stores_Svelte5_Migration.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -17,7 +18,7 @@ The `event.mod_pres_mgmt_json` config grew organically across several conference
|
|||||||
- Duplicate keys with different names (`file_purpose_option_kv` = `file_purpose_option_li`)
|
- Duplicate keys with different names (`file_purpose_option_kv` = `file_purpose_option_li`)
|
||||||
- Dead config (`HOLD__*` prefix)
|
- Dead config (`HOLD__*` prefix)
|
||||||
- Type inconsistency (`label__person_external_id: false` vs `"LCI member ID"` string)
|
- Type inconsistency (`label__person_external_id: false` vs `"LCI member ID"` string)
|
||||||
- Keys in the DB not consumed by `sync_config__event_pres_mgmt()`
|
- Keys in the DB not consumed by `sync_config__event_pres_mgmt()`
|
||||||
- Bug: `label__session_poc_name_short` is read then immediately overwritten (line 970-972 in ae_events__event.ts)
|
- Bug: `label__session_poc_name_short` is read then immediately overwritten (line 970-972 in ae_events__event.ts)
|
||||||
- `hide_launcher_link` / `hide_launcher_link_legacy` missing the `__` separator (inconsistent)
|
- `hide_launcher_link` / `hide_launcher_link_legacy` missing the `__` separator (inconsistent)
|
||||||
- `show_content__presentation_description` uses a third naming convention
|
- `show_content__presentation_description` uses a third naming convention
|
||||||
@@ -135,7 +136,7 @@ interface PressMgmtRemoteCfg {
|
|||||||
|
|
||||||
## New Svelte 5 Local Store
|
## New Svelte 5 Local Store
|
||||||
|
|
||||||
**Do NOT touch `events_loc` or the paused Svelte 5 migration.**
|
**Do NOT touch `events_loc` or the paused Svelte 5 migration.**
|
||||||
Instead, create a standalone store for pres_mgmt local config.
|
Instead, create a standalone store for pres_mgmt local config.
|
||||||
|
|
||||||
**File:** `src/lib/stores/ae_events_stores__pres_mgmt.svelte.ts`
|
**File:** `src/lib/stores/ae_events_stores__pres_mgmt.svelte.ts`
|
||||||
@@ -162,9 +163,9 @@ AFTER: pres_mgmt_loc.current.hide__session_code
|
|||||||
|
|
||||||
## Config UI Page
|
## Config UI Page
|
||||||
|
|
||||||
**Route:** `/events/[event_id]/(pres_mgmt)/pres_mgmt/config/`
|
**Route:** `/events/[event_id]/(pres_mgmt)/pres_mgmt/config/`
|
||||||
**Access:** `$ae_loc.manager_access` only
|
**Access:** `$ae_loc.manager_access` only
|
||||||
**Button visibility:** Edit mode only (`$ae_loc.edit_mode`)
|
**Button visibility:** Edit mode only (`$ae_loc.edit_mode`)
|
||||||
|
|
||||||
### Page behavior
|
### Page behavior
|
||||||
- Loads `event.mod_pres_mgmt_json` fresh from API on page open
|
- Loads `event.mod_pres_mgmt_json` fresh from API on page open
|
||||||
@@ -201,15 +202,27 @@ Safe and backward compatible — old DB records fall through to `?? false` defau
|
|||||||
|
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
- [ ] **Step 1** — Define `PressMgmtRemoteCfg` TypeScript interface (new file or in `ae_events__event.ts`)
|
- [x] **Step 1** — Define `PressMgmtRemoteCfg` TypeScript interface (new file or in `ae_events__event.ts`)
|
||||||
- [ ] **Step 2** — New `ae_events_stores__pres_mgmt.svelte.ts` with `PersistedState`; add version gate to `store_versions.ts`
|
- [x] **Step 2** — New `ae_events_stores__pres_mgmt.svelte.ts` with `PersistedState` ⚠️ **Missing:** version gate in `store_versions.ts`
|
||||||
- [ ] **Step 3** — Rewrite `sync_config__event_pres_mgmt()` in `ae_events__event.ts` to use canonical keys and write to the new store
|
- [x] **Step 3** — Rewrite `sync_config__event_pres_mgmt()` in `ae_events__event.ts` to use canonical keys ⚠️ **Issue:** `show__launcher_link_legacy` hard-coded instead of synced from remote
|
||||||
- [ ] **Step 4** — Build config UI page at `(pres_mgmt)/pres_mgmt/config/+page.svelte` (manager_access + edit_mode gated)
|
- [x] **Step 4** — Build config UI page at `(pres_mgmt)/pres_mgmt/config/+page.svelte` (manager_access + edit_mode gated)
|
||||||
- [ ] **Step 5** — Strip `ae_comp__event_settings_pres_mgmt_form.svelte` from settings page (or replace with a link to new page)
|
- [ ] **Step 5** — Strip `ae_comp__event_settings_pres_mgmt_form.svelte` from settings page (or replace with a link to new page) **BLOCKING**
|
||||||
- [ ] **Step 6** — Migrate all `$events_loc.pres_mgmt.*` references in pres_mgmt templates to `pres_mgmt_loc.current.*`
|
- [x] **Step 6** — Migrate all `$events_loc.pres_mgmt.*` references in pres_mgmt templates to `pres_mgmt_loc.current.*`
|
||||||
- [ ] **Step 7** — Update BGH (and any other active events) via new UI
|
- [ ] **Step 7** — Update BGH (and any other active events) via new UI (blocked on Step 5)
|
||||||
- [ ] **Step 8** — `npx svelte-check` clean; commit
|
- [ ] **Step 8** — `npx svelte-check` clean; commit
|
||||||
|
|
||||||
|
### Regression Fixes Needed (2026-06-12 Audit)
|
||||||
|
|
||||||
|
- [ ] **Add `show__launcher_link_legacy` to `PressMgmtRemoteCfg`** or remove entirely if deprecated
|
||||||
|
- Currently hard-coded to `true` in sync function (line 1054 `ae_events__event.ts`)
|
||||||
|
- Can't be controlled via config UI
|
||||||
|
- [ ] **Resolve `hide__launcher_link*` local/remote conflict**
|
||||||
|
- Menu toggles ([ae_comp__events_menu_opts.svelte](../src/routes/events/ae_comp__events_menu_opts.svelte) lines 462-494) use `hide__launcher_link` for LOCAL UI state
|
||||||
|
- Remote schema uses `show__launcher_link` (inverted)
|
||||||
|
- Decision: Keep separate? Document clearly? Unify?
|
||||||
|
- [ ] **Add `AE_PRES_MGMT_LOC_VERSION` to `store_versions.ts`** (Step 2 requirement)
|
||||||
|
- [ ] **Clean `hide__launcher_link*` from defaults** if truly deprecated (lines 154-155, 333-334 in `pres_mgmt_defaults.ts`)
|
||||||
|
|
||||||
### Step 6 scope (mechanical find-replace)
|
### Step 6 scope (mechanical find-replace)
|
||||||
|
|
||||||
The `$events_loc.pres_mgmt` pattern appears across:
|
The `$events_loc.pres_mgmt` pattern appears across:
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
# PROJECT: Site Passcode Security — API-Verified Auth
|
# PROJECT: Site Passcode Security — API-Verified Auth
|
||||||
|
|
||||||
**Last updated:** 2026-04-10
|
**Last Updated:** 2026-06-12
|
||||||
**Status:** Backend work in progress — frontend pending backend completion
|
**Last Verified Against Frontend Source:** 2026-06-12
|
||||||
**Priority:** High — passcodes for trusted/administrator access currently in localStorage plaintext
|
**Status:** Active security gap — frontend migration not started
|
||||||
|
**Priority:** High — passcodes for trusted/administrator access currently remain in localStorage plaintext
|
||||||
|
|
||||||
|
The frontend still caches `access_code_kv_json`, compares passcodes locally, and can log the
|
||||||
|
full passcode map when verbose logging is enabled. No frontend call to `/authenticate_passcode`
|
||||||
|
or passcode-JWT expiry restoration exists. Backend implementation is documented as completed,
|
||||||
|
but deployment must be confirmed in the backend repository/environment before frontend cutover.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -81,7 +87,11 @@ This gives session expiry without a network call on every page load.
|
|||||||
|
|
||||||
## Backend Changes Required
|
## Backend Changes Required
|
||||||
|
|
||||||
**Note:** The backend fixes described below have been implemented and tested in the `aether_api_fastapi` repository (the `/authenticate_passcode` endpoint now uses explicit role priority, returns a full passcode JWT with `auth_type: 'passcode'`, applies per-role TTLs, and validates passcode length). Frontend changes can proceed once the backend deployment with these fixes is available.
|
**Backend status note:** The fixes below were reported implemented and tested in the
|
||||||
|
`aether_api_fastapi` repository. This frontend-only audit did not verify the backend source or
|
||||||
|
deployment. Confirm that the deployed `/authenticate_passcode` uses explicit role priority,
|
||||||
|
returns a complete passcode JWT with `auth_type: 'passcode'`, applies per-role TTLs, and validates
|
||||||
|
passcode length before starting frontend cutover.
|
||||||
|
|
||||||
### Backend Agent Follow-Up
|
### Backend Agent Follow-Up
|
||||||
|
|
||||||
@@ -316,6 +326,19 @@ async def authenticate_passcode(
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Frontend Implementation Status
|
||||||
|
|
||||||
|
Verified 2026-06-12:
|
||||||
|
|
||||||
|
- [ ] Confirm the corrected backend endpoint is deployed and reachable.
|
||||||
|
- [ ] Replace local passcode comparison with API verification and JWT storage.
|
||||||
|
- [ ] Add pending/error UI for passcode authentication.
|
||||||
|
- [ ] Stop copying `access_code_kv_json` into frontend auth state.
|
||||||
|
- [ ] Validate passcode JWT expiry during session restoration.
|
||||||
|
- [ ] Remove `site_access_code_kv` from auth store defaults and types.
|
||||||
|
- [ ] Remove any logging of passcode maps or entered passcodes.
|
||||||
|
- [ ] Backend Phase 2: remove `access_code_kv_json` from the public bootstrap model.
|
||||||
|
|
||||||
## Frontend Changes Required
|
## Frontend Changes Required
|
||||||
|
|
||||||
**These depend on the backend fixes above being deployed first.**
|
**These depend on the backend fixes above being deployed first.**
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Project: Documentation Refresh and Archive Plan (2026)
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
|
**Goal:** Keep onboarding docs fast and current while preserving historical context in archive.
|
||||||
|
|
||||||
|
## 1) Naming Standard
|
||||||
|
|
||||||
|
Use one of these prefixes consistently:
|
||||||
|
- `BOOTSTRAP__` for first-read onboarding.
|
||||||
|
- `GUIDE__` for cross-module technical guides.
|
||||||
|
- `MODULE__` for module-specific docs.
|
||||||
|
- `PROJECT__` for active, time-bounded workstreams.
|
||||||
|
- `PROPOSAL__` for design proposals not yet adopted.
|
||||||
|
- `REFERENCE__` for evergreen troubleshooting/reference catalogs.
|
||||||
|
|
||||||
|
## 2) Module Coverage Baseline (Active)
|
||||||
|
|
||||||
|
Minimum one module doc per active module family:
|
||||||
|
- Events Presentation Management -> `MODULE__AE_Events_Presentation_Management.md`
|
||||||
|
- Events Launcher -> `MODULE__AE_Events_Launcher.md`
|
||||||
|
- Events Launcher Native -> `MODULE__AE_Events_Launcher_Native.md`
|
||||||
|
- Events Badges -> `MODULE__AE_Events_Badges.md`
|
||||||
|
- Events Leads -> `MODULE__AE_Events_Leads.md`
|
||||||
|
- Journals -> `MODULE__AE_Journals.md`
|
||||||
|
- IDAA Archives -> `MODULE__AE_IDAA_Archives.md`
|
||||||
|
- IDAA Bulletin Board -> `MODULE__AE_IDAA_Bulletin_Board.md`
|
||||||
|
- IDAA Recovery Meetings -> `MODULE__AE_IDAA_Recovery_Meetings.md`
|
||||||
|
- IDAA Video Conferences -> `MODULE__AE_IDAA_Video_Conferences.md`
|
||||||
|
|
||||||
|
## 3) Archive Strategy
|
||||||
|
|
||||||
|
Archive docs that are superseded, duplicate, or no longer operationally used.
|
||||||
|
Do not delete historical context; move to `documentation/archive/` with clear names.
|
||||||
|
|
||||||
|
### Completed in this pass
|
||||||
|
- Moved `MODULE__AE_Events_Launcher_Config_Menu_new.md` -> `archive/PROPOSAL__AE_Events_Launcher_Config_Menu_Unified_Vision_v3_1.md`.
|
||||||
|
- Moved `PROPOSAL__IDAA_UI_UX_Roadmap_2026.md` -> `archive/PROPOSAL__IDAA_Recovery_Meetings_UI_UX_Roadmap_2026.md`.
|
||||||
|
- Renamed `MODULE__AE Journals_config_map.md` -> `MODULE__AE_Journals_Config_Map.md`.
|
||||||
|
- Renamed `PROJECT__AE_UI_Journals_module_update_2026.md` -> `PROJECT__AE_UI_Journals_Module_Update_2026.md`.
|
||||||
|
- Renamed `AE_Docker_CI_cache_policy.md` -> `AE__Docker_CI_Cache_Policy.md`.
|
||||||
|
- Archived `PROJECT__AE_Style_Review.md` -> `archive/PROJECT__AE_Style_Review_2026-03.md` (active style source is `GUIDE__AE_UI_Style_Guidelines.md`).
|
||||||
|
- Clarified scope split: `GUIDE__AE_Events_Badges_Onsite.md` = deep badge-print reference, `GUIDE__AE_Events_Onsite_Runbook.md` = cross-module onsite operations runbook.
|
||||||
|
- Validated all `documentation/*.md` references in active docs; no missing targets remain.
|
||||||
|
- Added ownership and review-trigger metadata to the bootstrap, task list, and docs index.
|
||||||
|
- Reviewed active project docs for archive eligibility. Object Field Editor and Site Passcode Security remain active and were added to the docs index.
|
||||||
|
- Archived legacy API-object, component-inventory, data-structure, performance, and UI-pattern references that contradicted V3 IDs, Svelte 5, or current private-route execution rules.
|
||||||
|
- Refreshed `AE__Architecture.md` and `AE__Naming_Conventions.md` as the active replacements.
|
||||||
|
- Added `documentation/archive/README.md` to explain archive categories and restoration policy.
|
||||||
|
- Renamed `AE__Docker_CI_Cache_Policy.md` -> `GUIDE__Docker_CI_Cache_Policy.md`.
|
||||||
|
- Renamed `AE__UI_UX_future_ideas.md` -> `PROPOSAL__AE_UI_UX_Future_Ideas.md`.
|
||||||
|
- Audited the Journals UI update against current source and archived
|
||||||
|
`PROJECT__AE_UI_Journals_Module_Update_2026.md`; remaining security work was moved to
|
||||||
|
the active task list and module documentation.
|
||||||
|
- Audited the Badges review/print project against current source and archived
|
||||||
|
`PROJECT__AE_Events_Badges_Review_Print.md`; email delivery and permission-source
|
||||||
|
unification remain active follow-ups.
|
||||||
|
- Audited Site Passcode Security against current source. It remains an active high-priority
|
||||||
|
project because plaintext client storage and local passcode comparison are still present.
|
||||||
|
- Audited V3 CRUD upgrade project against source (2026-06-12). All production code migrated
|
||||||
|
to V3; legacy wrappers remain exported but unused. Archived `PROJECT__Use_AE_API_V3_CRUD_upgrade.md`.
|
||||||
|
Optional cleanup task added to TODO for removing dead wrapper code.
|
||||||
|
- Audited Field Editor V3 project against source (2026-06-12). Component complete with 50+ active
|
||||||
|
usages, GUIDE documentation, and all core field types. Searchable dropdowns deferred as optional
|
||||||
|
enhancement. Archived `PROJECT__AE_Object_Field_Editor_V3_upgrade.md`.
|
||||||
|
- Audited Pres Mgmt Config Cleanup project (2026-06-12). Core infrastructure working (~70% complete)
|
||||||
|
but regressions identified: `show__launcher_link_legacy` missing from schema, old settings form
|
||||||
|
not removed, local/remote key conflicts. Updated project doc with regression fixes; keeping active.
|
||||||
|
|
||||||
|
### Next archive candidates (review + approve)
|
||||||
|
- Older style-review snapshots once current style guide references are centralized.
|
||||||
|
- Closed project docs where TODO/archive already capture final status.
|
||||||
|
- Duplicate docs where one `MODULE__` file and one `PROJECT__` file now overlap heavily.
|
||||||
|
|
||||||
|
## 4) Review Cadence
|
||||||
|
|
||||||
|
Monthly lightweight review:
|
||||||
|
1. Verify `BOOTSTRAP__AI_Agent_Quickstart.md` links still resolve and reflect active work.
|
||||||
|
2. Verify each active module has one current `MODULE__` anchor doc.
|
||||||
|
3. Move stale proposals and completed projects into archive.
|
||||||
|
4. Update `REFERENCE__Common_Agent_Mistakes.md` keep/archive sections.
|
||||||
|
|
||||||
|
## 5) Immediate Follow-Up Tasks
|
||||||
|
|
||||||
|
1. Continue quarterly archive reviews for remaining stale `PROJECT__` docs; the Journals and Badges projects were archived on 2026-06-12, while Site Passcode Security remains active.
|
||||||
|
2. Continue the broader permission-helper and IDAA authentication review; the Site Passcode section was source-verified on 2026-06-12.
|
||||||
|
3. Review module docs against current routes and store names rather than relying only on filename/header freshness.
|
||||||
|
4. Add a lightweight reusable link-check script if manual path validation becomes frequent.
|
||||||
267
documentation/PROJECT__IDAA_Stores_Svelte5_Migration_2026.md
Normal file
267
documentation/PROJECT__IDAA_Stores_Svelte5_Migration_2026.md
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
# PROJECT: IDAA `idaa_loc` Migration to Svelte 5 `PersistedState`
|
||||||
|
|
||||||
|
**Last Updated:** 2026-06-12
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Migrate IDAA persisted local state from legacy `svelte-persisted-store` (`$idaa_loc`) to Svelte 5 `PersistedState` (`idaa_loc.current`) without behavior regressions, auth leaks, or broken page flows.
|
||||||
|
|
||||||
|
Primary target store:
|
||||||
|
- `src/lib/stores/ae_idaa_stores__idaa_loc.svelte.ts` ← new store (created, not yet wired in)
|
||||||
|
|
||||||
|
Legacy source currently used by routes:
|
||||||
|
- `src/lib/stores/ae_idaa_stores.ts` ← remove `idaa_loc` export after migration
|
||||||
|
|
||||||
|
## Why This Matters
|
||||||
|
- Removes coarse-grained reactivity side effects from legacy persisted store access.
|
||||||
|
- Aligns IDAA with the completed Events store migration pattern.
|
||||||
|
- Reduces risk of auth-state corruption from broad re-renders triggered by unrelated
|
||||||
|
writes to the same store key.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
In scope:
|
||||||
|
- Replace all `idaa_loc` imports from `ae_idaa_stores.ts` with imports from
|
||||||
|
`ae_idaa_stores__idaa_loc.svelte.ts`.
|
||||||
|
- Replace all `$idaa_loc.*` reads/writes with `idaa_loc.current.*`.
|
||||||
|
- Remove `idaa_loc` export and its `persisted()` definition from `ae_idaa_stores.ts`
|
||||||
|
after all consumers are migrated.
|
||||||
|
- Keep `store_versions.ts` wipe call for `ae_idaa_loc` — this cleans old data from
|
||||||
|
browsers of returning users. Keep it for at least one year post-migration (same rule
|
||||||
|
as `ae_events_loc`).
|
||||||
|
|
||||||
|
Out of scope:
|
||||||
|
- Migrating `idaa_sess`, `idaa_slct`, `idaa_trig`, `idaa_prom` — in-memory writables,
|
||||||
|
no coarse-reactivity problem.
|
||||||
|
- Backend/API changes.
|
||||||
|
- `ae_loc` migration (separate project).
|
||||||
|
|
||||||
|
## Consumer File Inventory
|
||||||
|
|
||||||
|
### Files requiring import + `$idaa_loc` → `idaa_loc.current` changes (29 files)
|
||||||
|
|
||||||
|
#### IDAA module root layouts (auth-critical — do these first and last)
|
||||||
|
|
||||||
|
| File | `$idaa_loc` hits | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `src/routes/idaa/+layout.svelte` | 3 | Top-level IDAA layout; `/idaa/clear-caches` lives outside this |
|
||||||
|
| `src/routes/idaa/(idaa)/+layout.svelte` | 40 | Most complex. Novi verification loop, auth escalation, admin/trusted list writes |
|
||||||
|
|
||||||
|
#### Bulletin Board (BB)
|
||||||
|
|
||||||
|
| File | `$idaa_loc` hits |
|
||||||
|
| --- | --- |
|
||||||
|
| `src/routes/idaa/(idaa)/bb/+layout.svelte` | 3 |
|
||||||
|
| `src/routes/idaa/(idaa)/bb/+page.svelte` | 11 |
|
||||||
|
| `src/routes/idaa/(idaa)/bb/[post_id]/+page.svelte` | 6 |
|
||||||
|
| `src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_obj_li.svelte` | 4 |
|
||||||
|
| `src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_obj_id_edit.svelte` | 11 |
|
||||||
|
| `src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_obj_id_view.svelte` | low |
|
||||||
|
| `src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_comment_obj_id_edit.svelte` | 7 |
|
||||||
|
| `src/routes/idaa/(idaa)/bb/ae_idaa_comp__post_options.svelte` | 18 |
|
||||||
|
|
||||||
|
#### Archives
|
||||||
|
|
||||||
|
| File | `$idaa_loc` hits |
|
||||||
|
| --- | --- |
|
||||||
|
| `src/routes/idaa/(idaa)/archives/+layout.svelte` | low |
|
||||||
|
| `src/routes/idaa/(idaa)/archives/+page.svelte` | 6 |
|
||||||
|
| `src/routes/idaa/(idaa)/archives/ae_idaa_comp__media_player.svelte` | low |
|
||||||
|
| `src/routes/idaa/(idaa)/archives/[archive_id]/+page.svelte` | 11 |
|
||||||
|
| `src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_obj_id_edit.svelte` | 4 |
|
||||||
|
| `src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_obj_id_view.svelte` | 16 |
|
||||||
|
| `src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_content_obj_id_edit.svelte` | 4 |
|
||||||
|
| `src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__archive_content_obj_li.svelte` | low |
|
||||||
|
| `src/routes/idaa/(idaa)/archives/[archive_id]/ae_idaa_comp__modal_media_player.svelte` | low |
|
||||||
|
|
||||||
|
#### Recovery Meetings
|
||||||
|
|
||||||
|
| File | `$idaa_loc` hits |
|
||||||
|
| --- | --- |
|
||||||
|
| `src/routes/idaa/(idaa)/recovery_meetings/+layout.svelte` | low |
|
||||||
|
| `src/routes/idaa/(idaa)/recovery_meetings/+page.svelte` | 37 |
|
||||||
|
| `src/routes/idaa/(idaa)/recovery_meetings/[event_id]/+page.svelte` | 10 |
|
||||||
|
| `src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_qry.svelte` | 41 |
|
||||||
|
| `src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li.svelte` | 8 |
|
||||||
|
| `src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_id_edit.svelte` | 12 |
|
||||||
|
| `src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_id_view.svelte` | low |
|
||||||
|
|
||||||
|
#### Other IDAA
|
||||||
|
|
||||||
|
| File | `$idaa_loc` hits |
|
||||||
|
| --- | --- |
|
||||||
|
| `src/routes/idaa/(idaa)/jitsi_reports/+page.svelte` | low |
|
||||||
|
| `src/routes/idaa/(idaa)/video_conferences/+page.svelte` | 12 |
|
||||||
|
|
||||||
|
### Files that reference `ae_idaa_loc` as a raw string only — NO changes needed
|
||||||
|
- `src/routes/events/+layout.svelte` — `localStorage.removeItem('ae_idaa_loc')` (sign-out)
|
||||||
|
- `src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_local_actions.svelte` — same
|
||||||
|
- `src/lib/stores/store_versions.ts` — `_check_and_wipe('ae_idaa_loc', ...)` — keep as-is
|
||||||
|
|
||||||
|
## Safety Rules
|
||||||
|
1. Do this as one atomic migration — no split old/new `idaa_loc` consumers in the same session.
|
||||||
|
2. Do not loosen any IDAA auth gating — IDAA is private data, always authenticated.
|
||||||
|
3. Do not move IDAA private data loads into `+page.ts` / `+layout.ts` where prefetch can run.
|
||||||
|
4. Keep route behavior and permissions exactly as current production behavior.
|
||||||
|
5. The `(idaa)/+layout.svelte` auth gate is the most sensitive file. Review it by hand after
|
||||||
|
the mechanical pass — do not rely solely on `svelte-check` to catch auth logic regressions.
|
||||||
|
|
||||||
|
## Key Syntax Changes
|
||||||
|
|
||||||
|
| Before | After |
|
||||||
|
| --- | --- |
|
||||||
|
| `import { idaa_loc } from '$lib/stores/ae_idaa_stores'` | `import { idaa_loc } from '$lib/stores/ae_idaa_stores__idaa_loc.svelte'` |
|
||||||
|
| `$idaa_loc.novi_uuid` | `idaa_loc.current.novi_uuid` |
|
||||||
|
| `$idaa_loc.bb.qry__hidden = 'not_hidden'` | `idaa_loc.current.bb.qry__hidden = 'not_hidden'` |
|
||||||
|
| `$idaa_loc.recovery_meetings.qry__favorites_only` | `idaa_loc.current.recovery_meetings.qry__favorites_only` |
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Keep other imports from `ae_idaa_stores` (`idaa_sess`, `idaa_slct`, `idaa_trig`, `idaa_prom`)
|
||||||
|
unchanged — they stay in that file.
|
||||||
|
- No `$` sigil — access via `.current` property, not Svelte store subscription.
|
||||||
|
|
||||||
|
## Execution Plan
|
||||||
|
|
||||||
|
### Phase 0: Baseline Checkpoint
|
||||||
|
- Ensure clean compile baseline: `npx svelte-check` → 0/0.
|
||||||
|
- Confirm new store file committed: `ae_idaa_stores__idaa_loc.svelte.ts` ✅ (done 2026-06-11)
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
- `svelte-check` 0 errors / 0 warnings.
|
||||||
|
|
||||||
|
### Phase 1: Store Consumer Conversion (Mechanical)
|
||||||
|
|
||||||
|
Recommended order — least to most auth-critical, so layouts are done fresh at the end:
|
||||||
|
|
||||||
|
#### 1a. Sub-module components (BB, Archives, Recovery Meetings)
|
||||||
|
|
||||||
|
All `ae_idaa_comp__*` component files — pure consumers, no auth logic.
|
||||||
|
|
||||||
|
#### 1b. Sub-module pages and layouts (BB, Archives, Recovery Meetings)
|
||||||
|
|
||||||
|
Page and layout files for the three sub-modules. Sub-layouts (`bb/+layout.svelte`,
|
||||||
|
`archives/+layout.svelte`, `recovery_meetings/+layout.svelte`) check auth indirectly
|
||||||
|
but don't own the Novi verification loop.
|
||||||
|
|
||||||
|
#### 1c. Jitsi reports + video conferences
|
||||||
|
|
||||||
|
Low-hit, isolated pages.
|
||||||
|
|
||||||
|
#### 1d. `src/routes/idaa/+layout.svelte` (top-level)
|
||||||
|
|
||||||
|
Reads `$idaa_loc` for display only; writes happen in the (idaa) inner layout.
|
||||||
|
|
||||||
|
#### 1e. `src/routes/idaa/(idaa)/+layout.svelte` (innermost — do last)
|
||||||
|
|
||||||
|
Most complex file (40 hits). Owns the Novi verification loop and all auth escalation writes.
|
||||||
|
The `$idaa_loc.bb.qry__hidden`, `$idaa_loc.novi_admin_li`, etc. writes all live here.
|
||||||
|
Review by hand after mechanical pass.
|
||||||
|
|
||||||
|
For each file:
|
||||||
|
1. Update import — change `idaa_loc` source, keep `idaa_sess` / `idaa_slct` / etc. from `ae_idaa_stores`.
|
||||||
|
2. Replace `$idaa_loc.` → `idaa_loc.current.` (global find-replace within file).
|
||||||
|
3. No other logic changes — mechanical pass only.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
- No remaining `$idaa_loc.` usages in `src/routes/idaa/`.
|
||||||
|
- No `idaa_loc` imports pointing to `ae_idaa_stores` in route files.
|
||||||
|
|
||||||
|
### Phase 2: Store File Cleanup
|
||||||
|
After all consumers are migrated:
|
||||||
|
- Remove `idaa_loc` export and `persisted('ae_idaa_loc', idaa_local_data_struct)` from
|
||||||
|
`ae_idaa_stores.ts`.
|
||||||
|
- Remove `AE_IDAA_LOC_VERSION` import from `ae_idaa_stores.ts` (no longer needed there).
|
||||||
|
- Keep `store_versions.ts` unchanged — the `_check_and_wipe` call stays to clean old data.
|
||||||
|
|
||||||
|
### Phase 3: Critical Auth Layout Validation
|
||||||
|
Manually review after conversion:
|
||||||
|
- `src/routes/idaa/(idaa)/+layout.svelte` — Novi verification `$effect`, auth escalation, sign-out
|
||||||
|
- `src/routes/idaa/+layout.svelte` — top-level auth gate template
|
||||||
|
- `src/routes/idaa/(idaa)/bb/+layout.svelte`
|
||||||
|
- `src/routes/idaa/(idaa)/archives/+layout.svelte`
|
||||||
|
- `src/routes/idaa/(idaa)/recovery_meetings/+layout.svelte`
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
- Auth gate still blocks unauthenticated users on all sub-routes.
|
||||||
|
- `novi_verified`, `novi_uuid`, trusted/admin flag writes work correctly.
|
||||||
|
- No duplicate or skipped verification loops.
|
||||||
|
- `bb.qry__hidden`, `bb.qry__enabled` reset after Novi verification still fires.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
- Auth flow matches current production behavior exactly.
|
||||||
|
|
||||||
|
### Phase 4: Compile + Search Guards
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
npx svelte-check
|
||||||
|
grep -rn '\$idaa_loc\.' src/
|
||||||
|
grep -rn "from '\$lib/stores/ae_idaa_stores'" src/routes/idaa/
|
||||||
|
```
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
- `svelte-check`: 0 errors / 0 warnings.
|
||||||
|
- No `$idaa_loc.` references remaining in source.
|
||||||
|
- No `idaa_loc` imports from `ae_idaa_stores` in route files.
|
||||||
|
|
||||||
|
### Phase 5: Test File Updates
|
||||||
|
The IDAA Novi auth test (`tests/idaa_novi_auth.test.ts`) seeds `ae_idaa_loc` via
|
||||||
|
`addInitScript`. After migration:
|
||||||
|
- The seeded structure remains valid (same key, same shape).
|
||||||
|
- Remove any `ver:` field from the seed if present — `PersistedState` stores don't use it.
|
||||||
|
- Verify the full nested structure is still seeded (the `bb`, `archives`, `recovery_meetings`
|
||||||
|
objects must be present — see the "Seed the Full ae_idaa_loc Structure" lesson in `tests/README.md`).
|
||||||
|
|
||||||
|
### Phase 6: Runtime Smoke Test
|
||||||
|
Test flows:
|
||||||
|
1. Direct navigation to `/idaa/` — auth gate behavior correct.
|
||||||
|
2. Bulletin Board: list, post view, post edit, comment.
|
||||||
|
3. Archives: list, archive detail, media player.
|
||||||
|
4. Recovery Meetings: list, search/filter, favorites toggle, edit form.
|
||||||
|
5. Video Conferences page loads.
|
||||||
|
6. Jitsi Reports page loads.
|
||||||
|
7. Cache clear page (`/idaa/clear-caches`) still clears state and posts message to parent.
|
||||||
|
8. Sign-out clears `ae_idaa_loc` (the localStorage key name is unchanged, so this works
|
||||||
|
automatically for any caller using `localStorage.removeItem('ae_idaa_loc')`).
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
- No regressions in primary user paths.
|
||||||
|
|
||||||
|
## Risk Register
|
||||||
|
|
||||||
|
### R1: Split-brain state
|
||||||
|
**Risk:** Mixed old/new `idaa_loc` consumers in the same session can lead to inconsistent state.
|
||||||
|
**Mitigation:** Convert all consumers in one agent pass before committing. Do not commit partial migrations.
|
||||||
|
|
||||||
|
### R2: Auth regression in `(idaa)/+layout.svelte`
|
||||||
|
**Risk:** The Novi verification loop is complex (40 `$idaa_loc` hits). A subtle change to
|
||||||
|
`$effect` dependency tracking between old and new store access could break or skip verification.
|
||||||
|
**Mitigation:** Review this file by hand after the mechanical pass. Test auth flow explicitly.
|
||||||
|
|
||||||
|
### R3: Nested object merge gap
|
||||||
|
**Risk:** The `deserialize` function does a shallow spread at the top level only:
|
||||||
|
`{ ...idaa_loc_defaults, ...JSON.parse(raw) }`. If a new field is added inside `bb`,
|
||||||
|
`archives`, or `recovery_meetings` after a user has stored data, that field will get
|
||||||
|
`undefined` rather than its default.
|
||||||
|
**Mitigation:** This is the same accepted trade-off as the events sub-stores. If a new
|
||||||
|
nested field is added in the future, add a migration step or accept that the old stored
|
||||||
|
value takes over wholesale.
|
||||||
|
|
||||||
|
### R4: Mechanical typo / missed reference
|
||||||
|
**Risk:** High replacement count (29 files, ~300+ hits) introduces missed `$idaa_loc.` references.
|
||||||
|
**Mitigation:** Run the grep guard in Phase 4 before declaring done.
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
If issues are found after migration:
|
||||||
|
1. `git revert` the migration commit(s).
|
||||||
|
2. Re-run `npx svelte-check`.
|
||||||
|
3. Re-attempt using smaller batches per sub-module (BB only, then Archives, then Recovery Meetings).
|
||||||
|
|
||||||
|
## Deliverables
|
||||||
|
- All 29 consumer files converted from `$idaa_loc.*` → `idaa_loc.current.*`.
|
||||||
|
- `idaa_loc` export removed from `ae_idaa_stores.ts`.
|
||||||
|
- Passing compile check (0/0).
|
||||||
|
- Smoke-tested all IDAA sub-modules.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- Full `idaa_loc` migration complete.
|
||||||
|
- No auth/privacy regressions.
|
||||||
|
- `svelte-check` 0/0.
|
||||||
|
- IDAA smoke-tested (auth gate, BB, Archives, Recovery Meetings, cache clear).
|
||||||
@@ -1,97 +1,122 @@
|
|||||||
# Project: Svelte 4 Store → Svelte 5 State Migration
|
# Project: Svelte 4 Store → Svelte 5 State Migration
|
||||||
|
|
||||||
**Status:** Execution / Phase B (In Progress)
|
**Status:** Events module — COMPLETE. Core / IDAA — In Progress (field cleanup done, PersistedState pending).
|
||||||
**Priority:** High (post-April 2026 conference)
|
**Priority:** High
|
||||||
**Created:** 2026-03-30
|
**Created:** 2026-03-30
|
||||||
**Related:** `TODO__Agents.md` — [Stores] Svelte 5 State Migration entry
|
**Last Updated:** 2026-06-11
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Background
|
## Background
|
||||||
|
|
||||||
All core Aether stores (`ae_loc`, `idaa_loc`, `ae_events_loc`, etc.) are being migrated from
|
All core Aether stores were built with `svelte-persisted-store` (Svelte 4 contract). This provides
|
||||||
Svelte 4 stores to Svelte 5 `$state` using the `runed` library's `PersistedState`. This provides
|
coarse reactivity: any write to any field notifies *all* subscribers and re-serializes the entire
|
||||||
fine-grained reactivity, ensuring that effects only re-run when specific fields they access are
|
object. For large stores like `ae_loc` and `ae_events_loc`, this caused unnecessary re-renders and
|
||||||
updated, rather than on every write to the store object.
|
was the root cause of the IDAA "Access Denied" corruption bug (a bootstrap write to `ae_loc` would
|
||||||
|
overwrite `authenticated_access` if a persisted value was slightly different, corrupting IDAA
|
||||||
|
member state stored in the same key).
|
||||||
|
|
||||||
### Phase B Progress & Learnings (Updated 2026-03-30)
|
The migration target: replace all `persisted()` stores with `runed`'s `PersistedState`, which uses
|
||||||
|
Svelte 5 fine-grained reactivity — a write to one field only triggers effects that read that field.
|
||||||
1. **Dependency Installed**: `runed` is now a project dependency.
|
|
||||||
2. **Module Resolution Strategy**:
|
|
||||||
- Core store files renamed: `ae_stores.ts` → `ae_stores.svelte.ts`, etc.
|
|
||||||
- **Critical Discovery**: SvelteKit and CLI tools (`svelte-check`) struggled to resolve
|
|
||||||
extension-less imports (like `$lib/stores/ae_stores`) when only `.svelte.ts` existed.
|
|
||||||
- **Solution**: Created `.ts` wrapper files (e.g., `src/lib/stores/ae_stores.ts`) that
|
|
||||||
simply re-export everything via `export * from './ae_stores.svelte'`. This maintains
|
|
||||||
backward compatibility for all existing import paths without manual updates.
|
|
||||||
3. **API Confirmation**: Confirmed that `runed`'s `PersistedState` uses `.current` to access
|
|
||||||
the state object, matching the intended migration syntax.
|
|
||||||
4. **Mass Replacement**:
|
|
||||||
- `$ae_loc` → `ae_loc_v5.current`
|
|
||||||
- `$idaa_loc` → `idaa_loc_v5.current`
|
|
||||||
- `$events_loc` → `events_loc_v5.current`
|
|
||||||
- These replacements have been applied across the entire `src/` directory (~2000+ sites).
|
|
||||||
5. **Import Updates**: A robust Python script was used to surgically add `ae_loc_v5`,
|
|
||||||
`idaa_loc_v5`, and `events_loc_v5` to existing import blocks, avoiding `import type`
|
|
||||||
lines and duplicates.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Syntax Changes: Before / After
|
## Completed: Events Module (2026-06-11)
|
||||||
|
|
||||||
### Store declaration
|
All `ae_events_stores` sub-modules have been promoted to their own `PersistedState` stores and
|
||||||
|
`events_loc` (the old `persisted()` store) has been **fully retired**.
|
||||||
|
|
||||||
```typescript
|
| Store | File | localStorage key | Status |
|
||||||
// BEFORE (ae_stores.svelte.ts)
|
|---|---|---|---|
|
||||||
export const ae_loc: Writable<key_val> = persisted('ae_loc', defaults);
|
| `badges_loc` | `ae_events_stores__badges.svelte.ts` | `ae_badges_loc` | ✅ Done (2026-04-02) |
|
||||||
|
| `leads_loc` | `ae_events_stores__leads.svelte.ts` | `ae_leads_loc` | ✅ Done (2026-04-03) |
|
||||||
|
| `pres_mgmt_loc` | `ae_events_stores__pres_mgmt.svelte.ts` | `ae_pres_mgmt_loc` | ✅ Done (2026-04-03) |
|
||||||
|
| `launcher_loc` | `ae_events_stores__launcher.svelte.ts` | `ae_launcher_loc` | ✅ Done (2026-06-11) |
|
||||||
|
| `events_auth_loc` | `ae_events_stores__auth.svelte.ts` | `ae_events_auth_loc` | ✅ Done (2026-06-11) |
|
||||||
|
| `events_loc` | *(retired)* | `ae_events_loc` | ✅ Store removed (2026-06-11) |
|
||||||
|
|
||||||
// AFTER (ae_stores.svelte.ts)
|
`ae_events_stores.ts` now only exports `events_sess` (in-memory writable, no migration needed),
|
||||||
export const ae_loc_v5 = new PersistedState('ae_loc', defaults);
|
`events_slct`, `events_trig`, `events_trig_kv`, `events_trigger`, and the `EVENTS_MODULE_TITLE`
|
||||||
```
|
constant. The file no longer imports `svelte-persisted-store`.
|
||||||
|
|
||||||
### Reading/Writing (Consumers)
|
`store_versions.ts` still calls `_check_and_wipe('ae_events_loc', AE_EVENTS_LOC_VERSION)` to clean
|
||||||
|
old data out of users' browsers — this is intentional and should be kept for at least one year.
|
||||||
```svelte
|
|
||||||
<!-- BEFORE -->
|
|
||||||
{$ae_loc.theme_mode}
|
|
||||||
{#if $ae_loc.trusted_access}
|
|
||||||
|
|
||||||
<!-- AFTER -->
|
|
||||||
{ae_loc_v5.current.theme_mode}
|
|
||||||
{#if ae_loc_v5.current.trusted_access}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Batch Update Pattern
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Use Object.assign for multi-field updates to maintain fine-grained reactivity
|
|
||||||
Object.assign(ae_loc_v5.current, { theme_mode: 'dark', edit_mode: true });
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Implementation Notes
|
## In Progress / Remaining: `ae_stores.ts` and `ae_idaa_stores.ts`
|
||||||
|
|
||||||
### Prettier & Formatting
|
Both stores had their unused default properties pruned (2026-06-11), reducing migration scope:
|
||||||
Because the mass migration touches ~250 files, formatting may be inconsistent (especially in
|
|
||||||
import blocks). It is recommended to run `npm run format` (if available) or rely on standard
|
|
||||||
linting after the imports are stabilized.
|
|
||||||
|
|
||||||
### Batching vs. "Big Sweep"
|
**`ae_loc`** (in `ae_stores.ts`) — still `persisted('ae_loc', ...)`:
|
||||||
While the original plan suggested smaller batches, the global nature of `ae_loc` and the
|
- Remaining fields: auth/identity, theme, permissions, ui config, file upload tracking, query prefs
|
||||||
hundreds of interdependent files made a "Big Sweep" more efficient to ensure the app remains
|
- This is the highest-impact remaining migration — used in nearly every route
|
||||||
in a consistent state. However, verification should still be done module-by-module.
|
- Root cause of IDAA "Access Denied" bug (coarse write during bootstrap stomps on permission fields)
|
||||||
|
|
||||||
|
**`idaa_loc`** (in `ae_idaa_stores.ts`) — still `persisted('ae_idaa_loc', ...)`:
|
||||||
|
- Remaining fields: `novi_uuid/verified/ts`, `novi_admin_li/trusted_li`, `archives/bb/recovery_meetings` sub-objects
|
||||||
|
- Fields pruned (2026-06-11): `ds`, `idaa_cfg_json`, top-level `qry__*`, `novi_*_base_url`, `novi_rate_limited_until`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Current Status (Pause Point)
|
## Migration Pattern (Established)
|
||||||
|
|
||||||
- [x] Phase A: Plan written.
|
Each sub-store follows this pattern, using the `badges` store as the canonical reference:
|
||||||
- [x] Phase B: Core infrastructure setup (runed, renames, wrappers).
|
|
||||||
- [x] Phase B: Mass variable replacement ($ae_loc -> ae_loc_v5.current).
|
|
||||||
- [/] Phase B: Mass import update (In Progress/Verifying).
|
|
||||||
- [ ] Phase B: Final validation (`svelte-check` clean).
|
|
||||||
|
|
||||||
**Do NOT stage or commit until `npx svelte-check` is fully verified.**
|
### 1. Defaults file (`*_defaults.ts`)
|
||||||
The app currently has a high error count due to the transition period where imports are being
|
Define the shape and defaults as a plain TypeScript object (and interface if complex):
|
||||||
re-aligned. Final verification is the next step after the pause.
|
|
||||||
|
```ts
|
||||||
|
export interface BadgesLocState { ... }
|
||||||
|
export const badges_loc_defaults: BadgesLocState = { ... };
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Store file (`*.svelte.ts`)
|
||||||
|
```ts
|
||||||
|
import { PersistedState } from 'runed';
|
||||||
|
import { badges_loc_defaults } from './ae_events_stores__badges_defaults';
|
||||||
|
|
||||||
|
export const badges_loc = new PersistedState('ae_badges_loc', badges_loc_defaults, {
|
||||||
|
serializer: {
|
||||||
|
serialize: JSON.stringify,
|
||||||
|
// Merge with defaults so new fields added after first session get their defaults.
|
||||||
|
deserialize: (raw: string) => ({ ...badges_loc_defaults, ...JSON.parse(raw) })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Consumer syntax
|
||||||
|
```ts
|
||||||
|
// Import (note .svelte extension, not .svelte.ts):
|
||||||
|
import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte';
|
||||||
|
|
||||||
|
// Read:
|
||||||
|
badges_loc.current.fulltext_search_qry_str
|
||||||
|
|
||||||
|
// Write (fine-grained — only triggers effects that read this field):
|
||||||
|
badges_loc.current.fulltext_search_qry_str = 'hello';
|
||||||
|
|
||||||
|
// Bulk reset:
|
||||||
|
badges_loc.current = { ...badges_loc_defaults };
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key differences from old `svelte-persisted-store` pattern:
|
||||||
|
| | Old (`persisted()`) | New (`PersistedState`) |
|
||||||
|
|---|---|---|
|
||||||
|
| Import | `import { events_loc } from '...ae_events_stores'` | `import { badges_loc } from '...ae_events_stores__badges.svelte'` |
|
||||||
|
| Read | `$events_loc.badges.field` | `badges_loc.current.field` |
|
||||||
|
| Write | `$events_loc.badges.field = x` | `badges_loc.current.field = x` |
|
||||||
|
| Reactivity | Coarse — entire store notified | Fine-grained — only affected fields |
|
||||||
|
| In `$effect` | Subscribes to entire store | Only subscribes to fields you read |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **`idaa_loc` → PersistedState** — Highest priority for IDAA stability. Promotes `novi_uuid/verified`,
|
||||||
|
`archives`, `bb`, `recovery_meetings` sub-objects to their own stores following the same pattern.
|
||||||
|
Primary benefit: eliminates the IDAA "Access Denied" corruption from `ae_loc` bootstrap writes.
|
||||||
|
|
||||||
|
2. **`ae_loc` → PersistedState** — Largest scope (~every route in the app). Defer until after
|
||||||
|
`idaa_loc` is done. Consider extracting `auth_loc` (the identity/permission fields) as the
|
||||||
|
first sub-store since those are the fields implicated in the IDAA corruption bug.
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
> Collection of concrete UX improvements for the Aether frontend. Each entry includes
|
> Collection of concrete UX improvements for the Aether frontend. Each entry includes
|
||||||
> the rationale, current behavior, proposed change, and implementation notes.
|
> the rationale, current behavior, proposed change, and implementation notes.
|
||||||
> **Date:** 2026-05-17
|
> **Created:** 2026-05-17
|
||||||
|
> **Last Updated:** 2026-05-18
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
68
documentation/README__Docs_Index.md
Normal file
68
documentation/README__Docs_Index.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Aether SvelteKit — Documentation Index
|
||||||
|
|
||||||
|
**Doc Owner:** Frontend platform maintainers (OSIT)
|
||||||
|
**Review Trigger:** Update whenever a documentation file is added, renamed, archived, or restored.
|
||||||
|
|
||||||
|
Use this file as the routing map for project documentation.
|
||||||
|
|
||||||
|
## 1) First Read
|
||||||
|
|
||||||
|
- `documentation/BOOTSTRAP__AI_Agent_Quickstart.md`
|
||||||
|
- `documentation/TODO__Agents.md`
|
||||||
|
|
||||||
|
## 2) Core Guides
|
||||||
|
|
||||||
|
- `documentation/GUIDE__Development.md`
|
||||||
|
- `documentation/GUIDE__AE_API_V3_for_Frontend.md`
|
||||||
|
- `documentation/GUIDE__AE_API_V3_for_Frontend_websockets.md`
|
||||||
|
- `documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md`
|
||||||
|
- `documentation/GUIDE__AE_UI_Style_Guidelines.md`
|
||||||
|
- `documentation/GUIDE__Docker_CI_Cache_Policy.md`
|
||||||
|
|
||||||
|
## 3) Safety and Reference
|
||||||
|
|
||||||
|
- `documentation/AE__Architecture.md`
|
||||||
|
- `documentation/AE__Permissions_and_Security.md`
|
||||||
|
- `documentation/REFERENCE__Common_Agent_Mistakes.md`
|
||||||
|
- `documentation/AE__Naming_Conventions.md`
|
||||||
|
|
||||||
|
## 4) Module Docs (Active)
|
||||||
|
|
||||||
|
### Events
|
||||||
|
- `documentation/MODULE__AE_Events_Presentation_Management.md`
|
||||||
|
- `documentation/MODULE__AE_Events_Launcher.md`
|
||||||
|
- `documentation/MODULE__AE_Events_Launcher_Native.md`
|
||||||
|
- `documentation/MODULE__AE_Events_Launcher_Config_Menu.md`
|
||||||
|
- `documentation/MODULE__AE_Events_Badges.md`
|
||||||
|
- `documentation/MODULE__AE_Events_Badge_Templates.md`
|
||||||
|
- `documentation/MODULE__AE_Events_Leads.md`
|
||||||
|
|
||||||
|
### Journals
|
||||||
|
- `documentation/MODULE__AE_Journals.md`
|
||||||
|
- `documentation/MODULE__AE_Journals_Config_Map.md`
|
||||||
|
|
||||||
|
### IDAA
|
||||||
|
- `documentation/CLIENT__IDAA_and_customized_mods.md`
|
||||||
|
- `documentation/MODULE__AE_IDAA_Archives.md`
|
||||||
|
- `documentation/MODULE__AE_IDAA_Bulletin_Board.md`
|
||||||
|
- `documentation/MODULE__AE_IDAA_Recovery_Meetings.md`
|
||||||
|
- `documentation/MODULE__AE_IDAA_Video_Conferences.md`
|
||||||
|
|
||||||
|
## 5) Active Projects
|
||||||
|
|
||||||
|
- `documentation/PROJECT__Documentation_Refresh_and_Archive_Plan_2026.md`
|
||||||
|
- `documentation/PROJECT__Stores_Svelte5_Migration.md`
|
||||||
|
- `documentation/PROJECT__IDAA_Stores_Svelte5_Migration_2026.md`
|
||||||
|
- `documentation/PROJECT__AE_Events_PressMgmt_Config_Cleanup.md`
|
||||||
|
- `documentation/PROJECT__AE_Site_Passcode_Security.md`
|
||||||
|
|
||||||
|
## 6) Active Proposals
|
||||||
|
|
||||||
|
- `documentation/PROPOSAL__AE_UI_UX_Future_Ideas.md`
|
||||||
|
|
||||||
|
## 7) Archive
|
||||||
|
|
||||||
|
- `documentation/archive/README.md`
|
||||||
|
|
||||||
|
Archive contains completed historical project notes and superseded proposals.
|
||||||
|
Legacy API-object, component-inventory, data-structure, performance, and UI-pattern snapshots are archived because they describe pre-V3 or pre-runes behavior. Use the active V3, Dexie, style, architecture, and module docs instead.
|
||||||
171
documentation/REFERENCE__Common_Agent_Mistakes.md
Normal file
171
documentation/REFERENCE__Common_Agent_Mistakes.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# Aether SvelteKit — Common Agent Mistakes (Reference)
|
||||||
|
|
||||||
|
This is the detailed mistake catalog referenced by `BOOTSTRAP__AI_Agent_Quickstart.md`.
|
||||||
|
Use it as a troubleshooting and prevention guide, not as first-pass onboarding.
|
||||||
|
|
||||||
|
Review policy: balanced curation.
|
||||||
|
- Keep: high-impact or recurring mistakes.
|
||||||
|
- Archive: one-off or lower-signal historical incidents.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Active Mistakes (Curated)
|
||||||
|
|
||||||
|
### 1) IDAA content exposed publicly
|
||||||
|
**Impact:** Sev-1 privacy breach.
|
||||||
|
|
||||||
|
**What happened:** A route guard was removed on an IDAA BB route.
|
||||||
|
|
||||||
|
**Rule:** All `/idaa/` content is private. Never remove auth guards on IDAA routes.
|
||||||
|
|
||||||
|
**Verify:** Confirm route/layout auth checks still gate all IDAA pages before data load.
|
||||||
|
|
||||||
|
### 2) Object ID included in PATCH body (`data_kv`)
|
||||||
|
**Impact:** 400 errors (`Unknown column in SET`).
|
||||||
|
|
||||||
|
**What happened:** Object ID (for URL path) was also sent in `data_kv`.
|
||||||
|
|
||||||
|
**Rule:** Keep object ID in the URL only; `data_kv` contains changed fields only.
|
||||||
|
|
||||||
|
**Verify:** In `update_ae_obj__*` calls, confirm `data_kv` excludes `*_id` fields.
|
||||||
|
|
||||||
|
### 3) Coarse-store `$effect` reactivity loops
|
||||||
|
**Impact:** repeated fetches, duplicate side effects, noisy state churn.
|
||||||
|
|
||||||
|
**What happened:** `$effect` read fields from `svelte-persisted-store` stores (`$ae_loc`, `$idaa_loc`), subscribing to entire store.
|
||||||
|
|
||||||
|
**Rule:** Be minimal about coarse-store reads in `$effect`; move transient triggers to session state and keep duplicate guards in executor paths.
|
||||||
|
|
||||||
|
**Verify:** Check effect dependencies and ensure unrelated writes do not retrigger critical effects.
|
||||||
|
|
||||||
|
### 4) Dexie `.get()` used with string object ID
|
||||||
|
**Impact:** false misses (`undefined`) despite cached data.
|
||||||
|
|
||||||
|
**What happened:** `.get()` queried primary key `id`, but V3 records rely on object string IDs (e.g. `person_id`).
|
||||||
|
|
||||||
|
**Rule:** Use `.where('<obj_id>').equals(value).first()` for object lookups.
|
||||||
|
|
||||||
|
**Verify:** Search for `.get(` against object IDs and replace with indexed `where` query.
|
||||||
|
|
||||||
|
### 5) Misunderstanding `$effect` and auth gates
|
||||||
|
**Impact:** security confusion and wrong mitigations.
|
||||||
|
|
||||||
|
**What happened:** Child-component `$effect` blocks were treated as possible bypass vectors.
|
||||||
|
|
||||||
|
**Rule:** Child effects cannot bypass parent layout auth gates; pre-gate risk is in universal `+page.ts` / `+layout.ts` loads and prefetch.
|
||||||
|
|
||||||
|
**Verify:** Keep private-module data loads out of universal load files unless explicitly authenticated.
|
||||||
|
|
||||||
|
### 6) Using query `key` like `x-no-account-id: bypass`
|
||||||
|
**Impact:** dropped `x-account-id`, unexpected 403 on scoped requests.
|
||||||
|
|
||||||
|
**What happened:** `key` was treated as bypass intent and account context was stripped.
|
||||||
|
|
||||||
|
**Rule:** `key` is business data, not bypass intent. Only explicit `x-no-account-id: bypass` drops account context.
|
||||||
|
|
||||||
|
**Verify:** Check request headers for account-scoped calls and preserve `x-account-id` unless bypass is explicitly required.
|
||||||
|
|
||||||
|
### 7) Pre-stringifying `*_json` before API wrappers
|
||||||
|
**Impact:** double encoding risk and inconsistent payloads.
|
||||||
|
|
||||||
|
**What happened:** Callers stringified JSON fields manually before wrapper serialization.
|
||||||
|
|
||||||
|
**Rule:** Pass plain objects for `*_json` fields; wrappers handle serialization.
|
||||||
|
|
||||||
|
**Verify:** Remove `JSON.stringify(...)` around `cfg_json`, `data_json`, and related fields before wrapper calls.
|
||||||
|
|
||||||
|
### 8) Broad Dexie result windows silently clipped
|
||||||
|
**Impact:** “All” views show fewer rows than narrower filters.
|
||||||
|
|
||||||
|
**What happened:** page limits or API revalidation replaced local broad result sets.
|
||||||
|
|
||||||
|
**Rule:** For empty text search, local IDB result set should drive visible results; server refresh updates cache without shrinking display.
|
||||||
|
|
||||||
|
**Verify:** Compare empty-search “All” view vs narrower filter counts and inspect revalidation merge behavior.
|
||||||
|
|
||||||
|
### 9) Missing `IDB_CONTENT_VERSIONS` bump after `properties_to_save` changes
|
||||||
|
**Impact:** long-lived stale cache bugs (e.g. IDAA “no meetings found”).
|
||||||
|
|
||||||
|
**What happened:** shape persisted in IDB changed, but table content version was not bumped.
|
||||||
|
|
||||||
|
**Rule:** When persisted object shape/behavior changes, bump matching `IDB_CONTENT_VERSIONS` entry in `src/lib/stores/store_versions.ts`.
|
||||||
|
|
||||||
|
**Verify:** For relevant object changes, confirm both version increment and table-wiring path are in the same change set.
|
||||||
|
|
||||||
|
### 10) API retry loop broken by returning transient errors
|
||||||
|
**Impact:** no retries on common WiFi/network blips.
|
||||||
|
|
||||||
|
**What happened:** transient error paths returned `false` instead of throwing, bypassing retry loop.
|
||||||
|
|
||||||
|
**Rule:** Keep retry classification strict:
|
||||||
|
- transient `TypeError` and timeout aborts -> `throw`
|
||||||
|
- navigation abort -> `return false`
|
||||||
|
- deterministic 4xx -> `return false`
|
||||||
|
- 5xx -> `throw`
|
||||||
|
|
||||||
|
**Verify:** Simulate transient network failure and confirm retry/backoff attempts occur.
|
||||||
|
|
||||||
|
### 11) Account-scoped trigger fired before bootstrap account is ready
|
||||||
|
**Impact:** wrong-account API fetch and cached cross-account data.
|
||||||
|
|
||||||
|
**What happened:** trigger effects ran before account bootstrap settled, reading stale context.
|
||||||
|
|
||||||
|
**Rule:** Gate account-scoped load triggers on `$slct.account_id` readiness, not persisted `$ae_loc.account_id`.
|
||||||
|
|
||||||
|
**Verify:** Ensure trigger effects require browser + account_id + api readiness before fetch trigger assignment.
|
||||||
|
|
||||||
|
### 12) `tmp_sort_*` comparator direction inverted
|
||||||
|
**Impact:** priority ordering reversed.
|
||||||
|
|
||||||
|
**What happened:** descending comparator used with `build_tmp_sort` encoding (which expects ascending).
|
||||||
|
|
||||||
|
**Rule:** Use ascending compare for `build_tmp_sort` outputs, with documented exceptions for legacy encodings.
|
||||||
|
|
||||||
|
**Verify:** Confirm comparator direction per module encoding and avoid `collection.reverse().sortBy(...)` assumptions.
|
||||||
|
|
||||||
|
### 13) `$` sigil used on plain prop values
|
||||||
|
**Impact:** runtime `store_invalid_shape` errors.
|
||||||
|
|
||||||
|
**What happened:** child component treated normal prop values as Svelte stores.
|
||||||
|
|
||||||
|
**Rule:** In Svelte 5, props passed as values are plain values. Do not use `$prop` unless prop is an actual store.
|
||||||
|
|
||||||
|
**Verify:** In migrated components, replace `$lq__...` prop reads with plain `lq__...` prop access.
|
||||||
|
|
||||||
|
### 14) Null JSON blob fields not guarded
|
||||||
|
**Impact:** read/write crashes (`cannot access property of null`).
|
||||||
|
|
||||||
|
**What happened:** `*_json` / `*_kv_json` DB columns were null before first write.
|
||||||
|
|
||||||
|
**Rule:** Use optional chaining for reads and `?? {}` initialization before object spread writes.
|
||||||
|
|
||||||
|
**Verify:** Audit reads/writes for `cfg_json`, `data_json`, `poc_kv_json`, and similar nullable blob fields.
|
||||||
|
|
||||||
|
### 15) Service worker stale-tab behavior misunderstood
|
||||||
|
**Impact:** users run old code longer than expected, “can’t reproduce” bug reports.
|
||||||
|
|
||||||
|
**What happened:** deployment assumptions ignored SW activation lifecycle.
|
||||||
|
|
||||||
|
**Rule:** Keep SW activation behavior explicit (`skipWaiting`, `clients.claim`) and evaluate trade-offs for session-heavy flows.
|
||||||
|
|
||||||
|
**Verify:** After deploy, validate that long-lived tabs pick up new SW behavior as intended.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Archived Historical Items (Pruned)
|
||||||
|
|
||||||
|
These are retained as project memory but removed from the active mistake list because they are lower-signal or one-off.
|
||||||
|
|
||||||
|
1. **Bad `.d.ts` module declaration masking errors** (important incident, low recurrence recently).
|
||||||
|
2. **Launcher `file_purpose == 'admin'` filtering gap** (specific historical enum-audit miss).
|
||||||
|
3. **Deleting files with `rm`** (still a workflow rule, now maintained in bootstrap File Safety section rather than as a mistake entry).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Docs
|
||||||
|
|
||||||
|
- `documentation/BOOTSTRAP__AI_Agent_Quickstart.md`
|
||||||
|
- `documentation/TODO__Agents.md`
|
||||||
|
- `documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md`
|
||||||
|
- `documentation/GUIDE__AE_API_V3_for_Frontend.md`
|
||||||
|
- `documentation/PROJECT__Stores_Svelte5_Migration.md`
|
||||||
@@ -1,438 +1,197 @@
|
|||||||
# Frontend Agent Task List
|
# Frontend Agent Task List
|
||||||
|
> **Doc Owner:** Active frontend implementation team (human + agent)
|
||||||
|
> **Review Trigger:** Update when work starts, completes, changes priority, or moves to an archive.
|
||||||
> Use this file to track steps for complex features or bug fixes.
|
> Use this file to track steps for complex features or bug fixes.
|
||||||
> **Status:** Stable — ongoing development.
|
> **Status:** Stable — ongoing development.
|
||||||
|
> **Scope:** Active/open work only. Completed detail lives in archive files.
|
||||||
|
|
||||||
|
## 🔴 LCI October — Pres Mgmt Restoration (in progress 2026-06-12)
|
||||||
|
|
||||||
|
These features regressed over the last 6 months and must be working before the LCI conference.
|
||||||
|
Reference commit for original working implementation: `bb993a102`.
|
||||||
|
|
||||||
|
### Session POC (Champion/Moderator) — `session_view.svelte`
|
||||||
|
|
||||||
|
**Root cause of visible bugs:** The POC section is placed *below* the session hero card as a
|
||||||
|
separate disconnected block. In the original it was part of a structured `<ul>` with the session
|
||||||
|
name, code, datetime, location, and description all together. The current layout looks and feels
|
||||||
|
wrong to users.
|
||||||
|
|
||||||
|
- [x] **[Pres Mgmt] POC section — move inside session hero card** (2026-06-12)
|
||||||
|
Restructured hero card as a `<ul>` with datetime, room, and POC as rows inside the card.
|
||||||
|
Session name and code are now always visible (not just in edit_mode — that was a bug).
|
||||||
|
|
||||||
|
- [x] **[Pres Mgmt] POC assignment — "Select Person" flow broken** (2026-06-12)
|
||||||
|
Gated the select editor on `person_options_loaded` (`Object.keys($slct.person_obj_kv).length > 0`).
|
||||||
|
"Select Person" button renders as "Reload Person" after list is loaded.
|
||||||
|
|
||||||
|
- [x] **[Pres Mgmt] Email Session POC sign-in link — UI missing** (2026-06-12)
|
||||||
|
Restored email button in POC row with `sending/sent/error` state feedback.
|
||||||
|
Shown when `require__session_agree && show__email_access_link && poc_person_primary_email`.
|
||||||
|
|
||||||
|
- [x] **[Pres Mgmt] Copy Session POC access link — UI missing from session view** (2026-06-12)
|
||||||
|
Restored inline `MyClipboard` copy button in POC row for trusted staff.
|
||||||
|
Shown when `show__copy_access_link && trusted_access && poc_sign_in_url`.
|
||||||
|
|
||||||
|
### Presenter Sign-In
|
||||||
|
|
||||||
|
- [ ] **[Pres Mgmt] Presenter email sign-in link routes to wrong page**
|
||||||
|
`email_sign_in__event_presenter()` builds a URL to `/presenter/[id]?person_id=...&person_pass=...`.
|
||||||
|
The URL param parser (`sign_in_out.svelte`) is only mounted on the *session* page menu, not the
|
||||||
|
presenter page. A presenter clicking their email link lands on their page with no auth granted.
|
||||||
|
Fix: mount `Sign_in_out` in `presenter_page_menu.svelte` (same way session menu does it), or
|
||||||
|
change the email link to route to the session page (which already has the parser) and include
|
||||||
|
the presenter/presentation IDs as params — which is how it worked originally.
|
||||||
|
|
||||||
|
- [ ] **[Pres Mgmt] Presenter agreement not enforced before file upload**
|
||||||
|
`require__presenter_agree` is stored and displayed but the upload components are gated on
|
||||||
|
`auth__kv.presenter[id]` only, not on `presenter.agree`. A presenter who signs in but has not
|
||||||
|
agreed can still upload. The original blocked the upload section until `agree === true`.
|
||||||
|
|
||||||
|
### Session POC Sign-In
|
||||||
|
|
||||||
|
- [ ] **[Pres Mgmt] `session_page_menu.svelte` sign-in prop still wrong**
|
||||||
|
`event_session_id` prop passed to `Sign_in_out` was just changed from `event_id` to
|
||||||
|
`event_session_id` — verify this is actually `$lq__event_session_obj?.event_session_id`
|
||||||
|
(the real session ID string) not the URL param `url_session_id`. The sign-in component
|
||||||
|
uses this value to set `auth__kv.session[event_session_id]`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
## 🔴 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
|
- [ ] **[Launcher/Electron] Wallpaper reliability (post-CMSC)**
|
||||||
with the profiles used to open/launch presentation files. Architecture decision: move launch
|
- [ ] Use timestamp/randomized temp filename so macOS always sees a new path.
|
||||||
logic to the Svelte side so it can be changed without an Electron rebuild. Electron becomes a
|
- [ ] Add resilient reconciliation loop or event-driven reapply on display topology changes.
|
||||||
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.
|
|
||||||
- [ ] **[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`).
|
|
||||||
- [ ] **[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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔴 Axonius DC — June 9 (Badge Printing)
|
## 🔴 Axonius DC — June 9 (Badge Printing)
|
||||||
**Setup/Registration:** June 8 | **Show:** June 9
|
**Setup/Registration:** June 8 | **Show:** June 9
|
||||||
|
|
||||||
- [ ] **[Badges] Epson C3500 fanfold badge layout** — Axonius is using Epson C3500 printers
|
- [x] **[Badges] Epson C3500 fanfold badge layout** — `badge_4x6_fanfold` layout CSS created,
|
||||||
with fanfold (continuous) badge stock. Create/configure a fanfold badge layout compatible
|
wired, and documented. First live use: Axonius Adapt DC, June 9, 2026. (2026-05-15)
|
||||||
with the C3500 format. Must be ready before the June 8 setup/registration day.
|
|
||||||
|
### Badges follow-ups
|
||||||
|
|
||||||
|
- [ ] **[Badges] Implement review-link email delivery** — current Email Link actions only show
|
||||||
|
placeholder alerts. Send to `event_badge.email`, never the attendee-editable `email_override`.
|
||||||
|
- [ ] **[Badges] Unify review and kiosk edit permissions** — remote review reads
|
||||||
|
`event.mod_badges_json.edit_permissions`; print controls read template `cfg_json.controls_cfg`.
|
||||||
|
Define precedence or consolidate them so both flows enforce one documented policy.
|
||||||
|
- [ ] **[Badges] Use template badge types in search filter** — replace the hardcoded badge-type
|
||||||
|
list in `ae_comp__badge_search.svelte` with the active template's `badge_type_list`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚧 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)
|
- [ ] **[Core] Legacy Utility Helpers** — Refactor `ae_core_functions.ts` to use V3 helpers.
|
||||||
|
- [ ] **[Cleanup] Delete Legacy Wrappers** — Once all callsites are migrated, remove
|
||||||
All known root causes fixed across 10+ commits to `src/routes/idaa/(idaa)/+layout.svelte`.
|
`src/lib/ae_api/api_get__crud_obj_id.ts` and the legacy exports from `api.ts`.
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### ~~[IDAA] Server-side Novi verification — 503 not auto-retried~~ ✅ Fixed (2026-05-20)
|
## 🚧 High Priority Workstreams
|
||||||
|
|
||||||
---
|
### [Security] Site Passcode JWT Migration
|
||||||
|
|
||||||
### [Launcher/VLC] Linux playback — fullscreen + pause-on-end not working
|
- [ ] **[Security] Verify `/authenticate_passcode` deployment** — confirm explicit role priority,
|
||||||
**Status:** Mac ✅ working perfectly; Linux 🚧 deferred for later investigation
|
complete role flags, `auth_type: 'passcode'`, per-role TTLs, and minimum length validation.
|
||||||
**Date discovered:** 2026-05-20
|
- [ ] **[Security] Replace local passcode comparison** — migrate
|
||||||
|
`e_app_access_type.svelte` to server verification, JWT storage, and pending/error UI.
|
||||||
|
- [ ] **[Security] Remove client-side passcode delivery/storage** — stop caching
|
||||||
|
`access_code_kv_json`, remove `site_access_code_kv` from auth state, and remove passcode logging.
|
||||||
|
- [ ] **[Security] Enforce passcode JWT expiry on restore** — expired passcode sessions must
|
||||||
|
return to anonymous without affecting user-login JWT handling.
|
||||||
|
|
||||||
macOS VLC profile (direct binary path) successfully:
|
Reference: `documentation/PROJECT__AE_Site_Passcode_Security.md`.
|
||||||
- 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:
|
### [Stores] Svelte 4 → Svelte 5 State Migration
|
||||||
- Does NOT go fullscreen
|
The app uses `svelte-persisted-store` (coarse reactivity). Migration target: replace with Svelte 5
|
||||||
- Does NOT pause on the last frame (plays through, returns to playlist)
|
`PersistedState` (from `runed`) for fine-grained updates. See `PROJECT__Stores_Svelte5_Migration.md`.
|
||||||
|
|
||||||
**Current state:** Both macOS and Linux commands in `ae_launcher__default_launch_profiles.ts`.
|
- [x] **Events module — COMPLETE (2026-06-11):** `events_loc` fully retired. All 5 sub-stores
|
||||||
macOS is the primary venue deployment platform; Linux support is nice-to-have.
|
(`badges_loc`, `leads_loc`, `pres_mgmt_loc`, `launcher_loc`, `events_auth_loc`) are on
|
||||||
|
`PersistedState`. Unused fields also pruned from `ae_stores.ts` and `ae_idaa_stores.ts`.
|
||||||
|
- [ ] **`idaa_loc` → PersistedState** — Highest remaining priority. Root cause of the IDAA
|
||||||
|
"Access Denied" corruption bug (`ae_loc` bootstrap writes stomp on `authenticated_access`).
|
||||||
|
Promote `novi_*` identity fields and `archives/bb/recovery_meetings` sub-objects.
|
||||||
|
- [ ] **`ae_loc` → PersistedState** — Largest scope. Extract `auth_loc` sub-store first
|
||||||
|
(the identity/permission fields are what get corrupted). Defer full migration until after `idaa_loc`.
|
||||||
|
- [ ] **Non-persisted writables** (`ae_sess`, `slct`, etc.) — Low priority; no coarse-reactivity problem.
|
||||||
|
|
||||||
**Investigation needed:** Determine if the VLC flags are being interpreted on Linux,
|
### [Data Layer] IDB sorting + content version rollout
|
||||||
or if there's a launcher execution layer issue (e.g. `shell:` prefix handling).
|
Sorting baseline is now `build_tmp_sort` (ASC chain, no `.reverse()` on tmp-sort lists).
|
||||||
File: `src/lib/ae_events/ae_launcher__default_launch_profiles.ts` — `make_vlc_mirror_linux_profile()`.
|
|
||||||
|
|
||||||
---
|
**⚠️ Exception:** `ae_events__event.ts` and `ae_events__event_session.ts` use **legacy encoding**
|
||||||
|
(`priority ? 1 : 0`, priority=true→`'1'`). Their sort comparators must remain **descending**
|
||||||
|
until the modules are migrated to `build_tmp_sort`. `ae_events__event_presentation.ts` already
|
||||||
|
uses `build_tmp_sort` (overrides generic encoding in its `specific_processor`). See
|
||||||
|
`CLIENT__IDAA_and_customized_mods.md` → "Sort Encoding" for full table.
|
||||||
|
|
||||||
### [Stores] Svelte 4 → Svelte 5 State Migration (prerequisite for Phase 2c)
|
- [ ] **[IDB Sort] Migrate `ae_events__event.ts` to `build_tmp_sort`** — requires bumping
|
||||||
The app uses `svelte-persisted-store` (Svelte 4 store contract) for all core persisted state
|
`IDB_CONTENT_VERSIONS.events.event` (currently v3) and switching all event sort comparators
|
||||||
(`ae_loc`, `idaa_loc`, `ae_api`, `ae_sess`, etc.). In Svelte 5 `$effect`, reading **any field**
|
to ascending. Check all pages that sort events before doing this.
|
||||||
of a Svelte 4 store subscribes to the **entire store** — coarse-grained reactivity. This is the
|
- [ ] **[IDB Sort] Roll out to `ae_events__event_session`** after sort behavior review.
|
||||||
root cause of the IDAA Novi re-auth bug (2026-03-30): unrelated `$ae_loc` writes (e.g. iframe
|
- [ ] **[IDB Sort] Roll out to `ae_events__event_presenter`** after sort behavior review.
|
||||||
height, SWR cfg reload) triggered the Novi verification effect repeatedly.
|
- [ ] **[IDB Sort] Roll out to `ae_events__event_location`** after sort behavior review.
|
||||||
|
- [ ] **[IDB Sort] Roll out to `ae_core__person` + `ae_core__account`** after sort behavior review.
|
||||||
Migration target: replace `svelte-persisted-store` with Svelte 5 `$state`-based persistence
|
- [ ] **[IDB Version] Roll out to `db_events.ts`** (session, presenter, badge, etc.).
|
||||||
(e.g. `runed` `PersistedState`, or a lightweight custom wrapper). This gives fine-grained
|
- [ ] **[IDB Version] Roll out to `db_core.ts`** (site_domain, person, user).
|
||||||
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] 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] Journal Entry Config follow-ups
|
||||||
|
- [ ] **[Journals] Entry passcode secondary auth** — implement `passcode_hash` comparison.
|
||||||
|
- [ ] **[Journals] Quick Add/import encryption behavior** — both creation paths currently
|
||||||
|
create plaintext entries; define the intended privacy UX and add encryption support before
|
||||||
|
claiming that these paths honor entry E2EE.
|
||||||
|
- [ ] **[Journals] Remove decrypted-content console preview** —
|
||||||
|
`ae_journals_decryption.ts` logs the first 30 plaintext characters after successful decryption.
|
||||||
|
Never log private journal content.
|
||||||
|
- [ ] **[Journals] Confirm outbound email-sharing requirement** — the archived UI project listed
|
||||||
|
this as unfinished, but no implementation exists. Confirm product/security requirements before
|
||||||
|
creating an email workflow for private journal content.
|
||||||
|
|
||||||
- [ ] **[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)**
|
## 🧪 Testing & Optimization
|
||||||
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.
|
|
||||||
|
|
||||||
|
- [ ] **[IDAA] IDB fast-path contact search** — parse `contact_li_json` in `search__event()`.
|
||||||
- [ ] **[IDAA] Optimize Recovery Meetings SQL VIEW and indexes.**
|
- [ ] **[IDAA] Optimize Recovery Meetings SQL VIEW and indexes.**
|
||||||
The current search query can be taxing on the server. With ~150 active meetings, the view
|
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage** in all other event search pages.
|
||||||
logic and supporting indexes need a performance review to ensure fast responses as the
|
- [ ] **[Launcher/VLC] Linux playback investigation** — fullscreen + pause-on-end flags.
|
||||||
database grows. (Requested 2026-05-18)
|
|
||||||
|
|
||||||
- [ ] **[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
|
## ⚙️ DevOps & Backend
|
||||||
- [ ] **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),
|
- [ ] **[Cleanup] Remove unused legacy API wrappers** — `create_ae_obj_crud()`,
|
||||||
alongside the existing Jitsi URL Builder section. Should allow editing key fields in
|
`get_ae_obj_id_crud()`, and `update_ae_obj_id_crud()` are still exported from `api.ts` but
|
||||||
`site_cfg_json` without needing phpMyAdmin:
|
no longer called anywhere in production code. V3 migration is 100% complete. Safe to delete:
|
||||||
- `jitsi_token_endpoint` — the JWT signing endpoint (needs to point to prod)
|
definitions in `api.ts` (lines 109-260), `src/lib/ae_api/api_get__crud_obj_id.ts`, unused
|
||||||
- Jitsi domain default (currently hardcoded as `jitsi.dgrzone.com` fallback in the page)
|
wrapper in `ae_core_functions.ts` (`get_site_domain_obj_from_fqdn`, `update_ae_obj_id_crud`).
|
||||||
- `novi_jitsi_mod_li` — list of Novi UUIDs who get moderator privileges
|
- [ ] **[Backend] `event_file` — add `cfg_json` column (post-CMSC)** — The per-file display
|
||||||
Read from `$ae_loc.site_cfg_json`, PATCH the site record via V3 CRUD
|
override currently uses a localStorage workaround (`launcher_loc.current.file_display_overrides`)
|
||||||
(`PATCH /v3/crud/site/{id}/`), reload `$ae_loc.site_cfg_json` on save so it takes
|
because `event_file` has no JSON blob column. Proper fix: add `cfg_json` to the `event_file` DB
|
||||||
effect without re-login.
|
table, expose it through the FastAPI model, then migrate the frontend back to reading/writing the
|
||||||
|
backend field (restoring global/cross-device persistence). Frontend code is in
|
||||||
|
`launcher_file_cont.svelte` — search for `file_display_overrides`.
|
||||||
|
- [ ] **[Backend] Re-add `Access-Control-Allow-Private-Network: true` CORS header.**
|
||||||
|
- [x] **[DevOps] Service worker `skipWaiting` + `clients.claim`** — Root cause of "users see
|
||||||
|
old code / can't reproduce in dev testing": the SW sat in waiting state until all tabs closed.
|
||||||
|
IDAA members leave idaa.org open all day. Fixed 2026-06-03: both calls added to
|
||||||
|
`src/service-worker.js`. See mistake #16 in `BOOTSTRAP__AI_Agent_Quickstart.md`.
|
||||||
|
- [ ] **[DevOps] Nginx proxy buffer tuning** — Buffer settings copied from PHP guide; not
|
||||||
|
optimal for Node.js. `proxy_busy_buffers_size` technically exceeds safe limit. Re-examine
|
||||||
|
when enabling compression (now re-enabled) stabilizes.
|
||||||
|
- [ ] **[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] 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)
|
## ✅ Completed (archived)
|
||||||
See the full completed history in:
|
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-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-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)
|
||||||
|
[documentation/archive/TODO__Agents__ARCHIVE_2026-06.md](documentation/archive/TODO__Agents__ARCHIVE_2026-06.md)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Deliverables (this PR)
|
|||||||
- `documentation/PROJECT__AE_Docker_CI_BuildKit_implement.md` (this file)
|
- `documentation/PROJECT__AE_Docker_CI_BuildKit_implement.md` (this file)
|
||||||
- `aether_container_env/Dockerfile.buildkit.example` — BuildKit-friendly multi-stage Dockerfile example.
|
- `aether_container_env/Dockerfile.buildkit.example` — BuildKit-friendly multi-stage Dockerfile example.
|
||||||
- `aether_container_env/ci_buildx_example.sh` — standalone CI script examples (registry cache + local cache usage).
|
- `aether_container_env/ci_buildx_example.sh` — standalone CI script examples (registry cache + local cache usage).
|
||||||
- `documentation/AE_Docker_CI_cache_policy.md` — cache rotation and prune guidance.
|
- `documentation/GUIDE__Docker_CI_Cache_Policy.md` — cache rotation and prune guidance.
|
||||||
|
|
||||||
Tasks (implementation checklist)
|
Tasks (implementation checklist)
|
||||||
- [ ] Review existing `Dockerfile`(s) under `aether_container_env/` and repository root.
|
- [ ] Review existing `Dockerfile`(s) under `aether_container_env/` and repository root.
|
||||||
@@ -50,4 +50,4 @@ Next steps for the container team
|
|||||||
Files included in this PR for reference:
|
Files included in this PR for reference:
|
||||||
- `aether_container_env/Dockerfile.buildkit.example`
|
- `aether_container_env/Dockerfile.buildkit.example`
|
||||||
- `aether_container_env/ci_buildx_example.sh`
|
- `aether_container_env/ci_buildx_example.sh`
|
||||||
- `documentation/AE_Docker_CI_cache_policy.md`
|
- `documentation/GUIDE__Docker_CI_Cache_Policy.md`
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
# PROJECT: AE Events Badges — Review Form & Print Font Controls
|
# Archived Project: AE Events Badges — Review Form & Print Font Controls
|
||||||
|
|
||||||
**Created:** 2026-02-27
|
**Created:** 2026-02-27
|
||||||
**Last Updated:** 2026-03-18
|
**Completed and Archived:** 2026-06-12
|
||||||
|
**Last Verified Against Source:** 2026-06-12
|
||||||
**Branch:** `ae_app_3x_llm`
|
**Branch:** `ae_app_3x_llm`
|
||||||
**Priority:** HIGH — first live event is Axonius, NYC, mid-April 2026
|
|
||||||
**Owner:** Scott Idem / One Sky IT
|
**Owner:** Scott Idem / One Sky IT
|
||||||
**Status:** ✅ TASK 1 COMPLETE | ✅ TASK 2 COMPLETE | ✅ TASK 3 COMPLETE | ✅ TASK 4.1 COMPLETE | ⏳ TASK 4.0 OPEN
|
**Status:** Complete — review form, kiosk controls, auto-scaling, QR rendering, layouts, and print tracking are implemented.
|
||||||
|
|
||||||
|
The original project scope is complete and this document is retained as implementation history.
|
||||||
|
Current behavior is documented in `documentation/MODULE__AE_Events_Badges.md` and
|
||||||
|
`documentation/MODULE__AE_Events_Badge_Templates.md`. Remaining email-delivery and permission-config
|
||||||
|
unification work is tracked in `documentation/TODO__Agents.md`. Planning statements later in this
|
||||||
|
archived document describe the state at the time they were written and are not current instructions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -44,32 +50,24 @@ Both flows should respect the same permission model:
|
|||||||
- Permissions are configured per-event in `event.mod_badges_json.edit_permissions`.
|
- Permissions are configured per-event in `event.mod_badges_json.edit_permissions`.
|
||||||
Hardcoded defaults are used until that config is implemented.
|
Hardcoded defaults are used until that config is implemented.
|
||||||
|
|
||||||
**Current gap (TASK 4):** The print page edit button is currently gated to trusted_access only.
|
**Task 4 outcome:** The print controls now implement field-level editing. Authenticated users
|
||||||
It needs to be accessible to attendees at the kiosk (with appropriate field-level gating),
|
can edit template-approved fields, trusted staff can correct names, and trusted staff in global
|
||||||
matching the permission model already implemented in `ae_comp__badge_review_form.svelte`.
|
Edit Mode can edit all fields. First printing is available at public kiosk access; reprinting
|
||||||
|
requires trusted access plus Edit Mode. Remote review uses event-level `edit_permissions`, while
|
||||||
|
the print controls currently use template-level `controls_cfg`; unification is tracked separately.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Next Up for Badges (TASK 4)
|
## Task 4 Outcomes
|
||||||
|
|
||||||
### 0. Kiosk Editing — Print Page Permission Model Alignment
|
### 0. Kiosk Editing — Complete
|
||||||
**This is the most important gap before the first live event.**
|
|
||||||
|
|
||||||
Currently the print page edit button is staff-only (trusted_access gate). At the kiosk,
|
`ae_comp__badge_print_controls.svelte` provides the inline controls and live preview. Its default
|
||||||
attendees need to be able to edit their own fields (same attendee-level permissions as the
|
authenticated fields are title, affiliations, location, lead tracking, and pronouns; template
|
||||||
review form), with staff-only fields gated appropriately.
|
`controls_cfg` can narrow the fields shown and editable. Email delivery remains a placeholder;
|
||||||
|
when implemented it must send to `event_badge.email`, never `email_override`.
|
||||||
|
|
||||||
Work needed:
|
### 1. Auto-Scaling Badge Text — Complete
|
||||||
- Wire the same `can_edit_fields` / `can_edit(field)` permission logic into the print page
|
|
||||||
that `ae_comp__badge_review_form.svelte` already uses.
|
|
||||||
- The edit panel on the print page should show attendee-editable fields to all authenticated
|
|
||||||
users, and staff-only fields to trusted_access+.
|
|
||||||
- The badge render (v1 or v2) should update live as the attendee edits fields.
|
|
||||||
- Consider whether the print page needs its own inline edit panel (sidebar or overlay)
|
|
||||||
or whether it should share/reuse the review form component alongside the badge render.
|
|
||||||
- **Do NOT use `email_override` as the send-to address** — always use `event_badge.email`.
|
|
||||||
|
|
||||||
### 1. Auto-Scaling Badge Text — In Progress
|
|
||||||
`ae_comp__badge_obj_view.svelte` using `element_fit_text.svelte` (binary search auto-scale).
|
`ae_comp__badge_obj_view.svelte` using `element_fit_text.svelte` (binary search auto-scale).
|
||||||
Toggle between v1 (heuristic) and v2 (auto-scale) on the print page via the `v1`/`v2` header button.
|
Toggle between v1 (heuristic) and v2 (auto-scale) on the print page via the `v1`/`v2` header button.
|
||||||
Heights tuned per layout in `fit_heights` derived object. Still needs visual tuning with real badges.
|
Heights tuned per layout in `fit_heights` derived object. Still needs visual tuning with real badges.
|
||||||
@@ -105,10 +103,11 @@ badge data, gated by `allow_tracking` on the badge.
|
|||||||
|
|
||||||
## Implementation Status
|
## Implementation Status
|
||||||
|
|
||||||
### ⏳ TASK 4.0: Kiosk Editing — NOT STARTED (updated 2026-03-18)
|
### ✅ TASK 4.0: Kiosk Editing — COMPLETE (verified 2026-06-12)
|
||||||
Print page edit access needs to be opened to attendee-level permissions, not just trusted_access.
|
The print controls implement authenticated field editing, trusted name correction, trusted + Edit
|
||||||
The permission model, field list, and `can_edit()` helper from `ae_comp__badge_review_form.svelte`
|
Mode full editing, and live preview. The print path uses template `controls_cfg`; the review path
|
||||||
should be the reference. See Design Intent section above.
|
uses event `mod_badges_json.edit_permissions`. Aligning those configuration sources is a follow-up,
|
||||||
|
not a blocker to the completed kiosk controls.
|
||||||
|
|
||||||
**Note (2026-03-18):** `style_href` and `duplex` are both fully implemented and verified in code —
|
**Note (2026-03-18):** `style_href` and `duplex` are both fully implemented and verified in code —
|
||||||
the MODULE doc TODO list was stale. `duplex` is in `properties_to_save`; v2 badge render gates
|
the MODULE doc TODO list was stale. `duplex` is in `properties_to_save`; v2 badge render gates
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
# Project Plan: Aether AE Obj Field Editor v3 (Consolidated)
|
# Project Plan: Aether AE Obj Field Editor v3 (Consolidated)
|
||||||
|
|
||||||
> **Status:** 🟡 Mostly Complete — Phase 3 items + GUIDE update remaining
|
> **Status:** ✅ **Completed and archived 2026-06-12**
|
||||||
> **Date:** February 13, 2026 (last updated: 2026-03-20)
|
> **Completion:** V3 component deployed and documented. Legacy V1/V2 removed. Searchable dropdowns deferred as optional enhancement.
|
||||||
|
> **Created:** 2026-02-13
|
||||||
|
> **Last Updated:** 2026-06-12
|
||||||
> **Target Component:** `src/lib/elements/element_ae_obj_field_editor.svelte`
|
> **Target Component:** `src/lib/elements/element_ae_obj_field_editor.svelte`
|
||||||
> **Replaces:** `element_ae_crud.svelte` and `element_ae_crud_v2.svelte`
|
> **Replaces:** `element_ae_crud.svelte` and `element_ae_crud_v2.svelte`
|
||||||
|
|
||||||
@@ -30,15 +32,17 @@ Consolidate the legacy CRUD components into a single, high-performance "Aether O
|
|||||||
- [x] Add a "Save" loading state with Lucide's `LoaderCircle` spinner.
|
- [x] Add a "Save" loading state with Lucide's `LoaderCircle` spinner.
|
||||||
- [x] Implement a clear "Cancel" path that restores the original value.
|
- [x] Implement a clear "Cancel" path that restores the original value.
|
||||||
|
|
||||||
### Phase 3: Field Type Parity (IN PROGRESS)
|
### Phase 3: Field Type Parity (COMPLETE)
|
||||||
- [x] Support `text`, `textarea`, `select`, `tiptap`, and `checkbox`.
|
- [x] Support `text`, `textarea`, `select`, `tiptap`, and `checkbox`.
|
||||||
- [x] Add `datetime` support using native browser pickers — `date` and `datetime-local` inputs implemented.
|
- [x] Add `datetime` support using native browser pickers — `date` and `datetime-local` inputs implemented.
|
||||||
- [ ] Implement searchable dropdowns for the `select` type.
|
- [x] Add `number` field type support.
|
||||||
|
- [ ] *(Optional)* Implement searchable dropdowns for the `select` type — deferred as UX enhancement; basic select works for all current use cases.
|
||||||
|
|
||||||
### Phase 4: Migration & Cleanup
|
### Phase 4: Migration & Cleanup (COMPLETE)
|
||||||
- [x] Create a playground route for V3 verification (`/testing/ae_obj_field_editor`).
|
- [x] Create a playground route for V3 verification (`/testing/ae_obj_field_editor`).
|
||||||
- [x] Deprecate and remove `v1` and `v2` files — `element_ae_crud.svelte` and `element_ae_crud_v2.svelte` removed 2026-03-20.
|
- [x] Deprecate and remove `v1` and `v2` files — `element_ae_crud.svelte` and `element_ae_crud_v2.svelte` removed 2026-03-20.
|
||||||
- [ ] Update `GUIDE__Development.md` with the new usage patterns.
|
- [x] Update `GUIDE__Development.md` with the new usage patterns — documented at lines 57-109.
|
||||||
|
- [x] Production deployment — 50+ active usages across session views, person views, locations, presentations, and leads.
|
||||||
|
|
||||||
## ⚠️ Security & Reliability Stabilization (NEW)
|
## ⚠️ Security & Reliability Stabilization (NEW)
|
||||||
- [x] **Account Context:** Fixed 403 errors by unifying API helpers to the `/v3/crud/` standard.
|
- [x] **Account Context:** Fixed 403 errors by unifying API helpers to the `/v3/crud/` standard.
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
# Aether Journals UI Update (2026)
|
# Archived Project: Aether Journals UI Update (2026)
|
||||||
|
|
||||||
> **Status:** 🚧 Phase 4 Active (Security/Encryption Blockers remain; Journal Entry config rework in progress)
|
> **Status:** Completed and archived 2026-06-12
|
||||||
> **Last Updated:** 2026-05-05
|
> **Last Verified Against Source:** 2026-06-12
|
||||||
> **Primary Agent:** Frontend SvelteKit Agent
|
> **Primary Agent:** Frontend SvelteKit Agent
|
||||||
|
|
||||||
|
The UI modernization scope is complete: V3 CRUD, Quick Add, Append/Prepend,
|
||||||
|
import/export, auto-save, configuration modals, decryption isolation, and the
|
||||||
|
Journals style pass are implemented. Unfinished security and product follow-ups
|
||||||
|
were transferred to `documentation/TODO__Agents.md`; current operational behavior
|
||||||
|
and limitations live in `documentation/MODULE__AE_Journals.md`.
|
||||||
|
|
||||||
## 1. Project Overview
|
## 1. Project Overview
|
||||||
This document outlines the modernization of the Journals module UI in the SvelteKit frontend (`aether_app_sveltekit`). The primary goals are to fully leverage the generic V3 API architecture and introduce high-velocity productivity features for journal management.
|
This document outlines the modernization of the Journals module UI in the SvelteKit frontend (`aether_app_sveltekit`). The primary goals are to fully leverage the generic V3 API architecture and introduce high-velocity productivity features for journal management.
|
||||||
|
|
||||||
@@ -29,7 +35,7 @@ This document outlines the modernization of the Journals module UI in the Svelte
|
|||||||
* **Definitions:** `app/ae_obj_types_def.py` -> `app/object_definitions/journals.py`
|
* **Definitions:** `app/ae_obj_types_def.py` -> `app/object_definitions/journals.py`
|
||||||
* **Endpoints:** `/v3/crud/journal/...` and `/v3/crud/journal_entry/...`
|
* **Endpoints:** `/v3/crud/journal/...` and `/v3/crud/journal_entry/...`
|
||||||
|
|
||||||
### Frontend (In Progress)
|
### Frontend (Completed UI modernization scope)
|
||||||
* **State Management:** `src/lib/ae_journals/ae_journals_stores.ts`
|
* **State Management:** `src/lib/ae_journals/ae_journals_stores.ts`
|
||||||
* **Local Storage:** Dexie.js (`db_journals`)
|
* **Local Storage:** Dexie.js (`db_journals`)
|
||||||
* **API Client:** `src/lib/api/api.ts` -> `get_ae_obj`
|
* **API Client:** `src/lib/api/api.ts` -> `get_ae_obj`
|
||||||
@@ -68,7 +74,7 @@ This document outlines the modernization of the Journals module UI in the Svelte
|
|||||||
- [x] Implement Bulk Export/Import system.
|
- [x] Implement Bulk Export/Import system.
|
||||||
- [x] Establish centralized Export Template engine.
|
- [x] Establish centralized Export Template engine.
|
||||||
|
|
||||||
### Phase 4: Polish & Security (ACTIVE)
|
### Phase 4: Polish & Security (UI scope complete; security follow-ups transferred)
|
||||||
- [x] Implement Auto-Save toggle and visual status indicators.
|
- [x] Implement Auto-Save toggle and visual status indicators.
|
||||||
- [x] Extract decryption workflow to non-reactive helper.
|
- [x] Extract decryption workflow to non-reactive helper.
|
||||||
- [x] **Standardize Configuration Modals:** Refactored Module, Journal, and Entry configuration into a unified tabbed UI.
|
- [x] **Standardize Configuration Modals:** Refactored Module, Journal, and Entry configuration into a unified tabbed UI.
|
||||||
@@ -81,9 +87,9 @@ This document outlines the modernization of the Journals module UI in the Svelte
|
|||||||
- [x] **Dark mode fixes:** Entry content hover, journal view section/description background and text colors.
|
- [x] **Dark mode fixes:** Entry content hover, journal view section/description background and text colors.
|
||||||
- [x] **Modal close button:** All 3 config modals use `dismissable={false}` + explicit `<X>` button in header snippet for correct right-aligned placement.
|
- [x] **Modal close button:** All 3 config modals use `dismissable={false}` + explicit `<X>` button in header snippet for correct right-aligned placement.
|
||||||
- [x] **Global select padding:** Added `padding-inline: 0.5rem` to `@layer base` in `app.css` (safe — utility `px-*` classes override it where intentional).
|
- [x] **Global select padding:** Added `padding-inline: 0.5rem` to `@layer base` in `app.css` (safe — utility `px-*` classes override it where intentional).
|
||||||
- [ ] Solidify E2EE passcode system for Journals and Entries.
|
- [ ] Solidify E2EE passcode system for Journals and Entries. See active task list.
|
||||||
- [ ] Audit encryption flow for Quick Added and Imported entries.
|
- [ ] Audit encryption flow for Quick Added and Imported entries. See active task list.
|
||||||
- [ ] Integrate Outbound Email sharing.
|
- [ ] Integrate Outbound Email sharing. Deferred pending product confirmation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
# Project: CRUD V3 Final Migration
|
# Project: CRUD V3 Final Migration
|
||||||
|
|
||||||
> **Status:** Active / In Progress
|
> **Status:** ✅ **Completed and archived 2026-06-12**
|
||||||
> **Last Updated:** 2026-01-20
|
> **Completion:** All production code migrated to V3. Legacy wrappers remain defined but unused.
|
||||||
|
> **Last Updated:** 2026-06-12
|
||||||
> **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/...`).
|
> **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 +22,23 @@ While the **Journals** and **Identity (User/Account)** modules have been success
|
|||||||
The following files have been identified as using legacy CRUD wrappers.
|
The following files have been identified as using legacy CRUD wrappers.
|
||||||
|
|
||||||
### 🔴 High Priority: Events Module
|
### 🔴 High Priority: Events Module
|
||||||
The entire `ae_events` library is heavily dependent on legacy `v2` list and `v1` CRUD wrappers.
|
- [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)
|
||||||
- [ ] `src/lib/ae_events/ae_events__event_session.ts`
|
- [x] `src/lib/ae_events/ae_events__event_presentation.ts` (Migrated 2026-01-30)
|
||||||
- [ ] `src/lib/ae_events/ae_events__event_presenter.ts`
|
- [x] `src/lib/ae_events/ae_events__event_location.ts` (Migrated 2026-01-30)
|
||||||
- [ ] `src/lib/ae_events/ae_events__event_presentation.ts`
|
- [x] `src/lib/ae_events/ae_events__event_badge_template.ts` (Migrated 2026-01-30)
|
||||||
- [ ] `src/lib/ae_events/ae_events__event_location.ts`
|
- [x] `src/lib/ae_events/ae_events__event_device.ts` (Migrated 2026-01-30)
|
||||||
- [ ] `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__exhibit.ts` (Migrated 2026-01-28)
|
- [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
|
### 🟠 Medium Priority: Core & Sponsorships
|
||||||
Legacy patterns persisting in core logic and config modules.
|
Legacy patterns persisting in core logic and config modules.
|
||||||
|
|
||||||
- [ ] `src/lib/ae_sponsorships/ae_sponsorships_functions.ts`
|
- [ ] `src/lib/ae_sponsorships/ae_sponsorships_functions.ts`
|
||||||
- [ ] `src/lib/ae_core/core__hosted_files.ts` (Uses `get_ae_obj_id_crud`)
|
- [x] `src/lib/ae_core/core__hosted_files.ts` (Migrated 2026-01-20)
|
||||||
- [ ] `src/lib/ae_core/core__site.ts` (Uses `get_ae_obj_id_crud`)
|
- [x] `src/lib/ae_core/core__site.ts` (Migrated 2026-01-26; bootstrap path uses V3 `search_ae_obj` by `fqdn`)
|
||||||
|
- [x] `src/lib/ae_core/core__site_domain.ts` (Retired 2026-06-02; helper removed after bootstrap migration to `core__site.ts`)
|
||||||
|
- [ ] `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__country_subdivisions.ts`
|
||||||
- [ ] `src/lib/ae_core/core__time_zones.ts`
|
- [ ] `src/lib/ae_core/core__time_zones.ts`
|
||||||
- [ ] `src/lib/ae_core/core__countries.ts`
|
- [ ] `src/lib/ae_core/core__countries.ts`
|
||||||
@@ -48,9 +49,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`)
|
- [ ] `src/lib/elements/element_data_store.svelte` (Direct `create_ae_obj_crud`)
|
||||||
- [x] `src/lib/elements/element_data_store_v2.svelte`
|
- [x] `src/lib/elements/element_data_store_v2.svelte`
|
||||||
- [ ] `src/routes/events/[event_id]/event_page_menu.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/events/ae_comp__event_session_obj_li.svelte`
|
||||||
- [ ] `src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_id_edit.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`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# Aether Events — Unified Launcher Configuration (Vision v3.1)
|
||||||
|
|
||||||
|
> **Status:** Strategic Design / Unified Proposal
|
||||||
|
> **Author:** Gemini CLI (Interactive Agent)
|
||||||
|
> **Target:** Full consistency across all configuration modules.
|
||||||
|
|
||||||
|
## 1. Unified Design Language
|
||||||
|
|
||||||
|
To eliminate the "created by 3 different people" feel, all components must strictly adhere to this shared specification.
|
||||||
|
|
||||||
|
### 1.1 Color Palette & Semantics
|
||||||
|
- **Primary (Blue):** Main actions, active tabs, and standard configuration toggles.
|
||||||
|
- **Secondary (Green):** Safe actions (Connect, Sync, Apply).
|
||||||
|
- **Warning (Orange):** Technical overrides that require caution (Timers, Native Shell).
|
||||||
|
- **Error (Red):** Destructive actions (Resets, Shutdown, Kill Apps).
|
||||||
|
- **Surface (Gray):** Containers, input backgrounds, and inactive states.
|
||||||
|
|
||||||
|
### 1.2 Typography & Spacing
|
||||||
|
- **Section Headers:** `text-sm font-bold uppercase tracking-tight` (Provided by Wrapper).
|
||||||
|
- **Field Labels:** `text-[10px] font-bold uppercase tracking-wider opacity-60 mb-1`.
|
||||||
|
- **Sub-Descriptions:** `text-[9px] italic opacity-40 leading-snug mt-1`.
|
||||||
|
- **Status Badges:** `text-[8px] font-bold uppercase tracking-tighter`.
|
||||||
|
- **Grid Standard:**
|
||||||
|
* Single Column for complex fields.
|
||||||
|
* `grid-cols-2` with `gap-4` for standard inputs.
|
||||||
|
* `grid-cols-3` or `grid-cols-4` only for small buttons or icon toggles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Structural Reorganization (The "Aether" Layout)
|
||||||
|
|
||||||
|
The menu is now a **Vertical Sidebar Modal**. This allows for persistent navigation while dedicating the large right pane to content.
|
||||||
|
|
||||||
|
### Tab 1: 🖥️ Display (General Operator)
|
||||||
|
*Focus: What the screen looks like.*
|
||||||
|
- **Category: Layout & UI**
|
||||||
|
- Presets: Oral/Default vs Poster Kiosk (One-tap setup).
|
||||||
|
- Toggles: Header, Menu, Footer, Times visibility.
|
||||||
|
- Formatting: Clock (12/24h), Date formats.
|
||||||
|
- **Category: Screen Saver**
|
||||||
|
- Idle Timeout (Minutes).
|
||||||
|
- Mode: Image Cycle vs Video vs Custom.
|
||||||
|
|
||||||
|
### Tab 2: 🔌 Connectivity (Onsite Tech)
|
||||||
|
*Focus: How it talks to the network.*
|
||||||
|
- **Category: WebSocket Control**
|
||||||
|
- Connection Status & Signal Strength.
|
||||||
|
- Controller Mode: Local vs Remote vs Push.
|
||||||
|
- Group Code: Channel sharding for multi-room management.
|
||||||
|
- **Category: API Context**
|
||||||
|
- Current Endpoint, Account, and Site context.
|
||||||
|
|
||||||
|
### Tab 3: 🔄 Sync & Health (Onsite Tech)
|
||||||
|
*Focus: Data integrity and performance.*
|
||||||
|
- **Category: Sync Engine**
|
||||||
|
- Status: Active vs Paused.
|
||||||
|
- Action: Force Sync Location (recursive metadata fetch).
|
||||||
|
- Stats: Cached Files vs Total Files (Progress bar).
|
||||||
|
- **Category: System Telemetry**
|
||||||
|
- CPU & RAM usage (Visual gauges).
|
||||||
|
- Heartbeat monitor (Last success timestamp).
|
||||||
|
- Device Identity: Hostname, IP list, Local paths.
|
||||||
|
|
||||||
|
### Tab 4: 🛠️ Native Shell (Specialized / Mac)
|
||||||
|
*Focus: OS-level capabilities.*
|
||||||
|
- **Category: App Control**
|
||||||
|
- Window: Maximize, Kiosk Mode, Fullscreen.
|
||||||
|
- Automation: Kill presentation apps (Clean slate).
|
||||||
|
- Remote: Virtual clicker (Prev/Next/Start/Stop).
|
||||||
|
- **Category: System Action**
|
||||||
|
- Displays: Extend vs Mirror (Native bridge).
|
||||||
|
- Folders: Open Cache / Open Temp.
|
||||||
|
- Power: Reboot / Shutdown (With confirmation).
|
||||||
|
|
||||||
|
### Tab 5: 🖼️ Wallpaper (Branding)
|
||||||
|
*Focus: Event-specific aesthetics.*
|
||||||
|
- **Category: Customization**
|
||||||
|
- Primary Display: URL/Preset.
|
||||||
|
- Secondary/Projector: URL/Preset.
|
||||||
|
- Action: Apply to OS (Native) + Preview (Web).
|
||||||
|
|
||||||
|
### Tab 6: 🧪 Advanced (Developer Mode)
|
||||||
|
*Focus: Fine-tuning and updates.*
|
||||||
|
- **Category: Performance**
|
||||||
|
- Polling Intervals (Event, Device, Room, Session, Presenter).
|
||||||
|
- Cache Sharding (Prefix length).
|
||||||
|
- **Category: Launch Logic**
|
||||||
|
- Per-Profile Post-Open Delays (ms).
|
||||||
|
- **Category: Updates**
|
||||||
|
- Source: File vs URL.
|
||||||
|
- Version: Current vs Target.
|
||||||
|
- Action: Download/Install.
|
||||||
|
|
||||||
|
### Tab 7: 🧹 Maintenance (Emergency)
|
||||||
|
*Focus: Troubleshooting.*
|
||||||
|
- **Category: Resets**
|
||||||
|
- Wipe IndexedDB (Module selective).
|
||||||
|
- Clear LocalStorage (Reset config).
|
||||||
|
- **Category: Diagnostics**
|
||||||
|
- Raw Device JSON inspector.
|
||||||
|
- Terminal Command Entry.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Implementation Plan: The "Cohesion" Refactor
|
||||||
|
|
||||||
|
1. **Standardize `Launcher_Cfg_Section.svelte`:** Ensure padding and spacing are baked into the wrapper so children don't have to define it.
|
||||||
|
2. **Create `Launcher_Cfg_Field.svelte`:** A new helper component to handle the Label + Description + Input pattern consistently.
|
||||||
|
3. **Audit Sub-Components:** Update all 10 components to use the new colors, grid patterns, and typography.
|
||||||
|
4. **Polish Transitions:** Ensure the Modal entry and Tab switching are butter-smooth with Svelte 5 transitions.
|
||||||
34
documentation/archive/README.md
Normal file
34
documentation/archive/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Documentation Archive Index
|
||||||
|
|
||||||
|
This directory preserves completed projects, superseded proposals, historical task logs, and legacy technical references.
|
||||||
|
|
||||||
|
Archived files are not authoritative for current implementation. Start with `documentation/README__Docs_Index.md` and the active module/guides directory.
|
||||||
|
|
||||||
|
## Categories
|
||||||
|
|
||||||
|
### Completed or Superseded Projects
|
||||||
|
|
||||||
|
Files prefixed with `PROJECT__` document completed implementation phases or superseded project plans.
|
||||||
|
|
||||||
|
### Historical Proposals
|
||||||
|
|
||||||
|
Files prefixed with `PROPOSAL__` preserve design ideas that are not the current implementation source.
|
||||||
|
|
||||||
|
### Task History
|
||||||
|
|
||||||
|
Files prefixed with `TODO__Agents__ARCHIVE_` contain completed task history by month.
|
||||||
|
|
||||||
|
### Legacy References
|
||||||
|
|
||||||
|
Files prefixed with `REFERENCE__Legacy_` are retained for historical context but contain pre-V3, pre-runes, or otherwise superseded guidance.
|
||||||
|
|
||||||
|
Do not copy implementation patterns from legacy references without validating them against current source and active guides.
|
||||||
|
|
||||||
|
## Restore Policy
|
||||||
|
|
||||||
|
Move an archived doc back to the active documentation root only when:
|
||||||
|
|
||||||
|
1. Its subject is active again.
|
||||||
|
2. Its content has been reviewed against current source.
|
||||||
|
3. Legacy paths, IDs, stores, and API conventions have been updated.
|
||||||
|
4. It is added to `documentation/README__Docs_Index.md`.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
- [x] **[Stores] Phase 1 — Dead code cleanup** (`ae_stores.ts`, `ae_events_stores.ts`, `ae_idaa_stores.ts`): removed `ver_idb`, stale comments, `console.log` lines, Stripe button block (zero consumers), personal Novi UUIDs, dead alternatives. Net: −202 lines across 3 files. svelte-check: 0 errors. (2026-03-16)
|
- [x] **[Stores] Phase 1 — Dead code cleanup** (`ae_stores.ts`, `ae_events_stores.ts`, `ae_idaa_stores.ts`): removed `ver_idb`, stale comments, `console.log` lines, Stripe button block (zero consumers), personal Novi UUIDs, dead alternatives. Net: −202 lines across 3 files. svelte-check: 0 errors. (2026-03-16)
|
||||||
- [x] **[Stores] Phase 2a — Split defaults into domain sub-files**: `ae_stores__auth_loc_defaults.ts`; `ae_events_stores__badges/launcher/leads/pres_mgmt_defaults.ts`. Spread-merged back into store structs — zero consumer changes. (2026-03-16)
|
- [x] **[Stores] Phase 2a — Split defaults into domain sub-files**: `ae_stores__auth_loc_defaults.ts`; `ae_events_stores__badges/launcher/leads/pres_mgmt_defaults.ts`. Spread-merged back into store structs — zero consumer changes. (2026-03-16)
|
||||||
- [x] **[Stores] Phase 2b — TypeScript interfaces for defaults sub-files**: `SiteCfgJson`, `AePerson`, `AeUser`, `AccessType`, `AuthLocState`; `BadgesLocState/SessState`; `SectionState`, `LauncherLocState/SessState`; `LeadsLocState/SessState`, `TmpLicense`; `PresMgmtLocState/SessState`. svelte-check: 0 errors. (2026-03-16)
|
- [x] **[Stores] Phase 2b — TypeScript interfaces for defaults sub-files**: `SiteCfgJson`, `AePerson`, `AeUser`, `AccessType`, `AuthLocState`; `BadgesLocState/SessState`; `SectionState`, `LauncherLocState/SessState`; `LeadsLocState/SessState`, `TmpLicense`; `PresMgmtLocState/SessState`. svelte-check: 0 errors. (2026-03-16)
|
||||||
- [x] **[UI]** Style Review Phase 1 & 2 complete — all non-frozen, non-IDAA routes migrated: FA→Lucide (events, pres_mgmt, core, badges, leads, hosted_files), `variant-*`→`preset-*` (all modules), `code_to_html` badge dict refactored to Lucide component map, FA CDN scoped to IDAA layout, global `svg.lucide { display: inline }` CSS rule added to fix icon inline flow. See `documentation/PROJECT__AE_Style_Review.md`. (2026-03-16)
|
- [x] **[UI]** Style Review Phase 1 & 2 complete — all non-frozen, non-IDAA routes migrated: FA→Lucide (events, pres_mgmt, core, badges, leads, hosted_files), `variant-*`→`preset-*` (all modules), `code_to_html` badge dict refactored to Lucide component map, FA CDN scoped to IDAA layout, global `svg.lucide { display: inline }` CSS rule added to fix icon inline flow. See `documentation/archive/PROJECT__AE_Style_Review_2026-03.md`. (2026-03-16)
|
||||||
- [x] **[UI]** Pres Mgmt Phase 3 — FA→Lucide icon migration across all 24 pres_mgmt files. (2026-03-16)
|
- [x] **[UI]** Pres Mgmt Phase 3 — FA→Lucide icon migration across all 24 pres_mgmt files. (2026-03-16)
|
||||||
- [x] **[IDAA]** `ae_idaa_comp__event_obj_id_edit.svelte` — inlined Tailwind utilities, removed `<style>` block; eliminated all 23 `@apply`/`@reference` svelte-check warnings. (2026-03-16)
|
- [x] **[IDAA]** `ae_idaa_comp__event_obj_id_edit.svelte` — inlined Tailwind utilities, removed `<style>` block; eliminated all 23 `@apply`/`@reference` svelte-check warnings. (2026-03-16)
|
||||||
- [x] **[Badges]** Badge print page svelte-check fix: extracted print CSS to `static/ae-print-badge.css`; fixed unclosed `<script>` tag in `print/+page.svelte`. (2026-03-16)
|
- [x] **[Badges]** Badge print page svelte-check fix: extracted print CSS to `static/ae-print-badge.css`; fixed unclosed `<script>` tag in `print/+page.svelte`. (2026-03-16)
|
||||||
|
|||||||
@@ -1,326 +1,54 @@
|
|||||||
# Frontend Agent Task List
|
# Frontend Agent Task List (Archived May 2026)
|
||||||
> Use this file to track steps for complex features or bug fixes.
|
|
||||||
> **Status:** Stable — ongoing development.
|
|
||||||
|
|
||||||
|
## ✅ 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
|
### [API] PATCH/DELETE retry hardening — parity with GET/POST
|
||||||
`load_ae_obj_li__event_location` on page load. Also fixed session query using stale
|
**Status:** ✅ Completed (2026-05-21)
|
||||||
`event_location_id_random` index (should be `event_location_id`). (2026-04-19)
|
- 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;
|
### [Testing] V3 API performance probe (basic stress rounds)
|
||||||
non-trusted users are fully blocked (`file_list_status = 'blocked_legacy'`); trusted users see
|
**Status:** ✅ Completed baseline harness (2026-05-21)
|
||||||
warnings but can still upload. Covers `.ppt`, `.doc` (block) and other legacy exts (warn-only).
|
- Implemented a gated Playwright probe for quick repeated list-query timing against live V3 endpoints.
|
||||||
(2026-04-19)
|
- Writes reports to `tests/results/`.
|
||||||
|
|
||||||
- [x] **[Files] Hide internal-purpose files from Launcher by default** — renamed `hide_draft` prop
|
### [IDAA] Random "Access Denied" — Root Cause Review & Fixes
|
||||||
to `show_internal_purpose_files` in `launcher_file_cont.svelte`; logic flipped so `false` (the
|
**Status:** ✅ Resolved (2026-05-19)
|
||||||
default) hides files with `file_purpose == 'outline'`, `'draft'`, or `'admin'`. Store key renamed
|
- Server-side Novi verification migrated to V3 action endpoint.
|
||||||
from `hide_content__draft_files` (inverted, misleading) to `show_content__internal_files: false`
|
- Extended Novi TTL to 12 hours.
|
||||||
(show-on-opt-in, consistent with all other `show_content__*` flags). Updated across all 8 Launcher
|
- Hardened retry and timeout logic in `+layout.svelte`.
|
||||||
templates that pass this prop. (2026-04-19, revised 2026-04-20)
|
|
||||||
|
|
||||||
- [x] **[Launcher] Remove duplicate session API call on session select** — `menu_session_list.svelte`
|
### [IDAA] Server-side Novi verification — 503 not auto-retried
|
||||||
was calling `load_ae_obj_id__event_session` directly AND then `goto()` triggered `+page.ts` which
|
**Status:** ✅ Fixed (2026-05-20)
|
||||||
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)
|
|
||||||
|
|
||||||
- [ ] **[Electron/Launcher] Deploy + test Aether Native Electron app on Mac laptops** — build,
|
### [IDAA] Jitsi Reports filters
|
||||||
deploy, and verify on onsite Mac laptops. Additional testing of cache/launch flow still needed
|
**Status:** ✅ Finished (2026-05-06)
|
||||||
before April 21.
|
- Added Novi UUID exclusion plus meeting-name whitelist filtering.
|
||||||
|
|
||||||
- [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)
|
|
||||||
|
|
||||||
### [PWA] Service worker ignoring `chrome-extension://` requests
|
### [PWA] Service worker ignoring `chrome-extension://` requests
|
||||||
Browser console shows repeated errors:
|
**Status:** ✅ Fixed (2026-05-14)
|
||||||
```text
|
- Added guard to filter out non-http/https requests before Attempting to cache.
|
||||||
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.
|
|
||||||
|
|
||||||
### [CSS] Global placeholder text color — too dark in light mode
|
### [Electron/Launcher] Display mirroring auto-detection
|
||||||
Placeholder text inherits full input text color in light mode (Tailwind CSS default), making
|
**Status:** ✅ Completed (2026-05-20)
|
||||||
placeholders indistinguishable from filled-in values. Most visible in badge print controls
|
- `native:set-display-layout` now auto-detects displays via `displayplacer list`.
|
||||||
where placeholders show the actual badge value (e.g. "John Smith").
|
|
||||||
|
|
||||||
Workaround: scoped `::placeholder` rule added to `ae_comp__badge_print_controls.svelte`
|
### [Launcher] Force Sync Location
|
||||||
(gray-400 light / gray-500 dark) — `commit 7733ef8`.
|
**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):
|
### [Launcher] Chronological Download Priority
|
||||||
```css
|
**Status:** ✅ Completed (2026-05-21)
|
||||||
::placeholder {
|
- Refactored download queue to prioritize Event Assets > Early Sessions > Presentation Order > Created Date.
|
||||||
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] Error handling + fallback
|
||||||
|
**Status:** ✅ Completed (2026-05-14)
|
||||||
### [Backend/DevOps] Re-add `Access-Control-Allow-Private-Network: true` CORS header
|
- Post-script failure surfaces 'fallback' status; `open_cmd` failure falls back to OS default.
|
||||||
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)
|
|
||||||
|
|||||||
139
documentation/archive/TODO__Agents__ARCHIVE_2026-06.md
Normal file
139
documentation/archive/TODO__Agents__ARCHIVE_2026-06.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# Frontend Agent Task List
|
||||||
|
> 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+
|
||||||
|
|
||||||
|
- [x] **[Launcher] Composable open flow** — `handle_open_file()` uses `copy_from_cache_to_temp` +
|
||||||
|
`run_osascript` / `run_cmd` directly with per-step error handling. Complete.
|
||||||
|
- [x] **[Launcher] Slide control scripts in Svelte config** — AppleScript post_scripts live in
|
||||||
|
`ae_launcher__default_launch_profiles.ts`. VLC focus-stealing fix applied. Complete.
|
||||||
|
- [x] **[Launcher] Kill Apps button** — "Kill Apps" button added to Native OS config (System
|
||||||
|
Actions, edit mode only). Kills PowerPoint, Keynote, Adobe Acrobat Reader DC, VLC, soffice.
|
||||||
|
List overridable via `event_device.other_json.launcher.kill_process_li`. Auto-cleanup on file
|
||||||
|
open (deferred — manual button sufficient for CMSC).
|
||||||
|
- [x] **[Launcher] Hidden/deleted files still visible in Presenter file list** — Fixed by
|
||||||
|
API-to-Dexie stale-record pruning plus Launcher background refresh loops for file lists.
|
||||||
|
`ae_events__event_file.ts` now prunes stale records after refresh, and
|
||||||
|
`launcher_background_sync.svelte` refreshes/prunes selected session and presenter file lists.
|
||||||
|
(`fix(launcher): refresh file lists periodically to prune deleted/hidden files`, 2026-05)
|
||||||
|
- [ ] **[Launcher/Electron] Wallpaper stops applying after several changes (post-CMSC)** —
|
||||||
|
Append timestamp/random suffix to temp filename so macOS always sees a new path.
|
||||||
|
- [ ] **[Launcher/Electron] Wallpaper drift after display hotplug (post-CMSC)** —
|
||||||
|
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** — Create/configure a fanfold badge layout
|
||||||
|
compatible with the Epson C3500 continuous stock format.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚧 V3 CRUD Migration (Surgical Cleanup)
|
||||||
|
Finalizing the 100% adoption of V3 Standard endpoints and retirement of legacy wrappers.
|
||||||
|
|
||||||
|
- [x] **[Badges] Presenter Agreement Form** — migrated to `update_ae_obj` (2026-05-21)
|
||||||
|
- [x] **[Core] Site Domain Bootstrap Refactor** — Bootstrap path is already on V3 in
|
||||||
|
`ae_core__site.ts` via `lookup_site_domain()` using `api.search_ae_obj` with FQDN filter
|
||||||
|
(used by `src/routes/+layout.ts`).
|
||||||
|
Follow-up cleanup complete: retired legacy helper `core__site_domain.ts`. (2026-06-02)
|
||||||
|
- [ ] **[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`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚧 High Priority Workstreams
|
||||||
|
|
||||||
|
### [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`.
|
||||||
|
- [ ] **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.
|
||||||
|
|
||||||
|
### [IDB Sort] `build_tmp_sort` rollout
|
||||||
|
Shared utility in `src/lib/ae_core/core__idb_sort.ts` — fixes priority direction (inverted,
|
||||||
|
true→'0' sorts first ASC) and zero-pads sort field (8 chars). No `.reverse()` needed.
|
||||||
|
Sort chain: `group → priority DESC → sort ASC → [module-specific fields] → name`.
|
||||||
|
**⚠️ Never use `.reverse()` on a `tmp_sort_*`-sorted list — inverted priority makes it wrong.**
|
||||||
|
Documented in `GUIDE__SvelteKit2_Svelte5_DexieJS.md` (IDB Sort section).
|
||||||
|
|
||||||
|
- [x] `ae_events__event_presentation` — group + priority + sort + start_datetime + code + name
|
||||||
|
- [x] `ae_journals__journal` + `ae_journals__journal_entry` — group + priority + sort + name + updated_on
|
||||||
|
- [ ] `ae_events__event_session` — roll out when sort behavior is reviewed
|
||||||
|
- [ ] `ae_events__event_presenter` — roll out when sort behavior is reviewed
|
||||||
|
- [ ] `ae_events__event_location` — roll out when sort behavior is reviewed
|
||||||
|
- [x] `ae_posts__post` + `ae_posts__post_comment` — migrated to `build_tmp_sort` with 8-char padding; BB comment list consumer updated for ASC tmp_sort ordering. (2026-06-02)
|
||||||
|
- [ ] `ae_core__person` + `ae_core__account` — roll out when sort behavior is reviewed
|
||||||
|
|
||||||
|
### [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).
|
||||||
|
|
||||||
|
### [TypeScript] svelte-check hidden errors
|
||||||
|
- [x] **[flowbite-svelte] `ModalProps.children` — 31 errors across 26 files.**
|
||||||
|
Verified no remaining `children={...}` bindings on `<Modal>` and `npx svelte-check` is clean. (2026-06-02)
|
||||||
|
|
||||||
|
### [Journals] Journal Entry Config follow-ups
|
||||||
|
- [ ] **[Journals] Entry passcode secondary auth** — implement `passcode_hash` comparison.
|
||||||
|
- [x] **[Journals] Summary AI shortcut** — added Quick Actions button in entry config modal and wired it to close modal + scroll to AI tools panel in entry edit view. (2026-06-02)
|
||||||
|
|
||||||
|
### [Cleanup] Migrate remaining `lucide-svelte` imports to `@lucide/svelte`
|
||||||
|
- [x] **[Cleanup] Migrate remaining `lucide-svelte` imports to `@lucide/svelte`**
|
||||||
|
Migrated all 5 listed files to `@lucide/svelte` and uninstalled `lucide-svelte` from dependencies. (2026-06-02)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### [Pres Mgmt] Sessions hide/show toggle
|
||||||
|
- [x] **[Pres Mgmt] Hidden sessions blink on initial load** — SCENARIO 2 fallback in
|
||||||
|
`pres_mgmt/+page.svelte` now captures `qry_hidden` as a `$derived.by` dependency and
|
||||||
|
applies the filter in the fallback path. No blink on page load. (2026-05-28)
|
||||||
|
- [x] **[Pres Mgmt] API call uses live store instead of snapshot** — changed
|
||||||
|
`pres_mgmt_loc.current.qry_hidden` → `params.qry_hidden` in `handle_search_refresh`
|
||||||
|
API call to be consistent with fast path snapshot. (2026-05-28)
|
||||||
|
- **Note:** `hide_event_launcher` is still active — used in `menu_session_list.svelte`
|
||||||
|
(Launcher) to CSS-hide sessions from the list. Button to toggle it is in
|
||||||
|
`session_page_menu.svelte`. Not used in Pres Mgmt (intentional — Pres Mgmt always shows all).
|
||||||
|
- **Note:** Non-trusted users always have `!item.hide` applied at the component level
|
||||||
|
in `ae_comp__event_session_obj_li.svelte` regardless of `qry_hidden`. Toggle is
|
||||||
|
trusted-access-only in practice; direct session links still work for non-trusted users.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing & Optimization
|
||||||
|
|
||||||
|
- [ ] **[IDAA] IDB fast-path contact search** — parse `contact_li_json` in `search__event()`.
|
||||||
|
- [ ] **[IDAA] Optimize Recovery Meetings SQL VIEW and indexes.**
|
||||||
|
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage** in all other event search pages.
|
||||||
|
- [ ] **[Launcher/VLC] Linux playback investigation** — fullscreen + pause-on-end flags.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ DevOps & Backend
|
||||||
|
|
||||||
|
- [ ] **[Backend] `event_file` — add `cfg_json` column (post-CMSC)** — The per-file display
|
||||||
|
override currently uses a localStorage workaround (`$events_loc.launcher.file_display_overrides`)
|
||||||
|
because `event_file` has no JSON blob column. Proper fix: add `cfg_json` to the `event_file` DB
|
||||||
|
table, expose it through the FastAPI model, then migrate the frontend back to reading/writing the
|
||||||
|
backend field (restoring global/cross-device persistence). Frontend code is in
|
||||||
|
`launcher_file_cont.svelte` — search for `file_display_overrides`.
|
||||||
|
- [ ] **[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`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 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)
|
||||||
273
package-lock.json
generated
273
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "osit-aether-app-svelte",
|
"name": "osit-aether-app-svelte",
|
||||||
"version": "3.00.10",
|
"version": "3.00.20",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "osit-aether-app-svelte",
|
"name": "osit-aether-app-svelte",
|
||||||
"version": "3.00.10",
|
"version": "3.00.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.20.0",
|
"@codemirror/autocomplete": "^6.20.0",
|
||||||
"@codemirror/commands": "^6.10.0",
|
"@codemirror/commands": "^6.10.0",
|
||||||
@@ -26,12 +26,10 @@
|
|||||||
"@lucide/svelte": "^0.*.0",
|
"@lucide/svelte": "^0.*.0",
|
||||||
"@popperjs/core": "^2.11.0",
|
"@popperjs/core": "^2.11.0",
|
||||||
"@tailwindcss/vite": "^4.1.10",
|
"@tailwindcss/vite": "^4.1.10",
|
||||||
"axios": "^1.7.0",
|
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dexie": "^4.0.0",
|
"dexie": "^4.0.0",
|
||||||
"flowbite-svelte": "^1.28.1",
|
"flowbite-svelte": "^1.28.1",
|
||||||
"html5-qrcode": "^2.3.8",
|
"html5-qrcode": "^2.3.8",
|
||||||
"lucide-svelte": "^0.*.0",
|
|
||||||
"marked": "^17.0.0",
|
"marked": "^17.0.0",
|
||||||
"openai": "^6.10.0",
|
"openai": "^6.10.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
@@ -3929,23 +3927,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/asynckit": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/axios": {
|
|
||||||
"version": "1.13.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
|
||||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"follow-redirects": "^1.15.11",
|
|
||||||
"form-data": "^4.0.5",
|
|
||||||
"proxy-from-env": "^1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
@@ -3976,19 +3957,6 @@
|
|||||||
"node": "18 || 20 || >=22"
|
"node": "18 || 20 || >=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-bind-apply-helpers": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"function-bind": "^1.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
@@ -4101,18 +4069,6 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/combined-stream": {
|
|
||||||
"version": "1.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"delayed-stream": "~1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/commondir": {
|
"node_modules/commondir": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||||
@@ -4240,15 +4196,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/delayed-stream": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dequal": {
|
"node_modules/dequal": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||||
@@ -4299,20 +4246,6 @@
|
|||||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dunder-proto": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"gopd": "^1.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
@@ -4332,24 +4265,6 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-define-property": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-errors": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-module-lexer": {
|
"node_modules/es-module-lexer": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||||
@@ -4357,33 +4272,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/es-object-atoms": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-errors": "^1.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-set-tostringtag": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"get-intrinsic": "^1.2.6",
|
|
||||||
"has-tostringtag": "^1.0.2",
|
|
||||||
"hasown": "^2.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.27.3",
|
"version": "0.27.3",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
||||||
@@ -4935,42 +4823,6 @@
|
|||||||
"mini-svg-data-uri": "^1.4.3"
|
"mini-svg-data-uri": "^1.4.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
|
||||||
"version": "1.15.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
|
||||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"debug": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/form-data": {
|
|
||||||
"version": "4.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
|
||||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"es-set-tostringtag": "^2.1.0",
|
|
||||||
"hasown": "^2.0.2",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
@@ -5003,43 +4855,6 @@
|
|||||||
"node": "6.* || 8.* || >= 10.*"
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-intrinsic": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
|
||||||
"es-define-property": "^1.0.1",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"es-object-atoms": "^1.1.1",
|
|
||||||
"function-bind": "^1.1.2",
|
|
||||||
"get-proto": "^1.0.1",
|
|
||||||
"gopd": "^1.2.0",
|
|
||||||
"has-symbols": "^1.1.0",
|
|
||||||
"hasown": "^2.0.2",
|
|
||||||
"math-intrinsics": "^1.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/get-proto": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"dunder-proto": "^1.0.1",
|
|
||||||
"es-object-atoms": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@@ -5065,18 +4880,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/gopd": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
@@ -5092,33 +4895,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/has-symbols": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/has-tostringtag": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"has-symbols": "^1.0.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
@@ -5654,15 +5430,6 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lucide-svelte": {
|
|
||||||
"version": "0.577.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.577.0.tgz",
|
|
||||||
"integrity": "sha512-0i88o57KsaHWnc80J57fY99CWzlZsSdtH5kKjLUJa7z8dum/9/AbINNLzJ7NiRFUdOgMnfAmJt8jFbW2zeC5qQ==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peerDependencies": {
|
|
||||||
"svelte": "^3 || ^4 || ^5.0.0-next.42"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lz-string": {
|
"node_modules/lz-string": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
@@ -5693,36 +5460,6 @@
|
|||||||
"node": ">= 20"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/math-intrinsics": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-db": {
|
|
||||||
"version": "1.52.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-types": {
|
|
||||||
"version": "2.1.35",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": "1.52.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mini-svg-data-uri": {
|
"node_modules/mini-svg-data-uri": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||||
@@ -6317,12 +6054,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/proxy-from-env": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "osit-aether-app-svelte",
|
"name": "osit-aether-app-svelte",
|
||||||
"version": "3.00.20",
|
"version": "3.00.30",
|
||||||
"description": "One Sky IT's Aether App created with Svelte, SvelteKit, Tailwind CSS, Lucide, Font Awesome, and Skeleton UI. -Scott Idem",
|
"description": "One Sky IT's Aether App created with Svelte, SvelteKit, Tailwind CSS, Lucide, Font Awesome, and Skeleton UI. -Scott Idem",
|
||||||
"homepage": "https://oneskyit.com/",
|
"homepage": "https://oneskyit.com/",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -105,12 +105,10 @@
|
|||||||
"@lucide/svelte": "^0.*.0",
|
"@lucide/svelte": "^0.*.0",
|
||||||
"@popperjs/core": "^2.11.0",
|
"@popperjs/core": "^2.11.0",
|
||||||
"@tailwindcss/vite": "^4.1.10",
|
"@tailwindcss/vite": "^4.1.10",
|
||||||
"axios": "^1.7.0",
|
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dexie": "^4.0.0",
|
"dexie": "^4.0.0",
|
||||||
"flowbite-svelte": "^1.28.1",
|
"flowbite-svelte": "^1.28.1",
|
||||||
"html5-qrcode": "^2.3.8",
|
"html5-qrcode": "^2.3.8",
|
||||||
"lucide-svelte": "^0.*.0",
|
|
||||||
"marked": "^17.0.0",
|
"marked": "^17.0.0",
|
||||||
"openai": "^6.10.0",
|
"openai": "^6.10.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
|
|||||||
@@ -163,9 +163,16 @@ html.light {
|
|||||||
background-color: rgb(55 65 81); /* gray-700 */
|
background-color: rgb(55 65 81); /* gray-700 */
|
||||||
border-color: rgb(75 85 99); /* gray-600 */
|
border-color: rgb(75 85 99); /* gray-600 */
|
||||||
}
|
}
|
||||||
|
.input::placeholder,
|
||||||
|
.textarea::placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
.dark .input::placeholder,
|
.dark .input::placeholder,
|
||||||
.dark .textarea::placeholder {
|
.dark .textarea::placeholder {
|
||||||
color: rgb(156 163 175); /* gray-400 — legible at reduced opacity */
|
color: rgb(156 163 175); /* gray-400 */
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.8; /* gray-400 is already dim; subtle additional fade */
|
||||||
}
|
}
|
||||||
/* Option elements in dark selects — forces browser native dark chrome */
|
/* Option elements in dark selects — forces browser native dark chrome */
|
||||||
.dark .select option {
|
.dark .select option {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { browser } from '$app/environment';
|
||||||
|
import { ae_auth_error } from '$lib/stores/ae_stores';
|
||||||
import type { key_val } from '$lib/stores/ae_stores';
|
import type { key_val } from '$lib/stores/ae_stores';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,7 +13,7 @@ export const delete_object = async function delete_object({
|
|||||||
headers = {},
|
headers = {},
|
||||||
params = {},
|
params = {},
|
||||||
data = {},
|
data = {},
|
||||||
timeout = 60000,
|
timeout = 20000,
|
||||||
return_meta = false,
|
return_meta = false,
|
||||||
log_lvl = 0,
|
log_lvl = 0,
|
||||||
retry_count = 5
|
retry_count = 5
|
||||||
@@ -97,9 +99,15 @@ export const delete_object = async function delete_object({
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= retry_count; attempt++) {
|
for (let attempt = 1; attempt <= retry_count; attempt++) {
|
||||||
|
// Keep timeout handle at attempt scope so catch can always clear it.
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => {
|
// AbortError alone is ambiguous. Track helper-timeout aborts so
|
||||||
|
// caller/navigation aborts can still fail fast with no retry.
|
||||||
|
let did_timeout_abort = false;
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
did_timeout_abort = true;
|
||||||
console.error(
|
console.error(
|
||||||
`API DELETE request timed out after ${timeout}ms.`
|
`API DELETE request timed out after ${timeout}ms.`
|
||||||
);
|
);
|
||||||
@@ -120,12 +128,48 @@ export const delete_object = async function delete_object({
|
|||||||
url.toString(),
|
url.toString(),
|
||||||
fetchOptions
|
fetchOptions
|
||||||
).catch(function (error: any) {
|
).catch(function (error: any) {
|
||||||
|
if (
|
||||||
|
error?.name === 'AbortError' ||
|
||||||
|
error?.name === 'TypeError' ||
|
||||||
|
error?.message?.includes('aborted')
|
||||||
|
) {
|
||||||
|
if (log_lvl > 1) {
|
||||||
|
console.log(
|
||||||
|
'API DELETE: Request aborted or browser-terminated.',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
'API DELETE Object *fetch* request was aborted or failed in an unexpected way.',
|
'API DELETE Object *fetch* request was aborted or failed in an unexpected way.',
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
|
return error;
|
||||||
});
|
});
|
||||||
clearTimeout(timeoutId);
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
// Error object was returned from fetch catch block; decide retry class.
|
||||||
|
if (
|
||||||
|
response instanceof Error ||
|
||||||
|
(response &&
|
||||||
|
(response.name === 'AbortError' ||
|
||||||
|
response.name === 'TypeError'))
|
||||||
|
) {
|
||||||
|
if (response.name === 'AbortError') {
|
||||||
|
if (did_timeout_abort) {
|
||||||
|
throw new Error(
|
||||||
|
`Timeout abort (attempt ${attempt}/${retry_count}) after ${timeout}ms`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Network error (attempt ${attempt}): ${response.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -151,7 +195,24 @@ export const delete_object = async function delete_object({
|
|||||||
errorBody
|
errorBody
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status >= 400 && response.status < 404) {
|
// Fail fast on client/auth/validation failures.
|
||||||
|
if (
|
||||||
|
response.status === 400 ||
|
||||||
|
response.status === 401 ||
|
||||||
|
response.status === 403 ||
|
||||||
|
response.status === 422
|
||||||
|
) {
|
||||||
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
console.warn(
|
||||||
|
`AUTH DIAGNOSTICS (DELETE): Headers sent for ${endpoint}:`,
|
||||||
|
{
|
||||||
|
has_api_key: !!headers_cleaned['x-aether-api-key'],
|
||||||
|
has_account_id: !!headers_cleaned['x-account-id']
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Signal the root layout to show the session-expired banner.
|
||||||
|
if (browser) ae_auth_error.set({ type: 'expired', ts: Date.now() });
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +235,8 @@ export const delete_object = async function delete_object({
|
|||||||
? json.data
|
? json.data
|
||||||
: json;
|
: json;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Ensure per-attempt timeout is always cleared on failure.
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
console.error(`API DELETE error on attempt ${attempt}:`, error);
|
console.error(`API DELETE error on attempt ${attempt}:`, error);
|
||||||
|
|
||||||
if (attempt === retry_count) {
|
if (attempt === retry_count) {
|
||||||
@@ -181,9 +244,12 @@ export const delete_object = async function delete_object({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log_lvl) {
|
// Backoff before retrying. Caps at 8s to match GET/POST/PATCH policy.
|
||||||
console.log(`Retrying... (${attempt}/${retry_count})`);
|
const delay_ms = Math.min(2000 * attempt, 8000);
|
||||||
}
|
console.log(
|
||||||
|
`API DELETE: Retrying in ${delay_ms}ms... (attempt ${attempt}/${retry_count})`
|
||||||
|
);
|
||||||
|
await new Promise<void>((resolve) => setTimeout(resolve, delay_ms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const get_object = async function get_object({
|
|||||||
headers = {},
|
headers = {},
|
||||||
params = {},
|
params = {},
|
||||||
data = {},
|
data = {},
|
||||||
timeout = 90000,
|
timeout = 20000,
|
||||||
return_meta = false,
|
return_meta = false,
|
||||||
return_blob = false,
|
return_blob = false,
|
||||||
filename = '',
|
filename = '',
|
||||||
@@ -73,9 +73,6 @@ export const get_object = async function get_object({
|
|||||||
url.searchParams.append(key, params[key])
|
url.searchParams.append(key, params[key])
|
||||||
);
|
);
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
||||||
|
|
||||||
// Clean and merge headers without mutating the original api_cfg
|
// Clean and merge headers without mutating the original api_cfg
|
||||||
const headers_cleaned: key_val = {};
|
const headers_cleaned: key_val = {};
|
||||||
const merged_headers = { ...api_cfg['headers'], ...headers };
|
const merged_headers = { ...api_cfg['headers'], ...headers };
|
||||||
@@ -169,10 +166,11 @@ export const get_object = async function get_object({
|
|||||||
console.log('Final cleaned headers:', headers_cleaned);
|
console.log('Final cleaned headers:', headers_cleaned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// signal is injected per-attempt inside the retry loop so each retry gets
|
||||||
|
// a fresh AbortController with its own independent timeout.
|
||||||
const fetchOptions: RequestInit = {
|
const fetchOptions: RequestInit = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: headers_cleaned,
|
headers: headers_cleaned,
|
||||||
signal: controller.signal,
|
|
||||||
// Be explicit about CORS behavior and redirect handling to avoid
|
// Be explicit about CORS behavior and redirect handling to avoid
|
||||||
// environment-dependent defaults that can cause opaque failures.
|
// environment-dependent defaults that can cause opaque failures.
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
@@ -203,10 +201,24 @@ export const get_object = async function get_object({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fresh AbortController per attempt — ensures each retry has its own
|
||||||
|
// independent timeout. Sharing a single controller across retries leaves
|
||||||
|
// retries unprotected once the first attempt's clearTimeout() runs.
|
||||||
|
const controller = new AbortController();
|
||||||
|
// Track whether THIS helper's timeout fired. AbortError alone is ambiguous:
|
||||||
|
// it can mean timeout OR intentional caller abort (navigation/unmount).
|
||||||
|
// We only retry timeout-aborts; intentional aborts should fail fast.
|
||||||
|
let did_timeout_abort = false;
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
did_timeout_abort = true;
|
||||||
|
console.warn(`API GET: Request timed out after ${timeout}ms (attempt ${attempt}/${retry_count}).`);
|
||||||
|
controller.abort();
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch_method(
|
const response = await fetch_method(
|
||||||
url.toString(),
|
url.toString(),
|
||||||
fetchOptions
|
{ ...fetchOptions, signal: controller.signal }
|
||||||
).catch(function (error: any) {
|
).catch(function (error: any) {
|
||||||
// SILENCE NOISE: Aborted requests (common in SWR/Background loads) shouldn't spam logs
|
// SILENCE NOISE: Aborted requests (common in SWR/Background loads) shouldn't spam logs
|
||||||
if (
|
if (
|
||||||
@@ -231,21 +243,36 @@ export const get_object = async function get_object({
|
|||||||
});
|
});
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
// Check if we should stop due to abort or network failure
|
// Check if we should stop due to abort or network failure.
|
||||||
if (
|
if (
|
||||||
response instanceof Error ||
|
response instanceof Error ||
|
||||||
(response &&
|
(response &&
|
||||||
(response.name === 'TypeError' ||
|
(response.name === 'TypeError' ||
|
||||||
response.name === 'AbortError'))
|
response.name === 'AbortError'))
|
||||||
) {
|
) {
|
||||||
// If it was an explicit abort, definitely stop
|
// AbortError can be either timeout or intentional abort.
|
||||||
if (response.name === 'AbortError') return false;
|
// Retry only helper-owned timeout aborts; fail fast on caller abort.
|
||||||
|
if (response.name === 'AbortError') {
|
||||||
|
if (did_timeout_abort) {
|
||||||
|
throw new Error(
|
||||||
|
`Timeout abort (attempt ${attempt}/${retry_count}) after ${timeout}ms`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (log_lvl > 1)
|
// TypeError = transient network failure (ERR_NETWORK_CHANGED,
|
||||||
console.log(
|
// ERR_NETWORK_IO_SUSPENDED, hotel/conference WiFi blip, etc.).
|
||||||
'API GET Object: Detected NetworkError or TypeError. Failing fast.'
|
// IMPORTANT: throw here so the retry loop's catch block handles it with
|
||||||
);
|
// backoff. Returning false would bypass retries entirely.
|
||||||
return false;
|
//
|
||||||
|
// WHY THIS WAS BROKEN: The Jan 2026 "offline-first fast-paths" commit
|
||||||
|
// (a10accfaa) changed .catch() to return the error as a value instead of
|
||||||
|
// not returning (undefined). The undefined path fell through to the
|
||||||
|
// `if (!response)` throw which DID retry. The explicit `return error` +
|
||||||
|
// this `return false` block silently killed the retry for the most common
|
||||||
|
// failure mode on conference/hotel WiFi.
|
||||||
|
throw new Error(`Network error (attempt ${attempt}): ${response.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
@@ -438,6 +465,8 @@ export const get_object = async function get_object({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Ensure the per-attempt timeout timer is always cancelled on failure.
|
||||||
|
clearTimeout(timeoutId);
|
||||||
console.log(
|
console.log(
|
||||||
`API GET object request *fetch* error on attempt ${attempt}:`,
|
`API GET object request *fetch* error on attempt ${attempt}:`,
|
||||||
error
|
error
|
||||||
@@ -448,10 +477,13 @@ export const get_object = async function get_object({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log retry information
|
// Backoff before retrying. Without a delay, rapid retries on a flaky
|
||||||
if (log_lvl) {
|
// connection accomplish nothing and add noise. Caps at 8s so later
|
||||||
console.log(`Retrying... (${attempt}/${retry_count})`);
|
// attempts don't wait excessively. Gives the network time to recover
|
||||||
}
|
// (ERR_NETWORK_CHANGED is typically a sub-second WiFi roam event).
|
||||||
|
const delay_ms = Math.min(2000 * attempt, 8000);
|
||||||
|
console.log(`API GET: Retrying in ${delay_ms}ms... (attempt ${attempt}/${retry_count})`);
|
||||||
|
await new Promise<void>((resolve) => setTimeout(resolve, delay_ms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const patch_object = async function patch_object({
|
|||||||
headers = {},
|
headers = {},
|
||||||
params = {},
|
params = {},
|
||||||
data = {},
|
data = {},
|
||||||
timeout = 60000,
|
timeout = 20000,
|
||||||
return_meta = false,
|
return_meta = false,
|
||||||
log_lvl = 0,
|
log_lvl = 0,
|
||||||
retry_count = 5
|
retry_count = 5
|
||||||
@@ -153,9 +153,15 @@ export const patch_object = async function patch_object({
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= retry_count; attempt++) {
|
for (let attempt = 1; attempt <= retry_count; attempt++) {
|
||||||
|
// Keep timeout handle at attempt scope so catch can always clear it.
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => {
|
// AbortError alone is ambiguous. Track whether the helper timeout
|
||||||
|
// fired so we can retry timeout-aborts but fail fast on caller abort.
|
||||||
|
let did_timeout_abort = false;
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
did_timeout_abort = true;
|
||||||
console.error(
|
console.error(
|
||||||
`API PATCH request timed out after ${timeout}ms.`
|
`API PATCH request timed out after ${timeout}ms.`
|
||||||
);
|
);
|
||||||
@@ -173,12 +179,52 @@ export const patch_object = async function patch_object({
|
|||||||
url.toString(),
|
url.toString(),
|
||||||
fetchOptions
|
fetchOptions
|
||||||
).catch(function (error: any) {
|
).catch(function (error: any) {
|
||||||
|
// Keep noisy abort/network conditions out of high-level logs.
|
||||||
|
if (
|
||||||
|
error?.name === 'AbortError' ||
|
||||||
|
error?.name === 'TypeError' ||
|
||||||
|
error?.message?.includes('aborted')
|
||||||
|
) {
|
||||||
|
if (log_lvl > 1) {
|
||||||
|
console.log(
|
||||||
|
'API PATCH: Request aborted or browser-terminated.',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
'API PATCH Object *fetch* request was aborted or failed in an unexpected way.',
|
'API PATCH Object *fetch* request was aborted or failed in an unexpected way.',
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
|
return error;
|
||||||
});
|
});
|
||||||
clearTimeout(timeoutId);
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
// Error object was returned from fetch catch block; decide retry class.
|
||||||
|
if (
|
||||||
|
response instanceof Error ||
|
||||||
|
(response &&
|
||||||
|
(response.name === 'AbortError' ||
|
||||||
|
response.name === 'TypeError'))
|
||||||
|
) {
|
||||||
|
if (response.name === 'AbortError') {
|
||||||
|
// Retry only helper-timeout aborts. Caller/navigation aborts
|
||||||
|
// should fail fast to avoid duplicate mutation side-effects.
|
||||||
|
if (did_timeout_abort) {
|
||||||
|
throw new Error(
|
||||||
|
`Timeout abort (attempt ${attempt}/${retry_count}) after ${timeout}ms`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transient browser/network failure class.
|
||||||
|
throw new Error(
|
||||||
|
`Network error (attempt ${attempt}): ${response.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -292,6 +338,8 @@ export const patch_object = async function patch_object({
|
|||||||
? json.data
|
? json.data
|
||||||
: json;
|
: json;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Ensure per-attempt timeout is always cleared on failure.
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
console.error(`API PATCH error on attempt ${attempt}:`, error);
|
console.error(`API PATCH error on attempt ${attempt}:`, error);
|
||||||
|
|
||||||
if (attempt === retry_count) {
|
if (attempt === retry_count) {
|
||||||
@@ -299,9 +347,12 @@ export const patch_object = async function patch_object({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log_lvl) {
|
// Backoff before retrying. Caps at 8s to match GET/POST policy.
|
||||||
console.log(`Retrying... (${attempt}/${retry_count})`);
|
const delay_ms = Math.min(2000 * attempt, 8000);
|
||||||
}
|
console.log(
|
||||||
|
`API PATCH: Retrying in ${delay_ms}ms... (attempt ${attempt}/${retry_count})`
|
||||||
|
);
|
||||||
|
await new Promise<void>((resolve) => setTimeout(resolve, delay_ms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const post_object = async function post_object({
|
|||||||
params = {},
|
params = {},
|
||||||
data = {},
|
data = {},
|
||||||
form_data = null,
|
form_data = null,
|
||||||
timeout = 90000,
|
timeout = 20000,
|
||||||
return_meta = false,
|
return_meta = false,
|
||||||
return_blob = false,
|
return_blob = false,
|
||||||
filename = '',
|
filename = '',
|
||||||
@@ -200,13 +200,19 @@ export const post_object = async function post_object({
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= retry_count; attempt++) {
|
for (let attempt = 1; attempt <= retry_count; attempt++) {
|
||||||
try {
|
// Declared at loop scope (not inside try) so the catch block can clearTimeout.
|
||||||
const controller = new AbortController();
|
// Fresh controller per attempt — same rationale as api_get_object.ts.
|
||||||
const timeoutId = setTimeout(() => {
|
const controller = new AbortController();
|
||||||
console.error(`API POST request timed out after ${timeout}ms.`);
|
// AbortError is not specific enough by itself. Distinguish timeout-aborts
|
||||||
controller.abort();
|
// (retryable transient class) from intentional caller aborts (fail-fast).
|
||||||
}, timeout);
|
let did_timeout_abort = false;
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
did_timeout_abort = true;
|
||||||
|
console.warn(`API POST: Request timed out after ${timeout}ms (attempt ${attempt}/${retry_count}).`);
|
||||||
|
controller.abort();
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
try {
|
||||||
const fetchOptions: RequestInit = {
|
const fetchOptions: RequestInit = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: headers_cleaned,
|
headers: headers_cleaned,
|
||||||
@@ -245,19 +251,28 @@ export const post_object = async function post_object({
|
|||||||
});
|
});
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
// Check if we should stop due to abort or network failure
|
// Check if we should stop due to abort or network failure.
|
||||||
if (
|
if (
|
||||||
response instanceof Error ||
|
response instanceof Error ||
|
||||||
(response &&
|
(response &&
|
||||||
(response.name === 'TypeError' ||
|
(response.name === 'TypeError' ||
|
||||||
response.name === 'AbortError'))
|
response.name === 'AbortError'))
|
||||||
) {
|
) {
|
||||||
if (response.name === 'AbortError') return false;
|
// Retry timeout-aborts from this helper; do not retry caller aborts
|
||||||
if (log_lvl > 1)
|
// (route change/unmount/manual cancellation).
|
||||||
console.log(
|
if (response.name === 'AbortError') {
|
||||||
'API POST Object: Detected NetworkError or TypeError. Failing fast.'
|
if (did_timeout_abort) {
|
||||||
);
|
throw new Error(
|
||||||
return false;
|
`Timeout abort (attempt ${attempt}/${retry_count}) after ${timeout}ms`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeError = transient network failure. Throw into the retry loop
|
||||||
|
// so backoff-and-retry applies. Same fix as api_get_object.ts — see
|
||||||
|
// comment there for the full history of why this was broken.
|
||||||
|
throw new Error(`Network error (attempt ${attempt}): ${response.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
@@ -411,6 +426,8 @@ export const post_object = async function post_object({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Ensure the per-attempt timeout timer is always cancelled on failure.
|
||||||
|
clearTimeout(timeoutId);
|
||||||
console.error(`API POST error on attempt ${attempt}:`, error);
|
console.error(`API POST error on attempt ${attempt}:`, error);
|
||||||
|
|
||||||
if (attempt === retry_count) {
|
if (attempt === retry_count) {
|
||||||
@@ -418,9 +435,10 @@ export const post_object = async function post_object({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log_lvl) {
|
// Backoff before retrying — same rationale as api_get_object.ts.
|
||||||
console.log(`Retrying... (${attempt}/${retry_count})`);
|
const delay_ms = Math.min(2000 * attempt, 8000);
|
||||||
}
|
console.log(`API POST: Retrying in ${delay_ms}ms... (attempt ${attempt}/${retry_count})`);
|
||||||
|
await new Promise<void>((resolve) => setTimeout(resolve, delay_ms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// *** Import Svelte specific
|
// *** Import Svelte specific
|
||||||
import * as Lucide from 'lucide-svelte';
|
import * as Lucide from '@lucide/svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
// *** Import Aether specific variables and functions
|
// *** Import Aether specific variables and functions
|
||||||
@@ -186,16 +186,13 @@ let is_url_file = $derived.by(() => {
|
|||||||
|
|
||||||
let direct_download_url = $derived.by(() => {
|
let direct_download_url = $derived.by(() => {
|
||||||
if (!show_direct_download || !hosted_file_obj) return '';
|
if (!show_direct_download || !hosted_file_obj) return '';
|
||||||
// IMPORTANT: For Direct Link Mode, we MUST use the V3 Action endpoint to support Random String IDs.
|
// Use event_file endpoint when event_file_id is present (canonical per API guide §5).
|
||||||
// Legacy endpoints often expect integer IDs and will return 404 for string IDs.
|
// Fall back to hosted_file endpoint for standalone hosted_file objects.
|
||||||
const file_id =
|
if (hosted_file_obj.event_file_id) {
|
||||||
hosted_file_obj.event_file_id ||
|
return `${$ae_api.base_url}/v3/action/event_file/${hosted_file_obj.event_file_id}/download?filename=${ae_util.clean_filename(final_filename)}&key=${$ae_api.account_id}`;
|
||||||
hosted_file_obj.hosted_file_id ||
|
}
|
||||||
hosted_file_id;
|
const file_id = hosted_file_obj.hosted_file_id || hosted_file_id;
|
||||||
const obj_type_path = hosted_file_obj.event_file_id
|
return `${$ae_api.base_url}/v3/action/hosted_file/${file_id}/download?filename=${ae_util.clean_filename(final_filename)}&key=${$ae_api.account_id}`;
|
||||||
? 'event_file'
|
|
||||||
: 'hosted_file';
|
|
||||||
return `${$ae_api.base_url}/v3/action/${obj_type_path}/${file_id}/download?filename=${ae_util.clean_filename(final_filename)}&key=${$ae_api.account_id}`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handle_click() {
|
async function handle_click() {
|
||||||
@@ -346,7 +343,20 @@ async function handle_click() {
|
|||||||
disabled={require_auth && !$ae_loc.authenticated_access}
|
disabled={require_auth && !$ae_loc.authenticated_access}
|
||||||
class={variant_classes}
|
class={variant_classes}
|
||||||
onclick={handle_click}
|
onclick={handle_click}
|
||||||
title={`Download this file:\n${final_filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}\n Linked to: ${linked_to_type} ID: ${linked_to_id}`}>
|
title={
|
||||||
|
`Download this file:
|
||||||
|
${final_filename}
|
||||||
|
[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...
|
||||||
|
Hosted ID: ${file_id}
|
||||||
|
|
||||||
|
File size: ${hosted_file_obj.file_size ? ae_util.format_bytes(hosted_file_obj.file_size) : 'Unknown size'}
|
||||||
|
Created on: ${ae_util.iso_datetime_formatter(hosted_file_obj.created_on, 'datetime_short')}
|
||||||
|
Updated on: ${ae_util.iso_datetime_formatter(hosted_file_obj.updated_on, 'datetime_short')}
|
||||||
|
|
||||||
|
Open with: ${hosted_file_obj.open_in_os == 'win' ? 'Windows' : hosted_file_obj.open_in_os == 'mac' ? 'macOS' : hosted_file_obj.open_in_os == 'linux' ? 'Linux' : '--not set--'}
|
||||||
|
|
||||||
|
Linked to Type: ${linked_to_type ?? '--none--'} ID: ${linked_to_id ?? '---'}`
|
||||||
|
}>
|
||||||
{@render content()}
|
{@render content()}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// untrack import removed — task_id sync now uses direct $effect (no untrack needed)
|
// untrack import removed — task_id sync now uses direct $effect (no untrack needed)
|
||||||
// Imports
|
// Imports
|
||||||
// Import components and elements
|
// Import components and elements
|
||||||
import * as Lucide from 'lucide-svelte';
|
import * as Lucide from '@lucide/svelte';
|
||||||
import Element_input_files_tbl from '$lib/elements/element_input_files_tbl.svelte';
|
import Element_input_files_tbl from '$lib/elements/element_input_files_tbl.svelte';
|
||||||
|
|
||||||
// Import storage, functions, and libraries
|
// Import storage, functions, and libraries
|
||||||
|
|||||||
54
src/lib/ae_core/core__idb_sort.ts
Normal file
54
src/lib/ae_core/core__idb_sort.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* src/lib/ae_core/core__idb_sort.ts
|
||||||
|
*
|
||||||
|
* Shared utility for computing tmp_sort_* fields stored in Dexie.
|
||||||
|
* All fields are designed for ascending .sortBy() — no .reverse() needed.
|
||||||
|
*
|
||||||
|
* Encoding rules:
|
||||||
|
* priority — inverted boolean: true→'0', false→'1' so priority=true sorts first (ASC)
|
||||||
|
* sort — zero-padded integer string so "00000010" < "00000020" (correct numeric order)
|
||||||
|
* all other fields — appended as-is; ISO 8601 datetimes already sort correctly
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({
|
||||||
|
* prefix: [obj.group ?? '0'], // fields before priority (optional)
|
||||||
|
* priority: obj.priority,
|
||||||
|
* sort: obj.sort,
|
||||||
|
* fields_1: [obj.start_datetime], // appended to base for tmp_sort_1
|
||||||
|
* fields_2: [obj.name], // appended after fields_1 for tmp_sort_2
|
||||||
|
* fields_3: [obj.updated_on], // appended after fields_2 for tmp_sort_3
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function build_tmp_sort({
|
||||||
|
prefix = [],
|
||||||
|
priority,
|
||||||
|
sort,
|
||||||
|
fields_1 = [],
|
||||||
|
fields_2 = [],
|
||||||
|
fields_3 = [],
|
||||||
|
pad_width = 8
|
||||||
|
}: {
|
||||||
|
prefix?: (string | null | undefined)[];
|
||||||
|
priority?: boolean | null;
|
||||||
|
sort?: number | string | null;
|
||||||
|
fields_1?: (string | null | undefined)[];
|
||||||
|
fields_2?: (string | null | undefined)[];
|
||||||
|
fields_3?: (string | null | undefined)[];
|
||||||
|
pad_width?: number;
|
||||||
|
}): { tmp_sort_1: string; tmp_sort_2: string; tmp_sort_3: string } {
|
||||||
|
const clean = (v: string | null | undefined): string => v ?? '';
|
||||||
|
|
||||||
|
const p = priority ? '0' : '1';
|
||||||
|
const s = String(Number(sort ?? 0)).padStart(pad_width, '0');
|
||||||
|
|
||||||
|
const parts_base = [...prefix.map(clean), p, s].join('_');
|
||||||
|
const parts_1 = fields_1.map(clean).filter(Boolean).join('_');
|
||||||
|
const parts_2 = fields_2.map(clean).filter(Boolean).join('_');
|
||||||
|
const parts_3 = fields_3.map(clean).filter(Boolean).join('_');
|
||||||
|
|
||||||
|
const tmp_sort_1 = [parts_base, parts_1].filter(Boolean).join('_');
|
||||||
|
const tmp_sort_2 = [tmp_sort_1, parts_2].filter(Boolean).join('_');
|
||||||
|
const tmp_sort_3 = [tmp_sort_2, parts_3].filter(Boolean).join('_');
|
||||||
|
|
||||||
|
return { tmp_sort_1, tmp_sort_2, tmp_sort_3 };
|
||||||
|
}
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
export interface Site_Domain {
|
|
||||||
id: string;
|
|
||||||
// id_random: string;
|
|
||||||
site_id: string;
|
|
||||||
site_id_random?: string;
|
|
||||||
|
|
||||||
fqdn: string;
|
|
||||||
access_key?: null | string;
|
|
||||||
required_referrer?: null | string;
|
|
||||||
valid_for?: null | number; // In hours
|
|
||||||
|
|
||||||
enable: null | boolean;
|
|
||||||
hide?: null | boolean;
|
|
||||||
priority?: null | boolean;
|
|
||||||
sort?: null | number;
|
|
||||||
group?: null | string;
|
|
||||||
notes?: null | string;
|
|
||||||
created_on: Date;
|
|
||||||
updated_on?: null | Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
import { api } from '$lib/api/api';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a site_domain object by its Fully Qualified Domain Name (FQDN).
|
|
||||||
*
|
|
||||||
* @param api_cfg - The API configuration object.
|
|
||||||
* @param fqdn - The FQDN of the site domain to fetch.
|
|
||||||
* @param timeout - The request timeout in milliseconds.
|
|
||||||
* @param log_lvl - The logging level.
|
|
||||||
* @returns The site domain object or null if not found.
|
|
||||||
*/
|
|
||||||
export async function load_ae_obj_by_fqdn__site_domain({
|
|
||||||
api_cfg,
|
|
||||||
fqdn,
|
|
||||||
timeout = 7000,
|
|
||||||
log_lvl = 0
|
|
||||||
}: {
|
|
||||||
api_cfg: any;
|
|
||||||
fqdn: string;
|
|
||||||
timeout?: number;
|
|
||||||
log_lvl?: number;
|
|
||||||
}): Promise<any> {
|
|
||||||
if (log_lvl) {
|
|
||||||
console.log(
|
|
||||||
`*** load_ae_obj_by_fqdn__site_domain() *** api.base_url=${api_cfg.base_url}, fqdn=${fqdn}, timeout=${timeout}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const site_domain_obj = await api.get_ae_obj_id_crud({
|
|
||||||
api_cfg: api_cfg,
|
|
||||||
no_account_id: true, // This seems to be a special case for this endpoint
|
|
||||||
obj_type: 'site_domain',
|
|
||||||
obj_id: fqdn, // NOTE: This is the FQDN, not the ID.
|
|
||||||
use_alt_table: true,
|
|
||||||
use_alt_base: true,
|
|
||||||
params: params,
|
|
||||||
timeout: timeout,
|
|
||||||
log_lvl: log_lvl
|
|
||||||
});
|
|
||||||
|
|
||||||
if (site_domain_obj) {
|
|
||||||
return site_domain_obj;
|
|
||||||
} else {
|
|
||||||
console.log('No results returned.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('No results returned or failed.', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -357,7 +357,7 @@ async function _handle_nested_loads(
|
|||||||
for_obj_type: 'event_location',
|
for_obj_type: 'event_location',
|
||||||
for_obj_id: current_location_id,
|
for_obj_id: current_location_id,
|
||||||
enabled: 'all',
|
enabled: 'all',
|
||||||
limit: 25,
|
hidden: 'all',
|
||||||
log_lvl
|
log_lvl
|
||||||
}).then((res) => (location_obj.event_file_li = res))
|
}).then((res) => (location_obj.event_file_li = res))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { key_val } from '$lib/stores/ae_stores';
|
|||||||
import { api } from '$lib/api/api';
|
import { api } from '$lib/api/api';
|
||||||
|
|
||||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||||
|
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
|
||||||
import { db_events } from '$lib/ae_events/db_events';
|
import { db_events } from '$lib/ae_events/db_events';
|
||||||
import type { ae_EventPresentation } from '$lib/types/ae_types';
|
import type { ae_EventPresentation } from '$lib/types/ae_types';
|
||||||
|
|
||||||
@@ -680,6 +681,18 @@ export async function process_ae_obj__event_presentation_props({
|
|||||||
if (obj.event_session_id_random)
|
if (obj.event_session_id_random)
|
||||||
obj.event_session_id = obj.event_session_id_random;
|
obj.event_session_id = obj.event_session_id_random;
|
||||||
if (obj.event_id_random) obj.event_id = obj.event_id_random;
|
if (obj.event_id_random) obj.event_id = obj.event_id_random;
|
||||||
|
|
||||||
|
// Override generic tmp_sort_* with presentation-specific encoding via
|
||||||
|
// build_tmp_sort. Order: priority DESC → sort ASC → start_datetime ASC → code ASC → name ASC
|
||||||
|
const { tmp_sort_1, tmp_sort_2 } = build_tmp_sort({
|
||||||
|
prefix: [obj.group ?? '0'],
|
||||||
|
priority: obj.priority,
|
||||||
|
sort: obj.sort,
|
||||||
|
fields_1: [obj.start_datetime, obj.code],
|
||||||
|
fields_2: [obj.name]
|
||||||
|
});
|
||||||
|
obj.tmp_sort_1 = tmp_sort_1;
|
||||||
|
obj.tmp_sort_2 = tmp_sort_2;
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -597,10 +597,13 @@ export async function email_sign_in__event_presenter({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const subject = `Pres Mgmt Hub Sign In Link for Presenter: ${to_name ?? 'Presenter'}`;
|
const subject = `Pres Mgmt Hub Sign In Link for Presenter: ${to_name ?? 'Presenter'}`;
|
||||||
|
// Routes to the session page (which has the sign-in handler mounted) not /presenter/[id]
|
||||||
|
// which has no sign-in handler. Includes presenter_id + presentation_id so the handler
|
||||||
|
// can grant presenter-level auth (not just session read access).
|
||||||
const sign_in_url = encodeURI(
|
const sign_in_url = encodeURI(
|
||||||
`${base_url}/events/${event_id}/presenter/${event_presenter_id}?person_id=${person_id}&person_pass=${person_passcode}`
|
`${base_url}/events/${event_id}/session/${event_session_id}?person_id=${person_id}&person_pass=${person_passcode}&presenter_id=${event_presenter_id}&presentation_id=${event_presentation_id}`
|
||||||
);
|
);
|
||||||
const body_html = `<div>${to_name},<p>Your sign-in link for ${presentation_name ?? 'Presentation'} (Session: ${session_name ?? 'Session'}): <a href="${sign_in_url}">${sign_in_url}</a></p></div>`;
|
const body_html = `<div>${to_name},<p>Your sign-in link for ${presentation_name ?? 'Presentation'} (Session: ${session_name ?? 'Session'}): <a href="${sign_in_url}">${sign_in_url}</a></p><p>This link takes you to the session page — your presentation and file upload sections will be available after you sign in.</p></div>`;
|
||||||
return await api.send_email({
|
return await api.send_email({
|
||||||
api_cfg,
|
api_cfg,
|
||||||
from_email: 'noreply+presmgmt@oneskyit.com',
|
from_email: 'noreply+presmgmt@oneskyit.com',
|
||||||
|
|||||||
@@ -738,11 +738,6 @@ export async function search__event_session({
|
|||||||
search_query.and.push({ field: 'enable', op: 'eq', value: 1 });
|
search_query.and.push({ field: 'enable', op: 'eq', value: 1 });
|
||||||
else if (enabled === 'not_enabled')
|
else if (enabled === 'not_enabled')
|
||||||
search_query.and.push({ field: 'enable', op: 'eq', value: 0 });
|
search_query.and.push({ field: 'enable', op: 'eq', value: 0 });
|
||||||
if (hidden === 'hidden')
|
|
||||||
search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
|
|
||||||
else if (hidden === 'not_hidden')
|
|
||||||
search_query.and.push({ field: 'hide', op: 'eq', value: 0 });
|
|
||||||
|
|
||||||
if (location_name) {
|
if (location_name) {
|
||||||
search_query.and.push({
|
search_query.and.push({
|
||||||
field: 'event_location_name',
|
field: 'event_location_name',
|
||||||
@@ -774,6 +769,7 @@ export async function search__event_session({
|
|||||||
view,
|
view,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
|
hidden,
|
||||||
log_lvl
|
log_lvl
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -833,7 +829,7 @@ export async function email_sign_in__event_session({
|
|||||||
}) {
|
}) {
|
||||||
const subject = `Pres Mgmt Hub Sign In Link for ${session_name}`;
|
const subject = `Pres Mgmt Hub Sign In Link for ${session_name}`;
|
||||||
const sign_in_url = encodeURI(
|
const sign_in_url = encodeURI(
|
||||||
`${base_url}/events/${event_id}/session/${event_session_id}?person_id=${person_id}&person_pass=${person_passcode}`
|
`${base_url}/events/${event_id}/session/${event_session_id}?person_id=${person_id}&person_pass=${person_passcode}&session_id=${event_session_id}`
|
||||||
);
|
);
|
||||||
const body_html = `<div>${to_name},<p>Your sign-in link for ${session_name}: <a href="${sign_in_url}">${sign_in_url}</a></p></div>`;
|
const body_html = `<div>${to_name},<p>Your sign-in link for ${session_name}: <a href="${sign_in_url}">${sign_in_url}</a></p></div>`;
|
||||||
return await api.send_email({
|
return await api.send_email({
|
||||||
|
|||||||
@@ -55,32 +55,35 @@ export interface LaunchProfile {
|
|||||||
/**
|
/**
|
||||||
* macOS VLC profile — uses direct binary path for max reliability.
|
* macOS VLC profile — uses direct binary path for max reliability.
|
||||||
* Bypasses `open -a` argument-handling quirks that could lose file path or re-use existing process.
|
* Bypasses `open -a` argument-handling quirks that could lose file path or re-use existing process.
|
||||||
|
*
|
||||||
|
* WHY nohup + &:
|
||||||
|
* run_cmd uses exec() which blocks until the child process exits (or the 30s timeout fires).
|
||||||
|
* The direct VLC binary forks a GUI process then exits — exec returns early and the code
|
||||||
|
* proceeds to the post_script. The old post_script polled for VLC focus (up to 10s) then
|
||||||
|
* sent Cmd+F, which was firing exactly 10–15 seconds into playback and stopping the video.
|
||||||
|
* nohup + & detaches VLC immediately so exec returns in ~0ms, decoupling run_cmd from
|
||||||
|
* VLC's lifecycle entirely.
|
||||||
|
*
|
||||||
|
* WHY --fullscreen:
|
||||||
|
* Starting VLC fullscreen via flag avoids the need to send Cmd+F via AppleScript. The old
|
||||||
|
* keystroke approach was the proximate cause of the video stopping — Cmd+F may have hit the
|
||||||
|
* wrong VLC window, triggered a menu action, or paused playback during the fullscreen
|
||||||
|
* transition. Using the flag is simpler and more reliable.
|
||||||
|
*
|
||||||
|
* WHY > /dev/null 2>&1:
|
||||||
|
* VLC logs verbosely to stdout/stderr. exec() buffers output (1MB default). Without
|
||||||
|
* redirection the buffer could overflow and kill VLC mid-playback.
|
||||||
*/
|
*/
|
||||||
function make_vlc_mirror_mac_profile(): LaunchProfile {
|
function make_vlc_mirror_mac_profile(): LaunchProfile {
|
||||||
return {
|
return {
|
||||||
app: 'VLC (macOS)',
|
app: 'VLC (macOS)',
|
||||||
display_mode: 'mirror',
|
display_mode: 'mirror',
|
||||||
// Direct binary path ensures VLC receives media file + flags reliably.
|
open_cmd: 'nohup /Applications/VLC.app/Contents/MacOS/VLC --no-play-and-exit --play-and-pause --fullscreen "{{path}}" > /dev/null 2>&1 &',
|
||||||
// `--no-play-and-exit` prevents closing on end, `--play-and-pause` holds final frame.
|
post_delay_ms: 3000,
|
||||||
open_cmd: '/Applications/VLC.app/Contents/MacOS/VLC --no-play-and-exit --play-and-pause "{{path}}"',
|
// Activate VLC after it has had time to open. Fullscreen is already set by the flag
|
||||||
post_delay_ms: 1000,
|
// above — this just ensures VLC is the frontmost app and the presenter sees it.
|
||||||
// Poll until VLC is frontmost before sending Cmd+F. A fixed delay is unreliable because
|
|
||||||
// VLC cold-start on a loaded conference Mac can take 3-5 seconds.
|
|
||||||
// Polling (15 × 0.5 s = up to 7.5 s after the initial wait) fires as soon as VLC is ready.
|
|
||||||
post_script: `tell application "VLC"
|
post_script: `tell application "VLC"
|
||||||
activate
|
activate
|
||||||
end tell
|
|
||||||
repeat 15 times
|
|
||||||
delay 0.5
|
|
||||||
tell application "System Events"
|
|
||||||
if frontmost of process "VLC" is true then exit repeat
|
|
||||||
end tell
|
|
||||||
end repeat
|
|
||||||
delay 0.3
|
|
||||||
tell application "System Events"
|
|
||||||
tell process "VLC"
|
|
||||||
keystroke "f" using command down
|
|
||||||
end tell
|
|
||||||
end tell`
|
end tell`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -104,10 +107,10 @@ const POWERPOINT_MAC_EXTEND_PROFILE: LaunchProfile = {
|
|||||||
display_mode: 'extend',
|
display_mode: 'extend',
|
||||||
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"',
|
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"',
|
||||||
post_delay_ms: 1000,
|
post_delay_ms: 1000,
|
||||||
post_script: `tell application "Microsoft PowerPoint"
|
post_script: `repeat 20 times
|
||||||
activate
|
tell application "Microsoft PowerPoint"
|
||||||
end tell
|
activate
|
||||||
repeat 15 times
|
end tell
|
||||||
delay 0.5
|
delay 0.5
|
||||||
tell application "System Events"
|
tell application "System Events"
|
||||||
if frontmost of process "Microsoft PowerPoint" is true then exit repeat
|
if frontmost of process "Microsoft PowerPoint" is true then exit repeat
|
||||||
@@ -148,10 +151,10 @@ const LIBREOFFICE_MAC_EXTEND_PROFILE: LaunchProfile = {
|
|||||||
display_mode: 'extend',
|
display_mode: 'extend',
|
||||||
open_cmd: 'open -a "LibreOffice" "{{path}}"',
|
open_cmd: 'open -a "LibreOffice" "{{path}}"',
|
||||||
post_delay_ms: 1000,
|
post_delay_ms: 1000,
|
||||||
post_script: `tell application "LibreOffice"
|
post_script: `repeat 20 times
|
||||||
activate
|
tell application "LibreOffice"
|
||||||
end tell
|
activate
|
||||||
repeat 15 times
|
end tell
|
||||||
delay 0.5
|
delay 0.5
|
||||||
tell application "System Events"
|
tell application "System Events"
|
||||||
if frontmost of process "soffice" is true then exit repeat
|
if frontmost of process "soffice" is true then exit repeat
|
||||||
@@ -170,10 +173,10 @@ const ACROBAT_MAC_MIRROR_PROFILE: LaunchProfile = {
|
|||||||
display_mode: 'mirror',
|
display_mode: 'mirror',
|
||||||
open_cmd: 'open -a "Adobe Acrobat Reader DC" "{{path}}"',
|
open_cmd: 'open -a "Adobe Acrobat Reader DC" "{{path}}"',
|
||||||
post_delay_ms: 1000,
|
post_delay_ms: 1000,
|
||||||
post_script: `tell application "Adobe Acrobat Reader DC"
|
post_script: `repeat 20 times
|
||||||
activate
|
tell application "Adobe Acrobat Reader DC"
|
||||||
end tell
|
activate
|
||||||
repeat 15 times
|
end tell
|
||||||
delay 0.5
|
delay 0.5
|
||||||
tell application "System Events"
|
tell application "System Events"
|
||||||
if frontmost of process "AdobeReader" is true then exit repeat
|
if frontmost of process "AdobeReader" is true then exit repeat
|
||||||
@@ -215,10 +218,10 @@ const LIBREOFFICE_WIN_EXTEND_PROFILE: LaunchProfile = {
|
|||||||
display_mode: 'extend',
|
display_mode: 'extend',
|
||||||
open_cmd: 'open -a "LibreOffice" "{{path}}"',
|
open_cmd: 'open -a "LibreOffice" "{{path}}"',
|
||||||
post_delay_ms: 1500,
|
post_delay_ms: 1500,
|
||||||
post_script: `tell application "LibreOffice"
|
post_script: `repeat 20 times
|
||||||
activate
|
tell application "LibreOffice"
|
||||||
end tell
|
activate
|
||||||
repeat 15 times
|
end tell
|
||||||
delay 0.5
|
delay 0.5
|
||||||
tell application "System Events"
|
tell application "System Events"
|
||||||
if frontmost of process "soffice" is true then exit repeat
|
if frontmost of process "soffice" is true then exit repeat
|
||||||
@@ -237,10 +240,10 @@ const ACROBAT_WIN_MIRROR_PROFILE: LaunchProfile = {
|
|||||||
display_mode: 'mirror',
|
display_mode: 'mirror',
|
||||||
open_cmd: 'open -a "Acrobat Reader Windows" "{{path}}"',
|
open_cmd: 'open -a "Acrobat Reader Windows" "{{path}}"',
|
||||||
post_delay_ms: 1500,
|
post_delay_ms: 1500,
|
||||||
post_script: `tell application "Acrobat Reader Windows"
|
post_script: `repeat 20 times
|
||||||
activate
|
tell application "Acrobat Reader Windows"
|
||||||
end tell
|
activate
|
||||||
repeat 15 times
|
end tell
|
||||||
delay 0.5
|
delay 0.5
|
||||||
tell application "System Events"
|
tell application "System Events"
|
||||||
if frontmost of process "Acrobat Reader Windows" is true then exit repeat
|
if frontmost of process "Acrobat Reader Windows" is true then exit repeat
|
||||||
|
|||||||
@@ -36,6 +36,52 @@ export interface BadgeTemplateCfg {
|
|||||||
// Leave unset (or "0") for no bleed.
|
// Leave unset (or "0") for no bleed.
|
||||||
bleed?: string;
|
bleed?: string;
|
||||||
|
|
||||||
|
// Header image vertical offset. CSS length applied as margin-top on the badge_header div.
|
||||||
|
// Default (unset) = "2rem" (matches the prior hardcoded mt-8).
|
||||||
|
// Negative values shift the image toward the top edge; larger values push it down.
|
||||||
|
// Any CSS length works: "-0.5in", "1rem", "8px".
|
||||||
|
header_margin_top?: string;
|
||||||
|
|
||||||
|
// Border drawn below the badge header image. Set header_border_color to enable.
|
||||||
|
// Unset = no border (default). Any valid CSS hex color.
|
||||||
|
header_border_color?: string;
|
||||||
|
// Thickness of the header bottom border. Any CSS length. Default "2px" when color is set.
|
||||||
|
header_border_width?: string;
|
||||||
|
// Per-side padding of the badge_header div. Any CSS length. Unset = 0.5rem (Tailwind p-2 default).
|
||||||
|
// Bottom padding creates space between the header image and the border line (e.g. "1.45in").
|
||||||
|
header_padding_top?: string;
|
||||||
|
header_padding_right?: string;
|
||||||
|
header_padding_bottom?: string;
|
||||||
|
header_padding_left?: string;
|
||||||
|
|
||||||
|
// Punch-out hole markers: show X overlays at the physical badge clip slot positions.
|
||||||
|
// Slots are pre-perforated on the badge stock — markers guide attendees to push them out.
|
||||||
|
// Hole dimensions: 5/8in wide × 1/8in tall, 1/4in from top, 3/8in from left/right edges.
|
||||||
|
// Center hole: horizontally centered, same vertical position.
|
||||||
|
// Colors: per-slot _fg/_bg override the shared fg/bg fallback. Unset = component defaults.
|
||||||
|
// fg = stroke + line color (hex). bg = rectangle fill color (hex).
|
||||||
|
punch_holes?: {
|
||||||
|
left?: boolean;
|
||||||
|
right?: boolean;
|
||||||
|
center?: boolean;
|
||||||
|
fg?: string; // shared fallback stroke/line color
|
||||||
|
bg?: string; // shared fallback fill color
|
||||||
|
left_fg?: string;
|
||||||
|
left_bg?: string;
|
||||||
|
left_rainbow?: boolean; // animated hue-rotate; overrides fg/bg base color with saturated red
|
||||||
|
right_fg?: string;
|
||||||
|
right_bg?: string;
|
||||||
|
right_rainbow?: boolean;
|
||||||
|
center_fg?: string;
|
||||||
|
center_bg?: string;
|
||||||
|
center_rainbow?: boolean;
|
||||||
|
slow_pulse?: boolean; // when true: slow breathing pulse instead of fast linear cycle
|
||||||
|
// Extra horizontal inset per side (mm) beyond the 1mm base safety margin.
|
||||||
|
// Shrinks the visible marker width to keep it inside the physical hole on
|
||||||
|
// printers or badge stock with variance. Default 2 when unset (see view component).
|
||||||
|
inset_x_mm?: number;
|
||||||
|
};
|
||||||
|
|
||||||
// Allow arbitrary extra keys to preserve forward-compatibility.
|
// Allow arbitrary extra keys to preserve forward-compatibility.
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { key_val } from '$lib/stores/ae_stores';
|
|||||||
import { api } from '$lib/api/api';
|
import { api } from '$lib/api/api';
|
||||||
|
|
||||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||||
|
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
|
||||||
import { db_journals } from '$lib/ae_journals/db_journals';
|
import { db_journals } from '$lib/ae_journals/db_journals';
|
||||||
import type { ae_Journal } from '$lib/types/ae_types';
|
import type { ae_Journal } from '$lib/types/ae_types';
|
||||||
|
|
||||||
@@ -885,9 +886,16 @@ export async function process_ae_obj__journal_props({
|
|||||||
|
|
||||||
const updated =
|
const updated =
|
||||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
||||||
obj.tmp_sort_3 = `${obj.group ?? '0'}_${obj.priority ? 1 : 0}_${obj.sort ?? '0'}_${
|
const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({
|
||||||
obj.name
|
prefix: [obj.group ?? '0'],
|
||||||
}_${updated}`;
|
priority: obj.priority,
|
||||||
|
sort: obj.sort,
|
||||||
|
fields_2: [obj.name],
|
||||||
|
fields_3: [updated]
|
||||||
|
});
|
||||||
|
obj.tmp_sort_1 = tmp_sort_1;
|
||||||
|
obj.tmp_sort_2 = tmp_sort_2;
|
||||||
|
obj.tmp_sort_3 = tmp_sort_3;
|
||||||
obj.combined_passcode = `${obj.passcode ?? ''}:${obj.private_passcode ?? ''}`;
|
obj.combined_passcode = `${obj.passcode ?? ''}:${obj.private_passcode ?? ''}`;
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { key_val } from '$lib/stores/ae_stores';
|
|||||||
import { api } from '$lib/api/api';
|
import { api } from '$lib/api/api';
|
||||||
|
|
||||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||||
|
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
|
||||||
import { db_journals } from '$lib/ae_journals/db_journals';
|
import { db_journals } from '$lib/ae_journals/db_journals';
|
||||||
import type { ae_JournalEntry } from '$lib/types/ae_types';
|
import type { ae_JournalEntry } from '$lib/types/ae_types';
|
||||||
|
|
||||||
@@ -1050,19 +1051,20 @@ export async function process_ae_obj__journal_entry_props({
|
|||||||
obj.history = history;
|
obj.history = history;
|
||||||
obj.history_md_html = history_md_html;
|
obj.history_md_html = history_md_html;
|
||||||
|
|
||||||
// Journal entry-specific computed sort fields, overriding generic ones if needed
|
// Journal entry-specific computed sort fields via build_tmp_sort.
|
||||||
const sort_val = (obj.sort ?? 0).toString().padStart(3, '0');
|
// Order: priority DESC → sort ASC → name ASC → updated ASC (all ascending, no .reverse())
|
||||||
const updated =
|
const updated =
|
||||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
||||||
obj.tmp_sort_1 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({
|
||||||
sort_val
|
prefix: [obj.group ?? ''],
|
||||||
}_${updated}`;
|
priority: obj.priority,
|
||||||
obj.tmp_sort_2 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
sort: obj.sort,
|
||||||
sort_val
|
fields_2: [obj.name],
|
||||||
}_${obj.name ?? ''}_${updated}`;
|
fields_3: [updated]
|
||||||
obj.tmp_sort_3 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
});
|
||||||
sort_val
|
obj.tmp_sort_1 = tmp_sort_1;
|
||||||
}_${obj.name ?? ''}_${updated}`;
|
obj.tmp_sort_2 = tmp_sort_2;
|
||||||
|
obj.tmp_sort_3 = tmp_sort_3;
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,8 +75,10 @@ export function journal_entry_matches_search(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function journal_entry_compare_for_list(a: any, b: any): number {
|
export function journal_entry_compare_for_list(a: any, b: any): number {
|
||||||
|
// tmp_sort_1 is built by build_tmp_sort() for ascending comparison:
|
||||||
|
// priority=true encodes as '0', priority=false as '1', so ASC puts priority first.
|
||||||
return (
|
return (
|
||||||
(b?.tmp_sort_1 ?? '').localeCompare(a?.tmp_sort_1 ?? '') ||
|
(a?.tmp_sort_1 ?? '').localeCompare(b?.tmp_sort_1 ?? '') ||
|
||||||
(b?.updated_on ?? '').localeCompare(a?.updated_on ?? '') ||
|
(b?.updated_on ?? '').localeCompare(a?.updated_on ?? '') ||
|
||||||
(b?.journal_entry_id ?? '').localeCompare(a?.journal_entry_id ?? '')
|
(b?.journal_entry_id ?? '').localeCompare(a?.journal_entry_id ?? '')
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { key_val } from '$lib/stores/ae_stores';
|
import type { key_val } from '$lib/stores/ae_stores';
|
||||||
import { api } from '$lib/api/api';
|
import { api } from '$lib/api/api';
|
||||||
|
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
|
||||||
|
|
||||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||||
import { db_posts } from '$lib/ae_posts/db_posts';
|
import { db_posts } from '$lib/ae_posts/db_posts';
|
||||||
@@ -570,15 +571,16 @@ export async function process_ae_obj__post_props({
|
|||||||
if (!obj.account_id_random) obj.account_id_random = account_id;
|
if (!obj.account_id_random) obj.account_id_random = account_id;
|
||||||
}
|
}
|
||||||
obj.name = obj.title;
|
obj.name = obj.title;
|
||||||
const sort_val = (obj.sort ?? 0).toString().padStart(3, '0');
|
const { tmp_sort_1, tmp_sort_2 } = build_tmp_sort({
|
||||||
const updated =
|
prefix: [obj.group ?? ''],
|
||||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
priority: obj.priority,
|
||||||
obj.tmp_sort_1 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
sort: obj.sort,
|
||||||
sort_val
|
fields_1: [obj.updated_on ?? obj.created_on ?? ''],
|
||||||
}_${updated}`;
|
fields_2: [obj.updated_on ?? '', obj.created_on ?? ''],
|
||||||
obj.tmp_sort_2 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
pad_width: 8
|
||||||
sort_val
|
});
|
||||||
}_${obj.updated_on ?? ''}_${obj.created_on ?? ''}`;
|
obj.tmp_sort_1 = tmp_sort_1;
|
||||||
|
obj.tmp_sort_2 = tmp_sort_2;
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { key_val } from '$lib/stores/ae_stores';
|
import type { key_val } from '$lib/stores/ae_stores';
|
||||||
import { api } from '$lib/api/api';
|
import { api } from '$lib/api/api';
|
||||||
|
import { build_tmp_sort } from '$lib/ae_core/core__idb_sort';
|
||||||
|
|
||||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||||
import { db_posts } from '$lib/ae_posts/db_posts';
|
import { db_posts } from '$lib/ae_posts/db_posts';
|
||||||
@@ -383,15 +384,16 @@ export async function process_ae_obj__post_comment_props({
|
|||||||
obj_type: 'post_comment',
|
obj_type: 'post_comment',
|
||||||
log_lvl,
|
log_lvl,
|
||||||
specific_processor: (obj) => {
|
specific_processor: (obj) => {
|
||||||
const sort_val = (obj.sort ?? 0).toString().padStart(3, '0');
|
const { tmp_sort_1, tmp_sort_2 } = build_tmp_sort({
|
||||||
const updated =
|
prefix: [obj.group ?? ''],
|
||||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
priority: obj.priority,
|
||||||
obj.tmp_sort_1 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
sort: obj.sort,
|
||||||
sort_val
|
fields_1: [obj.updated_on ?? obj.created_on ?? ''],
|
||||||
}_${updated}`;
|
fields_2: [obj.updated_on ?? '', obj.created_on ?? ''],
|
||||||
obj.tmp_sort_2 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
pad_width: 8
|
||||||
sort_val
|
});
|
||||||
}_${obj.updated_on ?? ''}_${obj.created_on ?? ''}`;
|
obj.tmp_sort_1 = tmp_sort_1;
|
||||||
|
obj.tmp_sort_2 = tmp_sort_2;
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ export const iso_datetime_formatter = function iso_datetime_formatter(
|
|||||||
named_format: string = 'datetime_iso_no_seconds', // date_iso, datetime_iso_no_seconds
|
named_format: string = 'datetime_iso_no_seconds', // date_iso, datetime_iso_no_seconds
|
||||||
// Pass true/false to resolve to the correct 12h or 24h variant automatically.
|
// Pass true/false to resolve to the correct 12h or 24h variant automatically.
|
||||||
// null (default) leaves named_format unchanged — all existing call sites unaffected.
|
// null (default) leaves named_format unchanged — all existing call sites unaffected.
|
||||||
use_12h: boolean | null = null
|
use_12h: boolean | null = null,
|
||||||
|
// When true, treats a naive datetime string (no Z / offset) as UTC so dayjs
|
||||||
|
// converts it to local browser time on display. Use for timestamps stored as
|
||||||
|
// UTC in the DB but returned without a timezone indicator.
|
||||||
|
treat_as_utc: boolean = false
|
||||||
) {
|
) {
|
||||||
// console.log('*** iso_datetime_formatter() ***');
|
// console.log('*** iso_datetime_formatter() ***');
|
||||||
|
|
||||||
@@ -74,6 +78,12 @@ export const iso_datetime_formatter = function iso_datetime_formatter(
|
|||||||
raw_datetime = new Date(); // Get the current datetime if one was not passed.
|
raw_datetime = new Date(); // Get the current datetime if one was not passed.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append 'Z' to naive UTC strings so dayjs converts to local browser time.
|
||||||
|
// Guards against double-appending if the backend ever adds timezone info.
|
||||||
|
if (treat_as_utc && typeof raw_datetime === 'string' && !raw_datetime.match(/Z$|[+-]\d{2}:?\d{2}$/)) {
|
||||||
|
raw_datetime = raw_datetime + 'Z';
|
||||||
|
}
|
||||||
|
|
||||||
if (use_12h !== null) {
|
if (use_12h !== null) {
|
||||||
named_format = use_12h
|
named_format = use_12h
|
||||||
? (TO_12H[named_format] ?? named_format)
|
? (TO_12H[named_format] ?? named_format)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as Lucide from 'lucide-svelte';
|
import * as Lucide from '@lucide/svelte';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Lucide icon component based on the provided file extension.
|
* Returns a Lucide icon component based on the provided file extension.
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import {
|
|||||||
} from '$lib/stores/ae_stores';
|
} from '$lib/stores/ae_stores';
|
||||||
// import { core_func } from '$lib/ae_core/ae_core_functions';
|
// import { core_func } from '$lib/ae_core/ae_core_functions';
|
||||||
// Ideally the Event related stores should not be imported here?
|
// Ideally the Event related stores should not be imported here?
|
||||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
|
||||||
import { pres_mgmt_loc } from '$lib/stores/ae_events_stores__pres_mgmt.svelte';
|
import { pres_mgmt_loc } from '$lib/stores/ae_events_stores__pres_mgmt.svelte';
|
||||||
// import { db_events } from "$lib/db_events";
|
// import { db_events } from "$lib/db_events";
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -165,9 +165,25 @@ $effect(() => {
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
const account_id = $slct.account_id;
|
const account_id = $slct.account_id;
|
||||||
const api_ready = !!$ae_api?.base_url;
|
const api_ready = !!$ae_api?.base_url;
|
||||||
const entry = $lq__ds_obj;
|
const entry = $lq__ds_obj as ae_DataStore | null | undefined;
|
||||||
|
|
||||||
if (browser && api_ready && !entry && ds_loading_status === 'starting') {
|
// Don't fire until the bootstrap Sync Effect has set a real account_id.
|
||||||
|
// Without this guard, the fetch runs with null account_id and the
|
||||||
|
// localStorage scavenge in get_object picks up a stale account_id from a
|
||||||
|
// previous session, returning the wrong account's record.
|
||||||
|
if (!browser || !account_id || !api_ready || ds_loading_status !== 'starting') return;
|
||||||
|
|
||||||
|
// Also reload when IDB has a record but it belongs to a different account
|
||||||
|
// (not null/global and not the current account). This handles the case where
|
||||||
|
// a previous dev/demo session left account-specific rows in IDB that score
|
||||||
|
// as the "best" liveQuery match even though they are for the wrong account.
|
||||||
|
const entry_is_stale_account =
|
||||||
|
entry !== undefined &&
|
||||||
|
entry !== null &&
|
||||||
|
entry.account_id !== null &&
|
||||||
|
entry.account_id !== account_id;
|
||||||
|
|
||||||
|
if (!entry || entry_is_stale_account) {
|
||||||
trigger = 'load__ds__code';
|
trigger = 'load__ds__code';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ import {
|
|||||||
slct,
|
slct,
|
||||||
slct_trigger
|
slct_trigger
|
||||||
} from '$lib/stores/ae_stores';
|
} from '$lib/stores/ae_stores';
|
||||||
import { db_events } from '$lib/ae_events/db_events';
|
|
||||||
import {
|
import {
|
||||||
events_loc,
|
|
||||||
events_sess,
|
events_sess,
|
||||||
events_slct,
|
events_slct,
|
||||||
events_trigger
|
events_trigger
|
||||||
@@ -79,6 +77,9 @@ let ae_promises: key_val = $state({});
|
|||||||
let ae_tmp: key_val = $state({});
|
let ae_tmp: key_val = $state({});
|
||||||
ae_tmp.show__file_li = true;
|
ae_tmp.show__file_li = true;
|
||||||
ae_tmp.show__direct_download = pres_mgmt_loc.current.show__direct_download;
|
ae_tmp.show__direct_download = pres_mgmt_loc.current.show__direct_download;
|
||||||
|
|
||||||
|
// Strip :443 from https URLs — redundant and clutters shareable links.
|
||||||
|
let base_url = $derived($ae_api.base_url.replace(/^(https:\/\/[^/:]+):443(\/|$)/, '$1$2'));
|
||||||
// let ae_triggers: key_val = {};
|
// let ae_triggers: key_val = {};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -151,30 +152,15 @@ async function handle_convert_pdf_to_image(event_file_obj: key_val) {
|
|||||||
<div class="float-right flex flex-row items-center">
|
<div class="float-right flex flex-row items-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={async () => {
|
||||||
console.log('*** Refresh button clicked ***');
|
await events_func.load_ae_obj_li__event_file({
|
||||||
|
|
||||||
db_events.file.clear();
|
|
||||||
|
|
||||||
// let params = {
|
|
||||||
// qry__enabled: 'all',
|
|
||||||
// qry__hidden: 'all',
|
|
||||||
// }
|
|
||||||
|
|
||||||
events_func.load_ae_obj_li__event_file({
|
|
||||||
api_cfg: $ae_api,
|
api_cfg: $ae_api,
|
||||||
for_obj_type: link_to_type,
|
for_obj_type: link_to_type,
|
||||||
for_obj_id: link_to_id,
|
for_obj_id: link_to_id,
|
||||||
enabled: 'all',
|
enabled: 'all',
|
||||||
hidden: 'all',
|
hidden: 'all',
|
||||||
// params: params,
|
|
||||||
try_cache: true
|
try_cache: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// ae_tmp.show__file_li = false;
|
|
||||||
// console.log(`$lq__event_file_obj_li:`, $lq__event_file_obj_li);
|
|
||||||
// $slct_trigger = 'load__event_file_obj_li';
|
|
||||||
// ae_tmp.show__file_li = true;
|
|
||||||
}}
|
}}
|
||||||
class="btn btn-sm preset-tonal-tertiary hover:preset-tonal-warning border-warning-500 m-1 border p-1 transition hover:transition-all *:hover:inline"
|
class="btn btn-sm preset-tonal-tertiary hover:preset-tonal-warning border-warning-500 m-1 border p-1 transition hover:transition-all *:hover:inline"
|
||||||
class:hidden={!$ae_loc.edit_mode || !$ae_loc.authenticated_access}
|
class:hidden={!$ae_loc.edit_mode || !$ae_loc.authenticated_access}
|
||||||
@@ -333,7 +319,7 @@ async function handle_convert_pdf_to_image(event_file_obj: key_val) {
|
|||||||
</span>
|
</span>
|
||||||
<MyClipboard
|
<MyClipboard
|
||||||
value={encodeURI(
|
value={encodeURI(
|
||||||
`${$ae_api.base_url}/v3/action/event_file/${event_file_obj?.event_file_id}/download?filename=${ae_util.clean_filename(event_file_obj?.filename)}&key=${$ae_api.account_id}`
|
`${base_url}/v3/action/event_file/${event_file_obj?.event_file_id}/download?filename=${ae_util.clean_filename(event_file_obj?.filename)}&key=${$ae_api.account_id}`
|
||||||
)}
|
)}
|
||||||
btn_text="Copy Original"
|
btn_text="Copy Original"
|
||||||
btn_title="Copy the direct download link to the clipboard."
|
btn_title="Copy the direct download link to the clipboard."
|
||||||
@@ -342,10 +328,10 @@ async function handle_convert_pdf_to_image(event_file_obj: key_val) {
|
|||||||
|
|
||||||
<MyClipboard
|
<MyClipboard
|
||||||
value={encodeURI(
|
value={encodeURI(
|
||||||
`${$ae_api.base_url}/v3/action/event_file/${event_file_obj?.event_file_id}/download?filename=${event_file_obj?.event_session_code}-${ae_util.clean_filename(event_file_obj?.event_session_name).substring(0, 20)}-${ae_util.clean_filename(event_file_obj?.event_presenter_full_name)}.${event_file_obj?.extension}&key=${$ae_api.account_id}`
|
`${base_url}/v3/action/event_file/${event_file_obj?.event_file_id}/download?filename=${ae_util.clean_filename(event_file_obj?.event_session_code ?? '')}_${ae_util.clean_filename(event_file_obj?.event_presentation_name ?? event_file_obj?.event_session_name ?? '').substring(0, 30)}_${ae_util.clean_filename(event_file_obj?.event_presenter_full_name ?? '')}.${event_file_obj?.extension}&key=${$ae_api.account_id}`
|
||||||
)}
|
)}
|
||||||
btn_text="Copy Renamed"
|
btn_text="Copy Renamed"
|
||||||
btn_title="Copy the renamed download link to the clipboard."
|
btn_title="Copy the renamed download link to the clipboard. Format: [session-code]_[presentation-name]_[presenter-name].[ext]"
|
||||||
btn_class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500"
|
btn_class="btn btn-xs preset-tonal-warning hover:preset-filled-warning-500"
|
||||||
></MyClipboard>
|
></MyClipboard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,100 +1,26 @@
|
|||||||
// store_versions MUST be first import — its side-effect wipes stale localStorage
|
|
||||||
// before svelte-persisted-store hydrates from it.
|
|
||||||
import { AE_EVENTS_LOC_VERSION } from '$lib/stores/store_versions';
|
|
||||||
|
|
||||||
import { persisted } from 'svelte-persisted-store';
|
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
import type { key_val } from '$lib/stores/ae_stores';
|
import type { key_val } from '$lib/stores/ae_stores';
|
||||||
|
|
||||||
import {
|
// Static display title for the Events module. Always this value — nothing writes to it.
|
||||||
badges_loc_defaults,
|
// Use this constant instead of $events_loc.title (which is being retired).
|
||||||
badges_sess_defaults
|
export const EVENTS_MODULE_TITLE = `OSIT's Æ Events`;
|
||||||
} from '$lib/stores/ae_events_stores__badges_defaults';
|
|
||||||
import {
|
import { badges_sess_defaults } from '$lib/stores/ae_events_stores__badges_defaults';
|
||||||
launcher_loc_defaults,
|
import { launcher_sess_defaults } from '$lib/stores/ae_events_stores__launcher_defaults';
|
||||||
launcher_sess_defaults
|
import { leads_sess_defaults } from '$lib/stores/ae_events_stores__leads_defaults';
|
||||||
} from '$lib/stores/ae_events_stores__launcher_defaults';
|
import { pres_mgmt_sess_defaults } from '$lib/stores/ae_events_stores__pres_mgmt_defaults';
|
||||||
import {
|
|
||||||
leads_loc_defaults,
|
|
||||||
leads_sess_defaults
|
|
||||||
} from '$lib/stores/ae_events_stores__leads_defaults';
|
|
||||||
import {
|
|
||||||
pres_mgmt_loc_defaults,
|
|
||||||
pres_mgmt_sess_defaults
|
|
||||||
} from '$lib/stores/ae_events_stores__pres_mgmt_defaults';
|
|
||||||
|
|
||||||
// Deployment version stamp. Compared against events_sess.ver in events/+layout.svelte
|
// Deployment version stamp. Compared against events_sess.ver in events/+layout.svelte
|
||||||
// to detect stale persisted data after a deploy (triggers a reload). Bump this alongside
|
// to detect stale persisted data after a deploy (triggers a reload). Bump this alongside
|
||||||
// events_session_data_struct.ver. See store_versions.ts for the schema-level wipe mechanism.
|
// events_session_data_struct.ver. See store_versions.ts for the schema-level wipe mechanism.
|
||||||
const ver = '2025-10-16_2139';
|
const ver = '2025-10-16_2139';
|
||||||
|
|
||||||
/* *** BEGIN *** Initialize events_local_data_struct */
|
|
||||||
// Persisted to localStorage. Retains user preferences and event-specific config across
|
|
||||||
// browser sessions. See store_versions.ts for the schema-level invalidation mechanism.
|
|
||||||
const events_local_data_struct: key_val = {
|
|
||||||
__version: AE_EVENTS_LOC_VERSION, // Schema version gate — see store_versions.ts
|
|
||||||
ver: ver, // Deployment stamp — compared against events_sess.ver to trigger reloads.
|
|
||||||
|
|
||||||
name: 'Aether - Events',
|
|
||||||
title: `OSIT's Æ Events`,
|
|
||||||
|
|
||||||
ds: {},
|
|
||||||
|
|
||||||
events_cfg_json: {},
|
|
||||||
|
|
||||||
event_id: null,
|
|
||||||
|
|
||||||
// all, disabled, enabled
|
|
||||||
qry__enabled: 'enabled',
|
|
||||||
// all, hidden, not_hidden
|
|
||||||
qry__hidden: 'not_hidden',
|
|
||||||
qry__limit: 20,
|
|
||||||
qry__offset: 0,
|
|
||||||
|
|
||||||
// The show details is intended for things like meta data and additional details that are not always needed.
|
|
||||||
show_details: false,
|
|
||||||
|
|
||||||
auth__person: {}, // allow, id, name, email, passcode, etc
|
|
||||||
// The auth__entered_key (usually email or person_id) and auth__entered_passcode is found under events_sess.entered_key and events_sess.entered_passcode because it should be temporary.
|
|
||||||
// auth__entered_passcode: null,
|
|
||||||
|
|
||||||
// auth__kv tracks which IDs the browser client is permitted to access.
|
|
||||||
// Each entry is an ID mapped to true, false, 'read', or 'write'.
|
|
||||||
// Keys should be no older than a configurable max age (checked on read).
|
|
||||||
auth__kv: {
|
|
||||||
event: {},
|
|
||||||
exhibit: {},
|
|
||||||
location: {},
|
|
||||||
session: {},
|
|
||||||
presentation: {},
|
|
||||||
presenter: {},
|
|
||||||
person: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Badge Printing — see ae_events_stores__badges_defaults.ts
|
|
||||||
badges: badges_loc_defaults,
|
|
||||||
|
|
||||||
// Event Presentation Launcher — see ae_events_stores__launcher_defaults.ts
|
|
||||||
launcher: launcher_loc_defaults,
|
|
||||||
|
|
||||||
// Lead Retrievals (Exhibit) — see ae_events_stores__leads_defaults.ts
|
|
||||||
leads: leads_loc_defaults,
|
|
||||||
|
|
||||||
// Presentation Management — see ae_events_stores__pres_mgmt_defaults.ts
|
|
||||||
pres_mgmt: pres_mgmt_loc_defaults
|
|
||||||
};
|
|
||||||
|
|
||||||
export const events_loc: Writable<key_val> = persisted(
|
|
||||||
'ae_events_loc',
|
|
||||||
events_local_data_struct
|
|
||||||
);
|
|
||||||
|
|
||||||
/* *** BEGIN *** Initialize events_session_data_struct */
|
/* *** BEGIN *** Initialize events_session_data_struct */
|
||||||
// In-memory only (writable, not persisted). Resets on page load.
|
// In-memory only (writable, not persisted). Resets on page load.
|
||||||
const events_session_data_struct: key_val = {
|
const events_session_data_struct: key_val = {
|
||||||
// Deployment stamp — compared against events_loc.ver in events/+layout.svelte.
|
// Deployment version stamp — bump alongside ver above when pushing breaking changes.
|
||||||
ver: ver,
|
ver: ver,
|
||||||
log_lvl: 1,
|
log_lvl: 1,
|
||||||
|
|
||||||
@@ -102,7 +28,6 @@ const events_session_data_struct: key_val = {
|
|||||||
ds: {
|
ds: {
|
||||||
submit_status: null
|
submit_status: null
|
||||||
},
|
},
|
||||||
ds_loaded: {},
|
|
||||||
|
|
||||||
qry__enabled: 'enabled', // all, disabled, enabled
|
qry__enabled: 'enabled', // all, disabled, enabled
|
||||||
qry__hidden: 'not_hidden', // all, hidden, not_hidden
|
qry__hidden: 'not_hidden', // all, hidden, not_hidden
|
||||||
@@ -162,31 +87,14 @@ const events_slct_obj_template: key_val = {
|
|||||||
event_obj_li: [],
|
event_obj_li: [],
|
||||||
|
|
||||||
// Sub-level event_
|
// Sub-level event_
|
||||||
abstract_id: null,
|
|
||||||
abstract_obj: {},
|
|
||||||
abstract_obj_li: [],
|
|
||||||
|
|
||||||
badge_id: null,
|
badge_id: null,
|
||||||
badge_obj: {},
|
badge_obj: {},
|
||||||
badge_obj_li: [],
|
badge_obj_li: [],
|
||||||
|
|
||||||
badge_template_id: null,
|
|
||||||
badge_template_obj: {},
|
|
||||||
badge_template_obj_li: [],
|
|
||||||
|
|
||||||
device_id: null,
|
|
||||||
device_obj: {},
|
|
||||||
device_obj_li: [],
|
|
||||||
|
|
||||||
exhibit_id: null,
|
exhibit_id: null,
|
||||||
exhibit_obj: {},
|
exhibit_obj: {},
|
||||||
exhibit_obj_li: [],
|
exhibit_obj_li: [],
|
||||||
|
|
||||||
// Rename these to badge_tracking_*?
|
|
||||||
exhibit_tracking_id: null,
|
|
||||||
exhibit_tracking_obj: {},
|
|
||||||
exhibit_tracking_obj_li: [],
|
|
||||||
|
|
||||||
file_id: null,
|
file_id: null,
|
||||||
file_obj: {},
|
file_obj: {},
|
||||||
file_obj_li: [],
|
file_obj_li: [],
|
||||||
@@ -218,8 +126,6 @@ const events_slct_obj_template: key_val = {
|
|||||||
session_obj_li: [],
|
session_obj_li: [],
|
||||||
event_session_obj: {},
|
event_session_obj: {},
|
||||||
|
|
||||||
lq__presenter_obj: {}, // Testing passing a LiveQuery object around...
|
|
||||||
|
|
||||||
auth__event_presenter_id: null,
|
auth__event_presenter_id: null,
|
||||||
auth__event_presentation_id: null
|
auth__event_presentation_id: null
|
||||||
};
|
};
|
||||||
|
|||||||
30
src/lib/stores/ae_events_stores__auth.svelte.ts
Normal file
30
src/lib/stores/ae_events_stores__auth.svelte.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { PersistedState } from 'runed';
|
||||||
|
|
||||||
|
const events_auth_loc_defaults = {
|
||||||
|
auth__person: {
|
||||||
|
id: null as string | null,
|
||||||
|
person_id: null as string | null,
|
||||||
|
entered_key: null as string | null,
|
||||||
|
email: null as string | null,
|
||||||
|
full_name: null as string | null,
|
||||||
|
presenter_id: null as string | null,
|
||||||
|
presentation_id: null as string | null,
|
||||||
|
session_id: null as string | null
|
||||||
|
},
|
||||||
|
auth__kv: {
|
||||||
|
event: {} as Record<string, any>,
|
||||||
|
exhibit: {} as Record<string, any>,
|
||||||
|
location: {} as Record<string, any>,
|
||||||
|
session: {} as Record<string, any>,
|
||||||
|
presentation: {} as Record<string, any>,
|
||||||
|
presenter: {} as Record<string, any>,
|
||||||
|
person: {} as Record<string, any>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const events_auth_loc = new PersistedState('ae_events_auth_loc', events_auth_loc_defaults, {
|
||||||
|
serializer: {
|
||||||
|
serialize: JSON.stringify,
|
||||||
|
deserialize: (raw: string) => ({ ...events_auth_loc_defaults, ...JSON.parse(raw) })
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -15,4 +15,11 @@
|
|||||||
import { PersistedState } from 'runed';
|
import { PersistedState } from 'runed';
|
||||||
import { badges_loc_defaults } from './ae_events_stores__badges_defaults';
|
import { badges_loc_defaults } from './ae_events_stores__badges_defaults';
|
||||||
|
|
||||||
export const badges_loc = new PersistedState('ae_badges_loc', badges_loc_defaults);
|
// Custom deserializer merges stored JSON with defaults so that new fields added
|
||||||
|
// after a user's first session get their default values rather than undefined.
|
||||||
|
export const badges_loc = new PersistedState('ae_badges_loc', badges_loc_defaults, {
|
||||||
|
serializer: {
|
||||||
|
serialize: JSON.stringify,
|
||||||
|
deserialize: (raw: string) => ({ ...badges_loc_defaults, ...JSON.parse(raw) })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ export const default_trusted_can_edit: string[] = [
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
export interface BadgesLocState {
|
export interface BadgesLocState {
|
||||||
auto_view: boolean;
|
auto_view: boolean;
|
||||||
show_hidden: boolean;
|
// 'default' = hide hidden+disabled | 'show_hidden' = show hidden | 'show_all' = show hidden+disabled
|
||||||
show_not_enabled: boolean;
|
visibility_filter: 'default' | 'show_hidden' | 'show_all';
|
||||||
show_printed: boolean;
|
show_printed: boolean;
|
||||||
allow_reprint: boolean;
|
allow_reprint: boolean;
|
||||||
show_element__cfg: boolean;
|
show_element__cfg: boolean;
|
||||||
@@ -110,6 +110,7 @@ export interface BadgesLocState {
|
|||||||
qry_printed_status: string; // 'all' | 'printed' | 'not_printed'
|
qry_printed_status: string; // 'all' | 'printed' | 'not_printed'
|
||||||
qry_affiliations: string | null;
|
qry_affiliations: string | null;
|
||||||
qry_sort_order: string;
|
qry_sort_order: string;
|
||||||
|
qry_result_limit: number; // UI override for max results (edit mode only; tier max enforced by stepper)
|
||||||
status_qry__search: string | null;
|
status_qry__search: string | null;
|
||||||
use_id_li: boolean;
|
use_id_li: boolean;
|
||||||
search_status: string | null;
|
search_status: string | null;
|
||||||
@@ -137,6 +138,10 @@ export interface BadgesLocState {
|
|||||||
trusted_search_min_chars: number;
|
trusted_search_min_chars: number;
|
||||||
// Timestamp when the remote config was last mirrored locally
|
// Timestamp when the remote config was last mirrored locally
|
||||||
remote_cfg_last_synced_on: string | null;
|
remote_cfg_last_synced_on: string | null;
|
||||||
|
// After-print navigation method. true (default) = SvelteKit goto() — faster, no full reload.
|
||||||
|
// false = window.location.href — full page reload, use as a fallback if goto causes issues.
|
||||||
|
// Per-device: stored in localStorage, not synced to the event config.
|
||||||
|
print_nav_use_goto: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BadgesSessState {
|
export interface BadgesSessState {
|
||||||
@@ -158,8 +163,7 @@ export interface BadgesSessState {
|
|||||||
export const badges_loc_defaults: BadgesLocState = {
|
export const badges_loc_defaults: BadgesLocState = {
|
||||||
auto_view: true,
|
auto_view: true,
|
||||||
|
|
||||||
show_hidden: false, // Hidden (archived) badges are excluded from the main list.
|
visibility_filter: 'default', // 'default' | 'show_hidden' | 'show_all'
|
||||||
show_not_enabled: false,
|
|
||||||
|
|
||||||
show_printed: false,
|
show_printed: false,
|
||||||
allow_reprint: false,
|
allow_reprint: false,
|
||||||
@@ -178,6 +182,7 @@ export const badges_loc_defaults: BadgesLocState = {
|
|||||||
qry_printed_status: 'all', // 'all' | 'printed' | 'not_printed'
|
qry_printed_status: 'all', // 'all' | 'printed' | 'not_printed'
|
||||||
qry_affiliations: null, // null = no affiliation filter
|
qry_affiliations: null, // null = no affiliation filter
|
||||||
qry_sort_order: '', // '' = default sort order
|
qry_sort_order: '', // '' = default sort order
|
||||||
|
qry_result_limit: 25,
|
||||||
|
|
||||||
status_qry__search: null,
|
status_qry__search: null,
|
||||||
use_id_li: true,
|
use_id_li: true,
|
||||||
@@ -202,7 +207,8 @@ export const badges_loc_defaults: BadgesLocState = {
|
|||||||
auth_search_min_chars: 2,
|
auth_search_min_chars: 2,
|
||||||
trusted_search_result_limit: 150,
|
trusted_search_result_limit: 150,
|
||||||
trusted_search_min_chars: 1,
|
trusted_search_min_chars: 1,
|
||||||
remote_cfg_last_synced_on: null
|
remote_cfg_last_synced_on: null,
|
||||||
|
print_nav_use_goto: true
|
||||||
};
|
};
|
||||||
|
|
||||||
// In-memory badge state — resets on page load.
|
// In-memory badge state — resets on page load.
|
||||||
|
|||||||
9
src/lib/stores/ae_events_stores__launcher.svelte.ts
Normal file
9
src/lib/stores/ae_events_stores__launcher.svelte.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { PersistedState } from 'runed';
|
||||||
|
import { launcher_loc_defaults } from './ae_events_stores__launcher_defaults';
|
||||||
|
|
||||||
|
export const launcher_loc = new PersistedState('ae_launcher_loc', launcher_loc_defaults, {
|
||||||
|
serializer: {
|
||||||
|
serialize: JSON.stringify,
|
||||||
|
deserialize: (raw: string) => ({ ...launcher_loc_defaults, ...JSON.parse(raw) })
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
* launcher_sess_defaults → events_sess.launcher (in-memory, resets on page load)
|
* launcher_sess_defaults → events_sess.launcher (in-memory, resets on page load)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { LaunchProfile } from '$lib/ae_events/ae_launcher__default_launch_profiles';
|
||||||
|
|
||||||
/** 3-way section collapse state used throughout the Launcher UI. */
|
/** 3-way section collapse state used throughout the Launcher UI. */
|
||||||
export type SectionState = 'collapsed' | 'auto' | 'pinned';
|
export type SectionState = 'collapsed' | 'auto' | 'pinned';
|
||||||
|
|
||||||
@@ -91,6 +93,18 @@ export interface LauncherLocState {
|
|||||||
* device/OS without deploying to the Mac laptop.
|
* device/OS without deploying to the Mac laptop.
|
||||||
*/
|
*/
|
||||||
native_test_mode: boolean;
|
native_test_mode: boolean;
|
||||||
|
/**
|
||||||
|
* Per-file display mode overrides, keyed by event_file_id.
|
||||||
|
* Stored locally (per-device) because event_file has no cfg_json column yet.
|
||||||
|
* TODO: migrate to event_file.cfg_json once the backend column is added.
|
||||||
|
*/
|
||||||
|
file_display_overrides: Record<string, 'extend' | 'mirror' | 'none'>;
|
||||||
|
/**
|
||||||
|
* Local per-device launch profile overrides, keyed by profile/extension name.
|
||||||
|
* Overrides DEFAULT_LAUNCH_PROFILES for this device only.
|
||||||
|
* Priority: device API config > this local override > built-in defaults.
|
||||||
|
*/
|
||||||
|
launch_profiles: Record<string, Partial<LaunchProfile>> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LauncherSessState {
|
export interface LauncherSessState {
|
||||||
@@ -119,6 +133,9 @@ export interface LauncherSessState {
|
|||||||
trigger_reload__event_session_obj_li: string | null;
|
trigger_reload__event_session_obj_li: string | null;
|
||||||
trigger_reload__event_location_obj_id: string | null;
|
trigger_reload__event_location_obj_id: string | null;
|
||||||
trigger_reload__event_location_obj_li: string | null;
|
trigger_reload__event_location_obj_li: string | null;
|
||||||
|
|
||||||
|
trigger__force_location_sync: any;
|
||||||
|
|
||||||
trigger__ws_connect: any;
|
trigger__ws_connect: any;
|
||||||
trigger__ws_disconnect: any;
|
trigger__ws_disconnect: any;
|
||||||
}
|
}
|
||||||
@@ -216,7 +233,9 @@ export const launcher_loc_defaults: LauncherLocState = {
|
|||||||
controller_client_id: null,
|
controller_client_id: null,
|
||||||
native_test_mode: false,
|
native_test_mode: false,
|
||||||
wallpaper_applied_url: null,
|
wallpaper_applied_url: null,
|
||||||
wallpaper_applied_url_external: null
|
wallpaper_applied_url_external: null,
|
||||||
|
file_display_overrides: {},
|
||||||
|
launch_profiles: null
|
||||||
// controller_cmd: null,
|
// controller_cmd: null,
|
||||||
// controller_trigger_send: null,
|
// controller_trigger_send: null,
|
||||||
};
|
};
|
||||||
@@ -254,6 +273,8 @@ export const launcher_sess_defaults: LauncherSessState = {
|
|||||||
trigger_reload__event_location_obj_id: null,
|
trigger_reload__event_location_obj_id: null,
|
||||||
trigger_reload__event_location_obj_li: null,
|
trigger_reload__event_location_obj_li: null,
|
||||||
|
|
||||||
|
trigger__force_location_sync: null,
|
||||||
|
|
||||||
trigger__ws_connect: null,
|
trigger__ws_connect: null,
|
||||||
trigger__ws_disconnect: null
|
trigger__ws_disconnect: null
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,4 +18,9 @@
|
|||||||
import { PersistedState } from 'runed';
|
import { PersistedState } from 'runed';
|
||||||
import { leads_loc_defaults } from './ae_events_stores__leads_defaults';
|
import { leads_loc_defaults } from './ae_events_stores__leads_defaults';
|
||||||
|
|
||||||
export const leads_loc = new PersistedState('ae_leads_loc', leads_loc_defaults);
|
export const leads_loc = new PersistedState('ae_leads_loc', leads_loc_defaults, {
|
||||||
|
serializer: {
|
||||||
|
serialize: JSON.stringify,
|
||||||
|
deserialize: (raw: string) => ({ ...leads_loc_defaults, ...JSON.parse(raw) })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
export interface LeadsLocState {
|
export interface LeadsLocState {
|
||||||
__version: number;
|
__version: number;
|
||||||
|
show_details: boolean; // Show extra metadata/details in the leads tab (was events_loc.show_details)
|
||||||
show_option__paid_tab: boolean;
|
show_option__paid_tab: boolean;
|
||||||
show_content__scan_alert: boolean;
|
show_content__scan_alert: boolean;
|
||||||
show_content__scan_requirements: boolean;
|
show_content__scan_requirements: boolean;
|
||||||
@@ -80,6 +81,7 @@ export interface LeadsSessState {
|
|||||||
// Persisted leads config — survives browser sessions.
|
// Persisted leads config — survives browser sessions.
|
||||||
export const leads_loc_defaults: LeadsLocState = {
|
export const leads_loc_defaults: LeadsLocState = {
|
||||||
__version: 1,
|
__version: 1,
|
||||||
|
show_details: false, // Show extra metadata/details in the leads tab (was events_loc.show_details)
|
||||||
show_option__paid_tab: true,
|
show_option__paid_tab: true,
|
||||||
show_content__scan_alert: true, // Workaround for QR scanner edge-case bug.
|
show_content__scan_alert: true, // Workaround for QR scanner edge-case bug.
|
||||||
show_content__scan_requirements: true,
|
show_content__scan_requirements: true,
|
||||||
|
|||||||
@@ -15,4 +15,9 @@
|
|||||||
import { PersistedState } from 'runed';
|
import { PersistedState } from 'runed';
|
||||||
import { pres_mgmt_loc_defaults } from './ae_events_stores__pres_mgmt_defaults';
|
import { pres_mgmt_loc_defaults } from './ae_events_stores__pres_mgmt_defaults';
|
||||||
|
|
||||||
export const pres_mgmt_loc = new PersistedState('ae_pres_mgmt_loc', pres_mgmt_loc_defaults);
|
export const pres_mgmt_loc = new PersistedState('ae_pres_mgmt_loc', pres_mgmt_loc_defaults, {
|
||||||
|
serializer: {
|
||||||
|
serialize: JSON.stringify,
|
||||||
|
deserialize: (raw: string) => ({ ...pres_mgmt_loc_defaults, ...JSON.parse(raw) })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ export interface PresMgmtSessState {
|
|||||||
recent_files: string | null;
|
recent_files: string | null;
|
||||||
presenters_agree: string | null;
|
presenters_agree: string | null;
|
||||||
presenters_biography: string | null;
|
presenters_biography: string | null;
|
||||||
|
file_downloads: string | null;
|
||||||
};
|
};
|
||||||
rpt__session_no_files: boolean;
|
rpt__session_no_files: boolean;
|
||||||
rpt__session_poc_agree: boolean;
|
rpt__session_poc_agree: boolean;
|
||||||
@@ -414,7 +415,8 @@ export const pres_mgmt_sess_defaults: PresMgmtSessState = {
|
|||||||
status_rpt: {
|
status_rpt: {
|
||||||
recent_files: null,
|
recent_files: null,
|
||||||
presenters_agree: null,
|
presenters_agree: null,
|
||||||
presenters_biography: null
|
presenters_biography: null,
|
||||||
|
file_downloads: null
|
||||||
},
|
},
|
||||||
rpt__session_no_files: true,
|
rpt__session_no_files: true,
|
||||||
rpt__session_poc_agree: false,
|
rpt__session_poc_agree: false,
|
||||||
|
|||||||
@@ -23,30 +23,12 @@ const idaa_local_data_struct: key_val = {
|
|||||||
// Timestamp (ms since epoch) when the last successful verification occurred.
|
// Timestamp (ms since epoch) when the last successful verification occurred.
|
||||||
// Used to cache verification results and avoid repeated Novi API calls.
|
// Used to cache verification results and avoid repeated Novi API calls.
|
||||||
novi_verified_ts: null,
|
novi_verified_ts: null,
|
||||||
// If set to a ms timestamp, verification attempts should be skipped until this time.
|
|
||||||
// Used to honor rate-limits and Retry-After behavior.
|
|
||||||
novi_rate_limited_until: null,
|
|
||||||
// Populated from $ae_loc.site_cfg_json at IDAA layout mount — not managed here.
|
// Populated from $ae_loc.site_cfg_json at IDAA layout mount — not managed here.
|
||||||
// See routes/idaa/(idaa)/+layout.svelte for the override logic.
|
// See routes/idaa/(idaa)/+layout.svelte for the override logic.
|
||||||
novi_admin_li: [],
|
novi_admin_li: [],
|
||||||
novi_trusted_li: [],
|
novi_trusted_li: [],
|
||||||
novi_jitsi_mod_li: [],
|
novi_jitsi_mod_li: [],
|
||||||
|
|
||||||
novi_archives_base_url: 'https://www.idaa.org/idaa-archives',
|
|
||||||
novi_bb_base_url: 'https://www.idaa.org/idaa-bulletin-board',
|
|
||||||
novi_meetings_base_url: 'https://www.idaa.org/idaa-meetings',
|
|
||||||
|
|
||||||
ds: {},
|
|
||||||
|
|
||||||
idaa_cfg_json: {},
|
|
||||||
|
|
||||||
// all, disabled, enabled
|
|
||||||
qry__enabled: 'enabled',
|
|
||||||
// all, hidden, not_hidden
|
|
||||||
qry__hidden: 'not_hidden',
|
|
||||||
qry__limit: 20,
|
|
||||||
qry__offset: 0,
|
|
||||||
|
|
||||||
archives: {
|
archives: {
|
||||||
enabled: 'enabled', // all, disabled, enabled
|
enabled: 'enabled', // all, disabled, enabled
|
||||||
hidden: 'not_hidden', // all, hidden, not_hidden
|
hidden: 'not_hidden', // all, hidden, not_hidden
|
||||||
|
|||||||
149
src/lib/stores/ae_idaa_stores__idaa_loc.svelte.ts
Normal file
149
src/lib/stores/ae_idaa_stores__idaa_loc.svelte.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import { PersistedState } from 'runed';
|
||||||
|
|
||||||
|
// ---- Types -------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface IdaaArchivesLoc {
|
||||||
|
enabled: string; // 'all' | 'disabled' | 'enabled'
|
||||||
|
hidden: string; // 'all' | 'hidden' | 'not_hidden'
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
edit_kv: Record<string, any>; // Tracks which archive objects are being edited.
|
||||||
|
edit__archive_obj: any;
|
||||||
|
edit__archive_content_obj: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IdaaBbLoc {
|
||||||
|
enabled: string; // 'all' | 'disabled' | 'enabled'
|
||||||
|
hidden: string; // 'all' | 'hidden' | 'not_hidden'
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
edit_kv: Record<string, any>; // Tracks which post objects are being edited.
|
||||||
|
edit__post_obj: any;
|
||||||
|
edit__post_comment_obj: any;
|
||||||
|
show_list__post_obj_li: boolean;
|
||||||
|
qry__enabled: string;
|
||||||
|
qry__hidden: string;
|
||||||
|
qry__limit: number;
|
||||||
|
qry__offset: number;
|
||||||
|
qry__order_by: string; // For the IDB index query
|
||||||
|
qry__order_by_li: Record<string, string>; // For the SQL query
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IdaaRecoveryMeetingsLoc {
|
||||||
|
edit_kv: Record<string, any>; // Tracks which meeting objects are being edited.
|
||||||
|
edit__event_obj: any;
|
||||||
|
qry__enabled: string;
|
||||||
|
qry__hidden: string;
|
||||||
|
qry__limit: number;
|
||||||
|
qry__order_by: string; // For the IDB index query; name, updated_on/created_on
|
||||||
|
qry__order_by_li: Record<string, string>; // For the SQL query
|
||||||
|
qry__offset: number;
|
||||||
|
qry__fulltext_str: string | null;
|
||||||
|
qry__physical: string | null;
|
||||||
|
qry__type: string | null;
|
||||||
|
qry__virtual: string | null;
|
||||||
|
// When true, only show meetings the member has starred. Favorites are stored
|
||||||
|
// server-side in event.mod_meetings_json.favorite (array of Novi UUIDs), so
|
||||||
|
// they persist across browsers without requiring a Novi API write capability.
|
||||||
|
qry__favorites_only: boolean;
|
||||||
|
// Collapse the "Meeting Info" data store panel. Persisted so preference survives reloads.
|
||||||
|
ds_info_collapsed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IdaaLocState {
|
||||||
|
novi_uuid: string | null;
|
||||||
|
novi_email: string | null;
|
||||||
|
novi_full_name: string | null;
|
||||||
|
// True after a successful Novi API verification (UUID confirmed to be a real Novi member).
|
||||||
|
// False on load, on verification failure, or for non-Novi sign-in paths.
|
||||||
|
novi_verified: boolean;
|
||||||
|
// Timestamp (ms since epoch) when the last successful verification occurred.
|
||||||
|
// Used to cache verification results and avoid repeated Novi API calls.
|
||||||
|
novi_verified_ts: number | null;
|
||||||
|
// Populated from $ae_loc.site_cfg_json at IDAA layout mount — not managed here.
|
||||||
|
// See routes/idaa/(idaa)/+layout.svelte for the override logic.
|
||||||
|
novi_admin_li: string[];
|
||||||
|
novi_trusted_li: string[];
|
||||||
|
novi_jitsi_mod_li: string[];
|
||||||
|
|
||||||
|
archives: IdaaArchivesLoc;
|
||||||
|
bb: IdaaBbLoc;
|
||||||
|
recovery_meetings: IdaaRecoveryMeetingsLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Defaults ----------------------------------------------------------------
|
||||||
|
|
||||||
|
export const idaa_loc_defaults: IdaaLocState = {
|
||||||
|
novi_uuid: null,
|
||||||
|
novi_email: null,
|
||||||
|
novi_full_name: null,
|
||||||
|
novi_verified: false,
|
||||||
|
novi_verified_ts: null,
|
||||||
|
novi_admin_li: [],
|
||||||
|
novi_trusted_li: [],
|
||||||
|
novi_jitsi_mod_li: [],
|
||||||
|
|
||||||
|
archives: {
|
||||||
|
enabled: 'enabled',
|
||||||
|
hidden: 'not_hidden',
|
||||||
|
limit: 150,
|
||||||
|
offset: 0,
|
||||||
|
edit_kv: {},
|
||||||
|
edit__archive_obj: null,
|
||||||
|
edit__archive_content_obj: null
|
||||||
|
},
|
||||||
|
|
||||||
|
bb: {
|
||||||
|
enabled: 'enabled',
|
||||||
|
hidden: 'not_hidden',
|
||||||
|
limit: 50,
|
||||||
|
offset: 0,
|
||||||
|
edit_kv: {},
|
||||||
|
edit__post_obj: null,
|
||||||
|
edit__post_comment_obj: null,
|
||||||
|
show_list__post_obj_li: true,
|
||||||
|
qry__enabled: 'enabled',
|
||||||
|
qry__hidden: 'not_hidden',
|
||||||
|
qry__limit: 25,
|
||||||
|
qry__offset: 0,
|
||||||
|
qry__order_by: 'updated_on',
|
||||||
|
qry__order_by_li: { updated_on: 'DESC', created_on: 'DESC' }
|
||||||
|
},
|
||||||
|
|
||||||
|
recovery_meetings: {
|
||||||
|
edit_kv: {},
|
||||||
|
edit__event_obj: null,
|
||||||
|
qry__enabled: 'enabled',
|
||||||
|
qry__hidden: 'not_hidden',
|
||||||
|
qry__limit: 100,
|
||||||
|
qry__order_by: 'updated_on',
|
||||||
|
qry__order_by_li: {
|
||||||
|
priority: 'DESC',
|
||||||
|
sort: 'DESC',
|
||||||
|
updated_on: 'DESC',
|
||||||
|
created_on: 'DESC',
|
||||||
|
name: 'ASC'
|
||||||
|
},
|
||||||
|
qry__offset: 0,
|
||||||
|
qry__fulltext_str: null,
|
||||||
|
qry__physical: null,
|
||||||
|
qry__type: null,
|
||||||
|
qry__virtual: null,
|
||||||
|
qry__favorites_only: false,
|
||||||
|
ds_info_collapsed: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Store -------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Note: the top-level spread in deserialize ensures new top-level fields get
|
||||||
|
// their defaults after a browser upgrade. Nested objects (archives, bb,
|
||||||
|
// recovery_meetings) are replaced wholesale from localStorage — if a new field
|
||||||
|
// is added to a nested object, it will not auto-populate until the user clears
|
||||||
|
// their storage. This is the same trade-off as the events sub-stores.
|
||||||
|
export const idaa_loc = new PersistedState('ae_idaa_loc', idaa_loc_defaults, {
|
||||||
|
serializer: {
|
||||||
|
serialize: JSON.stringify,
|
||||||
|
deserialize: (raw: string) => ({ ...idaa_loc_defaults, ...JSON.parse(raw) })
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -91,8 +91,6 @@ const ae_app_local_data_defaults: key_val = {
|
|||||||
qry__limit: 20,
|
qry__limit: 20,
|
||||||
qry__offset: 0,
|
qry__offset: 0,
|
||||||
|
|
||||||
qr_scanner_version: 'one',
|
|
||||||
|
|
||||||
admin: {
|
admin: {
|
||||||
show_element__sql_qry: false,
|
show_element__sql_qry: false,
|
||||||
show_element__sql_qry_results: false
|
show_element__sql_qry_results: false
|
||||||
@@ -143,7 +141,6 @@ const ae_app_local_data_defaults: key_val = {
|
|||||||
add_to_use_files_method: 'upload' // upload, select
|
add_to_use_files_method: 'upload' // upload, select
|
||||||
},
|
},
|
||||||
|
|
||||||
ds: {},
|
|
||||||
hub: {
|
hub: {
|
||||||
show_element__cfg: true,
|
show_element__cfg: true,
|
||||||
show_element__cfg_detail: false,
|
show_element__cfg_detail: false,
|
||||||
@@ -156,56 +153,6 @@ const ae_app_local_data_defaults: key_val = {
|
|||||||
|
|
||||||
qr: {}
|
qr: {}
|
||||||
},
|
},
|
||||||
mod: {
|
|
||||||
archives: {},
|
|
||||||
|
|
||||||
events: {
|
|
||||||
event_id: null,
|
|
||||||
|
|
||||||
show_edit__event_presenter_obj: false,
|
|
||||||
show_list__event_presenter_obj_li: true,
|
|
||||||
show_view__event_presenter_obj: false,
|
|
||||||
|
|
||||||
submit_status: null,
|
|
||||||
|
|
||||||
// When set, new presenters are automatically assigned to this session.
|
|
||||||
default_session_id: null
|
|
||||||
},
|
|
||||||
|
|
||||||
journals: {},
|
|
||||||
|
|
||||||
posts: {},
|
|
||||||
|
|
||||||
sponsorships: {
|
|
||||||
cfg_id: null,
|
|
||||||
|
|
||||||
for_type: null,
|
|
||||||
for_id: null,
|
|
||||||
|
|
||||||
// Max complimentary guests per sponsorship tier (0 = no tier).
|
|
||||||
// These are fallback defaults; the active event config should override via events_cfg_json.
|
|
||||||
level_guest_max_li: {
|
|
||||||
0: 0,
|
|
||||||
1: 4,
|
|
||||||
2: 8,
|
|
||||||
3: 8,
|
|
||||||
4: 8,
|
|
||||||
5: 8,
|
|
||||||
6: 16,
|
|
||||||
7: 16
|
|
||||||
},
|
|
||||||
|
|
||||||
show_edit__sponsorship_obj: false,
|
|
||||||
show_list__sponsorship_obj_li: true,
|
|
||||||
show_view__sponsorship_obj: false,
|
|
||||||
|
|
||||||
show_question__accommodations: false,
|
|
||||||
|
|
||||||
submit_status: null // 'saving', 'created', 'updated'
|
|
||||||
}
|
|
||||||
|
|
||||||
// testing: {},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ae_loc: Writable<key_val> = persisted(
|
export const ae_loc: Writable<key_val> = persisted(
|
||||||
@@ -258,31 +205,6 @@ const ae_app_session_data_defaults: key_val = {
|
|||||||
clip_complete: null
|
clip_complete: null
|
||||||
},
|
},
|
||||||
|
|
||||||
hub: {
|
|
||||||
show_xyz: null,
|
|
||||||
account_id_qry_status: null,
|
|
||||||
event_badge_id_status_qry__search: null,
|
|
||||||
event_presenter_id_qry_status: null,
|
|
||||||
site_domain_id_qry_status: null,
|
|
||||||
sponsorship_id_qry_status: null,
|
|
||||||
sponsorship_cfg_id_qry_status: null,
|
|
||||||
|
|
||||||
qr: {}
|
|
||||||
},
|
|
||||||
mod: {
|
|
||||||
archives: {},
|
|
||||||
events: {},
|
|
||||||
journals: {},
|
|
||||||
posts: {},
|
|
||||||
sponsorships: {
|
|
||||||
disable_submit__sponsorship_obj: false,
|
|
||||||
slct__level_num: 0,
|
|
||||||
show_question__accommodations: false,
|
|
||||||
submit_status: null // 'saving', 'created', 'updated', 'saved'
|
|
||||||
},
|
|
||||||
testing: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
person: {
|
person: {
|
||||||
show_report__person_li: false,
|
show_report__person_li: false,
|
||||||
|
|
||||||
@@ -291,7 +213,6 @@ const ae_app_session_data_defaults: key_val = {
|
|||||||
|
|
||||||
show__modal_change_password: false,
|
show__modal_change_password: false,
|
||||||
|
|
||||||
download: {},
|
|
||||||
// Per-file progress keyed by file_id (downloads) or temp_id (uploads).
|
// Per-file progress keyed by file_id (downloads) or temp_id (uploads).
|
||||||
// Shape: { status, endpoint, filename, size_total, size_loaded, percent_completed }
|
// Shape: { status, endpoint, filename, size_total, size_loaded, percent_completed }
|
||||||
api_download_kv: {},
|
api_download_kv: {},
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export const IDB_CONTENT_VERSIONS = {
|
|||||||
journal_entry: 3 // 2026-05-14: removed content_md_html + history_md_html from properties_to_save
|
journal_entry: 3 // 2026-05-14: removed content_md_html + history_md_html from properties_to_save
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
event: 2, // Bumped 2026-05-16: force-clear stale IDB data causing "no meetings found" on IDAA
|
event: 3, // Bumped 2026-06-03: precautionary clear after sort-direction and account_id gate fixes
|
||||||
event_session: 1,
|
event_session: 1,
|
||||||
event_presenter: 1,
|
event_presenter: 1,
|
||||||
event_badge: 1,
|
event_badge: 1,
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import '../app.css';
|
|||||||
// *** Import other supporting libraries
|
// *** Import other supporting libraries
|
||||||
import {
|
import {
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
|
WifiOff,
|
||||||
|
ChevronDown,
|
||||||
} from '@lucide/svelte';
|
} from '@lucide/svelte';
|
||||||
|
|
||||||
// Highlight JS
|
// Highlight JS
|
||||||
@@ -408,16 +410,45 @@ $effect(() => {
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{#if browser && (is_offline || api_unreachable)}
|
{#if browser && (is_offline || api_unreachable)}
|
||||||
<div
|
{#if show_connection_details}
|
||||||
class="preset-filled-error-200-800 fixed top-0 right-0 left-0 z-100 flex items-center justify-center gap-4 p-4 text-center shadow-2xl print:hidden">
|
<!-- Expanded banner -->
|
||||||
<span class="text-xl font-bold"
|
<div
|
||||||
>{is_offline ? 'Offline' : api_error_msg}</span>
|
class="preset-filled-error-200-800 fixed top-0 right-0 left-0 z-100 flex items-center justify-center gap-4 p-4 text-center shadow-2xl print:hidden">
|
||||||
<button
|
<WifiOff size="1.2em" class="shrink-0 opacity-70" />
|
||||||
class="btn btn-sm preset-tonal-surface"
|
<div class="flex flex-col items-center gap-0.5">
|
||||||
onclick={() => window.location.reload()}>
|
<span class="text-xl font-bold leading-tight">
|
||||||
|
{is_offline ? 'Device Offline' : api_error_msg}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs opacity-75">
|
||||||
|
{#if is_offline}
|
||||||
|
Cached event data is still available for search and print · Edits require network
|
||||||
|
{:else}
|
||||||
|
The Aether server could not be reached — check your connection or contact support
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm preset-tonal-surface"
|
||||||
|
onclick={() => window.location.reload()}>
|
||||||
<RefreshCw size="1em" class="opacity-60" /> Retry
|
<RefreshCw size="1em" class="opacity-60" /> Retry
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<button
|
||||||
|
class="btn btn-sm preset-tonal-surface"
|
||||||
|
title="Collapse"
|
||||||
|
onclick={() => { show_connection_details = false; }}>
|
||||||
|
<ChevronDown size="1em" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<!-- Collapsed indicator — small chip, top-right corner -->
|
||||||
|
<button
|
||||||
|
class="preset-filled-error-200-800 fixed top-2 right-2 z-100 flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-bold shadow-lg print:hidden"
|
||||||
|
title={is_offline ? 'Device Offline — click to expand' : `${api_error_msg} — click to expand`}
|
||||||
|
onclick={() => { show_connection_details = true; }}>
|
||||||
|
<WifiOff size="0.9em" class="shrink-0" />
|
||||||
|
{is_offline ? 'Offline' : 'API Error'}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if browser && flag_expired}
|
{#if browser && flag_expired}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user