2 Commits

Author SHA1 Message Date
Scott Idem
a5f2ae3835 fix(journals): decouple IDB cache writes from API data return
QuotaExceededError on db_save was propagating through .then() into the
outer .catch(), which returned undefined and discarded the successfully-
fetched API data. Wrapped all db_save calls in try/catch so IDB failures
log a warning but never kill the load/create/update return value.

Also removed content_md_html and history_md_html from properties_to_save
— these are computed from content/history on every background refresh via
marked.parse(), so storing the rendered HTML alongside the source was
doubling storage cost per entry and the primary cause of quota exhaustion.

Affects: load, list, create, update, and qry functions in both journal
and journal_entry modules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 12:43:18 -04:00
Scott Idem
b3ce65f7f6 docs: update TODO — mark error handling, PWA SW, launch_profiles editor as done; annotate slide control + kill_processes status
- Error handling + fallback: confirmed done (launcher_file_cont.svelte)
- PWA service worker chrome-extension guard: confirmed done (service-worker.js)
- Launcher config UI / launch_profiles editor: confirmed done (launcher_cfg_launch_timing.svelte)
- Slide control scripts: annotated partial state — Svelte buttons wired, Electron scripts still hardcoded, deferred post June 10
- kill_processes: documented as not started on Svelte side; noted Device tab length concern

Also bundle two prior-session Launcher fixes:
- VLC post_delay_ms: 2000 → 1500ms
- launcher_cfg_launch_timing: add min-w-22 to built-in/current delay display
2026-05-14 12:24:36 -04:00
5 changed files with 148 additions and 101 deletions

View File

