Compare commits
161 Commits
a56f520d4e
...
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 | ||
|
|
cfc5d237c7 |
15
.ae_brief
15
.ae_brief
@@ -1,22 +1,15 @@
|
||||
# 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
|
||||
|
||||
## 🛠️ What I Just Did
|
||||
Addressed multiple Svelte compiler warnings:
|
||||
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.
|
||||
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.
|
||||
|
||||
## 🚧 Current Blockers
|
||||
None. Remaining svelte-check warnings (219) require more granular ID/for linking in complex forms.
|
||||
None.
|
||||
|
||||
## ➡️ Exact Next Steps
|
||||
1. Granular fix for remaining 68 label/ID associations in address/person forms.
|
||||
2. Systematic application of untrack() for remaining state-from-props warnings.
|
||||
3. Clean up unused TipTap CSS selectors identified by svelte-check.
|
||||
User to review changes. Ready for onsite testing/deployment.
|
||||
|
||||
---
|
||||
*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
|
||||
|
||||
**Last Updated:** 2026-06-12
|
||||
|
||||
This document outlines the overall architecture and key technologies used in the Aether SvelteKit frontend project.
|
||||
|
||||
## 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)
|
||||
- **Icons:** Lucide Icons (SVG Icons)
|
||||
- **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
|
||||
|
||||
@@ -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.
|
||||
|
||||
- `ae_core_db`: Core database instance.
|
||||
- `<module>`: Module-specific database instances.
|
||||
- `db_core`: Core database instance.
|
||||
- `db_<module>`: Module-specific database instances (for example, `db_events` and `db_journals`).
|
||||
- `<custom>`: Custom module-specific database instances (none currently defined).
|
||||
|
||||
## 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.
|
||||
|
||||
- `id`: Primary key for an object (internal use, often a UUID).
|
||||
- `id_random`: Randomly generated ID for an object (often used for external exposure or URL parameters).
|
||||
- `<object_type>_id_random`: Specific random ID for an object (e.g., `person_id_random`).
|
||||
- `<object_type>_id`: Canonical randomized string ID returned by V3 (for example, `person_id`). Use this for URLs, relationships, and Dexie indexed lookups.
|
||||
- `id`: Generic randomized string alias when returned by V3; do not assume it is a DB autonumber.
|
||||
- `id_random` / `<object_type>_id_random`: Legacy aliases. Do not introduce new usage.
|
||||
- `code`: Short, unique identifier.
|
||||
- `name`: Display name.
|
||||
- `enable`: Boolean for active/inactive status.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Aether Project Naming Conventions
|
||||
|
||||
**Last Updated:** 2026-06-12
|
||||
|
||||
## 1. General Principles
|
||||
|
||||
- **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`)
|
||||
- **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:**
|
||||
- **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`)
|
||||
- **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`)
|
||||
@@ -37,9 +40,9 @@
|
||||
|
||||
- **Singularity:** Use singular nouns for objects and properties (e.g., `example.id`, not `examples.id`).
|
||||
- **IDs:**
|
||||
- `id`: Primary key for an object (internal use, often a UUID).
|
||||
- `<object_type>_id`: Specific ID for an object (e.g., `person_id`).
|
||||
- `<object_type>_id_random`: Randomly generated ID for an object (often used for external exposure or URL parameters).
|
||||
- `<object_type>_id`: Canonical randomized string ID returned by V3 (for example, `person_id`).
|
||||
- `id`: Generic randomized string alias when V3 returns one; never assume it is an integer autonumber.
|
||||
- `<object_type>_id_random`: Legacy alias; do not introduce new usage.
|
||||
- `account_id`, `site_id`, `user_id`, etc.: Foreign keys.
|
||||
- **Common Properties:**
|
||||
- `code`: Short, unique identifier.
|
||||
@@ -88,6 +91,6 @@
|
||||
- `<module>` (extended modules)
|
||||
- `<custom>` (custom modules)
|
||||
- **IndexedDB:**
|
||||
- `ae_core_db`
|
||||
- `<module>`
|
||||
- `db_core`
|
||||
- `db_<module>` (for example, `db_events`, `db_journals`)
|
||||
- `<custom>`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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`
|
||||
|
||||
---
|
||||
@@ -76,15 +76,18 @@ $ae_loc.adv_mode // boolean — advanced mode toggle
|
||||
| AE Username + Password | `trusted` and above | Staff with AE accounts |
|
||||
| Novi UUID | `authenticated` | IDAA members (Novi membership system) |
|
||||
|
||||
Passcodes are stored per-level in `$ae_loc.site_access_code_kv`:
|
||||
```typescript
|
||||
site_access_code_kv: {
|
||||
administrator: null, // highest passcode tier
|
||||
trusted: null, // onsite staff passcode
|
||||
public: 'public1980', // example
|
||||
authenticated: 'auth1980'
|
||||
}
|
||||
```
|
||||
### Site Passcode Security Warning
|
||||
|
||||
The current frontend receives every site passcode in `access_code_kv_json`, copies the map into
|
||||
persisted `$ae_loc.site_access_code_kv`, and compares entered passcodes locally. Verbose logging
|
||||
can also expose the complete map. This is a known active security gap, not the target design.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# 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.
|
||||
> It covers the rules, patterns, and gotchas that matter most.
|
||||
> 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) |
|
||||
| Native | Electron app for onsite launcher (`src/lib/electron/electron_relay.ts`) |
|
||||
| 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.
|
||||
|
||||
### Store reactivity trap (important for `$effect`)
|
||||
The app uses `svelte-persisted-store` (Svelte 4 contract) for `$ae_loc`, `$ae_api`,
|
||||
`$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.
|
||||
The app has two kinds of persisted stores — know which you're reading:
|
||||
|
||||
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 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
|
||||
- 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
|
||||
```svelte
|
||||
{#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
|
||||
route. All IDAA content must be behind authentication. Always check route guards when
|
||||
touching `/idaa/` routes.
|
||||
Read this section first, then open the reference doc when your task touches one of these areas:
|
||||
|
||||
2. **`event_file_id` in PATCH body (400 error)** — including the object ID in `data_kv`
|
||||
when calling `update_ae_obj__*`. The V3 API tries to `SET event_file_id = ...` which
|
||||
fails because it's a view alias, not a DB column. See Section 2 above.
|
||||
1. **Security/Auth** — private route guards, account scoping, and pre-gate data load risks.
|
||||
2. **V3 API payloads** — object ID in URL, `data_kv` field-only PATCH payloads.
|
||||
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`
|
||||
(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.
|
||||
The reference doc also includes a short archive list for older, less-relevant historical incidents.
|
||||
|
||||
---
|
||||
|
||||
## 8. Source Layout (Quick Reference)
|
||||
|
||||
```
|
||||
```text
|
||||
src/lib/
|
||||
ae_api/ — API helpers (V3 preferred)
|
||||
ae_core/ — Account, User, Person, Site, hosted files
|
||||
@@ -404,12 +339,23 @@ Start here, then go deeper as needed:
|
||||
| What you need | Read |
|
||||
|---|---|
|
||||
| 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` |
|
||||
| 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` |
|
||||
| Common mistakes reference | `documentation/REFERENCE__Common_Agent_Mistakes.md` |
|
||||
| Svelte 5 patterns + pitfalls | `documentation/GEMINI__Svelte_and_Me.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` |
|
||||
| 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` |
|
||||
|
||||
@@ -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.
|
||||
|
||||
### 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
|
||||
@@ -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
|
||||
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)
|
||||
|
||||
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
|
||||
**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)
|
||||
|
||||
**Last Updated:** 2026-06-12
|
||||
|
||||
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`
|
||||
* **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
|
||||
Modify data in the system.
|
||||
* **Endpoints:**
|
||||
@@ -266,7 +288,7 @@ When seeding new lookup data (e.g., adding timezones in bulk):
|
||||
|
||||
## 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}`
|
||||
* **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)
|
||||
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)
|
||||
@@ -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`)
|
||||
- Optional query params: `filename_no_ext` (defaults to `automated_hosted_file_clip_video`), `reencode` (bool), `scale_down` (bool)
|
||||
- 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`.
|
||||
- Defaults to stream-copying to be fast; set `reencode=true` to force H.264 or `scale_down=true` to resize.
|
||||
- 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; returns 202 when scheduled successfully.
|
||||
- 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.
|
||||
- Add `?background=true` to schedule the clip asynchronously — returns `202 Accepted` immediately; poll the `hosted_file` record for completion.
|
||||
- Returns 400 on synchronous failure; 202 when scheduled successfully.
|
||||
|
||||
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.
|
||||
- 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.
|
||||
- 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.
|
||||
- Prefer `?background=true` for large inputs to avoid request timeouts. 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 |
|
||||
| `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 no identity data | `404` | `denied` |
|
||||
| `404` | `404` | `denied` |
|
||||
| `429` | `429` | Auto-retry after 10s; `'rate_limited'` if retry fails |
|
||||
| Network error / Novi 5xx | `503` | Auto-retry after 3s; `'api_error'` if retry fails |
|
||||
| `429` | `429` | `'rate_limited'` |
|
||||
| Network error / Novi 5xx | `503` | `'api_error'` |
|
||||
|
||||
### Caching
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# Aether Events — Onsite Badge Printing
|
||||
|
||||
**Last Updated:** 2026-06-12
|
||||
|
||||
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
|
||||
@@ -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.
|
||||
Fan-fold stock is typically 4" × 3" or 4" × 6" paper labels.
|
||||
The C3500 is a color inkjet label printer — it prints continuous fan-fold paper stock,
|
||||
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
|
||||
|
||||
Fan-fold badges would use a layout sized to the specific label stock.
|
||||
A new CSS layout file will need to be created per stock size if not already present.
|
||||
Naming convention: `badge_layout_epson_[model]_[size].css`
|
||||
The C3500 uses the `badge_4x6_fanfold` layout. CSS file:
|
||||
`src/lib/ae_events/badges/css/badge_layout_epson_4x6_fanfold.css`
|
||||
|
||||
#### Setup Notes
|
||||
|
||||
*(To be filled in after testing — cover: driver source, CUPS setup, paper size, Chrome settings)*
|
||||
Created 2026-05-15 for Axonius Adapt DC. Key specs:
|
||||
- `badge_front` 4" × 6", portrait orientation
|
||||
- `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
|
||||
|
||||
*(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
|
||||
> **Version:** 1.2 (2026-03-20)
|
||||
> **Last Updated:** 2026-03-20
|
||||
> **Author:** One Sky IT / Scott Idem
|
||||
> **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)
|
||||
> **Version:** 1.2 (2026-03-17)
|
||||
> **Last Updated:** 2026-03-17
|
||||
> **Location:** documentation/GUIDE__Development.md
|
||||
|
||||
## 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/AE__Architecture.md` | System architecture overview |
|
||||
| `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 |
|
||||
|
||||
## 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
|
||||
- Provide a straightforward policy to keep build caches useful but bounded.
|
||||
@@ -1,5 +1,7 @@
|
||||
# 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).
|
||||
- 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.
|
||||
|
||||
### 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
|
||||
|
||||
**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:
|
||||
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:**
|
||||
```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.
|
||||
|
||||
## 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)
|
||||
- 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.
|
||||
@@ -244,7 +380,7 @@ The `createLiveQueryStore` function creates a readable store that automatically
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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`
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
`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
|
||||
|
||||
@@ -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**.
|
||||
|
||||
**❌ 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
|
||||
<!-- Error: Can only bind to an Identifier or MemberExpression -->
|
||||
<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/`
|
||||
**API Module:** `src/lib/ae_events/ae_events__event_badge_template.ts`
|
||||
**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 |
|
||||
| `notes` | str | Internal notes |
|
||||
|
||||
### New Field (pending backend addition)
|
||||
### Duplex / Single-Sided
|
||||
| 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.
|
||||
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.
|
||||
|
||||
The first event using this system (Axonius, NYC, mid-April 2026) uses single-sided PVC
|
||||
cards on a Zebra ZC10L — `duplex` will be `false` for that event's templates.
|
||||
`duplex` is in `properties_to_save` and `show_badge_back` is derived from it in
|
||||
`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>
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
### 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
|
||||
in DB and may be needed:
|
||||
|
||||
- `style_href` — needed once external CSS is wired via `<svelte:head>`
|
||||
- `passcode` — not needed client-side
|
||||
- `footer_title`, `footer_left`, `footer_right` — not needed (legacy)
|
||||
- `header_background`, `footer_background` — not needed (legacy)
|
||||
- `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`-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] 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.
|
||||
- [ ] 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/`
|
||||
**API Module:** `src/lib/ae_events/ae_events__event_badge.ts`
|
||||
**Database:** `db_events.badge` (Dexie IndexedDB table)
|
||||
**Last Updated:** 2026-02-27 (rev 6)
|
||||
**Related Docs:** `documentation/PROJECT__AE_Events_Badges_Review_Print.md` (implementation guide)
|
||||
**Last Updated:** 2026-06-12
|
||||
|
||||
The Badges module manages event attendee records and their physical badge configurations. It supports multi-source imports, field protection for onsite edits, and multi-tier access control for self-service review.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
## Data Model & Hierarchy
|
||||
|
||||
The Badges module manages event attendee badges with support for:
|
||||
- **External system imports as needed** (CSV/Excel, iMIS, Zoom, Novi, Impexium, Confex, Cvent, and others)
|
||||
- **Field override protection** to prevent staff/attendee edits from being overwritten by automated syncs
|
||||
- **Multi-tier access control** for field editing
|
||||
- **QR code generation** for badge scanning
|
||||
- **Print tracking** (count, first/last print datetime)
|
||||
- **Advanced search and filtering**
|
||||
- **HTML rendering** in display fields for rich text formatting
|
||||
- **Accessibility features** (text enlargement toggle)
|
||||
### Core Objects
|
||||
- **Event Badge** (`event_badge`): The attendee record containing name, title, affiliations, and tracking flags.
|
||||
- **Badge Template** (`event_badge_template`): The visual and structural configuration for printing (branding, layout, QR placement).
|
||||
|
||||
### Relationships
|
||||
- **Badge → Event:** Many-to-one.
|
||||
- **Badge → Template:** Many-to-one (via `event_badge_template_id`).
|
||||
- **Badge → Person:** Optional link to core Aether Person record for unified profiles.
|
||||
|
||||
---
|
||||
|
||||
## Critical Design Pattern: Override Fields
|
||||
|
||||
### Purpose
|
||||
The `*_override` fields pattern protects data from being overwritten during scheduled cron syncs from external systems. This is essential because:
|
||||
1. Staff may need to correct imported data
|
||||
2. Attendees may be allowed to self-update certain fields (e.g., preferred name, pronouns)
|
||||
3. External systems often have outdated or incorrect data
|
||||
4. Changes should persist across multiple sync cycles
|
||||
The `*_override` fields pattern (established in 2018) protects data from being overwritten during scheduled cron syncs from external systems (iMIS, Novi, etc.). This ensures that staff corrections or attendee self-updates persist across multiple sync cycles.
|
||||
|
||||
### How It Works
|
||||
1. **Import:** External systems populate **REGULAR** fields only.
|
||||
2. **Display Logic:** The UI displays the `*_override` field if it has a value; otherwise, it falls back to the regular field.
|
||||
3. **HTML Rendering:** Certain display fields (Name, Title, Affiliations, Location) support HTML markup for rich text formatting (bold, italics, line breaks) on the physical badge.
|
||||
|
||||
**Import Behavior:**
|
||||
```
|
||||
External System → Aether API → Populates REGULAR fields only
|
||||
(never touches *_override fields)
|
||||
```
|
||||
### Standard Override Pairs
|
||||
|
||||
**Display Behavior:**
|
||||
```
|
||||
UI Display Logic:
|
||||
1. IF `*_override` field has value → USE IT (highest priority)
|
||||
2. ELSE IF regular field has value → USE IT (fallback)
|
||||
3. ELSE → Display placeholder/empty
|
||||
```
|
||||
|
||||
**HTML Rendering (implemented 2026-02-27):**
|
||||
|
||||
Certain fields support HTML markup for rich text formatting. When viewing (not editing), these fields use Svelte's `{@html}` directive to render the markup:
|
||||
- `full_name` / `full_name_override`
|
||||
- `professional_title` / `professional_title_override`
|
||||
- `affiliations` / `affiliations_override`
|
||||
- `location` / `location_override`
|
||||
|
||||
This allows for formatting like:
|
||||
- Bold/italic: `<b>Dr.</b> Jane Smith` or `<i>Chief Medical Officer</i>`
|
||||
- Line breaks: `Hospital Name<br>Department Name`
|
||||
- Special characters and entities
|
||||
|
||||
**Example — Full Name:**
|
||||
```typescript
|
||||
// API imports from iMIS
|
||||
badge.given_name = "Robert"
|
||||
badge.family_name = "Smith"
|
||||
badge.full_name = "Robert Smith" // Auto-computed
|
||||
|
||||
// Staff edits to preferred name with HTML
|
||||
badge.full_name_override = "<b>Bob</b> Smith"
|
||||
|
||||
// Display in UI (review form)
|
||||
{@html badge.full_name_override || badge.full_name || "— no name —"}
|
||||
// Result: **Bob** Smith (bold rendered)
|
||||
|
||||
// Edit mode shows raw HTML
|
||||
<input bind:value={editable_full_name_override} />
|
||||
// Shows: <b>Bob</b> Smith (editable as text)
|
||||
|
||||
// Next cron sync from iMIS
|
||||
// ✅ badge.full_name updated to "Robert J. Smith" (middle initial added)
|
||||
// ✅ badge.full_name_override remains "<b>Bob</b> Smith" (PROTECTED)
|
||||
// ✅ Display still shows **Bob** Smith (bold rendered)
|
||||
```
|
||||
|
||||
### Override Fields
|
||||
|
||||
| Regular Field | Override Field | Purpose | Editable By | HTML Rendering |
|
||||
|---|---|---|---|---|
|
||||
| `pronouns` | `pronouns_override` | Preferred pronouns | Staff, Attendee | No |
|
||||
| `professional_title` | `professional_title_override` | Job title display | Staff, Attendee | ✅ Yes |
|
||||
| `full_name` | `full_name_override` | Preferred name display | Staff, Attendee | ✅ Yes |
|
||||
| `affiliations` | `affiliations_override` | Organization display | Staff, Attendee | ✅ Yes |
|
||||
| `phone` | `phone_override` | Phone number | Staff, Attendee | No |
|
||||
| `email` | `email_override` | Contact email override | Staff only | No |
|
||||
| `location` | `location_override` | City/State/Country display | Staff, Attendee | ✅ Yes |
|
||||
| `badge_type` | `badge_type_override` | Badge category label text | Staff only | No |
|
||||
| `badge_type_code` | `badge_type_code_override` | Badge access level code | Staff only | No |
|
||||
| `registration_type` | `registration_type_override` | Registration category label text | Staff only | No |
|
||||
| `registration_type_code` | `registration_type_code_override` | Registration category code | Staff only | No |
|
||||
|
||||
> **Note:** `phone`, `phone_override`, `pronouns_override`, `registration_type`, `registration_type_code`, `registration_type_override`, `registration_type_code_override` may need to be confirmed against the DB schema via `ae_describe event_badge` and added to `properties_to_save` in `ae_events__event_badge.ts` if not already present.
|
||||
|
||||
### Sync Safety Rules
|
||||
|
||||
**Automated Sync (Cron Jobs):**
|
||||
- ✅ CAN update: All regular fields (`given_name`, `family_name`, `email`, `affiliations`, etc.)
|
||||
- ❌ CANNOT update: Any `*_override` field
|
||||
- ❌ CANNOT delete: Any `*_override` value
|
||||
|
||||
**Manual Staff Edit:**
|
||||
- ✅ CAN update: Any field (including overrides)
|
||||
- ✅ CAN clear: Override fields (reverts to regular field)
|
||||
|
||||
**Attendee Self-Service Edit:**
|
||||
- ✅ CAN update: Only specific override fields (per event config)
|
||||
- ✅ CAN clear: Their own override fields
|
||||
- ❌ CANNOT edit: Regular fields, badge_type, email_override
|
||||
| Regular Field | Override Field | Editable By | HTML? |
|
||||
|---|---|---|---|
|
||||
| `full_name` | `full_name_override` | Staff, Attendee | ✅ |
|
||||
| `professional_title` | `professional_title_override` | Staff, Attendee | ✅ |
|
||||
| `affiliations` | `affiliations_override` | Staff, Attendee | ✅ |
|
||||
| `location` | `location_override` | Staff, Attendee | ✅ |
|
||||
| `email` | `email_override` | Staff Only | No |
|
||||
| `badge_type` | `badge_type_override` | Staff Only | No |
|
||||
|
||||
---
|
||||
|
||||
## External System Integration
|
||||
|
||||
### Supported Import Sources
|
||||
- **iMIS** (Association Management)
|
||||
- **Zoom** (Virtual event registration)
|
||||
- **Novi AMS** (Association Management)
|
||||
- **Impexium** (Association Management)
|
||||
- **Confex** (Event abstract management)
|
||||
- **Cvent** (Event registration)
|
||||
- **Custom CSV/Excel** imports
|
||||
Aether acts as a **Pull-Only** consumer for registration data. It does not push changes back to external systems, maintaining them as the source of truth for base registration while Aether handles the "Onsite Truth."
|
||||
|
||||
### Data Flow Direction
|
||||
```
|
||||
External Systems ─────────> Aether
|
||||
(READ ONLY) (WRITE + DISPLAY)
|
||||
```
|
||||
|
||||
**Important:** Aether is **pull-only** — does not push changes back to external systems. This prevents sync conflicts and maintains external systems as the source of truth for base data.
|
||||
|
||||
### Sync Behavior
|
||||
- **Frequency:** Scheduled cron jobs (typically hourly, daily, or on-demand)
|
||||
- **Method:** Full sync or incremental (depends on external system API)
|
||||
- **Conflict Resolution:** Override fields always win
|
||||
|
||||
**Pseudocode:**
|
||||
```python
|
||||
def sync_badge_from_external(external_badge_data, existing_badge):
|
||||
# Update regular fields from external source
|
||||
existing_badge.given_name = external_badge_data.first_name
|
||||
existing_badge.family_name = external_badge_data.last_name
|
||||
existing_badge.email = external_badge_data.email
|
||||
existing_badge.affiliations = external_badge_data.organization
|
||||
existing_badge.badge_type_code = external_badge_data.registration_type
|
||||
|
||||
# NEVER TOUCH OVERRIDE FIELDS
|
||||
# existing_badge.full_name_override ← PROTECTED
|
||||
# existing_badge.affiliations_override ← PROTECTED
|
||||
# existing_badge.email_override ← PROTECTED
|
||||
|
||||
return existing_badge
|
||||
```
|
||||
### Supported Sources
|
||||
- **iMIS**, **Novi AMS**, **Impexium** (Associations)
|
||||
- **Zoom**, **Cvent** (Registrations)
|
||||
- **Confex** (Abstracts/Presenters)
|
||||
- **Custom CSV/Excel**
|
||||
|
||||
---
|
||||
|
||||
## Access Control & Edit Permissions
|
||||
## Access Control & Permissions
|
||||
|
||||
### Access Levels (Ascending)
|
||||
1. **Anonymous** — No access to badges
|
||||
2. **Public** — View public event info only (no badge access)
|
||||
3. **Authenticated** — View own badge, limited self-edit
|
||||
4. **Trusted** — Search all badges, view all, edit own
|
||||
5. **Administrator** — Full CRUD, bulk operations, override any field
|
||||
6. **Manager** — All administrator + event configuration
|
||||
7. **Super** — All manager + cross-event operations
|
||||
| Level | Access |
|
||||
|---|---|
|
||||
| **Public kiosk** | View badge and perform the first print; cannot edit fields without authenticated access. |
|
||||
| **Authenticated** | Edit fields allowed by the active permission config. |
|
||||
| **Trusted** | Search all badges, view all, and correct names; reprint requires global Edit Mode. |
|
||||
| **Administrator** | Full CRUD, bulk operations, and override access. |
|
||||
| **Manager** | All Administrator capabilities plus Event/Template configuration. |
|
||||
|
||||
### 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 |
|
||||
| --- | --- |
|
||||
| Below Trusted (incl. anonymous) | Only badges where `print_count < 1` and not hidden |
|
||||
| Trusted, not Edit Mode | Only badges where `print_count < 1` and not hidden |
|
||||
| Trusted + Edit Mode | All badges where `hide === false` (including already-printed) |
|
||||
|
||||
#### Print Button Behavior (per result row)
|
||||
|
||||
| Access Level | Print Action |
|
||||
| --- | --- |
|
||||
| Below Trusted | No print action — name shown with User icon, non-interactive |
|
||||
| Trusted, `print_count < 1` | Clickable link → `/print` page, Printer icon |
|
||||
| Trusted, `print_count >= 1`, not Edit Mode | Disabled (already printed safety lock), shows `Nx` count |
|
||||
| Trusted, `print_count >= 1`, Edit Mode | Clickable reprint — shows `Nx` count badge next to icon |
|
||||
|
||||
Print count displayed as `[Printer][2×] Name` when `print_count >= 1`.
|
||||
|
||||
#### Review Area Buttons (per result row, up to 3 buttons total)
|
||||
|
||||
| Button | Visible To | Behavior |
|
||||
| --- | --- | --- |
|
||||
| Email Review Link | All users | Placeholder `alert()` — will trigger email API |
|
||||
| Review Link (clipboard) | Trusted + Edit Mode only | Copies `/review` URL to clipboard; shows `Copied!` feedback |
|
||||
| *(direct Review link)* | *(future)* | *(not yet implemented as separate nav button)* |
|
||||
|
||||
#### Badge Edit Form (`ae_comp__badge_obj_view.svelte`)
|
||||
|
||||
**Currently editable fields (local `edit_mode_active`, not global `edit_mode`):**
|
||||
```typescript
|
||||
editable_full_name_override: string | null
|
||||
editable_professional_title_override: string | null
|
||||
editable_affiliations_override: string | null // textarea
|
||||
editable_location_override: string | null
|
||||
editable_allow_tracking: boolean | null
|
||||
editable_email: string | null
|
||||
editable_badge_type_code: string | null
|
||||
```
|
||||
|
||||
- Save button → `handle_save_changes()` — only changed fields sent to API
|
||||
- Cancel button → `handle_cancel_changes()` — reverts to IDB values
|
||||
- **IMPORTANT:** This component must NEVER write to `$ae_loc.edit_mode` — it uses its own local `edit_mode_active` flag only. (Bug fixed 2026-02-27)
|
||||
|
||||
#### Badge Review Form (`ae_comp__badge_review_form.svelte`)
|
||||
|
||||
Form-based review (NOT a badge render). Used by the `/review` page.
|
||||
|
||||
**Props:**
|
||||
- `can_edit_fields: string[]` prop controls which fields are editable per user level
|
||||
- `['*']` = administrator (all fields)
|
||||
- `is_staff: boolean` prop shows/hides the staff-only fields
|
||||
- Fields show "(overridden)" label when an override value differs from the base field
|
||||
|
||||
**Features (implemented 2026-02-27):**
|
||||
- **HTML Rendering**: `full_name_override`, `professional_title_override`, `affiliations_override`, and `location_override` fields render HTML markup using `{@html}` directive when viewing (not when editing)
|
||||
- **Accessibility**: Text enlargement toggle button switches between text-2xl (normal) and text-4xl (enlarged) for improved readability
|
||||
- **Help Modal**: Flowbite Modal component with 6 help sections (Reviewing Badge, Editing Info, Accessibility, QR Code, Lead Scanning, Assistance)
|
||||
- **QR Code Display**: Generates QR code using `core_func.js_generate_qr_code()` with badge ID, supports hover zoom and click-to-expand
|
||||
- **Print Status**: Shows print count, first print datetime, and last print datetime at top of form
|
||||
- **Local Edit Mode**: Independent `local_edit_active` state (never writes to `$ae_loc.edit_mode`)
|
||||
- **Save/Cancel**: Only changed fields sent to API; revert button for override fields
|
||||
|
||||
**Editable Fields:**
|
||||
- Pronouns, Full Name, Professional Title, Affiliations, Phone, Location (all with override support)
|
||||
- Allow Tracking checkbox (lead scanning permission)
|
||||
- Staff-only: Email, Badge Type, Registration Type, Hide, Priority, Notes
|
||||
- Staff-only: Options (`other_1_code` through `other_8_code`) and Tickets (`ticket_1_code` through `ticket_8_code`)
|
||||
- Agree to Terms & Conditions checkbox (attendee-visible when in can_edit_fields)
|
||||
|
||||
#### Badge Review Page — Header Buttons (implemented 2026-02-27)
|
||||
|
||||
| Button | Visible To | Behavior |
|
||||
| --- | --- | --- |
|
||||
| Back → Search (ArrowLeft) | Staff (`has_staff_access`) only | `<a href="/events/{id}/badges">` |
|
||||
| Print (Printer icon) | Trusted+, not printed OR Trusted+Edit if printed | `<a href="/print">`, shows `Nx` count if reprinting |
|
||||
| Copy Link (clipboard) | Trusted + Edit Mode only | Copies review URL to clipboard; `Copied!` feedback for 2s |
|
||||
| Email Link (Mail icon) | All if not printed; Trusted+Edit if printed | Placeholder `alert()` — email API pending |
|
||||
|
||||
#### Badge Print Page — Header Buttons (implemented 2026-02-27)
|
||||
|
||||
| Button | Visible To | Behavior |
|
||||
| --- | --- | --- |
|
||||
| Back → Search (ArrowLeft) | Always (when badge loaded) | `<a href="/events/{id}/badges">` |
|
||||
| Print Now (Printer icon) | Trusted+, not printed OR Trusted+Edit if printed | Calls `window.print()` directly (convenience duplicate); print count tracked by component button |
|
||||
| Review (Eye icon) | Trusted + Edit Mode only | `<a href="/review">` nav link |
|
||||
| Email Link (Mail icon) | All if not printed; Trusted+Edit if printed | Placeholder `alert()` — email API pending |
|
||||
|
||||
#### Badge Review Page — Display Sections (implemented 2026-02-27)
|
||||
|
||||
The review form (`ae_comp__badge_review_form.svelte`) displays:
|
||||
|
||||
1. **Print status** ✅ — print count + first/last print timestamps (read-only, hidden if never printed)
|
||||
2. **QR Code** ✅ — the attendee's badge QR code for scanning at the badge kiosk (for automatic badge search + print flow). Generated using `core_func.js_generate_qr_code()` with `obj_type: 'event_badge'` and badge ID. Supports hover zoom overlay and click-to-expand.
|
||||
3. **Editable Fields** ✅ — all fields with access-level gating, override support, and HTML rendering for display fields
|
||||
4. **Options** ✅ (`other_1_code` through `other_8_code`) — Staff: editable text inputs; Attendees: shown as `[✓] Option X` checkmark display only when value exists
|
||||
5. **Tickets** ✅ (`ticket_1_code` through `ticket_8_code`) — Staff: editable text inputs; Attendees: shown as `[✓] Ticket X` checkmark display only when value exists
|
||||
6. **Accessibility Toggle** ✅ — Font size enlargement button in sticky header (text-2xl ↔ text-4xl)
|
||||
7. **Help Modal** ✅ — Attendee guidance modal with 6 sections explaining the review process, editing, QR codes, and lead scanning
|
||||
|
||||
#### Default Field Permissions (hardcoded for now — Axonius first show, mid-April 2026)
|
||||
|
||||
These are hardcoded in `review/+page.svelte` pending connection to `mod_badges_json.edit_permissions`.
|
||||
|
||||
**Attendee (passcode-authenticated / anonymous with link):**
|
||||
```typescript
|
||||
[
|
||||
'pronouns_override',
|
||||
'full_name_override',
|
||||
'professional_title_override',
|
||||
'affiliations_override',
|
||||
'phone_override',
|
||||
'location_override',
|
||||
'allow_tracking', // Exhibitor Leads opt-in
|
||||
'agree_to_tc', // Terms & Conditions placeholder
|
||||
]
|
||||
```
|
||||
|
||||
**Trusted Staff and above:**
|
||||
```typescript
|
||||
[
|
||||
'pronouns_override',
|
||||
'full_name_override',
|
||||
'professional_title_override',
|
||||
'affiliations_override',
|
||||
'email_override',
|
||||
'phone_override',
|
||||
'location_override',
|
||||
'badge_type_code_override', // + badge_type_override (text label)
|
||||
'registration_type_code_override', // + registration_type_override (text label)
|
||||
'option_1' ... 'option_8', // i.e. other_1_code ... other_8_code
|
||||
'ticket_1_code' ... 'ticket_8_code',
|
||||
'allow_tracking',
|
||||
'agree_to_tc',
|
||||
'hide',
|
||||
'priority',
|
||||
'notes',
|
||||
]
|
||||
```
|
||||
|
||||
**Administrator** — `can_edit_fields = ['*']` (all fields)
|
||||
|
||||
**Badge type options (hardcoded for now):** `member`, `non-member`, `guest`, `exhibitor`, `staff`, `test`
|
||||
(In future: read from Event Badge Template's configured list)
|
||||
|
||||
**Registration type options:** Same list as badge type for now — identical select options.
|
||||
|
||||
#### Future: Per-Event Configuration
|
||||
|
||||
`event.mod_badges_json.edit_permissions` — placeholder settings UI exists in
|
||||
`ae_comp__event_settings_badges_form.svelte`. Review page uses hardcoded defaults for now.
|
||||
The settings form and review page are not yet connected.
|
||||
|
||||
```json
|
||||
{
|
||||
"authenticated": {
|
||||
"can_edit": ["pronouns_override", "full_name_override", "professional_title_override", "affiliations_override", "phone_override", "location_override", "allow_tracking", "agree_to_tc"]
|
||||
},
|
||||
"trusted": {
|
||||
"can_edit": ["*attendee_fields", "email_override", "badge_type_code_override", "registration_type_code_override", "option_x", "ticket_x_code", "allow_tracking", "agree_to_tc", "hide", "priority", "notes"]
|
||||
}
|
||||
}
|
||||
```
|
||||
### Review-Link Email
|
||||
Email Link actions are placeholders and do not currently send mail. When delivery is implemented,
|
||||
it must use the imported `event_badge.email` address, never attendee-editable `email_override`.
|
||||
|
||||
---
|
||||
|
||||
## Search & Filter Capabilities
|
||||
|
||||
### Search Component
|
||||
**File:** `ae_comp__badge_search.svelte`
|
||||
- **Fulltext Search:** Matches against a consolidated `default_qry_str` (Name, email, IDs).
|
||||
- **Multi-Word Logic:** Queries like "Scott Idem" are split and treated as `LIKE %Scott% AND LIKE %Idem%`.
|
||||
- **QR Scan Search:** Scanning an attendee's QR code (from a confirmation email or old badge) immediately jumps to their record.
|
||||
- **Advanced Filters (Trusted + Edit Mode):** Badge Type, Printed Status, Affiliations, Sort Order.
|
||||
|
||||
### Multi-Word Search Fix (2026-02-26)
|
||||
Fulltext search now correctly handles multi-word queries by splitting on whitespace and applying AND logic per word:
|
||||
```typescript
|
||||
// "scott idem" → LIKE '%scott%' AND LIKE '%idem%'
|
||||
// Previously: LIKE '%scott idem%' (failed to match)
|
||||
const words = qry.split(/\s+/).filter(w => w.length > 0);
|
||||
for (const word of words) {
|
||||
search_query.and.push({ field: 'default_qry_str', op: 'like', value: `%${word}%` });
|
||||
}
|
||||
```
|
||||
**Committed:** dc0f3066
|
||||
### Visibility Filter (Trusted + Edit Mode)
|
||||
|
||||
### Available Filters
|
||||
Three-option select controlling which records are shown:
|
||||
|
||||
**Fulltext Search** (All Users)
|
||||
- Searches: `default_qry_str` database field
|
||||
- Includes: Name, email, external IDs
|
||||
- Type: `LIKE %query%` (case-insensitive)
|
||||
- Trigger: Enter key or 3+ characters typed
|
||||
| Option | Who can set it | Effect |
|
||||
| --- | --- | --- |
|
||||
| **Default** | Any | Hides hidden and disabled badges |
|
||||
| **Show Hidden** | Trusted | Shows hidden badges alongside normal ones |
|
||||
| **Show Disabled + Hidden** | Manager only | Shows all records regardless of enable/hide flags |
|
||||
|
||||
**Advanced Filters** (Trusted Access & Above)
|
||||
```typescript
|
||||
// Badge Type Filter
|
||||
badge_type_code: 'current_member' | 'inactive_member' | 'ex_all' | 'staff' | etc.
|
||||
// Note: Badge types are defined per Event and Event Badge Template in database table records.
|
||||
// Common types include: member, nonmember, guest, exhibitor, staff
|
||||
// This is a work in progress - types vary by event configuration.
|
||||
### Result Limit Stepper (Edit Mode)
|
||||
|
||||
// Print Status Filter
|
||||
qry_printed_status: 'all' | 'printed' | 'not_printed'
|
||||
Controls the maximum number of results returned. Only visible in edit mode.
|
||||
|
||||
// Affiliations Search
|
||||
qry_affiliations: string // Separate filter for organization search
|
||||
| Access Level | Range | Step |
|
||||
| --- | --- | --- |
|
||||
| Below Trusted | Fixed 25 | — |
|
||||
| Trusted | 25 – 250 | 25 |
|
||||
| Manager+ | 25 – 2550 | 25 up to 250, then 100 |
|
||||
|
||||
// Sort Options
|
||||
qry_sort_order:
|
||||
- 'name_asc' / 'name_desc'
|
||||
- 'updated_desc' / 'updated_asc'
|
||||
- 'print_count_desc'
|
||||
- 'print_first_desc' / 'print_last_desc'
|
||||
- 'badge_type_asc'
|
||||
- 'affiliations_asc'
|
||||
```
|
||||
### Badge Type Filter — Known Limitation
|
||||
|
||||
### QR Scan Search
|
||||
- Scans badge QR code
|
||||
- Extracts badge ID
|
||||
- Auto-fills search with ID
|
||||
- Jumps to badge detail view
|
||||
|
||||
### Search Implementation Pattern
|
||||
**File:** `badges/+page.svelte` (Lines 117-365)
|
||||
|
||||
**Strategy:** Standardized Reactive Search Pattern (Aether UI V3)
|
||||
1. **Isolate dependencies** into stable `$derived` object
|
||||
2. **Debounced effect** (300ms) triggers search
|
||||
3. **Fast Path:** Search IDB first (if not `remote_first`)
|
||||
4. **Revalidate:** API request updates IDB
|
||||
5. **LiveQuery:** UI auto-updates from IDB changes
|
||||
|
||||
**Search API:** `events_func.search__event_badge()`
|
||||
```typescript
|
||||
await search__event_badge({
|
||||
api_cfg: $ae_api,
|
||||
event_id: event_id,
|
||||
fulltext_search_qry_str: qry_str || null,
|
||||
type_code: type_code || null,
|
||||
printed_status: printed_status,
|
||||
affiliations_qry_str: aff_str || null,
|
||||
order_by_li: order_by_li,
|
||||
limit: 150,
|
||||
log_lvl: 0
|
||||
})
|
||||
```
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Badge Display Logic
|
||||
## Print Rendering and Tracking
|
||||
|
||||
### Name Display Priority
|
||||
```typescript
|
||||
// Component: ae_comp__badge_obj_li.svelte (Lines 113-121)
|
||||
if (event_badge_obj?.full_name_override)
|
||||
display: full_name_override
|
||||
else if (event_badge_obj?.full_name)
|
||||
display: full_name
|
||||
else
|
||||
display: given_name + ' ' + family_name
|
||||
```
|
||||
- The canonical badge render uses binary-search text fitting for name, title, affiliations, and location.
|
||||
- Template `show_qr_front`/`show_qr_back` settings control QR placement.
|
||||
- Template `style_href` loads event-specific CSS on the print page.
|
||||
- Template `duplex = false` suppresses the badge back for single-sided stock.
|
||||
- Chromium PDF proofing requires margins set to None; physical printer paper size remains driver-controlled.
|
||||
|
||||
### Badge View Page
|
||||
**Route:** `/events/[event_id]/badges/[badge_id]`
|
||||
Aether tracks the lifecycle of every physical badge to prevent unauthorized reprints and monitor kiosk activity.
|
||||
|
||||
**Components:**
|
||||
- `+page.svelte` — Container with LiveQuery for badge data
|
||||
- `ae_comp__badge_obj_view.svelte` — Full badge display + edit UI
|
||||
| Field | Purpose |
|
||||
|---|---|
|
||||
| `print_count` | Increments on every "Print Badge" action. |
|
||||
| `print_first_datetime` | Timestamp of the very first print. |
|
||||
| `print_last_datetime` | Timestamp of the most recent print. |
|
||||
|
||||
**LiveQueries:**
|
||||
```typescript
|
||||
lq__event_badge_obj = liveQuery(() => db_events.badge.get(event_badge_id))
|
||||
lq__event_badge_template_obj = liveQuery(() =>
|
||||
db_events.badge_template.get(badge.event_badge_template_id)
|
||||
)
|
||||
```
|
||||
|
||||
**Loading States:**
|
||||
- `is_loading_idb` — Waiting for initial IDB lookup
|
||||
- If badge not found → "Badge Not Found" error with reload button
|
||||
- Loader spinner while fetching
|
||||
> **Operational Note:** Reprints triggered via the Edit Mode shortcut do not increment the count; only the formal "Print Badge" workflow does.
|
||||
|
||||
---
|
||||
|
||||
## Badge Templates
|
||||
|
||||
### Purpose
|
||||
Badge templates define the visual layout and content structure for printed badges:
|
||||
- Header images/logos
|
||||
- Field positions and font sizes
|
||||
- QR code placement
|
||||
- Ticket/option indicator display
|
||||
- WiFi credentials display
|
||||
|
||||
### Template Selection
|
||||
Each badge references an `event_badge_template_id`. The template controls:
|
||||
- Layout (front/back)
|
||||
- Branding elements
|
||||
- Which fields to show
|
||||
- Field formatting rules
|
||||
|
||||
### Template Loading
|
||||
Templates are loaded alongside badges via `inc_template` parameter:
|
||||
```typescript
|
||||
load_ae_obj_id__event_badge({
|
||||
event_badge_id: badge_id,
|
||||
inc_template: true // Also loads template
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
```
|
||||
## Route Map (Badges)
|
||||
|
||||
| URL | Purpose |
|
||||
|---|---|
|
||||
| `/events/[id]/badges` | Main search and attendee list. |
|
||||
| `/events/[id]/badges/templates` | Badge template management. |
|
||||
| `/events/[id]/badges/[id]/print` | The actual print-ready render page. |
|
||||
| `/events/[id]/badges/[id]/review` | Attendee-facing self-service form. |
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
- [AE API V3 for Frontend](./GUIDE__AE_API_V3_for_Frontend.md)
|
||||
- [Development Guide](./GUIDE__Development.md)
|
||||
- [Events Launcher Native Integration](./PROJECT__AE_Events_Launcher_Native_integration.md)
|
||||
- [Naming Conventions](./AE__Naming_Conventions.md)
|
||||
|
||||
---
|
||||
|
||||
**Document Status:** 🔄 In Progress
|
||||
**Last Verified:** 2026-02-27 (rev 5 — field permissions spec added, header buttons implemented, review form fields pending)
|
||||
**Verified Against:** Code as of 2026-02-27 (branch ae_app_3x_llm)
|
||||
👉 **[MODULE__AE_Events_Badge_Templates.md](./MODULE__AE_Events_Badge_Templates.md)** (Technical reference for layouts)
|
||||
👉 **[GUIDE__AE_Events_Badges_Onsite.md](./GUIDE__AE_Events_Badges_Onsite.md)** (Hardware & station setup)
|
||||
👉 **[GUIDE__AE_Events_Onsite_Runbook.md](./GUIDE__AE_Events_Onsite_Runbook.md)** (Onsite operational checklists)
|
||||
|
||||
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
|
||||
> **Last Updated:** 2026-05-11
|
||||
> **Status:** Operational / Permanent Reference
|
||||
> **Last Updated:** 2026-05-21 (Reorganized)
|
||||
> **Primary Platform:** macOS (Darwin)
|
||||
> **Fallback Platform:** Linux / Windows
|
||||
|
||||
@@ -72,10 +72,15 @@ Files are stored persistently using their SHA-256 hash to prevent filename colli
|
||||
- **Filename:** `{hash}.file`
|
||||
|
||||
### 4.2 Background Sync (File Warming)
|
||||
When a user navigates to a session in the Launcher UI, the `LauncherBackgroundSync` component:
|
||||
1. Extracts all `event_file_id` values for that session.
|
||||
2. Checks the native cache via `aetherNative.check_cache`.
|
||||
3. Triggers background downloads for missing files via `aetherNative.download_to_cache`.
|
||||
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. **Metadata Fetch:** The system fetches all sessions, presentations, and presenters for the current location into the local database (Dexie).
|
||||
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)
|
||||
When a user clicks "Open", the system follows a non-destructive sequence:
|
||||
@@ -1,6 +1,7 @@
|
||||
# Aether Events — Exhibitor Leads Module (v3)
|
||||
|
||||
**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.
|
||||
**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
|
||||
|
||||
**Last Updated:** 2026-06-12
|
||||
|
||||
This document tracks all available settings across the three levels of the Journals module.
|
||||
|
||||
## 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) |
|
||||
| `archive_on` | datetime | Scheduled date for automatic archiving. | 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) |
|
||||
| `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
|
||||
To prevent infinite reactivity loops and trivial save cycles, the following normalizations are applied before comparison:
|
||||
1. **Strings:** Trimmed and `null` treated as `""`.
|
||||
@@ -1,8 +1,9 @@
|
||||
# Project: Pres Mgmt Config Cleanup & Config UI
|
||||
|
||||
**Status:** Planning / Ready to Execute
|
||||
**Priority:** High (BGH conference in ~2 weeks; only one active event using pres_mgmt)
|
||||
**Created:** 2026-04-02
|
||||
**Status:** 🟡 ~70% Complete — Core Working, Cleanup Pass Needed
|
||||
**Priority:** Medium (core features functional; remaining work is deprecation + consistency)
|
||||
**Created:** 2026-04-02
|
||||
**Last Updated:** 2026-06-12 (regression audit)
|
||||
**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`)
|
||||
- Dead config (`HOLD__*` prefix)
|
||||
- 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)
|
||||
- `hide_launcher_link` / `hide_launcher_link_legacy` missing the `__` separator (inconsistent)
|
||||
- `show_content__presentation_description` uses a third naming convention
|
||||
@@ -135,7 +136,7 @@ interface PressMgmtRemoteCfg {
|
||||
|
||||
## 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.
|
||||
|
||||
**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
|
||||
|
||||
**Route:** `/events/[event_id]/(pres_mgmt)/pres_mgmt/config/`
|
||||
**Access:** `$ae_loc.manager_access` only
|
||||
**Button visibility:** Edit mode only (`$ae_loc.edit_mode`)
|
||||
**Route:** `/events/[event_id]/(pres_mgmt)/pres_mgmt/config/`
|
||||
**Access:** `$ae_loc.manager_access` only
|
||||
**Button visibility:** Edit mode only (`$ae_loc.edit_mode`)
|
||||
|
||||
### Page behavior
|
||||
- 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
|
||||
|
||||
- [ ] **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`
|
||||
- [ ] **Step 3** — Rewrite `sync_config__event_pres_mgmt()` in `ae_events__event.ts` to use canonical keys and write to the new store
|
||||
- [ ] **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 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
|
||||
- [x] **Step 1** — Define `PressMgmtRemoteCfg` TypeScript interface (new file or in `ae_events__event.ts`)
|
||||
- [x] **Step 2** — New `ae_events_stores__pres_mgmt.svelte.ts` with `PersistedState` ⚠️ **Missing:** version gate in `store_versions.ts`
|
||||
- [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
|
||||
- [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) **BLOCKING**
|
||||
- [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 (blocked on Step 5)
|
||||
- [ ] **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)
|
||||
|
||||
The `$events_loc.pres_mgmt` pattern appears across:
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
# PROJECT: Site Passcode Security — API-Verified Auth
|
||||
|
||||
**Last updated:** 2026-04-10
|
||||
**Status:** Backend work in progress — frontend pending backend completion
|
||||
**Priority:** High — passcodes for trusted/administrator access currently in localStorage plaintext
|
||||
**Last Updated:** 2026-06-12
|
||||
**Last Verified Against Frontend Source:** 2026-06-12
|
||||
**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
|
||||
|
||||
**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
|
||||
|
||||
@@ -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
|
||||
|
||||
**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
|
||||
|
||||
**Status:** Execution / Phase B (In Progress)
|
||||
**Priority:** High (post-April 2026 conference)
|
||||
**Status:** Events module — COMPLETE. Core / IDAA — In Progress (field cleanup done, PersistedState pending).
|
||||
**Priority:** High
|
||||
**Created:** 2026-03-30
|
||||
**Related:** `TODO__Agents.md` — [Stores] Svelte 5 State Migration entry
|
||||
**Last Updated:** 2026-06-11
|
||||
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
All core Aether stores (`ae_loc`, `idaa_loc`, `ae_events_loc`, etc.) are being migrated from
|
||||
Svelte 4 stores to Svelte 5 `$state` using the `runed` library's `PersistedState`. This provides
|
||||
fine-grained reactivity, ensuring that effects only re-run when specific fields they access are
|
||||
updated, rather than on every write to the store object.
|
||||
All core Aether stores were built with `svelte-persisted-store` (Svelte 4 contract). This provides
|
||||
coarse reactivity: any write to any field notifies *all* subscribers and re-serializes the entire
|
||||
object. For large stores like `ae_loc` and `ae_events_loc`, this caused unnecessary re-renders and
|
||||
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)
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
// BEFORE (ae_stores.svelte.ts)
|
||||
export const ae_loc: Writable<key_val> = persisted('ae_loc', defaults);
|
||||
| Store | File | localStorage key | Status |
|
||||
|---|---|---|---|
|
||||
| `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)
|
||||
export const ae_loc_v5 = new PersistedState('ae_loc', defaults);
|
||||
```
|
||||
`ae_events_stores.ts` now only exports `events_sess` (in-memory writable, no migration needed),
|
||||
`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)
|
||||
|
||||
```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 });
|
||||
```
|
||||
`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.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
## In Progress / Remaining: `ae_stores.ts` and `ae_idaa_stores.ts`
|
||||
|
||||
### Prettier & Formatting
|
||||
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.
|
||||
Both stores had their unused default properties pruned (2026-06-11), reducing migration scope:
|
||||
|
||||
### Batching vs. "Big Sweep"
|
||||
While the original plan suggested smaller batches, the global nature of `ae_loc` and the
|
||||
hundreds of interdependent files made a "Big Sweep" more efficient to ensure the app remains
|
||||
in a consistent state. However, verification should still be done module-by-module.
|
||||
**`ae_loc`** (in `ae_stores.ts`) — still `persisted('ae_loc', ...)`:
|
||||
- Remaining fields: auth/identity, theme, permissions, ui config, file upload tracking, query prefs
|
||||
- This is the highest-impact remaining migration — used in nearly every route
|
||||
- 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.
|
||||
- [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).
|
||||
Each sub-store follows this pattern, using the `badges` store as the canonical reference:
|
||||
|
||||
**Do NOT stage or commit until `npx svelte-check` is fully verified.**
|
||||
The app currently has a high error count due to the transition period where imports are being
|
||||
re-aligned. Final verification is the next step after the pause.
|
||||
### 1. Defaults file (`*_defaults.ts`)
|
||||
Define the shape and defaults as a plain TypeScript object (and interface if complex):
|
||||
|
||||
```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
|
||||
> 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,426 +1,197 @@
|
||||
# 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.
|
||||
> **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
|
||||
with the profiles used to open/launch presentation files. Architecture decision: move launch
|
||||
logic to the Svelte side so it can be changed without an Electron rebuild. Electron becomes a
|
||||
thin OS primitive layer; all business logic lives in Svelte and device config.
|
||||
|
||||
Why this matters: the profile map is policy, while the native template is the exact runtime
|
||||
command. Keeping those separate avoids a second hidden source of truth and keeps Electron from
|
||||
guessing defaults.
|
||||
|
||||
**Electron groundwork (2026-05-11) — DONE:**
|
||||
- [x] `run_osascript` hardened — temp `.scpt` file approach; handles multi-line + special chars
|
||||
- [x] `native:copy-from-cache-to-temp` primitive added — copy to tmp, caller decides launch
|
||||
- [x] `native:launch-from-cache` executes a provided `native_template` string — AppleScript or `shell:` prefix; no Electron-side fallback
|
||||
- [x] `get_launch_profile()` in `launcher_file_cont.svelte` reads from device config then event config; resolves to a `native_template` string and passes it to `launch_from_cache`
|
||||
- [x] Built-in Launcher defaults refactored into canonical profile names plus extension aliases (`ae_launcher__default_launch_profiles.ts`)
|
||||
- [x] Device-level Launch Timing section added under Launcher Configuration → Device, with per-profile `launch_profiles[profile].post_delay_ms` overrides
|
||||
- [x] **URL file launch support (2026-05-13)** — `event_file.extension = 'URL'` (or filename starting with `http://`/`https://`) is treated as a non-downloaded URL. Background sync skips URL files so they are never treated as cacheable hosted files. Shared `AE_Comp_Hosted_Files_Download_Button` now hard-bypasses the download path for URL records. In native mode, `handle_open_file()` routes to `native.open_external({ url, app: 'chrome' })` with `'default'` fallback. Health section crash fixed (guarded `native_device` against undefined in preview/edit mode).
|
||||
- [x] **[Launcher/Electron] Display mirroring auto-detection (2026-05-20)** — `native:set-display-layout` rewrote to auto-detect displays via `displayplacer list` when no `configStr` provided. Old code silently returned `{ success: false }` (swallowed by `.catch(() => {})` in relay). Now parses `displayplacer list` output, extracts quoted display strings, builds correct mirror/extend commands. Manual `configStr` still takes priority when provided.
|
||||
|
||||
**Svelte-side migration — remaining before May 26:**
|
||||
- [x] **[Launcher] Built-in Svelte default profiles (2026-05-11)** — canonical profile constants live in `ae_launcher__default_launch_profiles.ts` with extension aliases and a `resolve_launch_profile()` 3-step fallback (device config → event config → built-in defaults). Covers macOS (`pptx`, `ppt`, `key`, `odp`, `pdf`), media (`mp4`, `mkv`, `mp3`, etc.), Windows/Parallels variants, and URL path.
|
||||
- [ ] **[Launcher] Composable open flow** — refactor `handle_open_file()` to use
|
||||
`copy_from_cache_to_temp` + `run_osascript` / `run_cmd` directly instead of the all-in-one
|
||||
`launch_from_cache`. Finer error handling at each step (verify copy succeeded before
|
||||
attempting script; surface failure clearly in UI).
|
||||
- [x] **[Launcher] Error handling + fallback (2026-05-14)** — post-script failure is non-fatal: surfaces `'fallback'` status with error detail (file already open). If `open_cmd` itself fails, falls back to `open_local_file_v2` (OS default); surfaces combined error detail if that also fails. UI renders `'open'` / `'fallback'` / `'error'` states distinctly.
|
||||
- [ ] **[Launcher] Slide control scripts in Svelte config** — `control_presentation` AppleScript
|
||||
one-liners are hardcoded in Electron (`shell_handlers.ts` lines 149–159). The Svelte side
|
||||
(`launcher_cfg_native_os.svelte`) already calls `native.control_presentation({ app, action })`
|
||||
with next/prev/start/stop buttons wired and working. The Electron IPC call is stable enough
|
||||
for current shows.
|
||||
|
||||
**Remaining (post June 10):** Move scripts to device config (`data_json.control_scripts`) or
|
||||
Svelte constants so behavior can be changed without an Electron rebuild. Replace
|
||||
`native.control_presentation()` call with `native.run_osascript(script)` using the
|
||||
config-resolved script string. The `run_osascript` primitive is already in place; this is
|
||||
purely a wiring/config task. Low priority — hardcoded scripts work fine for PowerPoint and
|
||||
Keynote for now.
|
||||
- [ ] **[Launcher] `kill_processes` target list in config** — The `kill_processes` IPC primitive
|
||||
exists in Electron and is exposed via `electron_relay.ts`, but **is never called from anywhere
|
||||
in Svelte** — no routes, no config components, no file launch flow. No UI exists for it yet.
|
||||
|
||||
**What needs doing (post June 10):**
|
||||
- Decide where the "kill on cleanup" action lives: end of `handle_open_file()` (auto), or a
|
||||
manual "Kill Apps" button in Native OS config, or both.
|
||||
- Process names should come from device config (`data_json.kill_process_li`) with a built-in
|
||||
fallback list (e.g. `['Microsoft PowerPoint', 'Keynote', 'LibreOffice Impress']`).
|
||||
- The Native OS config section (`launcher_cfg_native_os.svelte`) is the natural home for a
|
||||
manual kill button; auto-cleanup on file open would go in `launcher_file_cont.svelte`.
|
||||
|
||||
**Device tab length note:** The Device tab now has 6 collapsible sections (Sync Timers, Health,
|
||||
Native OS, Wallpaper, Launch Timing, Updates). Consider moving Launch Timing + a future Kill
|
||||
Apps control into a dedicated **"Presentation"** section or a fourth tab to keep Device focused
|
||||
on machine/OS concerns rather than per-app launch behavior.
|
||||
- [x] **[Launcher] Launcher config UI — launch_profiles editor (2026-05-14)** — Launch Timing section in `launcher_cfg_launch_timing.svelte` exposes per-profile `post_delay_ms` overrides with Save/Reset per row. PATCHes `event_device.other_json.launcher.launch_profiles` via V3 CRUD. Wired into `launcher_cfg.svelte`. Shown under Technical Mode (`$ae_loc.edit_mode`).
|
||||
- [ ] **[Launcher] End-to-end test on macOS** — test pptx and key opens on a real podium Mac
|
||||
before May 26 setup day. Verify: file copies to tmp correctly, script fires, app opens in
|
||||
slideshow mode, error fallback works.
|
||||
- [ ] **[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] `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.
|
||||
- [ ] **[Launcher/Electron] Wallpaper reliability (post-CMSC)**
|
||||
- [ ] Use timestamp/randomized temp filename so macOS always sees a new path.
|
||||
- [ ] Add resilient reconciliation loop or event-driven reapply on display topology changes.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 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.
|
||||
- [x] **[Badges] Epson C3500 fanfold badge layout** — `badge_4x6_fanfold` layout CSS created,
|
||||
wired, and documented. First live use: Axonius Adapt DC, June 9, 2026. (2026-05-15)
|
||||
|
||||
### 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)
|
||||
|
||||
All known root causes fixed across 10+ commits to `src/routes/idaa/(idaa)/+layout.svelte`.
|
||||
Deploying as of 2026-05-19. Monitor for further member reports.
|
||||
|
||||
#### All fixes applied
|
||||
- [x] **Server-side Novi verification migrated (key fix for hotel/VPN/Cloudflare-filtered networks)**
|
||||
`verify_novi_uuid()` now calls `GET /v3/action/idaa/novi_member/{uuid}` through the Aether
|
||||
backend (server-to-server, Redis-cached 4h) instead of making a browser-to-Novi call.
|
||||
Eliminates false Access Denied for members on hotel/conference WiFi, VPNs, and corporate
|
||||
networks where the member's IP was rejected by Novi/Cloudflare.
|
||||
Frontend: `(idaa)/+layout.svelte` | Backend: FastAPI `aether_api_fastapi` | Docs: `GUIDE__AE_API_V3_for_Frontend.md` §12, `CLIENT__IDAA_and_customized_mods.md`
|
||||
- [x] Novi TTL extended to **12 hours** (5 min → 45 min → 12 h) — covers a full conference day
|
||||
- [x] Access Denied on iframe reload (sessionStorage URL preservation) — `2855e091f`
|
||||
- [x] TTL cache bypassed when `$ae_loc` auth flags reset — `2855e091f`
|
||||
- [x] "Verification Unavailable" screen distinct from "Access Denied" — `2855e091f`
|
||||
- [x] "Try Again" without page reload (`retry_count` pattern) — `2855e091f`
|
||||
- [x] 12 s AbortController hard timeout on Novi fetch — `e921ca973`
|
||||
- [x] Network/AbortError gets 3 s grace + one retry — `e921ca973`
|
||||
- [x] Clear Cache & Reload added to Access Denied state (iframe mode) — `2855e091f`
|
||||
- [x] `VERIFY_TIMEOUT_MS` 8 s → 35 s (was firing mid-retry, causing premature Reset clicks) — `53fd5e7de`
|
||||
- [x] `sessionStorage` try-catch (iOS Safari Private Browsing throws on access) — `53fd5e7de`
|
||||
- [x] Appshell stores guarded behind `account_id` — `8850db89c`
|
||||
- [x] Recovery meetings over-filtering bug (API `default_qry_str`) — `76e21b08f`
|
||||
- [x] A→Z sort in recovery meetings API revalidation path — `c0386f27b`
|
||||
- [x] `events.event` IDB content version bump (stale cache) — previous commit
|
||||
|
||||
#### Root layout SWR verified safe:
|
||||
The root `+layout.ts` builds `ae_loc_init` as a plain site-config object (no `authenticated_access`,
|
||||
`trusted_access`, or `access_type` fields). The root layout sync effect
|
||||
`$ae_loc = { ...current_loc, ...ae_acct.loc }` therefore cannot overwrite Novi-set auth flags.
|
||||
Confirmed safe — this is NOT a cause of Access Denied.
|
||||
|
||||
#### Remaining architectural note:
|
||||
The long-term fix for the coarse `$ae_loc` reactivity (Svelte 4 store) causing Effect 2 to
|
||||
re-run on unrelated writes is tracked under **[Stores] Svelte 4 → Svelte 5 State Migration**
|
||||
below. The TTL + `verify_in_flight` guards are the current mitigation.
|
||||
- [ ] **[Core] Legacy Utility Helpers** — Refactor `ae_core_functions.ts` to use V3 helpers.
|
||||
- [ ] **[Cleanup] Delete Legacy Wrappers** — Once all callsites are migrated, remove
|
||||
`src/lib/ae_api/api_get__crud_obj_id.ts` and the legacy exports from `api.ts`.
|
||||
|
||||
---
|
||||
|
||||
### ~~[IDAA] Server-side Novi verification — 503 not auto-retried~~ ✅ Fixed (2026-05-20)
|
||||
## 🚧 High Priority Workstreams
|
||||
|
||||
---
|
||||
### [Security] Site Passcode JWT Migration
|
||||
|
||||
### [Launcher/VLC] Linux playback — fullscreen + pause-on-end not working
|
||||
**Status:** Mac ✅ working perfectly; Linux 🚧 deferred for later investigation
|
||||
**Date discovered:** 2026-05-20
|
||||
- [ ] **[Security] Verify `/authenticate_passcode` deployment** — confirm explicit role priority,
|
||||
complete role flags, `auth_type: 'passcode'`, per-role TTLs, and minimum length validation.
|
||||
- [ ] **[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:
|
||||
- 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)
|
||||
Reference: `documentation/PROJECT__AE_Site_Passcode_Security.md`.
|
||||
|
||||
Linux VLC command (`vlc --no-play-and-exit --play-and-pause "{{path}}"`) currently:
|
||||
- Does NOT go fullscreen
|
||||
- Does NOT pause on the last frame (plays through, returns to playlist)
|
||||
### [Stores] Svelte 4 → Svelte 5 State Migration
|
||||
The app uses `svelte-persisted-store` (coarse reactivity). Migration target: replace with Svelte 5
|
||||
`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`.
|
||||
macOS is the primary venue deployment platform; Linux support is nice-to-have.
|
||||
- [x] **Events module — COMPLETE (2026-06-11):** `events_loc` fully retired. All 5 sub-stores
|
||||
(`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,
|
||||
or if there's a launcher execution layer issue (e.g. `shell:` prefix handling).
|
||||
File: `src/lib/ae_events/ae_launcher__default_launch_profiles.ts` — `make_vlc_mirror_linux_profile()`.
|
||||
### [Data Layer] IDB sorting + content version rollout
|
||||
Sorting baseline is now `build_tmp_sort` (ASC chain, no `.reverse()` on tmp-sort lists).
|
||||
|
||||
---
|
||||
**⚠️ 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)
|
||||
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] 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.
|
||||
- [ ] **[IDB Sort] Migrate `ae_events__event.ts` to `build_tmp_sort`** — requires bumping
|
||||
`IDB_CONTENT_VERSIONS.events.event` (currently v3) and switching all event sort comparators
|
||||
to ascending. Check all pages that sort events before doing this.
|
||||
- [ ] **[IDB Sort] Roll out to `ae_events__event_session`** after sort behavior review.
|
||||
- [ ] **[IDB Sort] Roll out to `ae_events__event_presenter`** after sort behavior review.
|
||||
- [ ] **[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.
|
||||
- [ ] **[IDB Version] Roll out to `db_events.ts`** (session, presenter, badge, etc.).
|
||||
- [ ] **[IDB Version] Roll out to `db_core.ts`** (site_domain, person, user).
|
||||
|
||||
### [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)**
|
||||
Full audit confirmed all protection layers are in place. No code changes required.
|
||||
- All `+page.ts` / `+layout.ts` under `src/routes/idaa/` are clean — no SWR loads run before auth resolves.
|
||||
- All `$effect` SWR calls in IDAA `+page.svelte` files are gated on `$idaa_loc.novi_verified || $ae_loc.trusted_access`.
|
||||
- `(idaa)/+layout.svelte` purges `db_posts`, `db_archives`, `db_events` on auth failure, no-UUID/no-session, and inconsistent state.
|
||||
- `sign_out()` calls `indexedDB.deleteDatabase()` on all IDAA databases.
|
||||
- API 401/403 responses fail-fast in `api_get_object.ts` (throw before any IDB write).
|
||||
- `idaa_trig` is in-memory `writable()` only — cannot carry stale trigger state across sessions.
|
||||
- `$effect` auth guards in IDAA page components are reactivity guards (prevent spurious SWR calls on coarse `$ae_loc` writes), NOT auth-bypass guards. SvelteKit layout hierarchy already prevents child components from mounting when `(idaa)/+layout.svelte` blocks rendering.
|
||||
- Doc: SvelteKit layout hierarchy security model captured in `GUIDE__SvelteKit2_Svelte5_DexieJS.md` and `BOOTSTRAP__AI_Agent_Quickstart.md` (Mistake #7).
|
||||
|
||||
- [ ] **[IDAA] IDB fast-path contact search — Recovery Meetings (2026-04-08, updated 2026-05-19)**
|
||||
**API path is now working** — `default_qry_str` already includes contact name/email.
|
||||
Two bugs were fixed 2026-05-19: (1) STORED GENERATED columns had stale values; forced
|
||||
rebuild via fake updates. (2) Frontend secondary filter was re-checking text against
|
||||
response fields, silently dropping API results that matched only via `default_qry_str`.
|
||||
|
||||
**Remaining gap — IDB fast-path only:** The local cache fast-path returns all cached meetings
|
||||
without text filtering; users see the unfiltered list first, then the API-filtered result
|
||||
replaces it. To make contact matches appear instantly from cache:
|
||||
- `src/lib/ae_events/ae_events__event.ts` → fast-path filter in `search__event()`: parse
|
||||
`contact_li_json` and include contact names/emails in the local text match.
|
||||
- `src/routes/idaa/(idaa)/recovery_meetings/+page.svelte` fast-path display: same filter.
|
||||
Backend enhancement (`contact_li_json_ext` whitelist) is not required for this — the IDB
|
||||
records already store `contact_li_json` raw JSON which can be parsed client-side.
|
||||
## 🧪 Testing & Optimization
|
||||
|
||||
- [ ] **[IDAA] IDB fast-path contact search** — parse `contact_li_json` in `search__event()`.
|
||||
- [ ] **[IDAA] Optimize Recovery Meetings SQL VIEW and indexes.**
|
||||
The current search query can be taxing on the server. With ~150 active meetings, the view
|
||||
logic and supporting indexes need a performance review to ensure fast responses as the
|
||||
database grows. (Requested 2026-05-18)
|
||||
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage** in all other event search pages.
|
||||
- [ ] **[Launcher/VLC] Linux playback investigation** — fullscreen + pause-on-end flags.
|
||||
|
||||
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage in other event search pages.**
|
||||
The backend was updated 2026-03-31 to expose `default_qry_str` in API responses.
|
||||
Frontend fix applied to Recovery Meetings (`+page.svelte` + `properties_to_save`).
|
||||
Check all other event search pages that use `db_events.event.filter()` or a secondary
|
||||
post-API text filter — they may have the same mismatch (local searches `name`/`description`
|
||||
only while server uses `default_qry_str`). Start with: any route under `/events/` or `/idaa/`
|
||||
that has a full-text search input.
|
||||
---
|
||||
|
||||
### [IDAA] Jitsi config editor + live site fix
|
||||
- [ ] **Fix live site (id=17) `jitsi_token_endpoint` pointing to dev-api:** DB has
|
||||
`https://dev-api.oneskyit.com/api/jitsi_token` for both site 10 and site 17 (IDAA live).
|
||||
Need to update site 17 in **production** to `https://api.oneskyit.com/api/jitsi_token`.
|
||||
SQL: `UPDATE site SET cfg_json = JSON_SET(cfg_json, '$.jitsi_token_endpoint', 'https://api.oneskyit.com/api/jitsi_token') WHERE id = 17;`
|
||||
## ⚙️ DevOps & Backend
|
||||
|
||||
- [ ] **Add IDAA Jitsi config editor UI** to the jitsi_reports page (administrator_access only),
|
||||
alongside the existing Jitsi URL Builder section. Should allow editing key fields in
|
||||
`site_cfg_json` without needing phpMyAdmin:
|
||||
- `jitsi_token_endpoint` — the JWT signing endpoint (needs to point to prod)
|
||||
- Jitsi domain default (currently hardcoded as `jitsi.dgrzone.com` fallback in the page)
|
||||
- `novi_jitsi_mod_li` — list of Novi UUIDs who get moderator privileges
|
||||
Read from `$ae_loc.site_cfg_json`, PATCH the site record via V3 CRUD
|
||||
(`PATCH /v3/crud/site/{id}/`), reload `$ae_loc.site_cfg_json` on save so it takes
|
||||
effect without re-login.
|
||||
- [ ] **[Cleanup] Remove unused legacy API wrappers** — `create_ae_obj_crud()`,
|
||||
`get_ae_obj_id_crud()`, and `update_ae_obj_id_crud()` are still exported from `api.ts` but
|
||||
no longer called anywhere in production code. V3 migration is 100% complete. Safe to delete:
|
||||
definitions in `api.ts` (lines 109-260), `src/lib/ae_api/api_get__crud_obj_id.ts`, unused
|
||||
wrapper in `ae_core_functions.ts` (`get_site_domain_obj_from_fqdn`, `update_ae_obj_id_crud`).
|
||||
- [ ] **[Backend] `event_file` — add `cfg_json` column (post-CMSC)** — The per-file display
|
||||
override currently uses a localStorage workaround (`launcher_loc.current.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.**
|
||||
- [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)
|
||||
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)
|
||||
[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)
|
||||
- `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).
|
||||
- `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)
|
||||
- [ ] 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:
|
||||
- `aether_container_env/Dockerfile.buildkit.example`
|
||||
- `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
|
||||
**Last Updated:** 2026-03-18
|
||||
**Completed and Archived:** 2026-06-12
|
||||
**Last Verified Against Source:** 2026-06-12
|
||||
**Branch:** `ae_app_3x_llm`
|
||||
**Priority:** HIGH — first live event is Axonius, NYC, mid-April 2026
|
||||
**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`.
|
||||
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.
|
||||
It needs to be accessible to attendees at the kiosk (with appropriate field-level gating),
|
||||
matching the permission model already implemented in `ae_comp__badge_review_form.svelte`.
|
||||
**Task 4 outcome:** The print controls now implement field-level editing. Authenticated users
|
||||
can edit template-approved fields, trusted staff can correct names, and trusted staff in global
|
||||
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
|
||||
**This is the most important gap before the first live event.**
|
||||
### 0. Kiosk Editing — Complete
|
||||
|
||||
Currently the print page edit button is staff-only (trusted_access gate). At the kiosk,
|
||||
attendees need to be able to edit their own fields (same attendee-level permissions as the
|
||||
review form), with staff-only fields gated appropriately.
|
||||
`ae_comp__badge_print_controls.svelte` provides the inline controls and live preview. Its default
|
||||
authenticated fields are title, affiliations, location, lead tracking, and pronouns; template
|
||||
`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:
|
||||
- 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
|
||||
### 1. Auto-Scaling Badge Text — Complete
|
||||
`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.
|
||||
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
|
||||
|
||||
### ⏳ TASK 4.0: Kiosk Editing — NOT STARTED (updated 2026-03-18)
|
||||
Print page edit access needs to be opened to attendee-level permissions, not just trusted_access.
|
||||
The permission model, field list, and `can_edit()` helper from `ae_comp__badge_review_form.svelte`
|
||||
should be the reference. See Design Intent section above.
|
||||
### ✅ TASK 4.0: Kiosk Editing — COMPLETE (verified 2026-06-12)
|
||||
The print controls implement authenticated field editing, trusted name correction, trusted + Edit
|
||||
Mode full editing, and live preview. The print path uses template `controls_cfg`; the review path
|
||||
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 —
|
||||
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)
|
||||
|
||||
> **Status:** 🟡 Mostly Complete — Phase 3 items + GUIDE update remaining
|
||||
> **Date:** February 13, 2026 (last updated: 2026-03-20)
|
||||
> **Status:** ✅ **Completed and archived 2026-06-12**
|
||||
> **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`
|
||||
> **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] 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] 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] 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)
|
||||
- [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)
|
||||
> **Last Updated:** 2026-05-05
|
||||
> **Status:** Completed and archived 2026-06-12
|
||||
> **Last Verified Against Source:** 2026-06-12
|
||||
> **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
|
||||
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`
|
||||
* **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`
|
||||
* **Local Storage:** Dexie.js (`db_journals`)
|
||||
* **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] 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] Extract decryption workflow to non-reactive helper.
|
||||
- [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] **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).
|
||||
- [ ] Solidify E2EE passcode system for Journals and Entries.
|
||||
- [ ] Audit encryption flow for Quick Added and Imported entries.
|
||||
- [ ] Integrate Outbound Email sharing.
|
||||
- [ ] Solidify E2EE passcode system for Journals and Entries. See active task list.
|
||||
- [ ] Audit encryption flow for Quick Added and Imported entries. See active task list.
|
||||
- [ ] Integrate Outbound Email sharing. Deferred pending product confirmation.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Project: CRUD V3 Final Migration
|
||||
|
||||
> **Status:** Active / In Progress
|
||||
> **Last Updated:** 2026-01-20
|
||||
> **Status:** ✅ **Completed and archived 2026-06-12**
|
||||
> **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/...`).
|
||||
|
||||
---
|
||||
@@ -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.
|
||||
|
||||
### 🔴 High Priority: Events Module
|
||||
The entire `ae_events` library is heavily dependent on legacy `v2` list and `v1` CRUD wrappers.
|
||||
|
||||
- [ ] `src/lib/ae_events/ae_events__event_session.ts`
|
||||
- [ ] `src/lib/ae_events/ae_events__event_presenter.ts`
|
||||
- [ ] `src/lib/ae_events/ae_events__event_presentation.ts`
|
||||
- [ ] `src/lib/ae_events/ae_events__event_location.ts`
|
||||
- [ ] `src/lib/ae_events/ae_events__event_badge_template.ts`
|
||||
- [ ] `src/lib/ae_events/ae_events__event_device.ts`
|
||||
- [x] `src/lib/ae_events/ae_events__event_session.ts` (Migrated 2026-01-30)
|
||||
- [x] `src/lib/ae_events/ae_events__event_presenter.ts` (Migrated 2026-01-30)
|
||||
- [x] `src/lib/ae_events/ae_events__event_presentation.ts` (Migrated 2026-01-30)
|
||||
- [x] `src/lib/ae_events/ae_events__event_location.ts` (Migrated 2026-01-30)
|
||||
- [x] `src/lib/ae_events/ae_events__event_badge_template.ts` (Migrated 2026-01-30)
|
||||
- [x] `src/lib/ae_events/ae_events__event_device.ts` (Migrated 2026-01-30)
|
||||
- [x] `src/lib/ae_events/ae_events__exhibit.ts` (Migrated 2026-01-28)
|
||||
- [ ] `src/lib/ae_events/ae_events__event_file.ts`
|
||||
- [x] `src/lib/ae_events/ae_events__event_file.ts` (Migrated 2026-01-30)
|
||||
|
||||
### 🟠 Medium Priority: Core & Sponsorships
|
||||
Legacy patterns persisting in core logic and config modules.
|
||||
|
||||
- [ ] `src/lib/ae_sponsorships/ae_sponsorships_functions.ts`
|
||||
- [ ] `src/lib/ae_core/core__hosted_files.ts` (Uses `get_ae_obj_id_crud`)
|
||||
- [ ] `src/lib/ae_core/core__site.ts` (Uses `get_ae_obj_id_crud`)
|
||||
- [x] `src/lib/ae_core/core__hosted_files.ts` (Migrated 2026-01-20)
|
||||
- [x] `src/lib/ae_core/core__site.ts` (Migrated 2026-01-26; 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__time_zones.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`)
|
||||
- [x] `src/lib/elements/element_data_store_v2.svelte`
|
||||
- [ ] `src/routes/events/[event_id]/event_page_menu.svelte`
|
||||
- [ ] `src/routes/events/[event_id]/(pres_mgmt)/session/ae_comp__event_session_alert.svelte`
|
||||
- [x] `src/routes/events/[event_id]/(pres_mgmt)/session/ae_comp__event_session_alert.svelte` (Migrated to `update_ae_obj`)
|
||||
- [ ] `src/routes/events/ae_comp__event_session_obj_li.svelte`
|
||||
- [ ] `src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_id_edit.svelte`
|
||||
- [ ] `src/routes/events/[event_id]/(pres_mgmt)/presenter/[presenter_id]/ae_comp__event_presenter_form_agree.svelte` (STILL USES `update_ae_obj_id_crud`)
|
||||
|
||||
---
|
||||
|
||||
@@ -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 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] **[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] **[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)
|
||||
|
||||
@@ -1,326 +1,54 @@
|
||||
# Frontend Agent Task List
|
||||
> Use this file to track steps for complex features or bug fixes.
|
||||
> **Status:** Stable — ongoing development.
|
||||
# Frontend Agent Task List (Archived May 2026)
|
||||
|
||||
## ✅ Completed (2026-05)
|
||||
|
||||
## 🔴 BGH Conference — April 21 (Must Fix Before Event)
|
||||
### [API] GET/POST retry hardening — differentiate timeout aborts vs intentional aborts
|
||||
**Status:** ✅ Completed (2026-05-21)
|
||||
- GET/POST now explicitly distinguish abort class in helper code.
|
||||
- Timeout-triggered aborts are retryable via existing retry loop; intentional aborts fail fast.
|
||||
- Backoff behavior retained (`2s -> 4s -> 6s -> 8s`).
|
||||
- Validation done via Playwright tests.
|
||||
|
||||
- [x] **[Locations] Event Locations list does not auto-load** — added `+page.ts` to trigger
|
||||
`load_ae_obj_li__event_location` on page load. Also fixed session query using stale
|
||||
`event_location_id_random` index (should be `event_location_id`). (2026-04-19)
|
||||
### [API] PATCH/DELETE retry hardening — parity with GET/POST
|
||||
**Status:** ✅ Completed (2026-05-21)
|
||||
- PATCH and DELETE now implement the same retry-classification model used in GET/POST.
|
||||
- Added explicit fail-fast for 400/401/403/422.
|
||||
- DELETE now triggers the session-expired banner on 401/403.
|
||||
|
||||
- [x] **[Files] Warn/error on `.ppt`/`.doc` upload** — warning rows shown per-file in upload table;
|
||||
non-trusted users are fully blocked (`file_list_status = 'blocked_legacy'`); trusted users see
|
||||
warnings but can still upload. Covers `.ppt`, `.doc` (block) and other legacy exts (warn-only).
|
||||
(2026-04-19)
|
||||
### [Testing] V3 API performance probe (basic stress rounds)
|
||||
**Status:** ✅ Completed baseline harness (2026-05-21)
|
||||
- Implemented a gated Playwright probe for quick repeated list-query timing against live V3 endpoints.
|
||||
- Writes reports to `tests/results/`.
|
||||
|
||||
- [x] **[Files] Hide internal-purpose files from Launcher by default** — renamed `hide_draft` prop
|
||||
to `show_internal_purpose_files` in `launcher_file_cont.svelte`; logic flipped so `false` (the
|
||||
default) hides files with `file_purpose == 'outline'`, `'draft'`, or `'admin'`. Store key renamed
|
||||
from `hide_content__draft_files` (inverted, misleading) to `show_content__internal_files: false`
|
||||
(show-on-opt-in, consistent with all other `show_content__*` flags). Updated across all 8 Launcher
|
||||
templates that pass this prop. (2026-04-19, revised 2026-04-20)
|
||||
### [IDAA] Random "Access Denied" — Root Cause Review & Fixes
|
||||
**Status:** ✅ Resolved (2026-05-19)
|
||||
- Server-side Novi verification migrated to V3 action endpoint.
|
||||
- Extended Novi TTL to 12 hours.
|
||||
- Hardened retry and timeout logic in `+layout.svelte`.
|
||||
|
||||
- [x] **[Launcher] Remove duplicate session API call on session select** — `menu_session_list.svelte`
|
||||
was calling `load_ae_obj_id__event_session` directly AND then `goto()` triggered `+page.ts` which
|
||||
also called it — two concurrent calls per session click. Removed the direct call entirely;
|
||||
`+page.ts` is now the sole owner of session data loading. `goto()` promise assigned to
|
||||
`ae_promises.slct__event_session_id` to drive the existing `{#await}` spinner. (2026-04-20)
|
||||
### [IDAA] Server-side Novi verification — 503 not auto-retried
|
||||
**Status:** ✅ Fixed (2026-05-20)
|
||||
|
||||
- [ ] **[Electron/Launcher] Deploy + test Aether Native Electron app on Mac laptops** — build,
|
||||
deploy, and verify on onsite Mac laptops. Additional testing of cache/launch flow still needed
|
||||
before April 21.
|
||||
|
||||
- [x] **[Pres Mgmt] POC column shown in "Sessions at this Location"** — wired
|
||||
`hide__session_poc={!pres_mgmt_loc.current.show__session_li_poc_field}` in
|
||||
`ae_comp__event_location_obj_li.svelte`; also set `hide__session_location={true}` since
|
||||
location is implicit in that context. (2026-04-19)
|
||||
|
||||
---
|
||||
|
||||
## 🔴 CMSC Charlotte — May 27 (Presentation Management)
|
||||
**Drive down:** May 25 | **Setup:** May 26 morning | **Show:** May 27+
|
||||
|
||||
- [ ] **[Electron/Launcher] Clean up presentation file launch scripts** — BGH show revealed
|
||||
issues with the scripts used to open/launch presentation files through the Electron Launcher.
|
||||
Audit and improve the launch/open flow (script reliability, error handling, file path resolution).
|
||||
Must be tested and ready before May 26 setup day.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Axonius DC — June 9 (Badge Printing)
|
||||
**Setup/Registration:** June 8 | **Show:** June 9
|
||||
|
||||
- [ ] **[Badges] Epson C3500 fanfold badge layout** — Axonius is using Epson C3500 printers
|
||||
with fanfold (continuous) badge stock. Create/configure a fanfold badge layout compatible
|
||||
with the C3500 format. Must be ready before the June 8 setup/registration day.
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Upcoming High Priority
|
||||
|
||||
### [Stores] Svelte 4 → Svelte 5 State Migration (prerequisite for Phase 2c)
|
||||
The app uses `svelte-persisted-store` (Svelte 4 store contract) for all core persisted state
|
||||
(`ae_loc`, `idaa_loc`, `ae_api`, `ae_sess`, etc.). In Svelte 5 `$effect`, reading **any field**
|
||||
of a Svelte 4 store subscribes to the **entire store** — coarse-grained reactivity. This is the
|
||||
root cause of the IDAA Novi re-auth bug (2026-03-30): unrelated `$ae_loc` writes (e.g. iframe
|
||||
height, SWR cfg reload) triggered the Novi verification effect repeatedly.
|
||||
|
||||
Migration target: replace `svelte-persisted-store` with Svelte 5 `$state`-based persistence
|
||||
(e.g. `runed` `PersistedState`, or a lightweight custom wrapper). This gives fine-grained
|
||||
reactivity — only effects that actually read a changed field re-run.
|
||||
|
||||
**Phased approach (do NOT do all at once):**
|
||||
|
||||
- [ ] **Phase A — Project plan + wrapper decision:** Write `PROJECT__Stores_Svelte5_Migration.md`.
|
||||
Decide: `runed` library vs. custom `$state` + localStorage wrapper. Audit all store consumers.
|
||||
Identify stores in priority order. Estimate blast radius per store.
|
||||
|
||||
- [ ] **Phase B — Core auth stores (highest impact, start here):**
|
||||
- `ae_loc` (persisted) — auth flags, site cfg, UI state; ~471 consumer sites across 150+ files
|
||||
- `idaa_loc` (persisted) — Novi auth, IDAA query prefs
|
||||
These two cause the most reactive noise. Migrating them also unlocks Phase 2c (separate `ae_auth`
|
||||
store) since the callsite sweep is now required anyway.
|
||||
|
||||
- [ ] **Phase C — Remaining persisted stores:**
|
||||
- `ae_api` (persisted) — API config / JWT
|
||||
- `ae_events_stores` persisted entries (badges, launcher, leads, pres_mgmt loc stores)
|
||||
|
||||
- [ ] **Phase D — Non-persisted writable stores:**
|
||||
- `ae_sess`, `idaa_sess`, `slct`, `slct_trigger`, `ae_auth_error`, `ae_trig`, `ae_snip`, etc.
|
||||
- Lower urgency (no localStorage churn), but fine-grained reactivity still beneficial.
|
||||
|
||||
- [ ] **Phase E — Phase 2c (unblocked after B):** Split `ae_loc` into `ae_auth` + `ae_app`
|
||||
(see entry below — ~471 callsites, but sweep is cheap once already touching every consumer).
|
||||
|
||||
**Project plan doc needed:** Yes — scope is app-wide. Do NOT start Phase B without Phase A.
|
||||
|
||||
---
|
||||
|
||||
### [Stores] Refactor — Phase 2c (deferred)
|
||||
Phases 1, 2a, 2b are complete (see ✅ Completed below). One phase remaining:
|
||||
|
||||
- [ ] **Phase 2c — Actual separate stores (`ae_auth`, `ae_app`):** Requires touching ~471
|
||||
`$ae_loc.*` auth-field read sites across 150+ files. Deferred until a Svelte runes migration
|
||||
of the store layer itself (touching every component anyway makes the callsite sweep cheap).
|
||||
|
||||
### [Backend] Join event_location_id onto event_presenter API view
|
||||
The `event_presenter` object currently has `event_session_id` but not `event_location_id`.
|
||||
When navigating from the Presenter View to the Launcher, the frontend has to do a secondary
|
||||
session lookup to discover the location (magic redirect in launcher base `+page.svelte`).
|
||||
Joining `event_session.event_location_id` into the presenter view/response would let the
|
||||
frontend pass the location directly in the Launcher URL without the extra lookup.
|
||||
- [x] Backend: added `event_location_id` (and `event_location_id_random`) to the `event_presenter` view or API response (2026-04-09)
|
||||
- [x] Frontend: updated `ae_EventPresenter` type and `properties_to_save`; now pass as `events__launcher_id` in `presenter_page_menu.svelte` (2026-04-09)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### [TypeScript] svelte-check hidden errors — discovered 2026-03-27
|
||||
**HOW WE FOUND THIS:** The `@lucide/svelte` 0.577.0 update (2026-03-10) dropped `class` from
|
||||
`IconProps`. Fixing it required a `declare module '@lucide/svelte'` augmentation. That
|
||||
augmentation was mistakenly placed in `app.d.ts`, which is a *script-context* declaration file
|
||||
(no `export {}`). In that context, `declare module` is an **ambient replacement**, not a merge —
|
||||
it wiped all icon exports from svelte-check's view, surfacing 1368 previously hidden errors.
|
||||
Once moved to `src/lucide-augment.d.ts` (a proper module file with `export {}`), the masking
|
||||
lifted and the real pre-existing errors became visible.
|
||||
|
||||
**Lesson:** A broken ambient declaration can silently hide unrelated errors. If svelte-check
|
||||
suddenly jumps to 0 errors, verify it's not because a bad `.d.ts` replaced a package's types.
|
||||
|
||||
**Current state (2026-03-31):** 32 errors, 0 warnings — all `ModalProps.children`.
|
||||
|
||||
- [ ] **[flowbite-svelte] `ModalProps.children` — 31 errors across 26 files.** The flowbite-svelte
|
||||
`Modal` component API changed; `children` is no longer a direct prop (now Svelte snippet-based).
|
||||
Affected files span journals, pres_mgmt, events/settings, and IDAA archives.
|
||||
Run `npx svelte-check 2>&1 | grep ModalProps` to get the current list.
|
||||
Fix pattern: replace `children` prop binding with Svelte snippet syntax per flowbite-svelte docs.
|
||||
|
||||
### [Journals] Journal Entry Config follow-ups
|
||||
|
||||
- [ ] **[Journals] Visibility / audience toggle contrast** — the flag buttons need a clearer
|
||||
selected state in both light and dark mode.
|
||||
- [ ] **[Journals] Footer button style** — the actual `Done` button should read like a real button,
|
||||
not a seamless footer spacer.
|
||||
- [ ] **[Journals] Entry passcode secondary auth** — `passcode_hash` stores a hash; compare the
|
||||
entered passcode hash to the stored hash, gate entry loading, and honor the TTL-based access
|
||||
window. This is secondary entry auth, not a plain-text passcode field.
|
||||
- [ ] **[Journals] Summary AI shortcut** — add an AI summarize button next to Entry Details
|
||||
Summary so staff can generate a summary directly from the modal.
|
||||
- [ ] **[Journals] Archive On sizing** — constrain the Archive On control to a reasonable width
|
||||
instead of letting it expand to full width.
|
||||
- [ ] **[Journals] Archive On behavior** — define what Archive On actually means and wire the
|
||||
behavior; it is currently just a UI field with no live effect.
|
||||
|
||||
- [x] **[IDAA] Do not cache IDAA data in IDB when access is denied (2026-04-19, audited 2026-04-28)**
|
||||
Full audit confirmed all protection layers are in place. No code changes required.
|
||||
- All `+page.ts` / `+layout.ts` under `src/routes/idaa/` are clean — no SWR loads run before auth resolves.
|
||||
- All `$effect` SWR calls in IDAA `+page.svelte` files are gated on `$idaa_loc.novi_verified || $ae_loc.trusted_access`.
|
||||
- `(idaa)/+layout.svelte` purges `db_posts`, `db_archives`, `db_events` on auth failure, no-UUID/no-session, and inconsistent state.
|
||||
- `sign_out()` calls `indexedDB.deleteDatabase()` on all IDAA databases.
|
||||
- API 401/403 responses fail-fast in `api_get_object.ts` (throw before any IDB write).
|
||||
- `idaa_trig` is in-memory `writable()` only — cannot carry stale trigger state across sessions.
|
||||
- `$effect` auth guards in IDAA page components are reactivity guards (prevent spurious SWR calls on coarse `$ae_loc` writes), NOT auth-bypass guards. SvelteKit layout hierarchy already prevents child components from mounting when `(idaa)/+layout.svelte` blocks rendering.
|
||||
- Doc: SvelteKit layout hierarchy security model captured in `GUIDE__SvelteKit2_Svelte5_DexieJS.md` and `BOOTSTRAP__AI_Agent_Quickstart.md` (Mistake #7).
|
||||
|
||||
- [ ] **[IDAA] Make `contact_li_json_ext` searchable — Recovery Meeting contact search (2026-04-08)**
|
||||
Members cannot search for meetings by contact name or email. `contact_li_json` data is not
|
||||
included in `default_qry_str` and MariaDB cannot substring-search a JSON longtext directly.
|
||||
The `event` table already has `contact_li_json_ext` (STORED GENERATED, indexed) to work around this.
|
||||
|
||||
**Backend (blocked on this first):** Add `contact_li_json_ext` to the searchable fields
|
||||
whitelist for the `event` object type — likely a one-line change in `ae_obj_types_def.py`
|
||||
or the event object definition. Message sent to backend agent 2026-04-08.
|
||||
|
||||
**Frontend (after backend ships):**
|
||||
- `src/lib/ae_events/ae_events__event.ts` → `search__event()`: add `contact_li_json_ext`
|
||||
as an OR condition alongside `default_qry_str` when `qry_str` is present.
|
||||
- `src/routes/idaa/(idaa)/recovery_meetings/+page.svelte` fast-path IDB filter: parse
|
||||
`contact_li_json` and include contact names/emails in the local text match check.
|
||||
|
||||
- [ ] **[IDAA / Events] Audit `default_qry_str` coverage in other event search pages.**
|
||||
The backend was updated 2026-03-31 to expose `default_qry_str` in API responses.
|
||||
Frontend fix applied to Recovery Meetings (`+page.svelte` + `properties_to_save`).
|
||||
Check all other event search pages that use `db_events.event.filter()` or a secondary
|
||||
post-API text filter — they may have the same mismatch (local searches `name`/`description`
|
||||
only while server uses `default_qry_str`). Start with: any route under `/events/` or `/idaa/`
|
||||
that has a full-text search input.
|
||||
|
||||
### [IDAA] Jitsi config editor + live site fix
|
||||
- [ ] **Fix live site (id=17) `jitsi_token_endpoint` pointing to dev-api:** DB has
|
||||
`https://dev-api.oneskyit.com/api/jitsi_token` for both site 10 and site 17 (IDAA live).
|
||||
Need to update site 17 in **production** to `https://api.oneskyit.com/api/jitsi_token`.
|
||||
SQL: `UPDATE site SET cfg_json = JSON_SET(cfg_json, '$.jitsi_token_endpoint', 'https://api.oneskyit.com/api/jitsi_token') WHERE id = 17;`
|
||||
|
||||
- [ ] **Add IDAA Jitsi config editor UI** to the jitsi_reports page (administrator_access only),
|
||||
alongside the existing Jitsi URL Builder section. Should allow editing key fields in
|
||||
`site_cfg_json` without needing phpMyAdmin:
|
||||
- `jitsi_token_endpoint` — the JWT signing endpoint (needs to point to prod)
|
||||
- Jitsi domain default (currently hardcoded as `jitsi.dgrzone.com` fallback in the page)
|
||||
- `novi_jitsi_mod_li` — list of Novi UUIDs who get moderator privileges
|
||||
Read from `$ae_loc.site_cfg_json`, PATCH the site record via V3 CRUD
|
||||
(`PATCH /v3/crud/site/{id}/`), reload `$ae_loc.site_cfg_json` on save so it takes
|
||||
effect without re-login.
|
||||
|
||||
### [IDAA] Jitsi Reports still incomplete
|
||||
- [x] **Finish Jitsi Reports filters** — added Novi UUID exclusion plus meeting-name whitelist
|
||||
filtering, with room-level unique counts based on Novi UUID when present. (2026-05-06)
|
||||
### [IDAA] Jitsi Reports filters
|
||||
**Status:** ✅ Finished (2026-05-06)
|
||||
- Added Novi UUID exclusion plus meeting-name whitelist filtering.
|
||||
|
||||
### [PWA] Service worker ignoring `chrome-extension://` requests
|
||||
Browser console shows repeated errors:
|
||||
```text
|
||||
TypeError: Failed to execute 'put' on 'Cache': Request scheme 'chrome-extension' is unsupported
|
||||
```
|
||||
The service worker's fetch/install handler is trying to cache requests with `chrome-extension://`
|
||||
URLs (injected by browser extensions), which the Cache API rejects. Fix: filter out non-`http`/`https`
|
||||
requests before attempting to cache. In the service worker fetch handler, add a guard:
|
||||
```js
|
||||
if (!event.request.url.startsWith('http')) return; // skip chrome-extension:// etc.
|
||||
```
|
||||
Locate in `static/service-worker.js` or the Vite PWA plugin config. Low severity — doesn't break
|
||||
functionality, but pollutes the console and may cause unhandled promise rejections.
|
||||
**Status:** ✅ Fixed (2026-05-14)
|
||||
- Added guard to filter out non-http/https requests before Attempting to cache.
|
||||
|
||||
### [CSS] Global placeholder text color — too dark in light mode
|
||||
Placeholder text inherits full input text color in light mode (Tailwind CSS default), making
|
||||
placeholders indistinguishable from filled-in values. Most visible in badge print controls
|
||||
where placeholders show the actual badge value (e.g. "John Smith").
|
||||
### [Electron/Launcher] Display mirroring auto-detection
|
||||
**Status:** ✅ Completed (2026-05-20)
|
||||
- `native:set-display-layout` now auto-detects displays via `displayplacer list`.
|
||||
|
||||
Workaround: scoped `::placeholder` rule added to `ae_comp__badge_print_controls.svelte`
|
||||
(gray-400 light / gray-500 dark) — `commit 7733ef8`.
|
||||
### [Launcher] Force Sync Location
|
||||
**Status:** ✅ Completed (2026-05-21)
|
||||
- Implemented manual trigger and background engine logic to pre-cache all location files.
|
||||
|
||||
**Long-term fix:** Add a global rule to the main CSS (e.g. `src/app.css` or a theme file):
|
||||
```css
|
||||
::placeholder {
|
||||
color: #9ca3af; /* gray-400 */
|
||||
opacity: 1; /* overrides Firefox's 0.54 default */
|
||||
}
|
||||
.dark ::placeholder {
|
||||
color: #6b7280; /* gray-500 */
|
||||
}
|
||||
```
|
||||
Once the global rule is in place, remove the scoped workaround from the badge controls.
|
||||
### [Launcher] Chronological Download Priority
|
||||
**Status:** ✅ Completed (2026-05-21)
|
||||
- Refactored download queue to prioritize Event Assets > Early Sessions > Presentation Order > Created Date.
|
||||
|
||||
|
||||
|
||||
### [Backend/DevOps] Re-add `Access-Control-Allow-Private-Network: true` CORS header
|
||||
Chrome's Private Network Access (PNA) policy blocks public-origin iframes from fetching
|
||||
private-network addresses. Symptom: when `dev-api.oneskyit.com` resolves to a LAN IP
|
||||
(testing from home), Chrome blocks the site domain lookup → ghost account → `site_cfg_json`
|
||||
never loads → `novi_idaa_api_key` is null → IDAA Novi verifier spins forever → timeout banner.
|
||||
Firefox unaffected. Production unaffected (public IPs only).
|
||||
|
||||
- [ ] **Re-add PNA header to API CORS config** — `dev-api` Nginx or FastAPI CORS middleware
|
||||
must respond with `Access-Control-Allow-Private-Network: true` when Chrome sends
|
||||
`Access-Control-Request-Private-Network: true` in the preflight. This was fixed ~1 month
|
||||
ago and regressed. Check Nginx site config and FastAPI `CORSMiddleware` settings.
|
||||
Low urgency (dev-only, Firefox workaround available), but blocks home-network iframe testing.
|
||||
|
||||
### [DevOps] Remaining deployment items
|
||||
|
||||
- [ ] **Simplify Dockerfile env file selection** — Currently the Dockerfile uses a `BUILD_MODE` arg to
|
||||
select between `.env.dev`, `.env.test`, `.env.prod` during the Docker build. This is unnecessary
|
||||
complexity: each server (test Linode, prod Linode, workstation) only ever runs one environment, so
|
||||
there will only ever be one env file present in that server's app directory.
|
||||
|
||||
**The fix:** Each server's app dir (`/srv/apps/test_aether_app_sveltekit/`, etc.) should have a
|
||||
plain `.env` file (gitignored, placed manually during server setup). The Dockerfile should just
|
||||
`COPY . .` and `cp .env .env.runtime` unconditionally — no `if prod / elif test / else dev`
|
||||
branching for env file selection.
|
||||
|
||||
**What this changes:**
|
||||
- `aether_app_sveltekit/Dockerfile` — remove the `BUILD_MODE`-driven `cp` block; always use `.env`
|
||||
- Each Linode app dir gets a plain `.env` instead of `.env.test` / `.env.prod`
|
||||
- Workstation keeps `.env.local` (for `npm run dev`) and `.env.dev` (for `build:docker:dev`) —
|
||||
those stay as-is since they legitimately coexist locally
|
||||
- `BUILD_MODE` arg can stay if needed for other build differences; just stop using it to pick the env file
|
||||
- Update `.gitignore` in sveltekit to un-ignore `.env.test` / remove stale entries if desired
|
||||
|
||||
**Do not touch before the April 21 show.** Low risk but unnecessary churn right before an event.
|
||||
|
||||
- [ ] **Branch strategy cleanup:** All environments (test, prod, bak) currently pull from the same
|
||||
branches. `deploy.sh` defaults are `ae_app_3x_llm` / `development` — acceptable for now but
|
||||
should establish proper branch separation (e.g. `main`/`master` for prod).
|
||||
|
||||
- [ ] **Tier 2 deploy (Gitea webhook):** Push-triggered deploys via Gitea webhook → listener on
|
||||
Linode → `deploy.sh`. Deferred until Gitea usage is more established.
|
||||
|
||||
|
||||
### [Files] Download button — wrong ID used in `handle_click()` (2026-04-22)
|
||||
`ae_comp__hosted_files_download_button.svelte` resolves `file_id` for the download call as
|
||||
`hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id`. When called from
|
||||
Manage Files with an `event_file_obj`, `hosted_file_obj.id` = `event_file_id` (set by
|
||||
`_process_generic_props` via the `_random` strip logic), so the chain stops at the wrong value.
|
||||
The download call goes to `/v3/action/hosted_file/{event_file_id}/download` instead of using the
|
||||
correct `hosted_file_id`. May work if the backend accepts event_file_id at that endpoint —
|
||||
needs live verification.
|
||||
|
||||
**Status (2026-04-22):** Tested — downloads ARE working despite the wrong ID. The backend
|
||||
V3 action endpoint appears to silently accept `event_file_id` at the `hosted_file` download
|
||||
path (or maps between the two). Working by accident, not by design. Needs proper fix before
|
||||
it breaks — if the backend ever tightens that endpoint, all Manage Files downloads will 404.
|
||||
|
||||
**Fix:** In `handle_click()` and both `$effect` blocks and the `content` snippet, replace:
|
||||
```ts
|
||||
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
|
||||
```
|
||||
with:
|
||||
```ts
|
||||
const file_id = hosted_file_obj?.hosted_file_id ?? hosted_file_id;
|
||||
```
|
||||
The direct-download `<a>` path is unaffected (already uses `event_file_id` → correct endpoint).
|
||||
|
||||
### [Files] `db_events.file.clear()` on upload clears all cached files (2026-04-22)
|
||||
In `ae_comp__event_files_upload.svelte` line 114, `db_events.file.clear()` wipes the entire
|
||||
`file` Dexie table, not just files for the current session/presenter. Normally harmless (the
|
||||
reload right after repopulates), but if multiple sessions' file lists are open simultaneously
|
||||
they'd briefly flash empty. Low priority — only noticeable in multi-panel workflows.
|
||||
|
||||
### [General]
|
||||
- **Input Field Audit:** Several input fields are missing `name`/`id` attributes or `data-testid`. Known examples: badge override fields in `ae_comp__badge_obj_view.svelte`; template name input in `ae_comp__badge_template_form.svelte`. Matters for: accessibility, autofill, label associations, and test targeting. (For tests, use `getByLabel()` rather than `input[value*=...]` which only checks the HTML attribute, not the Svelte-bound DOM property.)
|
||||
|
||||
## ✅ Completed (2026-04)
|
||||
## ✅ Completed (archived)
|
||||
See the full completed history in:
|
||||
[documentation/archive/TODO__Agents__ARCHIVE_2026-03.md](documentation/archive/TODO__Agents__ARCHIVE_2026-03.md)
|
||||
[documentation/archive/TODO__Agents__ARCHIVE_2026-04.md](documentation/archive/TODO__Agents__ARCHIVE_2026-04.md)
|
||||
### [Launcher] Error handling + fallback
|
||||
**Status:** ✅ Completed (2026-05-14)
|
||||
- Post-script failure surfaces 'fallback' status; `open_cmd` failure falls back to OS default.
|
||||
|
||||
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",
|
||||
"version": "3.00.10",
|
||||
"version": "3.00.20",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "osit-aether-app-svelte",
|
||||
"version": "3.00.10",
|
||||
"version": "3.00.20",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.20.0",
|
||||
"@codemirror/commands": "^6.10.0",
|
||||
@@ -26,12 +26,10 @@
|
||||
"@lucide/svelte": "^0.*.0",
|
||||
"@popperjs/core": "^2.11.0",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"axios": "^1.7.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"dexie": "^4.0.0",
|
||||
"flowbite-svelte": "^1.28.1",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"lucide-svelte": "^0.*.0",
|
||||
"marked": "^17.0.0",
|
||||
"openai": "^6.10.0",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
@@ -3929,23 +3927,6 @@
|
||||
"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": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
@@ -3976,19 +3957,6 @@
|
||||
"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": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -4101,18 +4069,6 @@
|
||||
"devOptional": true,
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
@@ -4240,15 +4196,6 @@
|
||||
"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": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@@ -4299,20 +4246,6 @@
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"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": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@@ -4332,24 +4265,6 @@
|
||||
"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": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
@@ -4357,33 +4272,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
||||
@@ -4935,42 +4823,6 @@
|
||||
"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": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
@@ -5003,43 +4855,6 @@
|
||||
"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": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -5065,18 +4880,6 @@
|
||||
"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": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -5092,33 +4895,6 @@
|
||||
"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": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
@@ -5654,15 +5430,6 @@
|
||||
"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": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
@@ -5693,36 +5460,6 @@
|
||||
"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": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||
@@ -6317,12 +6054,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
"homepage": "https://oneskyit.com/",
|
||||
"private": true,
|
||||
@@ -105,12 +105,10 @@
|
||||
"@lucide/svelte": "^0.*.0",
|
||||
"@popperjs/core": "^2.11.0",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"axios": "^1.7.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"dexie": "^4.0.0",
|
||||
"flowbite-svelte": "^1.28.1",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"lucide-svelte": "^0.*.0",
|
||||
"marked": "^17.0.0",
|
||||
"openai": "^6.10.0",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
|
||||
@@ -163,9 +163,16 @@ html.light {
|
||||
background-color: rgb(55 65 81); /* gray-700 */
|
||||
border-color: rgb(75 85 99); /* gray-600 */
|
||||
}
|
||||
.input::placeholder,
|
||||
.textarea::placeholder {
|
||||
font-style: italic;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.dark .input::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 */
|
||||
.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';
|
||||
|
||||
/**
|
||||
@@ -11,7 +13,7 @@ export const delete_object = async function delete_object({
|
||||
headers = {},
|
||||
params = {},
|
||||
data = {},
|
||||
timeout = 60000,
|
||||
timeout = 20000,
|
||||
return_meta = false,
|
||||
log_lvl = 0,
|
||||
retry_count = 5
|
||||
@@ -97,9 +99,15 @@ export const delete_object = async function delete_object({
|
||||
}
|
||||
|
||||
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 {
|
||||
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(
|
||||
`API DELETE request timed out after ${timeout}ms.`
|
||||
);
|
||||
@@ -120,12 +128,48 @@ export const delete_object = async function delete_object({
|
||||
url.toString(),
|
||||
fetchOptions
|
||||
).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(
|
||||
'API DELETE Object *fetch* request was aborted or failed in an unexpected way.',
|
||||
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) {
|
||||
throw new Error(
|
||||
@@ -151,7 +195,24 @@ export const delete_object = async function delete_object({
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -174,6 +235,8 @@ export const delete_object = async function delete_object({
|
||||
? json.data
|
||||
: json;
|
||||
} catch (error) {
|
||||
// Ensure per-attempt timeout is always cleared on failure.
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
console.error(`API DELETE error on attempt ${attempt}:`, error);
|
||||
|
||||
if (attempt === retry_count) {
|
||||
@@ -181,9 +244,12 @@ export const delete_object = async function delete_object({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (log_lvl) {
|
||||
console.log(`Retrying... (${attempt}/${retry_count})`);
|
||||
}
|
||||
// Backoff before retrying. Caps at 8s to match GET/POST/PATCH policy.
|
||||
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 = {},
|
||||
params = {},
|
||||
data = {},
|
||||
timeout = 90000,
|
||||
timeout = 20000,
|
||||
return_meta = false,
|
||||
return_blob = false,
|
||||
filename = '',
|
||||
@@ -73,9 +73,6 @@ export const get_object = async function get_object({
|
||||
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
|
||||
const headers_cleaned: key_val = {};
|
||||
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);
|
||||
}
|
||||
|
||||
// signal is injected per-attempt inside the retry loop so each retry gets
|
||||
// a fresh AbortController with its own independent timeout.
|
||||
const fetchOptions: RequestInit = {
|
||||
method: 'GET',
|
||||
headers: headers_cleaned,
|
||||
signal: controller.signal,
|
||||
// Be explicit about CORS behavior and redirect handling to avoid
|
||||
// environment-dependent defaults that can cause opaque failures.
|
||||
mode: 'cors',
|
||||
@@ -203,10 +201,24 @@ export const get_object = async function get_object({
|
||||
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 {
|
||||
const response = await fetch_method(
|
||||
url.toString(),
|
||||
fetchOptions
|
||||
{ ...fetchOptions, signal: controller.signal }
|
||||
).catch(function (error: any) {
|
||||
// SILENCE NOISE: Aborted requests (common in SWR/Background loads) shouldn't spam logs
|
||||
if (
|
||||
@@ -231,21 +243,36 @@ export const get_object = async function get_object({
|
||||
});
|
||||
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 (
|
||||
response instanceof Error ||
|
||||
(response &&
|
||||
(response.name === 'TypeError' ||
|
||||
response.name === 'AbortError'))
|
||||
) {
|
||||
// If it was an explicit abort, definitely stop
|
||||
if (response.name === 'AbortError') return false;
|
||||
// AbortError can be either timeout or intentional abort.
|
||||
// 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)
|
||||
console.log(
|
||||
'API GET Object: Detected NetworkError or TypeError. Failing fast.'
|
||||
);
|
||||
return false;
|
||||
// TypeError = transient network failure (ERR_NETWORK_CHANGED,
|
||||
// ERR_NETWORK_IO_SUSPENDED, hotel/conference WiFi blip, etc.).
|
||||
// IMPORTANT: throw here so the retry loop's catch block handles it with
|
||||
// backoff. Returning false would bypass retries entirely.
|
||||
//
|
||||
// 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) {
|
||||
@@ -438,6 +465,8 @@ export const get_object = async function get_object({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ensure the per-attempt timeout timer is always cancelled on failure.
|
||||
clearTimeout(timeoutId);
|
||||
console.log(
|
||||
`API GET object request *fetch* error on attempt ${attempt}:`,
|
||||
error
|
||||
@@ -448,10 +477,13 @@ export const get_object = async function get_object({
|
||||
return false;
|
||||
}
|
||||
|
||||
// Log retry information
|
||||
if (log_lvl) {
|
||||
console.log(`Retrying... (${attempt}/${retry_count})`);
|
||||
}
|
||||
// Backoff before retrying. Without a delay, rapid retries on a flaky
|
||||
// connection accomplish nothing and add noise. Caps at 8s so later
|
||||
// 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 = {},
|
||||
params = {},
|
||||
data = {},
|
||||
timeout = 60000,
|
||||
timeout = 20000,
|
||||
return_meta = false,
|
||||
log_lvl = 0,
|
||||
retry_count = 5
|
||||
@@ -153,9 +153,15 @@ export const patch_object = async function patch_object({
|
||||
}
|
||||
|
||||
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 {
|
||||
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(
|
||||
`API PATCH request timed out after ${timeout}ms.`
|
||||
);
|
||||
@@ -173,12 +179,52 @@ export const patch_object = async function patch_object({
|
||||
url.toString(),
|
||||
fetchOptions
|
||||
).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(
|
||||
'API PATCH Object *fetch* request was aborted or failed in an unexpected way.',
|
||||
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) {
|
||||
throw new Error(
|
||||
@@ -292,6 +338,8 @@ export const patch_object = async function patch_object({
|
||||
? json.data
|
||||
: json;
|
||||
} catch (error) {
|
||||
// Ensure per-attempt timeout is always cleared on failure.
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
console.error(`API PATCH error on attempt ${attempt}:`, error);
|
||||
|
||||
if (attempt === retry_count) {
|
||||
@@ -299,9 +347,12 @@ export const patch_object = async function patch_object({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (log_lvl) {
|
||||
console.log(`Retrying... (${attempt}/${retry_count})`);
|
||||
}
|
||||
// Backoff before retrying. Caps at 8s to match GET/POST policy.
|
||||
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 = {},
|
||||
data = {},
|
||||
form_data = null,
|
||||
timeout = 90000,
|
||||
timeout = 20000,
|
||||
return_meta = false,
|
||||
return_blob = false,
|
||||
filename = '',
|
||||
@@ -200,13 +200,19 @@ export const post_object = async function post_object({
|
||||
}
|
||||
|
||||
for (let attempt = 1; attempt <= retry_count; attempt++) {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => {
|
||||
console.error(`API POST request timed out after ${timeout}ms.`);
|
||||
controller.abort();
|
||||
}, timeout);
|
||||
// Declared at loop scope (not inside try) so the catch block can clearTimeout.
|
||||
// Fresh controller per attempt — same rationale as api_get_object.ts.
|
||||
const controller = new AbortController();
|
||||
// AbortError is not specific enough by itself. Distinguish timeout-aborts
|
||||
// (retryable transient class) from intentional caller aborts (fail-fast).
|
||||
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 = {
|
||||
method: 'POST',
|
||||
headers: headers_cleaned,
|
||||
@@ -245,19 +251,28 @@ export const post_object = async function post_object({
|
||||
});
|
||||
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 (
|
||||
response instanceof Error ||
|
||||
(response &&
|
||||
(response.name === 'TypeError' ||
|
||||
response.name === 'AbortError'))
|
||||
) {
|
||||
if (response.name === 'AbortError') return false;
|
||||
if (log_lvl > 1)
|
||||
console.log(
|
||||
'API POST Object: Detected NetworkError or TypeError. Failing fast.'
|
||||
);
|
||||
return false;
|
||||
// Retry timeout-aborts from this helper; do not retry caller aborts
|
||||
// (route change/unmount/manual cancellation).
|
||||
if (response.name === 'AbortError') {
|
||||
if (did_timeout_abort) {
|
||||
throw new Error(
|
||||
`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) {
|
||||
@@ -411,6 +426,8 @@ export const post_object = async function post_object({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ensure the per-attempt timeout timer is always cancelled on failure.
|
||||
clearTimeout(timeoutId);
|
||||
console.error(`API POST error on attempt ${attempt}:`, error);
|
||||
|
||||
if (attempt === retry_count) {
|
||||
@@ -418,9 +435,10 @@ export const post_object = async function post_object({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (log_lvl) {
|
||||
console.log(`Retrying... (${attempt}/${retry_count})`);
|
||||
}
|
||||
// Backoff before retrying — same rationale as api_get_object.ts.
|
||||
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">
|
||||
// *** Import Svelte specific
|
||||
import * as Lucide from 'lucide-svelte';
|
||||
import * as Lucide from '@lucide/svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
@@ -186,16 +186,13 @@ let is_url_file = $derived.by(() => {
|
||||
|
||||
let direct_download_url = $derived.by(() => {
|
||||
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.
|
||||
// Legacy endpoints often expect integer IDs and will return 404 for string IDs.
|
||||
const file_id =
|
||||
hosted_file_obj.event_file_id ||
|
||||
hosted_file_obj.hosted_file_id ||
|
||||
hosted_file_id;
|
||||
const obj_type_path = hosted_file_obj.event_file_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}`;
|
||||
// Use event_file endpoint when event_file_id is present (canonical per API guide §5).
|
||||
// Fall back to hosted_file endpoint for standalone hosted_file objects.
|
||||
if (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}`;
|
||||
}
|
||||
const file_id = hosted_file_obj.hosted_file_id || hosted_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}`;
|
||||
});
|
||||
|
||||
async function handle_click() {
|
||||
@@ -346,7 +343,20 @@ async function handle_click() {
|
||||
disabled={require_auth && !$ae_loc.authenticated_access}
|
||||
class={variant_classes}
|
||||
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()}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// untrack import removed — task_id sync now uses direct $effect (no untrack needed)
|
||||
// Imports
|
||||
// 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 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_id: current_location_id,
|
||||
enabled: 'all',
|
||||
limit: 25,
|
||||
hidden: 'all',
|
||||
log_lvl
|
||||
}).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 { 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 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)
|
||||
obj.event_session_id = obj.event_session_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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -597,10 +597,13 @@ export async function email_sign_in__event_presenter({
|
||||
return null;
|
||||
}
|
||||
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(
|
||||
`${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({
|
||||
api_cfg,
|
||||
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 });
|
||||
else if (enabled === 'not_enabled')
|
||||
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) {
|
||||
search_query.and.push({
|
||||
field: 'event_location_name',
|
||||
@@ -774,6 +769,7 @@ export async function search__event_session({
|
||||
view,
|
||||
limit,
|
||||
offset,
|
||||
hidden,
|
||||
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 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>`;
|
||||
return await api.send_email({
|
||||
|
||||
@@ -55,32 +55,35 @@ export interface LaunchProfile {
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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 {
|
||||
return {
|
||||
app: 'VLC (macOS)',
|
||||
display_mode: 'mirror',
|
||||
// Direct binary path ensures VLC receives media file + flags reliably.
|
||||
// `--no-play-and-exit` prevents closing on end, `--play-and-pause` holds final frame.
|
||||
open_cmd: '/Applications/VLC.app/Contents/MacOS/VLC --no-play-and-exit --play-and-pause "{{path}}"',
|
||||
post_delay_ms: 1000,
|
||||
// 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.
|
||||
open_cmd: 'nohup /Applications/VLC.app/Contents/MacOS/VLC --no-play-and-exit --play-and-pause --fullscreen "{{path}}" > /dev/null 2>&1 &',
|
||||
post_delay_ms: 3000,
|
||||
// Activate VLC after it has had time to open. Fullscreen is already set by the flag
|
||||
// above — this just ensures VLC is the frontmost app and the presenter sees it.
|
||||
post_script: `tell application "VLC"
|
||||
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`
|
||||
};
|
||||
}
|
||||
@@ -104,10 +107,10 @@ const POWERPOINT_MAC_EXTEND_PROFILE: LaunchProfile = {
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"',
|
||||
post_delay_ms: 1000,
|
||||
post_script: `tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
end tell
|
||||
repeat 15 times
|
||||
post_script: `repeat 20 times
|
||||
tell application "Microsoft PowerPoint"
|
||||
activate
|
||||
end tell
|
||||
delay 0.5
|
||||
tell application "System Events"
|
||||
if frontmost of process "Microsoft PowerPoint" is true then exit repeat
|
||||
@@ -148,10 +151,10 @@ const LIBREOFFICE_MAC_EXTEND_PROFILE: LaunchProfile = {
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "LibreOffice" "{{path}}"',
|
||||
post_delay_ms: 1000,
|
||||
post_script: `tell application "LibreOffice"
|
||||
activate
|
||||
end tell
|
||||
repeat 15 times
|
||||
post_script: `repeat 20 times
|
||||
tell application "LibreOffice"
|
||||
activate
|
||||
end tell
|
||||
delay 0.5
|
||||
tell application "System Events"
|
||||
if frontmost of process "soffice" is true then exit repeat
|
||||
@@ -170,10 +173,10 @@ const ACROBAT_MAC_MIRROR_PROFILE: LaunchProfile = {
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "Adobe Acrobat Reader DC" "{{path}}"',
|
||||
post_delay_ms: 1000,
|
||||
post_script: `tell application "Adobe Acrobat Reader DC"
|
||||
activate
|
||||
end tell
|
||||
repeat 15 times
|
||||
post_script: `repeat 20 times
|
||||
tell application "Adobe Acrobat Reader DC"
|
||||
activate
|
||||
end tell
|
||||
delay 0.5
|
||||
tell application "System Events"
|
||||
if frontmost of process "AdobeReader" is true then exit repeat
|
||||
@@ -215,10 +218,10 @@ const LIBREOFFICE_WIN_EXTEND_PROFILE: LaunchProfile = {
|
||||
display_mode: 'extend',
|
||||
open_cmd: 'open -a "LibreOffice" "{{path}}"',
|
||||
post_delay_ms: 1500,
|
||||
post_script: `tell application "LibreOffice"
|
||||
activate
|
||||
end tell
|
||||
repeat 15 times
|
||||
post_script: `repeat 20 times
|
||||
tell application "LibreOffice"
|
||||
activate
|
||||
end tell
|
||||
delay 0.5
|
||||
tell application "System Events"
|
||||
if frontmost of process "soffice" is true then exit repeat
|
||||
@@ -237,10 +240,10 @@ const ACROBAT_WIN_MIRROR_PROFILE: LaunchProfile = {
|
||||
display_mode: 'mirror',
|
||||
open_cmd: 'open -a "Acrobat Reader Windows" "{{path}}"',
|
||||
post_delay_ms: 1500,
|
||||
post_script: `tell application "Acrobat Reader Windows"
|
||||
activate
|
||||
end tell
|
||||
repeat 15 times
|
||||
post_script: `repeat 20 times
|
||||
tell application "Acrobat Reader Windows"
|
||||
activate
|
||||
end tell
|
||||
delay 0.5
|
||||
tell application "System Events"
|
||||
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.
|
||||
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.
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { key_val } from '$lib/stores/ae_stores';
|
||||
import { api } from '$lib/api/api';
|
||||
|
||||
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 type { ae_Journal } from '$lib/types/ae_types';
|
||||
|
||||
@@ -885,9 +886,16 @@ export async function process_ae_obj__journal_props({
|
||||
|
||||
const updated =
|
||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
||||
obj.tmp_sort_3 = `${obj.group ?? '0'}_${obj.priority ? 1 : 0}_${obj.sort ?? '0'}_${
|
||||
obj.name
|
||||
}_${updated}`;
|
||||
const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({
|
||||
prefix: [obj.group ?? '0'],
|
||||
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 ?? ''}`;
|
||||
|
||||
return obj;
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { key_val } from '$lib/stores/ae_stores';
|
||||
import { api } from '$lib/api/api';
|
||||
|
||||
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 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_md_html = history_md_html;
|
||||
|
||||
// Journal entry-specific computed sort fields, overriding generic ones if needed
|
||||
const sort_val = (obj.sort ?? 0).toString().padStart(3, '0');
|
||||
// Journal entry-specific computed sort fields via build_tmp_sort.
|
||||
// Order: priority DESC → sort ASC → name ASC → updated ASC (all ascending, no .reverse())
|
||||
const updated =
|
||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
||||
obj.tmp_sort_1 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${updated}`;
|
||||
obj.tmp_sort_2 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${obj.name ?? ''}_${updated}`;
|
||||
obj.tmp_sort_3 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${obj.name ?? ''}_${updated}`;
|
||||
const { tmp_sort_1, tmp_sort_2, tmp_sort_3 } = build_tmp_sort({
|
||||
prefix: [obj.group ?? ''],
|
||||
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;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -75,8 +75,10 @@ export function journal_entry_matches_search(
|
||||
}
|
||||
|
||||
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 (
|
||||
(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?.journal_entry_id ?? '').localeCompare(a?.journal_entry_id ?? '')
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
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_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;
|
||||
}
|
||||
obj.name = obj.title;
|
||||
const sort_val = (obj.sort ?? 0).toString().padStart(3, '0');
|
||||
const updated =
|
||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
||||
obj.tmp_sort_1 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${updated}`;
|
||||
obj.tmp_sort_2 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${obj.updated_on ?? ''}_${obj.created_on ?? ''}`;
|
||||
const { tmp_sort_1, tmp_sort_2 } = build_tmp_sort({
|
||||
prefix: [obj.group ?? ''],
|
||||
priority: obj.priority,
|
||||
sort: obj.sort,
|
||||
fields_1: [obj.updated_on ?? obj.created_on ?? ''],
|
||||
fields_2: [obj.updated_on ?? '', obj.created_on ?? ''],
|
||||
pad_width: 8
|
||||
});
|
||||
obj.tmp_sort_1 = tmp_sort_1;
|
||||
obj.tmp_sort_2 = tmp_sort_2;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
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_posts } from '$lib/ae_posts/db_posts';
|
||||
@@ -383,15 +384,16 @@ export async function process_ae_obj__post_comment_props({
|
||||
obj_type: 'post_comment',
|
||||
log_lvl,
|
||||
specific_processor: (obj) => {
|
||||
const sort_val = (obj.sort ?? 0).toString().padStart(3, '0');
|
||||
const updated =
|
||||
obj.updated_on ?? obj.created_on ?? new Date(0).toISOString();
|
||||
obj.tmp_sort_1 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${updated}`;
|
||||
obj.tmp_sort_2 = `${obj.group ?? ''}_${obj.priority ? '1' : '0'}_${
|
||||
sort_val
|
||||
}_${obj.updated_on ?? ''}_${obj.created_on ?? ''}`;
|
||||
const { tmp_sort_1, tmp_sort_2 } = build_tmp_sort({
|
||||
prefix: [obj.group ?? ''],
|
||||
priority: obj.priority,
|
||||
sort: obj.sort,
|
||||
fields_1: [obj.updated_on ?? obj.created_on ?? ''],
|
||||
fields_2: [obj.updated_on ?? '', obj.created_on ?? ''],
|
||||
pad_width: 8
|
||||
});
|
||||
obj.tmp_sort_1 = tmp_sort_1;
|
||||
obj.tmp_sort_2 = tmp_sort_2;
|
||||
|
||||
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
|
||||
// Pass true/false to resolve to the correct 12h or 24h variant automatically.
|
||||
// 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() ***');
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
// 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) {
|
||||
named_format = use_12h
|
||||
? (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.
|
||||
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
} from '$lib/stores/ae_stores';
|
||||
// import { core_func } from '$lib/ae_core/ae_core_functions';
|
||||
// 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 { db_events } from "$lib/db_events";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -165,9 +165,25 @@ $effect(() => {
|
||||
$effect(() => {
|
||||
const account_id = $slct.account_id;
|
||||
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';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,9 +16,7 @@ import {
|
||||
slct,
|
||||
slct_trigger
|
||||
} from '$lib/stores/ae_stores';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import {
|
||||
events_loc,
|
||||
events_sess,
|
||||
events_slct,
|
||||
events_trigger
|
||||
@@ -79,6 +77,9 @@ let ae_promises: key_val = $state({});
|
||||
let ae_tmp: key_val = $state({});
|
||||
ae_tmp.show__file_li = true;
|
||||
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 = {};
|
||||
|
||||
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">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
console.log('*** Refresh button clicked ***');
|
||||
|
||||
db_events.file.clear();
|
||||
|
||||
// let params = {
|
||||
// qry__enabled: 'all',
|
||||
// qry__hidden: 'all',
|
||||
// }
|
||||
|
||||
events_func.load_ae_obj_li__event_file({
|
||||
onclick={async () => {
|
||||
await events_func.load_ae_obj_li__event_file({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_type: link_to_type,
|
||||
for_obj_id: link_to_id,
|
||||
enabled: 'all',
|
||||
hidden: 'all',
|
||||
// params: params,
|
||||
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: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>
|
||||
<MyClipboard
|
||||
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_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
|
||||
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_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"
|
||||
></MyClipboard>
|
||||
</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 type { Writable } from 'svelte/store';
|
||||
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
|
||||
import {
|
||||
badges_loc_defaults,
|
||||
badges_sess_defaults
|
||||
} from '$lib/stores/ae_events_stores__badges_defaults';
|
||||
import {
|
||||
launcher_loc_defaults,
|
||||
launcher_sess_defaults
|
||||
} from '$lib/stores/ae_events_stores__launcher_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';
|
||||
// Static display title for the Events module. Always this value — nothing writes to it.
|
||||
// Use this constant instead of $events_loc.title (which is being retired).
|
||||
export const EVENTS_MODULE_TITLE = `OSIT's Æ Events`;
|
||||
|
||||
import { badges_sess_defaults } from '$lib/stores/ae_events_stores__badges_defaults';
|
||||
import { launcher_sess_defaults } from '$lib/stores/ae_events_stores__launcher_defaults';
|
||||
import { leads_sess_defaults } from '$lib/stores/ae_events_stores__leads_defaults';
|
||||
import { 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
|
||||
// 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.
|
||||
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 */
|
||||
// In-memory only (writable, not persisted). Resets on page load.
|
||||
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,
|
||||
log_lvl: 1,
|
||||
|
||||
@@ -102,7 +28,6 @@ const events_session_data_struct: key_val = {
|
||||
ds: {
|
||||
submit_status: null
|
||||
},
|
||||
ds_loaded: {},
|
||||
|
||||
qry__enabled: 'enabled', // all, disabled, enabled
|
||||
qry__hidden: 'not_hidden', // all, hidden, not_hidden
|
||||
@@ -162,31 +87,14 @@ const events_slct_obj_template: key_val = {
|
||||
event_obj_li: [],
|
||||
|
||||
// Sub-level event_
|
||||
abstract_id: null,
|
||||
abstract_obj: {},
|
||||
abstract_obj_li: [],
|
||||
|
||||
badge_id: null,
|
||||
badge_obj: {},
|
||||
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_obj: {},
|
||||
exhibit_obj_li: [],
|
||||
|
||||
// Rename these to badge_tracking_*?
|
||||
exhibit_tracking_id: null,
|
||||
exhibit_tracking_obj: {},
|
||||
exhibit_tracking_obj_li: [],
|
||||
|
||||
file_id: null,
|
||||
file_obj: {},
|
||||
file_obj_li: [],
|
||||
@@ -218,8 +126,6 @@ const events_slct_obj_template: key_val = {
|
||||
session_obj_li: [],
|
||||
event_session_obj: {},
|
||||
|
||||
lq__presenter_obj: {}, // Testing passing a LiveQuery object around...
|
||||
|
||||
auth__event_presenter_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 { 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 {
|
||||
auto_view: boolean;
|
||||
show_hidden: boolean;
|
||||
show_not_enabled: boolean;
|
||||
// 'default' = hide hidden+disabled | 'show_hidden' = show hidden | 'show_all' = show hidden+disabled
|
||||
visibility_filter: 'default' | 'show_hidden' | 'show_all';
|
||||
show_printed: boolean;
|
||||
allow_reprint: boolean;
|
||||
show_element__cfg: boolean;
|
||||
@@ -110,6 +110,7 @@ export interface BadgesLocState {
|
||||
qry_printed_status: string; // 'all' | 'printed' | 'not_printed'
|
||||
qry_affiliations: string | null;
|
||||
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;
|
||||
use_id_li: boolean;
|
||||
search_status: string | null;
|
||||
@@ -137,6 +138,10 @@ export interface BadgesLocState {
|
||||
trusted_search_min_chars: number;
|
||||
// Timestamp when the remote config was last mirrored locally
|
||||
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 {
|
||||
@@ -158,8 +163,7 @@ export interface BadgesSessState {
|
||||
export const badges_loc_defaults: BadgesLocState = {
|
||||
auto_view: true,
|
||||
|
||||
show_hidden: false, // Hidden (archived) badges are excluded from the main list.
|
||||
show_not_enabled: false,
|
||||
visibility_filter: 'default', // 'default' | 'show_hidden' | 'show_all'
|
||||
|
||||
show_printed: false,
|
||||
allow_reprint: false,
|
||||
@@ -178,6 +182,7 @@ export const badges_loc_defaults: BadgesLocState = {
|
||||
qry_printed_status: 'all', // 'all' | 'printed' | 'not_printed'
|
||||
qry_affiliations: null, // null = no affiliation filter
|
||||
qry_sort_order: '', // '' = default sort order
|
||||
qry_result_limit: 25,
|
||||
|
||||
status_qry__search: null,
|
||||
use_id_li: true,
|
||||
@@ -202,7 +207,8 @@ export const badges_loc_defaults: BadgesLocState = {
|
||||
auth_search_min_chars: 2,
|
||||
trusted_search_result_limit: 150,
|
||||
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.
|
||||
|
||||
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)
|
||||
*/
|
||||
|
||||
import type { LaunchProfile } from '$lib/ae_events/ae_launcher__default_launch_profiles';
|
||||
|
||||
/** 3-way section collapse state used throughout the Launcher UI. */
|
||||
export type SectionState = 'collapsed' | 'auto' | 'pinned';
|
||||
|
||||
@@ -91,6 +93,18 @@ export interface LauncherLocState {
|
||||
* device/OS without deploying to the Mac laptop.
|
||||
*/
|
||||
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 {
|
||||
@@ -119,6 +133,9 @@ export interface LauncherSessState {
|
||||
trigger_reload__event_session_obj_li: string | null;
|
||||
trigger_reload__event_location_obj_id: string | null;
|
||||
trigger_reload__event_location_obj_li: string | null;
|
||||
|
||||
trigger__force_location_sync: any;
|
||||
|
||||
trigger__ws_connect: any;
|
||||
trigger__ws_disconnect: any;
|
||||
}
|
||||
@@ -216,7 +233,9 @@ export const launcher_loc_defaults: LauncherLocState = {
|
||||
controller_client_id: null,
|
||||
native_test_mode: false,
|
||||
wallpaper_applied_url: null,
|
||||
wallpaper_applied_url_external: null
|
||||
wallpaper_applied_url_external: null,
|
||||
file_display_overrides: {},
|
||||
launch_profiles: null
|
||||
// controller_cmd: 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_li: null,
|
||||
|
||||
trigger__force_location_sync: null,
|
||||
|
||||
trigger__ws_connect: null,
|
||||
trigger__ws_disconnect: null
|
||||
};
|
||||
|
||||
@@ -18,4 +18,9 @@
|
||||
import { PersistedState } from 'runed';
|
||||
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 {
|
||||
__version: number;
|
||||
show_details: boolean; // Show extra metadata/details in the leads tab (was events_loc.show_details)
|
||||
show_option__paid_tab: boolean;
|
||||
show_content__scan_alert: boolean;
|
||||
show_content__scan_requirements: boolean;
|
||||
@@ -80,6 +81,7 @@ export interface LeadsSessState {
|
||||
// Persisted leads config — survives browser sessions.
|
||||
export const leads_loc_defaults: LeadsLocState = {
|
||||
__version: 1,
|
||||
show_details: false, // Show extra metadata/details in the leads tab (was events_loc.show_details)
|
||||
show_option__paid_tab: true,
|
||||
show_content__scan_alert: true, // Workaround for QR scanner edge-case bug.
|
||||
show_content__scan_requirements: true,
|
||||
|
||||
@@ -15,4 +15,9 @@
|
||||
import { PersistedState } from 'runed';
|
||||
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;
|
||||
presenters_agree: string | null;
|
||||
presenters_biography: string | null;
|
||||
file_downloads: string | null;
|
||||
};
|
||||
rpt__session_no_files: boolean;
|
||||
rpt__session_poc_agree: boolean;
|
||||
@@ -414,7 +415,8 @@ export const pres_mgmt_sess_defaults: PresMgmtSessState = {
|
||||
status_rpt: {
|
||||
recent_files: null,
|
||||
presenters_agree: null,
|
||||
presenters_biography: null
|
||||
presenters_biography: null,
|
||||
file_downloads: null
|
||||
},
|
||||
rpt__session_no_files: true,
|
||||
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.
|
||||
// Used to cache verification results and avoid repeated Novi API calls.
|
||||
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.
|
||||
// See routes/idaa/(idaa)/+layout.svelte for the override logic.
|
||||
novi_admin_li: [],
|
||||
novi_trusted_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: {
|
||||
enabled: 'enabled', // all, disabled, enabled
|
||||
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__offset: 0,
|
||||
|
||||
qr_scanner_version: 'one',
|
||||
|
||||
admin: {
|
||||
show_element__sql_qry: 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
|
||||
},
|
||||
|
||||
ds: {},
|
||||
hub: {
|
||||
show_element__cfg: true,
|
||||
show_element__cfg_detail: false,
|
||||
@@ -156,56 +153,6 @@ const ae_app_local_data_defaults: key_val = {
|
||||
|
||||
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(
|
||||
@@ -258,31 +205,6 @@ const ae_app_session_data_defaults: key_val = {
|
||||
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: {
|
||||
show_report__person_li: false,
|
||||
|
||||
@@ -291,7 +213,6 @@ const ae_app_session_data_defaults: key_val = {
|
||||
|
||||
show__modal_change_password: false,
|
||||
|
||||
download: {},
|
||||
// Per-file progress keyed by file_id (downloads) or temp_id (uploads).
|
||||
// Shape: { status, endpoint, filename, size_total, size_loaded, percent_completed }
|
||||
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
|
||||
},
|
||||
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_presenter: 1,
|
||||
event_badge: 1,
|
||||
|
||||
@@ -12,6 +12,8 @@ import '../app.css';
|
||||
// *** Import other supporting libraries
|
||||
import {
|
||||
RefreshCw,
|
||||
WifiOff,
|
||||
ChevronDown,
|
||||
} from '@lucide/svelte';
|
||||
|
||||
// Highlight JS
|
||||
@@ -408,16 +410,45 @@ $effect(() => {
|
||||
</svelte:head>
|
||||
|
||||
{#if browser && (is_offline || api_unreachable)}
|
||||
<div
|
||||
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">
|
||||
<span class="text-xl font-bold"
|
||||
>{is_offline ? 'Offline' : api_error_msg}</span>
|
||||
<button
|
||||
class="btn btn-sm preset-tonal-surface"
|
||||
onclick={() => window.location.reload()}>
|
||||
{#if show_connection_details}
|
||||
<!-- Expanded banner -->
|
||||
<div
|
||||
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">
|
||||
<WifiOff size="1.2em" class="shrink-0 opacity-70" />
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<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
|
||||
</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 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