Compare commits
6 Commits
1ef9080cda
...
a3d229c803
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3d229c803 | ||
|
|
f72454f379 | ||
|
|
c5c5292715 | ||
|
|
8ed7e0f8d7 | ||
|
|
68e5e01df1 | ||
|
|
611b1e6b51 |
@@ -9,6 +9,7 @@
|
||||
"autofetch",
|
||||
"Axonius",
|
||||
"displayplacer",
|
||||
"elif",
|
||||
"filelist",
|
||||
"gsettings",
|
||||
"onsave"
|
||||
|
||||
@@ -311,6 +311,13 @@ The Electron app zero-configs itself:
|
||||
3. Rename to original filename (e.g., `Abstract_101.pptx`)
|
||||
4. OS opens the file (Keynote, PowerPoint, Preview, etc.)
|
||||
|
||||
**Configurable launch behavior:** The open/launch command in step 4 can be overridden
|
||||
per file extension via `event_device.data_json.launch_scripts` (device-level config) or
|
||||
`event.launcher.launch_scripts` (event-level fallback). Templates use `{{path}}` as the
|
||||
file path placeholder; AppleScript or `shell:` prefixed commands are both supported. No
|
||||
Electron rebuild required to change how files open — edit config in Aether and it applies
|
||||
immediately. See `PROJECT__AE_Events_Launcher_Native_integration.md` Section 8.
|
||||
|
||||
Versioning is handled automatically: when a presenter uploads an updated file, the new
|
||||
hash is cached separately and the old one remains intact.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Aether Events Launcher: Native Electron Integration
|
||||
|
||||
> **Status:** Operational / Phase 5 Implementation
|
||||
> **Last Updated:** 2026-03-11
|
||||
> **Last Updated:** 2026-05-11
|
||||
> **Primary Platform:** macOS (Darwin)
|
||||
> **Fallback Platform:** Linux / Windows
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
The Aether Events Launcher utilizes an Electron-based "Native Shell" to provide OS-level capabilities that are normally restricted by browser sandboxing. This enables persistent file caching, direct control of presentation software (Keynote, PowerPoint), and hardware telemetry.
|
||||
|
||||
### Operational Modes
|
||||
|
||||
| Mode | Purpose | File Handling |
|
||||
| :--- | :--- | :--- |
|
||||
| **Default** | Standard web browser access. | Direct downloads; no local caching. |
|
||||
@@ -49,9 +50,9 @@ The integration is built on a decoupled three-layer communication model to ensur
|
||||
To support rapid onsite deployment, the native app requires zero manual setup.
|
||||
|
||||
1. **Seed:** On launch, the Main process reads a local `seed.json` (Device ID + API Key).
|
||||
2. **Identity:** Calls `GET /v3/data_store/code/{device_code}` or `GET /v3/crud/event_device/{id}` to pull operational context.
|
||||
3. **Hydrate:** Authenticates with the Aether V3 API and injects the **JWT** and **Device Config** into the UI environment.
|
||||
4. **Launch:** Navigates the SvelteKit frontend directly to the assigned Event Launcher route.
|
||||
2. **Identity:** Calls `GET /v3/crud/event_device/{id}` to pull device config and extract `app_base_url` (the event FQDN) and `account_id`.
|
||||
3. **Site Context:** POSTs to `/v3/crud/site_domain/search?limit=1` with the FQDN to resolve the correct site. No JWT — auth is `x-aether-api-key` + `x-account-id` throughout.
|
||||
4. **Launch:** Navigates the SvelteKit frontend directly to the assigned Event Launcher route (`/events/{eventId}/launcher/{locationId}`).
|
||||
|
||||
---
|
||||
|
||||
@@ -85,11 +86,12 @@ When a user clicks "Open", the system follows a non-destructive sequence:
|
||||
The native shell provides specialized handlers for controlling the "Podium Experience."
|
||||
|
||||
### 5.1 Presentation Acts
|
||||
|
||||
| Action | Handler | Actuator (macOS) |
|
||||
| :--- | :--- | :--- |
|
||||
| **Launch** | `launch_presentation` | `open` or `osascript` (slideshow start) |
|
||||
| **Control** | `control_presentation` | `osascript` (next/prev slide) |
|
||||
| **Clean Up**| `kill_processes` | `killall -INT` (graceful exit) |
|
||||
| **Clean Up** | `kill_processes` | `killall -INT` (graceful exit) |
|
||||
|
||||
### 5.2 System Management
|
||||
- **Telemetry:** Pushes `cpu_usage`, `memory_free_gb`, and `foreground_app` via heartbeats using the `get_device_info` relay.
|
||||
@@ -142,10 +144,10 @@ no-op when `window.aetherNative` is not present (i.e., in browser/non-native mod
|
||||
- `get_device_info()` — Returns OS metadata, IP list, hostname, and path placeholders (`[home]`, `[tmp]`).
|
||||
|
||||
### File Cache
|
||||
- `check_hash_file_cache({cache_root, hash, hash_prefix_length?})` — Verifies a file exists in the local hashed cache.
|
||||
- `download_to_cache({url, cache_root, hash, api_key, account_id, hash_prefix_length?})` — Streams a file download to the hashed cache with SHA-256 integrity check.
|
||||
- `launch_from_cache({cache_root, hash, temp_root, filename, hash_prefix_length?})` — Atomic "Safe Handover": copy from cache → tmp → rename → execute.
|
||||
- `cleanup_tmp_files({cache_root, max_age_minutes?})` — Removes stale `*.tmp` download artifacts. Default: 1440 min (24h). Called at launcher startup.
|
||||
- `check_cache({cache_root, hash, hash_prefix_length?, verify_hash?})` — Verifies a file exists in the local hashed cache. `verify_hash: true` re-hashes to confirm integrity.
|
||||
- `download_to_cache({url, cache_root, hash, api_key, account_id, hash_prefix_length?})` — Streams a file download to the hashed cache with SHA-256 integrity check. Stale `.tmp` files (older than 5 min) from crashed downloads are cleaned up automatically on each call.
|
||||
- `copy_from_cache_to_temp({cache_root, hash, temp_root, filename, hash_prefix_length?})` — **Preferred primitive.** Copies a cached file to temp and returns `{ success, path }`. The Svelte caller decides what to do next (run a script, open it, etc.).
|
||||
- `launch_from_cache({cache_root, hash, temp_root, filename, hash_prefix_length?, script_template?})` — Combines copy + launch in one call. Uses `script_template` if provided, otherwise falls back to hardcoded extension logic. See **Configurable Launch Scripts** below.
|
||||
|
||||
> `hash_prefix_length` defaults to `2` throughout. Do not change without coordinating all devices — mismatched values create orphaned cache subdirectories.
|
||||
|
||||
@@ -153,8 +155,8 @@ no-op when `window.aetherNative` is not present (i.e., in browser/non-native mod
|
||||
- `open_folder(path)` — Opens a path in the OS file manager.
|
||||
- `run_cmd({cmd, timeout?, return_stdout?})` — Async shell command execution.
|
||||
- `run_cmd_sync({cmd, return_stdout?})` — Synchronous shell command execution.
|
||||
- `run_osascript(script)` — Executes an AppleScript string. macOS only.
|
||||
- `kill_processes({process_name_li})` — Gracefully terminates processes by name.
|
||||
- `run_osascript(script)` — Executes an AppleScript string. macOS only. **Hardened (2026-05-11):** writes script to a temp `.scpt` file; multi-line scripts and paths with special characters now work correctly. No shell escaping needed in the passed string.
|
||||
- `kill_processes({process_name_li})` — Terminates processes by name. macOS/Linux: `pkill -f`. Windows: `taskkill /F`.
|
||||
- `open_local_file_v2(path)` — Opens a file with its default OS application.
|
||||
|
||||
### Presentations (Phase 5)
|
||||
@@ -176,5 +178,66 @@ All paths passed to native handlers should use tokens rather than hardcoded OS p
|
||||
- `[home]` — Resolved to the user's home directory by the native bridge.
|
||||
- `[tmp]` — Resolved to the system temporary directory.
|
||||
|
||||
---
|
||||
|
||||
## 8. Configurable Launch Scripts (No-Rebuild File Handling)
|
||||
|
||||
To avoid requiring a full Electron rebuild for changes to how files are opened, `launch_from_cache`
|
||||
supports an optional `script_template` parameter. When provided, Electron runs the template
|
||||
instead of its built-in hardcoded logic. The hardcoded logic remains intact as the fallback
|
||||
when no template is configured.
|
||||
|
||||
### Template Formats
|
||||
|
||||
| Format | Example |
|
||||
| :--- | :--- |
|
||||
| **AppleScript** (macOS) | Multi-line AppleScript string with `{{path}}` placeholder |
|
||||
| **Shell command** | String prefixed with `shell:` — e.g. `shell:open "{{path}}"` |
|
||||
|
||||
The placeholder `{{path}}` is replaced with the full resolved path to the file in the
|
||||
temp directory (after the atomic copy from cache).
|
||||
|
||||
### Where to Configure
|
||||
|
||||
Templates are resolved in priority order by `get_launch_script_template()` in
|
||||
`launcher_file_cont.svelte`:
|
||||
|
||||
1. **`event_device.data_json.launch_scripts`** — API-driven, per-device. Highest priority.
|
||||
Set via the `event_device` record (Pres Mgmt → Device Management or direct DB edit).
|
||||
2. **`$events_loc.launcher.launch_scripts`** — Local persistent config. Editable via the
|
||||
Launcher config UI (planned) or direct `localStorage` manipulation.
|
||||
|
||||
If neither is set, `script_template` is `null` and Electron uses its built-in hardcoded defaults.
|
||||
|
||||
### Key Format
|
||||
|
||||
Keys are lowercase file extensions without the dot. A `"default"` key catches all
|
||||
unrecognised extensions.
|
||||
|
||||
```json
|
||||
// event_device.data_json.launch_scripts example
|
||||
{
|
||||
"launch_scripts": {
|
||||
"pptx": "tell application \"Microsoft PowerPoint\"\n activate\n open (POSIX file \"{{path}}\")\n delay 3\nend tell\ntell application \"System Events\"\n keystroke return using command down\nend tell",
|
||||
"key": "tell application \"Keynote\"\n activate\n open (POSIX file \"{{path}}\")\n delay 1\n start (front document)\nend tell",
|
||||
"pdf": "shell:open \"{{path}}\"",
|
||||
"default": "shell:open \"{{path}}\""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### AppleScript Execution — All Handlers Hardened (2026-05-11)
|
||||
|
||||
All AppleScript execution in the native shell now writes scripts to a temp `.scpt` file and
|
||||
runs `osascript "<path>"` rather than the old `osascript -e "<inline>"` approach.
|
||||
|
||||
- **`run_osascript`** — hardened (2026-05-11, earlier batch)
|
||||
- **`launch_from_cache`** — hardened (same batch)
|
||||
- **`launch_presentation`** — hardened (2026-05-11, follow-up fix; was the last handler still using `-e`)
|
||||
- **`control_presentation`** — uses single-line scripts with no path interpolation; `-e` is safe here and retained for simplicity
|
||||
|
||||
The `-e` approach breaks on (1) multi-line scripts and (2) file paths containing spaces,
|
||||
quotes, or parentheses — common in conference presentation filenames.
|
||||
|
||||
### Not Exposed via Relay (intentional)
|
||||
- `get_seed_config` / `get_jwt` — Exposed in the preload but not relayed to the UI. The JWT and seed are injected into the environment at startup; components should not call these directly.
|
||||
|
||||
@@ -3,38 +3,56 @@
|
||||
> **Status:** Stable — ongoing development.
|
||||
|
||||
|
||||
## 🔴 BGH Conference — April 21 (Must Fix Before Event)
|
||||
## 🔴 CMSC Charlotte — May 27 (Presentation Management)
|
||||
**Drive down:** May 25 | **Setup:** May 26 morning | **Show:** May 27+
|
||||
|
||||
- [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)
|
||||
**[Electron/Launcher] Clean up presentation file launch scripts** — BGH show revealed issues
|
||||
with the scripts 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.
|
||||
|
||||
- [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)
|
||||
**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` accepts optional `script_template` — AppleScript or `shell:` prefix; falls back to hardcoded defaults when null
|
||||
- [x] `get_launch_script_template()` in `launcher_file_cont.svelte` reads from device config then event config; passes result to `launch_from_cache`
|
||||
|
||||
- [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)
|
||||
**Svelte-side migration — remaining before May 26:**
|
||||
- [ ] **[Launcher] Built-in Svelte default templates** — move the "known good" pptx/key/pdf
|
||||
AppleScript strings out of Electron hardcode and into a Svelte constants file (e.g.
|
||||
`ae_launcher__default_launch_scripts.ts`). Priority: `get_launch_script_template()` already
|
||||
checks device config and event config; add a 3rd fallback to these Svelte defaults before
|
||||
returning `null`. This means Electron's hardcoded defaults become the last-resort only.
|
||||
- [ ] **[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).
|
||||
- [ ] **[Launcher] Error handling + fallback** — when the launch script fails, offer fallback
|
||||
to `open_local_file_v2` (OS default handler) rather than silently failing. Show error detail
|
||||
in the launcher file row so staff can diagnose onsite.
|
||||
- [ ] **[Launcher] Slide control scripts in Svelte config** — `control_presentation` AppleScript
|
||||
one-liners are hardcoded in Electron. Move to device config (`data_json.control_scripts`) or
|
||||
Svelte constants so slide nav behavior (e.g. keystroke vs. AppleScript command) can be adjusted
|
||||
without a rebuild. Wire through `run_osascript` directly.
|
||||
- [ ] **[Launcher] `kill_processes` target list in config** — process names to kill on cleanup
|
||||
are currently caller-hardcoded. Allow device config to specify the process name list per
|
||||
file type / app, so adding a new presentation app doesn't require a Svelte code change.
|
||||
- [ ] **[Launcher] Launcher config UI — launch_scripts editor** — add a Technical Mode panel
|
||||
in the Launcher config (tabbed settings) to view and edit `launch_scripts` entries on the
|
||||
active device record. PATCH via `event_device` V3 CRUD. Lets OSIT staff tune scripts onsite
|
||||
without needing phpMyAdmin or a code deploy.
|
||||
- [ ] **[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.
|
||||
|
||||
- [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)
|
||||
---
|
||||
|
||||
- [ ] **[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.
|
||||
## 🔴 Axonius DC — June 9 (Badge Printing)
|
||||
**Setup/Registration:** June 8 | **Show:** June 9
|
||||
|
||||
- [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)
|
||||
- [ ] **[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.
|
||||
|
||||
---
|
||||
|
||||
@@ -268,28 +286,10 @@ Firefox unaffected. Production unaffected (public IPs only).
|
||||
|
||||
|
||||
### [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).
|
||||
- [x] **Fixed (2026-05-11):** All 5 spots in `ae_comp__hosted_files_download_button.svelte` updated
|
||||
to use `hosted_file_obj?.hosted_file_id ?? hosted_file_id` instead of the old
|
||||
`hosted_file_obj?.id || ...` chain that stopped at `event_file_id`. Needs live re-test to
|
||||
confirm downloads still work correctly from Manage Files.
|
||||
|
||||
### [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
|
||||
|
||||
326
documentation/archive/TODO__Agents__ARCHIVE_2026-05.md
Normal file
326
documentation/archive/TODO__Agents__ARCHIVE_2026-05.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# Frontend Agent Task List
|
||||
> Use this file to track steps for complex features or bug fixes.
|
||||
> **Status:** Stable — ongoing development.
|
||||
|
||||
|
||||
## 🔴 BGH Conference — April 21 (Must Fix Before Event)
|
||||
|
||||
- [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)
|
||||
|
||||
- [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)
|
||||
|
||||
- [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)
|
||||
|
||||
- [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)
|
||||
|
||||
- [ ] **[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)
|
||||
|
||||
### [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.
|
||||
|
||||
### [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
|
||||
|
||||
**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)
|
||||
@@ -128,10 +128,7 @@ $effect(() => {
|
||||
let ae_promises: key_val = $state({});
|
||||
|
||||
$effect(() => {
|
||||
const file_id =
|
||||
hosted_file_obj?.id ||
|
||||
hosted_file_obj?.hosted_file_id ||
|
||||
hosted_file_id;
|
||||
const file_id = hosted_file_obj?.hosted_file_id ?? hosted_file_id;
|
||||
if (file_id && $ae_sess?.api_download_kv[file_id]?.percent_completed) {
|
||||
download_percent = $ae_sess.api_download_kv[file_id].percent_completed;
|
||||
}
|
||||
@@ -139,10 +136,7 @@ $effect(() => {
|
||||
|
||||
// Reactive timer to alternate views during active download
|
||||
$effect(() => {
|
||||
const file_id =
|
||||
hosted_file_obj?.id ||
|
||||
hosted_file_obj?.hosted_file_id ||
|
||||
hosted_file_id;
|
||||
const file_id = hosted_file_obj?.hosted_file_id ?? hosted_file_id;
|
||||
const is_actively_downloading =
|
||||
ae_promises[file_id] && download_complete === undefined;
|
||||
|
||||
@@ -193,10 +187,7 @@ let direct_download_url = $derived.by(() => {
|
||||
});
|
||||
|
||||
async function handle_click() {
|
||||
const file_id =
|
||||
hosted_file_obj?.id ||
|
||||
hosted_file_obj?.hosted_file_id ||
|
||||
hosted_file_id;
|
||||
const file_id = hosted_file_obj?.hosted_file_id ?? hosted_file_id;
|
||||
download_complete = undefined;
|
||||
download_status_msg = 'Downloading...';
|
||||
|
||||
@@ -238,10 +229,7 @@ async function handle_click() {
|
||||
</script>
|
||||
|
||||
{#snippet content()}
|
||||
{@const file_id =
|
||||
hosted_file_obj?.id ||
|
||||
hosted_file_obj?.hosted_file_id ||
|
||||
hosted_file_id}
|
||||
{@const file_id = hosted_file_obj?.hosted_file_id ?? hosted_file_id}
|
||||
{#await ae_promises[file_id]}
|
||||
<div class="flex min-h-[1.5rem] w-full items-center">
|
||||
<div
|
||||
@@ -316,8 +304,7 @@ async function handle_click() {
|
||||
{/snippet}
|
||||
|
||||
{#if hosted_file_id && hosted_file_obj}
|
||||
{@const file_id =
|
||||
hosted_file_obj.id || hosted_file_obj.hosted_file_id || hosted_file_id}
|
||||
{@const file_id = hosted_file_obj.hosted_file_id ?? hosted_file_id}
|
||||
|
||||
{#if show_direct_download}
|
||||
<a
|
||||
|
||||
@@ -787,7 +787,7 @@ export const properties_to_save = [
|
||||
'event_id',
|
||||
'code',
|
||||
'account_id',
|
||||
'account_id_random',
|
||||
// 'account_id_random',
|
||||
'conference',
|
||||
'type',
|
||||
'name',
|
||||
|
||||
@@ -359,7 +359,7 @@ export async function create_event_file_obj_from_hosted_file_async({
|
||||
});
|
||||
|
||||
if (return_obj) return result;
|
||||
return result?.event_file_id || result?.id || result?.event_file_id_random;
|
||||
return result?.event_file_id || result?.id;
|
||||
}
|
||||
|
||||
export async function delete_ae_obj_id__event_file({
|
||||
@@ -527,15 +527,11 @@ export const qry__event_file = search__event_file;
|
||||
export const properties_to_save = [
|
||||
'id',
|
||||
'event_file_id',
|
||||
// 'event_file_id_random', // DO NOT UNCOMMENT
|
||||
'hosted_file_id',
|
||||
// 'hosted_file_id_random', // DO NOT UNCOMMENT
|
||||
'hash_sha256',
|
||||
'for_type',
|
||||
'for_id',
|
||||
// 'for_id_random', // DO NOT UNCOMMENT
|
||||
'event_id',
|
||||
// 'event_id_random', // DO NOT UNCOMMENT
|
||||
'event_session_id',
|
||||
'event_presentation_id',
|
||||
'event_presenter_id',
|
||||
@@ -598,22 +594,9 @@ async function _process_generic_props<T extends Record<string, any>>({
|
||||
const processed_obj_li: T[] = [];
|
||||
for (const original_obj of obj_li) {
|
||||
let processed_obj = { ...original_obj };
|
||||
for (const key in processed_obj) {
|
||||
if (key.endsWith('_random')) {
|
||||
const newKey = key.slice(0, -7);
|
||||
// ONLY overwrite if the random variant has a valid value
|
||||
if (
|
||||
processed_obj[key] !== null &&
|
||||
processed_obj[key] !== undefined &&
|
||||
processed_obj[key] !== ''
|
||||
) {
|
||||
(processed_obj as any)[newKey] = processed_obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
const random_id_key = `${obj_type}_id_random`;
|
||||
if (processed_obj[random_id_key])
|
||||
(processed_obj as any).id = processed_obj[random_id_key];
|
||||
const base_id_key = `${obj_type}_id`;
|
||||
if (processed_obj[base_id_key])
|
||||
(processed_obj as any).id = processed_obj[base_id_key];
|
||||
const group = processed_obj.group ?? '0';
|
||||
const priority = processed_obj.priority ? 1 : 0;
|
||||
const sort = processed_obj.sort ?? '0';
|
||||
|
||||
@@ -836,12 +836,10 @@ export async function email_sign_in__event_session({
|
||||
export const properties_to_save = [
|
||||
'id',
|
||||
'event_session_id',
|
||||
'event_session_id_random',
|
||||
'external_id',
|
||||
'code',
|
||||
'for_type',
|
||||
'for_id',
|
||||
'for_id_random',
|
||||
'type_code',
|
||||
'event_id',
|
||||
'event_location_id',
|
||||
@@ -898,18 +896,8 @@ async function _process_generic_props<T extends Record<string, any>>({
|
||||
const processed_obj_li: T[] = [];
|
||||
for (const original_obj of obj_li) {
|
||||
let processed_obj = { ...original_obj };
|
||||
for (const key in processed_obj) {
|
||||
if (key.endsWith('_random')) {
|
||||
const newKey = key.slice(0, -7);
|
||||
(processed_obj as any)[newKey] = processed_obj[key];
|
||||
}
|
||||
}
|
||||
const randomIdKey = `${obj_type}_id_random`;
|
||||
const baseIdKey = `${obj_type}_id`;
|
||||
if (processed_obj[randomIdKey]) {
|
||||
(processed_obj as any).id = processed_obj[randomIdKey];
|
||||
(processed_obj as any)[baseIdKey] = processed_obj[randomIdKey];
|
||||
} else if (processed_obj[baseIdKey])
|
||||
if (processed_obj[baseIdKey])
|
||||
(processed_obj as any).id = processed_obj[baseIdKey];
|
||||
|
||||
const group = processed_obj.group ?? '0';
|
||||
|
||||
@@ -64,11 +64,68 @@ export async function launch_from_cache({
|
||||
hash,
|
||||
temp_root,
|
||||
filename,
|
||||
hash_prefix_length = 2
|
||||
}: any) {
|
||||
hash_prefix_length = 2,
|
||||
script_template = null
|
||||
}: {
|
||||
cache_root: string;
|
||||
hash: string;
|
||||
temp_root: string;
|
||||
filename: string;
|
||||
hash_prefix_length?: number;
|
||||
/**
|
||||
* Optional data-driven launch script. If provided, Electron runs this instead of
|
||||
* its hardcoded extension-based logic — no app rebuild needed for script changes.
|
||||
*
|
||||
* Two formats:
|
||||
* - AppleScript: multi-line string with {{path}} placeholder (macOS only)
|
||||
* - Shell command: prefix with "shell:" → e.g. "shell:open \"{{path}}\""
|
||||
*
|
||||
* Configure via event_device.data_json.launch_scripts or $events_loc.launcher.launch_scripts.
|
||||
* If null, Electron falls through to its built-in hardcoded defaults.
|
||||
*/
|
||||
script_template?: string | null;
|
||||
}) {
|
||||
if (!native)
|
||||
return { success: false, error: 'Native bridge not available' };
|
||||
return await native.launch_from_cache({
|
||||
cache_root,
|
||||
hash,
|
||||
temp_root,
|
||||
filename,
|
||||
hash_prefix_length,
|
||||
script_template
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin cache primitive — copies a cached file to the temp directory and returns
|
||||
* the resolved path. The caller decides what happens next.
|
||||
*
|
||||
* Preferred building block for composable launch flows on the Svelte side:
|
||||
* 1. copy_from_cache_to_temp(...) → { path }
|
||||
* 2. run_osascript(template.replace('{{path}}', path))
|
||||
* OR run_cmd(`open "${path}"`)
|
||||
* OR whatever you need
|
||||
*
|
||||
* Use launch_from_cache when the built-in hardcoded logic is sufficient.
|
||||
* Use this when you want full control over what happens after the file lands in temp.
|
||||
*/
|
||||
export async function copy_from_cache_to_temp({
|
||||
cache_root,
|
||||
hash,
|
||||
temp_root,
|
||||
filename,
|
||||
hash_prefix_length = 2
|
||||
}: {
|
||||
cache_root: string;
|
||||
hash: string;
|
||||
temp_root: string;
|
||||
filename: string;
|
||||
hash_prefix_length?: number;
|
||||
}): Promise<{ success: boolean; path?: string; error?: string }> {
|
||||
if (!native)
|
||||
return { success: false, error: 'Native bridge not available' };
|
||||
return await native.copy_from_cache_to_temp({
|
||||
cache_root,
|
||||
hash,
|
||||
temp_root,
|
||||
@@ -129,6 +186,18 @@ export async function cleanup_tmp_files({
|
||||
return await native.run_cmd({ cmd, timeout: 30000, return_stdout: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an AppleScript string. macOS only.
|
||||
*
|
||||
* HARDENED (2026-05-11): The Electron handler now writes the script to a temp .scpt
|
||||
* file and runs `osascript "/path/to/file.scpt"` rather than passing it inline via
|
||||
* the -e flag. This means:
|
||||
* - Multi-line scripts work correctly
|
||||
* - Paths with spaces or special characters work correctly
|
||||
* - No shell escaping required in the script string you pass here
|
||||
*
|
||||
* The .scpt file is deleted immediately after execution.
|
||||
*/
|
||||
export async function run_osascript(script: string) {
|
||||
if (!native)
|
||||
return { success: false, error: 'Native bridge not available' };
|
||||
|
||||
@@ -197,9 +197,7 @@ $effect(() => {
|
||||
untrack(() => {
|
||||
$events_slct.event_device_id =
|
||||
native_dev.event_device_id ||
|
||||
native_dev.id ||
|
||||
native_dev.event_device_id_random ||
|
||||
native_dev.id_random;
|
||||
native_dev.id;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -370,9 +370,7 @@ async function run_device_heartbeat() {
|
||||
// String-Only ID Vision: Prioritize semantic string IDs, then generic, then legacy random strings
|
||||
const device_id =
|
||||
dev?.event_device_id ||
|
||||
dev?.id ||
|
||||
dev?.event_device_id_random ||
|
||||
dev?.id_random;
|
||||
dev?.id;
|
||||
|
||||
if (!device_id) {
|
||||
// Only log warning if we are actually supposed to be in native mode
|
||||
|
||||
@@ -88,6 +88,41 @@ let open_file_status_message: null | string = $state(null);
|
||||
|
||||
let screen_saver_exts = ['jpg', 'png', 'PNG', 'webp'];
|
||||
|
||||
/**
|
||||
* Resolves a data-driven launch script template for a given file extension.
|
||||
* Checked in priority order:
|
||||
* 1. event_device.data_json.launch_scripts (API-driven, per-device, most specific)
|
||||
* 2. $events_loc.launcher.launch_scripts (local persistent override)
|
||||
* Keys are lowercase extensions without the dot ("pptx", "key", "pdf", etc.).
|
||||
* A "default" key acts as a catch-all for unrecognised extensions.
|
||||
*
|
||||
* Returns null when no config is found → Electron falls back to its hardcoded defaults.
|
||||
*
|
||||
* Template formats:
|
||||
* - AppleScript (macOS): plain string with {{path}} placeholder
|
||||
* - Shell command: prefix with "shell:" → "shell:open \"{{path}}\""
|
||||
*/
|
||||
function get_launch_script_template(extension: string): string | null {
|
||||
const ext = (extension || '').toLowerCase().replace('.', '');
|
||||
|
||||
// 1. Device-level config (from API, per device — highest priority)
|
||||
const device_scripts = ($ae_loc as any).native_device?.launch_scripts;
|
||||
if (device_scripts) {
|
||||
if (device_scripts[ext]) return device_scripts[ext];
|
||||
if (device_scripts['default']) return device_scripts['default'];
|
||||
}
|
||||
|
||||
// 2. Launcher local config override (set manually via Launcher config UI)
|
||||
const local_scripts = ($events_loc as any).launcher?.launch_scripts;
|
||||
if (local_scripts) {
|
||||
if (local_scripts[ext]) return local_scripts[ext];
|
||||
if (local_scripts['default']) return local_scripts['default'];
|
||||
}
|
||||
|
||||
// 3. No override — let Electron use its built-in hardcoded defaults
|
||||
return null;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (screen_saver_exts.includes(event_file_obj.extension)) {
|
||||
if (!$events_loc.launcher.screen_saver_img_kv)
|
||||
@@ -149,11 +184,14 @@ async function handle_open_file() {
|
||||
// Phase 2/5: Use the atomic copy-and-launch operation.
|
||||
// The main process handler (file_handlers.ts) now handles the
|
||||
// specialized LibreOffice/AppleScript logic internally after copying.
|
||||
// script_template is null when no device/local config exists → Electron uses hardcoded defaults.
|
||||
const script_template = get_launch_script_template(event_file_obj.extension);
|
||||
const launch_result = await native.launch_from_cache({
|
||||
cache_root,
|
||||
hash: event_file_obj.hash_sha256,
|
||||
temp_root,
|
||||
filename: event_file_obj.filename
|
||||
filename: event_file_obj.filename,
|
||||
script_template
|
||||
});
|
||||
|
||||
if (!launch_result.success) {
|
||||
|
||||
@@ -77,10 +77,10 @@ let lq__event_obj = $derived(
|
||||
);
|
||||
|
||||
// It is important that these not be set to a value! It messes with the Dexie LiveQuery.
|
||||
// let event_file_id_random_li: Array<string> = $state();
|
||||
// let event_session_id_random_li: Array<string> = $state();
|
||||
// let event_presentation_id_random_li: Array<string>;
|
||||
// let event_presenter_id_random_li: Array<string> = $state();
|
||||
// let event_file_id_li: Array<string> = $state();
|
||||
// let event_session_id_li: Array<string> = $state();
|
||||
// let event_presentation_id_li: Array<string>;
|
||||
// let event_presenter_id_li: Array<string> = $state();
|
||||
|
||||
// let load_obj_li_results: Promise<any>|key_val;
|
||||
// let search_submit_results: Promise<any>|key_val;
|
||||
@@ -142,9 +142,11 @@ $effect(() => {
|
||||
</span>
|
||||
<!-- Reports for: -->
|
||||
{#if $lq__event_obj?.cfg_json?.short_name}
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html $lq__event_obj?.cfg_json.short_name ??
|
||||
ae_snip.html__not_set}
|
||||
{:else}
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html $lq__event_obj?.name ?? ae_snip.html__not_set}
|
||||
{/if}
|
||||
</h2>
|
||||
|
||||
@@ -18,7 +18,7 @@ interface Props {
|
||||
let {
|
||||
container_class_li = [],
|
||||
// display_mode = 'default',
|
||||
// event_file_id_random_li = $bindable(),
|
||||
// event_file_id_li = $bindable(),
|
||||
event_file_obj_li = $bindable(),
|
||||
link_to_type,
|
||||
link_to_id,
|
||||
@@ -55,9 +55,9 @@ $effect(() => {
|
||||
// let ae_tmp: key_val = {};
|
||||
// let ae_triggers: key_val = {};
|
||||
|
||||
let event_file_id_random_li: Array<string> = $state([]);
|
||||
// let event_file_id_li: Array<string> = $state([]);
|
||||
|
||||
let dq__where_type_id_val = $derived(`${link_to_type}_id_random`);
|
||||
let dq__where_type_id_val = $derived(`${link_to_type}_id`);
|
||||
let dq__where_eq_id_val = $derived(link_to_id ?? '');
|
||||
|
||||
// *** Functions and Logic
|
||||
@@ -122,7 +122,7 @@ let lq__event_file_obj_li = $derived(
|
||||
</script>
|
||||
|
||||
{#if event_file_obj_li && event_file_obj_li?.length}
|
||||
<!-- {#if event_file_id_random_li && event_file_id_random_li?.length} -->
|
||||
<!-- {#if event_file_id_li && event_file_id_li?.length} -->
|
||||
<Comp_event_file_obj_tbl
|
||||
{container_class_li}
|
||||
{lq__event_file_obj_li}
|
||||
|
||||
@@ -595,15 +595,6 @@ $effect(() => {
|
||||
{#each $idaa_slct.post_obj.linked_li_json as linked_obj, index (linked_obj.hosted_file_id ?? index)}
|
||||
<span
|
||||
class="flex flex-col items-center gap-1 rounded-lg border bg-white/50 p-1">
|
||||
<!-- <a
|
||||
href={linked_obj.url}
|
||||
target="_blank"
|
||||
class="badge badge-info variant-filled-info"
|
||||
>
|
||||
<span class="fas fa-paperclip m-1"></span>
|
||||
{linked_obj.filename}
|
||||
({linked_obj.hosted_file_id})
|
||||
</a> -->
|
||||
|
||||
{#if $ae_loc.authenticated_access}
|
||||
{@const file_id =
|
||||
@@ -650,8 +641,6 @@ $effect(() => {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ae_promises[linked_obj.event_file_id] = handle_delete__event_file({event_file_id: linked_obj.event_file_id});
|
||||
|
||||
// First - Attempt to delete the hosted file
|
||||
ae_promises.delete__linked_obj =
|
||||
await core_func
|
||||
|
||||
Reference in New Issue
Block a user