@@ -30,20 +30,36 @@ guessing defaults.
`copy_from_cache_to_temp` + `run_osascript` / `run_cmd` directly instead of the all-in-one `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 `launch_from_cache`. Finer error handling at each step (verify copy succeeded before
attempting script; surface failure clearly in UI). attempting script; surface failure clearly in UI).
- [ ] **[Launcher] Error handling + fallback** — when the launch script fails, offer fallback - [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.
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 - [ ] **[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 one-liners are hardcoded in Electron (`shell_handlers.ts` lines 149159). The Svelte side
Svelte constants so slide nav behavior (e.g. keystroke vs. AppleScript command) can be adjusted (`launcher_cfg_native_os.svelte`) already calls `native.control_presentation({ app, action })`
without a rebuild. Wire through `run_osascript` directly. with next/prev/start/stop buttons wired and working. The Electron IPC call is stable enough
- [ ] **[Launcher] `kill_processes` target list in config** — process names to kill on cleanup for current shows.
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. **Remaining (post June 10):** Move scripts to device config (`data_json.control_scripts`) or
- [ ] **[Launcher] Launcher config UI — launch_profiles editor** — add a Technical Mode panel Svelte constants so behavior can be changed without an Electron rebuild. Replace
in the Launcher config (tabbed settings) to view and edit `launch_profiles` entries on the `native.control_presentation()` call with `native.run_osascript(script)` using the
active device record. PATCH via `event_device` V3 CRUD. Lets OSIT staff tune launch behavior onsite config-resolved script string. The `run_osascript` primitive is already in place; this is
without needing phpMyAdmin or a code deploy. 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 - [ ] **[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 before May 26 setup day. Verify: file copies to tmp correctly, script fires, app opens in
slideshow mode, error fallback works. slideshow mode, error fallback works.
@@ -209,19 +225,9 @@ suddenly jumps to 0 errors, verify it's not because a bad `.d.ts` replaced a pac
- [x] **Finish Jitsi Reports filters** — added Novi UUID exclusion plus meeting-name whitelist - [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) filtering, with room-level unique counts based on Novi UUID when present. (2026-05-06)
### [PWA] Service worker ignoring `chrome-extension://` requests ### ~~[PWA] Service worker ignoring `chrome-extension://` requests~~ ✅ Fixed (2026-05-14)
Browser console shows repeated errors: Guard added to `src/service-worker.js` fetch handler: `if (!event.request.url.startsWith('http')) return;`
```text Also skips cross-origin requests entirely (origin check). No console errors from extension URLs.
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 ### [CSS] Global placeholder text color — too dark in light mode
Placeholder text inherits full input text color in light mode (Tailwind CSS default), making Placeholder text inherits full input text color in light mode (Tailwind CSS default), making

View File

@@ -57,7 +57,7 @@ function make_vlc_mirror_profile(): LaunchProfile {
app: 'VLC', app: 'VLC',
display_mode: 'mirror', display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"', open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000, post_delay_ms: 1500,
post_script: `tell application "VLC" post_script: `tell application "VLC"
activate activate
end tell end tell

View File

@@ -145,13 +145,18 @@ async function _refresh_journal_id_background({
obj_li: [result], obj_li: [result],
log_lvl log_lvl
}); });
await db_save_ae_obj_li__ae_obj({ // IDB write is optional caching — quota failures must not discard the API result.
db_instance: db_journals, try {
table_name: 'journal', await db_save_ae_obj_li__ae_obj({
obj_li: processed, db_instance: db_journals,
properties_to_save, table_name: 'journal',
log_lvl obj_li: processed,
}); properties_to_save,
log_lvl
});
} catch (save_error) {
console.warn('IDB cache write failed for journal (quota?):', save_error);
}
// Yield to microtask queue so Dexie liveQuery observers fire before we return // Yield to microtask queue so Dexie liveQuery observers fire before we return
await Promise.resolve(); await Promise.resolve();
} }
@@ -333,13 +338,18 @@ async function _refresh_journal_li_background({
obj_li: results, obj_li: results,
log_lvl log_lvl
}); });
await db_save_ae_obj_li__ae_obj({ // IDB write is optional caching — quota failures must not discard the API result.
db_instance: db_journals, try {
table_name: 'journal', await db_save_ae_obj_li__ae_obj({
obj_li: processed, db_instance: db_journals,
properties_to_save, table_name: 'journal',
log_lvl obj_li: processed,
}); properties_to_save,
log_lvl
});
} catch (save_error) {
console.warn('IDB cache write failed for journal (quota?):', save_error);
}
// Yield to microtask queue so Dexie liveQuery observers fire before we return // Yield to microtask queue so Dexie liveQuery observers fire before we return
await Promise.resolve(); await Promise.resolve();
} }
@@ -418,14 +428,18 @@ export async function create_ae_obj__journal({
if (log_lvl) { if (log_lvl) {
console.log('Processed object list:', processed_obj_li); console.log('Processed object list:', processed_obj_li);
} }
// Save the updated results list to the database // IDB write is optional caching — quota failures must not discard the API result.
await db_save_ae_obj_li__ae_obj({ try {
db_instance: db_journals, await db_save_ae_obj_li__ae_obj({
table_name: 'journal', db_instance: db_journals,
obj_li: processed_obj_li, table_name: 'journal',
properties_to_save: properties_to_save, obj_li: processed_obj_li,
log_lvl: log_lvl properties_to_save: properties_to_save,
}); log_lvl: log_lvl
});
} catch (save_error) {
console.warn('IDB cache write failed for journal (quota?):', save_error);
}
} }
return journal_obj_create_result; return journal_obj_create_result;
} else { } else {
@@ -531,14 +545,18 @@ export async function update_ae_obj__journal({
if (log_lvl) { if (log_lvl) {
console.log('Processed object list:', processed_obj_li); console.log('Processed object list:', processed_obj_li);
} }
// Save the updated results list to the database // IDB write is optional caching — quota failures must not discard the API result.
await db_save_ae_obj_li__ae_obj({ try {
db_instance: db_journals, await db_save_ae_obj_li__ae_obj({
table_name: 'journal', db_instance: db_journals,
obj_li: processed_obj_li, table_name: 'journal',
properties_to_save: properties_to_save, obj_li: processed_obj_li,
log_lvl: log_lvl properties_to_save: properties_to_save,
}); log_lvl: log_lvl
});
} catch (save_error) {
console.warn('IDB cache write failed for journal (quota?):', save_error);
}
} }
return result; return result;
} else { } else {

View File

@@ -46,14 +46,19 @@ export async function load_ae_obj_id__journal_entry({
if (log_lvl) { if (log_lvl) {
console.log('Processed object list:', processed_obj_li); console.log('Processed object list:', processed_obj_li);
} }
// Save the updated results list to the database // Save the updated results list to the database.
await db_save_ae_obj_li__ae_obj({ // IDB write is optional caching — quota failures must not discard the API result.
db_instance: db_journals, try {
table_name: 'journal_entry', await db_save_ae_obj_li__ae_obj({
obj_li: processed_obj_li, db_instance: db_journals,
properties_to_save: properties_to_save, table_name: 'journal_entry',
log_lvl: log_lvl obj_li: processed_obj_li,
}); properties_to_save: properties_to_save,
log_lvl: log_lvl
});
} catch (save_error) {
console.warn('IDB cache write failed for journal_entry (quota?):', save_error);
}
} }
return journal_entry_obj_get_result; return journal_entry_obj_get_result;
} else { } else {
@@ -153,19 +158,24 @@ export async function load_ae_obj_li__journal_entry({
if (log_lvl) { if (log_lvl) {
console.log('Processed object list:', processed_obj_li); console.log('Processed object list:', processed_obj_li);
} }
// Save the updated results list to the database // Save the updated results list to the database.
// IDB write is optional caching — quota failures must not discard the API result.
if (log_lvl) { if (log_lvl) {
console.log('Saving to DB...'); console.log('Saving to DB...');
} }
await db_save_ae_obj_li__ae_obj({ try {
db_instance: db_journals, await db_save_ae_obj_li__ae_obj({
table_name: 'journal_entry', db_instance: db_journals,
obj_li: processed_obj_li, table_name: 'journal_entry',
properties_to_save: properties_to_save, obj_li: processed_obj_li,
log_lvl: log_lvl properties_to_save: properties_to_save,
}); log_lvl: log_lvl
if (log_lvl) { });
console.log('DB save completed.'); if (log_lvl) {
console.log('DB save completed.');
}
} catch (save_error) {
console.warn('IDB cache write failed for journal_entry (quota?):', save_error);
} }
} }
return journal_entry_obj_li_get_result; return journal_entry_obj_li_get_result;
@@ -237,14 +247,18 @@ export async function create_ae_obj__journal_entry({
if (log_lvl) { if (log_lvl) {
console.log('Processed object list:', processed_obj_li); console.log('Processed object list:', processed_obj_li);
} }
// Save the updated results list to the database // IDB write is optional caching — quota failures must not discard the API result.
await db_save_ae_obj_li__ae_obj({ try {
db_instance: db_journals, await db_save_ae_obj_li__ae_obj({
table_name: 'journal_entry', db_instance: db_journals,
obj_li: processed_obj_li, table_name: 'journal_entry',
properties_to_save: properties_to_save, obj_li: processed_obj_li,
log_lvl: log_lvl properties_to_save: properties_to_save,
}); log_lvl: log_lvl
});
} catch (save_error) {
console.warn('IDB cache write failed for journal_entry (quota?):', save_error);
}
} }
return journal_entry_obj_create_result; return journal_entry_obj_create_result;
} else { } else {
@@ -457,13 +471,18 @@ export async function qry__journal_entry({
journal_id, journal_id,
log_lvl log_lvl
}); });
await db_save_ae_obj_li__ae_obj({ // IDB write is optional caching — quota failures must not discard the API result.
db_instance: db_journals, try {
table_name: 'journal_entry', await db_save_ae_obj_li__ae_obj({
obj_li: processed_obj_li, db_instance: db_journals,
properties_to_save, table_name: 'journal_entry',
log_lvl obj_li: processed_obj_li,
}); properties_to_save,
log_lvl
});
} catch (save_error) {
console.warn('IDB cache write failed for journal_entry (quota?):', save_error);
}
} }
return valid_result_li; return valid_result_li;
} else { } else {
@@ -588,14 +607,18 @@ export async function update_ae_obj__journal_entry({
if (log_lvl) { if (log_lvl) {
console.log('Processed object list:', processed_obj_li); console.log('Processed object list:', processed_obj_li);
} }
// Save the updated results list to the database // IDB write is optional caching — quota failures must not discard the API result.
await db_save_ae_obj_li__ae_obj({ try {
db_instance: db_journals, await db_save_ae_obj_li__ae_obj({
table_name: 'journal_entry', db_instance: db_journals,
obj_li: processed_obj_li, table_name: 'journal_entry',
properties_to_save: properties_to_save, obj_li: processed_obj_li,
log_lvl: log_lvl properties_to_save: properties_to_save,
}); log_lvl: log_lvl
});
} catch (save_error) {
console.warn('IDB cache write failed for journal_entry (quota?):', save_error);
}
} }
return result; return result;
} else { } else {
@@ -848,13 +871,13 @@ const properties_to_save = [
// 'description', // 'description',
'content', 'content',
'content_md_html', // content_md_html is computed from content on every load — not stored to save IDB quota
'content_html', 'content_html',
'content_json', 'content_json',
'content_encrypted', 'content_encrypted',
'history', 'history',
'history_md_html', // history_md_html is computed from history on every load — not stored to save IDB quota
'history_encrypted', 'history_encrypted',
'passcode_hash', 'passcode_hash',

View File

@@ -317,7 +317,7 @@ async function reset_profile_delay(profile_name: string) {
</p> </p>
</div> </div>
<div class="text-right text-[9px] leading-tight opacity-65"> <div class="text-right text-[9px] min-w-22 leading-tight opacity-65">
<div> <div>
Built-in: Built-in:
{row.profile.post_delay_ms ?? 'n/a'} ms {row.profile.post_delay_ms ?? 'n/a'} ms