feat(launcher): add Native Test Mode for profile/command preview
Enables testing the LaunchProfile system from any device (no Mac/Electron
needed). When active, the Open button simulates the full native flow and
shows a debug popup with everything that WOULD be sent to Electron.
- ae_events_stores__launcher_defaults.ts: add native_test_mode boolean
(persisted, default false) to LauncherLocState and launcher_loc_defaults.
- launcher_cfg_app_modes.svelte: add Native Test Mode checkbox toggle in
the Advanced Toggles (Edit Mode Only) section with active-state warning.
- launcher_file_cont.svelte:
- Add test_mode_popup_open/test_mode_popup_data state vars.
- Add branch 0 in handle_open_file(): when native_test_mode + app_mode=native,
skip all Electron calls; resolve the real LaunchProfile, build a data
snapshot, open the debug popup.
- Debug popup shows: file info, simulated temp path, cache/copy pass,
resolved LaunchProfile fields, set_display_layout call, open command
(run_cmd or open_local_file_v2 fallback), sleep delay, post-script
(AppleScript or shell: prefix). Click backdrop or Close to dismiss.
This commit is contained in:
@@ -78,6 +78,13 @@ export interface LauncherLocState {
|
||||
controller: string;
|
||||
controller_group_code: string;
|
||||
controller_client_id: string | null;
|
||||
/**
|
||||
* Native test mode: simulates the full native-branch open flow without Electron.
|
||||
* Shows a debug popup with the resolved profile, commands, and AppleScript instead
|
||||
* of actually launching files. Useful for testing LaunchProfile config from any
|
||||
* device/OS without deploying to the Mac laptop.
|
||||
*/
|
||||
native_test_mode: boolean;
|
||||
}
|
||||
|
||||
export interface LauncherSessState {
|
||||
@@ -198,7 +205,8 @@ export const launcher_loc_defaults: LauncherLocState = {
|
||||
|
||||
controller: 'local',
|
||||
controller_group_code: 'launcher-00',
|
||||
controller_client_id: null
|
||||
controller_client_id: null,
|
||||
native_test_mode: false
|
||||
// controller_cmd: null,
|
||||
// controller_trigger_send: null,
|
||||
};
|
||||
|
||||
@@ -230,6 +230,32 @@ function apply_mode(mode: 'poster' | 'oral') {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. Native Test Mode (Edit Mode Only) -->
|
||||
<!-- Simulates the full native open flow without Electron/Mac hardware.
|
||||
Shows what commands, profile, and AppleScript WOULD be sent,
|
||||
as a popup, instead of actually running them. -->
|
||||
<div
|
||||
class="border-surface-500/20 col-span-full mt-1 flex flex-col gap-2 border-t pt-3">
|
||||
<p class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||
Dev / Testing
|
||||
</p>
|
||||
<label class="group flex cursor-pointer items-center gap-2 p-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$events_loc.launcher.native_test_mode}
|
||||
class="checkbox checkbox-sm" />
|
||||
<span class="group-hover:text-warning-500 text-xs italic">
|
||||
Native Test Mode
|
||||
</span>
|
||||
</label>
|
||||
{#if $events_loc.launcher.native_test_mode}
|
||||
<p class="ml-1 text-[8px] leading-tight italic text-yellow-400/70">
|
||||
⚠ Active: Open buttons will simulate native launch and
|
||||
show a debug popup instead of running commands.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Launcher_Cfg_Section>
|
||||
|
||||
@@ -91,6 +91,10 @@ let open_file_status: null | string = $state(null);
|
||||
let open_file_status_message: null | string = $state(null);
|
||||
let open_file_error_detail: string | null = $state(null);
|
||||
|
||||
/** State for the native test mode debug popup */
|
||||
let test_mode_popup_open: boolean = $state(false);
|
||||
let test_mode_popup_data: Record<string, any> | null = $state(null);
|
||||
|
||||
/** Simple promise-based delay for post-open script timing */
|
||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
@@ -132,6 +136,43 @@ async function handle_open_file() {
|
||||
$events_slct.event_file_id = event_file_id;
|
||||
$events_slct.event_file_obj = event_file_obj;
|
||||
|
||||
// 0. NATIVE TEST MODE — simulate full native flow, show debug popup instead of running commands
|
||||
// Active when native_test_mode toggle is on (regardless of is_native / app_mode).
|
||||
// Lets you preview the resolved profile, open command, and post-script from any device.
|
||||
if ($events_loc.launcher.native_test_mode && $events_loc.launcher.app_mode === 'native') {
|
||||
open_file_clicked = true;
|
||||
open_file_status = 'checking_cache';
|
||||
open_file_status_message = 'Test Mode: simulating cache check...';
|
||||
|
||||
await sleep(400); // Brief simulated cache check
|
||||
|
||||
open_file_status = 'opening_file';
|
||||
open_file_status_message = 'Test Mode: resolving launch profile...';
|
||||
|
||||
const profile = get_launch_profile(event_file_obj.extension, event_file_obj);
|
||||
const open_cmd_resolved = profile.open_cmd
|
||||
? profile.open_cmd.replaceAll('{{path}}', `/tmp/ae_test/${event_file_obj.filename}`)
|
||||
: null;
|
||||
|
||||
test_mode_popup_data = {
|
||||
filename: event_file_obj.filename,
|
||||
extension: event_file_obj.extension,
|
||||
hash_sha256: event_file_obj.hash_sha256,
|
||||
simulated_temp_path: `/tmp/ae_test/${event_file_obj.filename}`,
|
||||
profile,
|
||||
open_cmd_resolved,
|
||||
display_override: event_file_obj?.cfg_json?.display_override ?? null,
|
||||
cache_check: 'PASS (simulated)',
|
||||
copy_to_temp: 'PASS (simulated)'
|
||||
};
|
||||
test_mode_popup_open = true;
|
||||
|
||||
open_file_status = 'open';
|
||||
open_file_status_message = 'Test Mode: profile resolved — see popup';
|
||||
setTimeout(() => (open_file_clicked = false), 6000);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1. NATIVE MODE (Electron)
|
||||
if ($ae_loc.is_native && $events_loc.launcher.app_mode === 'native') {
|
||||
const cache_root = $ae_loc.local_file_cache_path;
|
||||
@@ -595,3 +636,121 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Native Test Mode Debug Popup -->
|
||||
<!-- Shows what WOULD be sent to Electron: resolved profile, open command, post-script.
|
||||
Appears when native_test_mode is active and a file is "opened". -->
|
||||
{#if test_mode_popup_open && test_mode_popup_data}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 p-4"
|
||||
role="presentation"
|
||||
onclick={() => (test_mode_popup_open = false)}>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="bg-surface-900 border-warning-500/40 relative flex max-h-[90vh] w-full max-w-2xl flex-col gap-0 overflow-hidden rounded-xl border shadow-2xl"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Native Test Mode Debug Info"
|
||||
tabindex="-1"
|
||||
onclick={(e) => e.stopPropagation()}>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="bg-warning-500/10 border-warning-500/30 flex items-center gap-2 border-b px-4 py-3">
|
||||
<span class="text-warning-400 font-mono text-xs font-bold uppercase tracking-wider">
|
||||
🧪 Native Test Mode — What would run on Mac
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (test_mode_popup_open = false)}
|
||||
class="btn btn-xs preset-tonal-surface ml-auto">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable content -->
|
||||
<div class="flex flex-col gap-3 overflow-y-auto p-4 font-mono text-xs">
|
||||
|
||||
<!-- File info -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[9px] font-bold uppercase opacity-50">File</span>
|
||||
<div class="rounded bg-black/30 px-3 py-2 leading-relaxed">
|
||||
<div><span class="opacity-50">filename: </span>{test_mode_popup_data.filename}</div>
|
||||
<div><span class="opacity-50">extension: </span>{test_mode_popup_data.extension}</div>
|
||||
<div><span class="opacity-50">hash: </span><span class="opacity-60">{test_mode_popup_data.hash_sha256}</span></div>
|
||||
<div><span class="opacity-50">temp path: </span><span class="text-green-400">{test_mode_popup_data.simulated_temp_path}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cache / copy -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[9px] font-bold uppercase opacity-50">Steps 1–2</span>
|
||||
<div class="rounded bg-black/30 px-3 py-2 leading-relaxed">
|
||||
<div><span class="opacity-50">check_hash_file_cache: </span><span class="text-green-400">{test_mode_popup_data.cache_check}</span></div>
|
||||
<div><span class="opacity-50">copy_from_cache_to_temp: </span><span class="text-green-400">{test_mode_popup_data.copy_to_temp}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resolved profile -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[9px] font-bold uppercase opacity-50">Resolved LaunchProfile</span>
|
||||
<div class="rounded bg-black/30 px-3 py-2 leading-relaxed">
|
||||
<div><span class="opacity-50">app: </span><span class="text-primary-300">{test_mode_popup_data.profile.app}</span></div>
|
||||
<div><span class="opacity-50">display_mode: </span><span class:text-primary-300={test_mode_popup_data.profile.display_mode === 'extend'} class:text-yellow-300={test_mode_popup_data.profile.display_mode === 'mirror'} class:opacity-40={test_mode_popup_data.profile.display_mode === 'none'}>{test_mode_popup_data.profile.display_mode}</span></div>
|
||||
{#if test_mode_popup_data.display_override}
|
||||
<div><span class="text-yellow-400 opacity-80">display_override (cfg_json): </span><span class="text-yellow-300">{test_mode_popup_data.display_override}</span></div>
|
||||
{/if}
|
||||
<div><span class="opacity-50">post_delay_ms: </span>{test_mode_popup_data.profile.post_delay_ms ?? 2000}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: set_display_layout -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[9px] font-bold uppercase opacity-50">Step 3 — set_display_layout</span>
|
||||
<div class="rounded bg-black/30 px-3 py-2">
|
||||
{#if test_mode_popup_data.profile.display_mode !== 'none'}
|
||||
<span class="text-primary-300">native.set_display_layout({{ mode: '{test_mode_popup_data.profile.display_mode}' }})</span>
|
||||
{:else}
|
||||
<span class="opacity-40">skipped (display_mode: none)</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 4: open command -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[9px] font-bold uppercase opacity-50">Step 4 — Open File</span>
|
||||
<div class="rounded bg-black/30 px-3 py-2">
|
||||
{#if test_mode_popup_data.open_cmd_resolved}
|
||||
<div class="mb-1 opacity-50 text-[9px]">native.run_cmd()</div>
|
||||
<pre class="whitespace-pre-wrap break-all text-green-300">{test_mode_popup_data.open_cmd_resolved}</pre>
|
||||
{:else}
|
||||
<div class="opacity-50 text-[9px]">native.open_local_file_v2()</div>
|
||||
<span class="text-yellow-300">{test_mode_popup_data.simulated_temp_path}</span>
|
||||
<span class="ml-2 opacity-40">(OS default handler)</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 5: post-script -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[9px] font-bold uppercase opacity-50">Steps 5–6 — Wait + Post-Script</span>
|
||||
<div class="rounded bg-black/30 px-3 py-2">
|
||||
<div class="mb-1 opacity-50">sleep({test_mode_popup_data.profile.post_delay_ms ?? 2000}ms)</div>
|
||||
{#if test_mode_popup_data.profile.post_script}
|
||||
{#if test_mode_popup_data.profile.post_script.startsWith('shell:')}
|
||||
<div class="mb-1 opacity-50 text-[9px]">native.run_cmd() [shell prefix]</div>
|
||||
<pre class="whitespace-pre-wrap break-all text-yellow-300">{test_mode_popup_data.profile.post_script.slice(6)}</pre>
|
||||
{:else}
|
||||
<div class="mb-1 opacity-50 text-[9px]">native.run_osascript() [AppleScript]</div>
|
||||
<pre class="whitespace-pre-wrap text-purple-300">{test_mode_popup_data.profile.post_script}</pre>
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="opacity-40">no post_script</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- end scroll -->
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user