5 Commits

Author SHA1 Message Date
Scott Idem
18cbe256de fix(pres_mgmt): increase file upload timeout to 20 min, guard null result
- Set post_object timeout to 1200000ms (20 min) for hosted file uploads;
  the 90s default was killing large presentation file uploads
- Guard result[0] access in .then() to prevent crash when upload
  times out or is aborted (TypeError: can't access property "hosted_file_id")

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 00:36:14 -04:00
Scott Idem
2b2324ee8a Updated to do list 2026-04-20 15:31:29 -04:00
Scott Idem
6c6fccdfb4 Tweaking the colors and timing for the Session Menu in the Launcher 2026-04-20 13:33:54 -04:00
Scott Idem
ef5188aa6d refactor(launcher): remove duplicate session load from menu_session_list
On session click/hover, the menu was calling load_ae_obj_id__event_session
directly AND then navigating via goto(), which re-runs +page.ts and calls
it again. Both fired concurrently on cold cache, causing two identical API
requests for the same session.

Fix: remove the direct load call entirely. The goto() promise is assigned
to ae_promises.slct__event_session_id so the existing #await spinner still
works — it now reflects actual navigation + page.ts load time rather than
a redundant parallel fetch. Remove events_func and ae_api imports (unused).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 13:01:16 -04:00
Scott Idem
c4fdc8efa4 fix(launcher): hidden sessions collapse space, sort by datetime, rename internal-file flag
- menu_session_list: move class:hidden to <li> so fixed-height rows fully collapse
- launcher/+layout.svelte: sort sessions by start_datetime (ascending) instead of name
- Rename hide_content__draft_files → show_content__internal_files (default false);
  remove redundant show_content__draft_files; rename prop hide_draft →
  show_internal_purpose_files in launcher_file_cont; update all 7 call sites and
  the menu_launcher_controls toggle. Now hides admin/draft/outline purpose files
  by default with consistent naming across the flag, prop, and toggle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 12:49:39 -04:00
14 changed files with 87 additions and 103 deletions

View File

@@ -14,9 +14,18 @@
warnings but can still upload. Covers `.ppt`, `.doc` (block) and other legacy exts (warn-only).
(2026-04-19)
- [x] **[Files] Hide draft/flagged files from list** — `hide_draft` prop in `launcher_file_cont.svelte`
now hides files with `file_purpose == 'outline'`, `'draft'`, or `'admin'` when the launcher's
hide-draft toggle is enabled. (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

View File

@@ -26,13 +26,12 @@ export interface LauncherLocState {
hide__ws_form: boolean;
hide__ws_messages: boolean;
hide__ws_commands: boolean;
hide_content__draft_files: boolean;
show_content__disabled_files: boolean;
show_content__hidden_files: boolean;
show_content__hidden_presentations: boolean;
show_content__hidden_presenters: boolean;
show_content__hidden_sessions: boolean;
show_content__draft_files: boolean;
show_content__internal_files: boolean;
show_content__session_code: boolean;
show_content__presentation_code: boolean;
show_content__presenter_code: boolean;
@@ -134,14 +133,12 @@ export const launcher_loc_defaults: LauncherLocState = {
hide__ws_messages: true,
hide__ws_commands: true,
hide_content__draft_files: true,
show_content__disabled_files: false,
show_content__hidden_files: false,
show_content__hidden_presentations: false,
show_content__hidden_presenters: false,
show_content__hidden_sessions: false,
show_content__draft_files: false,
show_content__internal_files: false,
// These should be renamed to match the pres_mgmt section. Use "hide" instead of "show".
show_content__session_code: true,

View File

@@ -16,7 +16,7 @@ let { children }: Props = $props();
// WHY: When running inside Electron (is_native), app_mode must be 'native' for
// the file launch path to work correctly. On a fresh install the persisted store
// has no value set, so we auto-initialise it here. This preserves the three-mode
// has no value set, so we auto-initialize it here. This preserves the three-mode
// design (default / onsite / native) — browser sessions are unaffected because
// is_native is only ever true inside the Electron shell.
$effect(() => {

View File

@@ -298,11 +298,10 @@ let lq__event_session_obj_li = $derived.by(() => {
if (!id) return [];
if (log_lvl > 1)
console.log(`LQ - Event Session list location_id: ${id}`);
// Note: .reverse() before .sortBy() is a no-op — sortBy always re-sorts.
return await db_events.session
.where('event_location_id')
.equals(id)
.sortBy('name');
.sortBy('start_datetime');
});
});

View File

@@ -9,7 +9,7 @@ interface Props {
hide_created_on?: boolean;
hide_os?: boolean;
hide_size?: boolean;
hide_draft?: boolean;
show_internal_purpose_files?: boolean;
show_bak_download?: boolean;
btn_size?: string;
btn_text_align?: string;
@@ -34,7 +34,7 @@ let {
hide_created_on = $bindable(false),
hide_os = $bindable(false),
hide_size = $bindable(false),
hide_draft = $bindable(false),
show_internal_purpose_files = $bindable(false),
show_bak_download = false,
btn_size = $bindable('btn-sm'),
btn_text_align = $bindable('text-left'),
@@ -235,7 +235,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
<div
class:justify-between={!hide_meta}
class:justify-center={hide_meta}
class:hidden={hide_draft &&
class:hidden={!show_internal_purpose_files &&
(event_file_obj.file_purpose == 'outline' ||
event_file_obj.file_purpose == 'draft' ||
event_file_obj.file_purpose == 'admin')}
@@ -386,16 +386,28 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
});
}}
class="btn btn-sm group transition-all"
class:preset-tonal-success={event_file_obj?.open_in_os == 'win'}
class:preset-tonal-warning={event_file_obj?.open_in_os == 'mac'}
disabled={!$ae_loc.trusted_access}>
{#if event_file_obj?.open_in_os == 'win'}<Monitor
class:preset-tonal-warning={event_file_obj?.open_in_os == 'win'}
class:preset-tonal-success={event_file_obj?.open_in_os == 'mac'}
disabled={!$ae_loc.trusted_access}
title={`Open in OS: ${
event_file_obj?.open_in_os
? event_file_obj.open_in_os.toUpperCase()
: 'None'
}`}
>
{#if event_file_obj?.open_in_os == 'win'}
<!-- <Monitor
size="1em"
class="m-1" />
{:else if event_file_obj?.open_in_os == 'mac'}<Laptop
class="m-1" /> -->
Win
{:else if event_file_obj?.open_in_os == 'mac'}
<!-- <Laptop
size="1em"
class="m-1" />
{:else}<FolderOpen size="1em" class="m-1" />{/if}
class="m-1" /> -->
Mac
{:else}
<FolderOpen size="1em" class="m-1" />
{/if}
</button>
<span

View File

@@ -171,6 +171,7 @@ let ae_promises: key_val = $state({
hide_created_on={true}
hide_os={true}
hide_size={true}
show_internal_purpose_files={$events_loc.launcher.show_content__internal_files}
show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode}
btn_size={'btn-sm'}
@@ -213,6 +214,7 @@ let ae_promises: key_val = $state({
hide_created_on={true}
hide_os={true}
hide_size={true}
show_internal_purpose_files={$events_loc.launcher.show_content__internal_files}
show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode}
btn_size={'btn-sm'}

View File

@@ -67,8 +67,8 @@ let lq__event_file_obj_li = $derived(
{event_file_obj}
hide_created_on={false}
hide_meta={session_type == 'poster'}
bind:hide_draft={
$events_loc.launcher.hide_content__draft_files
bind:show_internal_purpose_files={
$events_loc.launcher.show_content__internal_files
}
show_bak_download={$ae_loc.trusted_access}
session_type={session_type || 'oral'}

View File

@@ -96,8 +96,8 @@ let lq__event_file_obj_li = $derived(
event_file_id={event_file_obj.event_file_id}
{event_file_obj}
hide_created_on={false}
bind:hide_draft={
$events_loc.launcher.hide_content__draft_files
bind:show_internal_purpose_files={
$events_loc.launcher.show_content__internal_files
}
show_bak_download={$ae_loc.trusted_access}
session_type={session_type || 'oral'}

View File

@@ -91,6 +91,7 @@ let lq__event_file_obj_li = $derived(
{event_file_obj}
hide_created_on={false}
hide_meta={true}
show_internal_purpose_files={$events_loc.launcher.show_content__internal_files}
show_bak_download={$ae_loc.trusted_access}
session_type="poster"
open_method="modal"

View File

@@ -306,6 +306,8 @@ let ae_promises: key_val = $state({});
event_file_id={event_file_obj.event_file_id}
{event_file_obj}
hide_created_on={true}
show_internal_purpose_files={$events_loc.launcher
.show_content__internal_files}
show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode}
session_type={type_code || 'oral'}

View File

@@ -149,6 +149,8 @@ let poster_count = $derived($lq__event_presentation_obj_li?.length ?? 0);
event_file_id={event_file_obj.event_file_id}
{event_file_obj}
hide_created_on={true}
show_internal_purpose_files={$events_loc.launcher
.show_content__internal_files}
show_bak_download={$ae_loc.trusted_access &&
$ae_loc.edit_mode}
session_type="poster"

View File

@@ -40,10 +40,10 @@ let { log_lvl = $bindable(0) }: Props = $props();
onclick={() => {
if ($events_loc.launcher.show_content__hidden_files) {
$events_loc.launcher.show_content__hidden_files = false;
$events_loc.launcher.hide_content__draft_files = true;
$events_loc.launcher.show_content__internal_files = false;
} else {
$events_loc.launcher.show_content__hidden_files = true;
$events_loc.launcher.hide_content__draft_files = false;
$events_loc.launcher.show_content__internal_files = true;
}
}}
class="

View File

@@ -79,7 +79,6 @@ import {
ae_snip,
ae_loc,
ae_sess,
ae_api,
ae_trig,
slct,
slct_trigger
@@ -90,7 +89,6 @@ import {
events_slct,
events_trigger
} from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events/ae_events_functions';
import {
CalendarCheck,
CalendarDays,
@@ -115,7 +113,7 @@ let ae_promises: key_val = $state({
// focus on a row for over a second before it fires — still fast for intentional use.
// NOTE: hover-timer only triggers a data PRE-LOAD (preview). The session does not
// actually switch until the operator clicks. See onclick handler below.
let hover_timer_wait = 1200;
let hover_timer_wait = 1400;
let hover_timer: any = $state(null);
// Navigation Shield Pattern (Refactored 2026-02-11)
@@ -142,26 +140,26 @@ $effect(() => {
slct__event_session_id = event_session_id;
}
// 3. Background Data Fetch
handle_load_ae_obj_id__event_session(event_session_id);
// 4. Remote Control Sync
// 3. Remote Control Sync
if ($events_loc.launcher.controller == 'local_push') {
$events_sess.launcher.controller_cmd = `ae_load:event_session=${event_session_id}`;
$events_sess.launcher.controller_trigger_send = true;
}
// 5. URL Navigation
// 4. URL Navigation — the single load fires from +page.ts when the URL updates.
// Assigning the goto promise to ae_promises drives the #await spinner in the template.
loading__session_id_status = 'loading';
let new_url_obj = new URL(page.url);
new_url_obj.searchParams.set('session_id', event_session_id);
if (log_lvl) console.log(`[UI Trace] Initiating SvelteKit goto...`);
goto(new_url_obj.toString(), {
ae_promises.slct__event_session_id = goto(new_url_obj.toString(), {
replaceState: false,
noScroll: true,
keepFocus: true
}).then(() => {
loading__session_id_status = false;
if (log_lvl)
console.log(
`🏁 [Trace] Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`
@@ -170,45 +168,6 @@ $effect(() => {
});
}
});
function handle_load_ae_obj_id__event_session(event_session_id: any) {
const start = performance.now();
if (log_lvl) {
console.log(
`[UI Trace] handle_load_ae_obj_id__event_session: Calling library for id=${event_session_id}`
);
}
loading__session_id_status = 'loading';
ae_promises.slct__event_session_id = events_func
.load_ae_obj_id__event_session({
api_cfg: $ae_api,
event_session_id: event_session_id,
inc_file_li: true,
inc_all_file_li: true,
inc_presentation_li: true,
inc_presenter_li: true,
log_lvl: log_lvl
})
.then(async (load_results) => {
if (log_lvl)
console.log(
`[UI Trace] handle_load_ae_obj_id: Library returned results in ${(performance.now() - start).toFixed(2)}ms.`
);
if (load_results) {
$events_slct.event_session_obj = load_results;
$events_slct.event_file_obj_li =
load_results.event_file_li ?? [];
$events_slct.event_presentation_obj_li =
load_results.event_presentation_li ?? [];
}
})
.finally(() => {
loading__session_id_status = false;
});
}
</script>
<div
@@ -252,7 +211,11 @@ function handle_load_ae_obj_id__event_session(event_session_id: any) {
max-w-full p-0
"
class:session-active={slct__event_session_id ===
event_session_obj?.id}>
event_session_obj?.id}
class:hidden={!$events_loc.launcher
.show_content__hidden_sessions &&
(event_session_obj?.hide ||
event_session_obj?.hide_event_launcher)}>
<button
type="button"
onmouseenter={() => {
@@ -280,34 +243,26 @@ function handle_load_ae_obj_id__event_session(event_session_id: any) {
$events_slct.event_file_obj = null;
}}
class="
session-btn
btn btn-sm
focus-visible:ring-primary-400 m-0 flex
session-btn
btn btn-sm
focus-visible:ring-primary-400 m-0 flex
w-full
max-w-full flex-row
items-center
justify-start
rounded-md px-1.5
w-full
max-w-full flex-row
items-center
justify-start
rounded-md px-1.5
py-1
text-left text-sm transition-colors duration-200
focus-visible:ring-2 focus-visible:ring-offset-1
"
class:preset-filled-primary={slct__event_session_id ===
event_session_obj?.id}
class:preset-tonal-secondary={slct__event_session_id !=
event_session_obj?.id}
class:border-secondary-500={slct__event_session_id !=
event_session_obj?.id}
class:font-bold={slct__event_session_id ===
event_session_obj?.id}
class:hidden={!$events_loc.launcher
.show_content__hidden_sessions &&
(event_session_obj?.hide ||
event_session_obj?.hide_event_launcher)}
class:opacity-40={event_session_obj?.hide ||
event_session_obj?.hide_event_launcher}
py-1
text-left text-sm transition-colors duration-200
focus-visible:ring-2 focus-visible:ring-offset-1
"
class:font-bold={slct__event_session_id === event_session_obj?.id}
class:preset-tonal-primary={slct__event_session_id === event_session_obj?.id}
class:preset-outlined-primary-200-800={slct__event_session_id === event_session_obj?.id}
class:preset-tonal-secondary={slct__event_session_id != event_session_obj?.id}
class:preset-outlined-secondary-200-800={slct__event_session_id != event_session_obj?.id}
class:opacity-40={event_session_obj?.hide || event_session_obj?.hide_event_launcher}
title={`Session: ${event_session_obj?.name}\nID: ${event_session_obj?.id} | ${ae_util.iso_datetime_formatter(event_session_obj?.start_datetime, $events_loc.launcher.time_format)}`}>
<!-- Session row layout: [date column | session name]
Date column is fixed-width (shrink-0) so name column always

View File

@@ -153,11 +153,16 @@ async function handle_input_upload_files({
api_cfg: $ae_api,
endpoint: '/v3/action/hosted_file/upload',
form_data: form_data,
timeout: 1200000, // 20 minutes — large presentation files can be very slow to upload
task_id: task_id,
log_lvl: log_lvl
})
.then(async function (result) {
// WARNING: endpoint returns a list, we sequentially handle one at a time.
if (!result || !result[0]) {
console.error('Upload failed or timed out — no result returned.');
return false;
}
const hosted_file_obj = result[0];
const hosted_file_id = hosted_file_obj.hosted_file_id;