refactor(launcher): canonicalize default profiles

This commit is contained in:
Scott Idem
2026-05-13 12:15:13 -04:00
parent c79ae92be0
commit 1374f0728e
5 changed files with 210 additions and 286 deletions

View File

@@ -37,7 +37,7 @@ platform is flexible enough to handle the full range.
### Object Hierarchy ### Object Hierarchy
``` ```text
Event Event
├── Event File (walk-in/out, hold slides for the whole event) ├── Event File (walk-in/out, hold slides for the whole event)
├── Location (physical room — assigned to Sessions, not the other way around) ├── Location (physical room — assigned to Sessions, not the other way around)
@@ -314,11 +314,30 @@ The Electron app zero-configs itself:
**Configurable launch behavior:** The file-open behavior is driven by a Launch Profile, not **Configurable launch behavior:** The file-open behavior is driven by a Launch Profile, not
just a command string. Profiles are stored per file extension in just a command string. Profiles are stored per file extension in
`event_device.data_json.launch_profiles` (device-level config) or `event_device.data_json.launch_profiles` (device-level config) or
`event.launcher.launch_profiles` (event-level fallback). A profile can choose the app, `event.launcher.launch_profiles` (event-level fallback). The built-in Svelte defaults are the
display mode, open command, and post-open automation. The resolved native template uses final fallback and are documented below. A profile can choose the app, display mode, open
`{{path}}` as the file path placeholder; AppleScript or `shell:` prefixed commands are both command, and post-open automation. The resolved native template uses `{{path}}` as the file
supported. No Electron rebuild is required to change how files open — edit config in Aether path placeholder; AppleScript or `shell:` prefixed commands are both supported. No Electron
and it applies immediately. See `PROJECT__AE_Events_Launcher_Native_integration.md` Section 8. rebuild is required to change how files open — edit config in Aether and it applies
immediately. See `PROJECT__AE_Events_Launcher_Native_integration.md` Section 8.
### Built-In Default Launch Profiles
These are the initial built-in defaults shipped with the Launcher. They are the Svelte-side
fallbacks used when neither device config nor event config defines a profile for the file
extension. Each canonical profile can have multiple extension aliases.
| Profile name | Extension aliases | Default app | Display mode | Notes |
|---|---|---|---|---|
| `powerpoint_mac_extend` | `pptx`, `ppt` | Microsoft PowerPoint for macOS | `extend` | Open in the presentation app and extend to an external display if one is present. |
| `keynote_mac_extend` | `key` | Keynote | `extend` | Keynote slideshow on the external display if available. |
| `libreoffice_mac_extend` | `odp` | LibreOffice for macOS | `extend` | LibreOffice Impress for OpenDocument presentations. |
| `acrobat_mac_mirror` | `pdf` | Adobe Acrobat for macOS | `mirror` | PDF handout / deck view uses mirrored display. |
| `vlc_mirror` | `mp4`, `mkv`, `mp3`, `m4v`, `m4a`, `webm`, `wav`, `aac`, `flac`, `mov`, `mpeg`, `avi`, `flv`, `ogg`, `ogv`, `wmv` | VLC for macOS | `mirror` | Media playback is mirrored so the room sees the same output as the operator. |
| `powerpoint_win_extend` | `pptxwin`, `pptwin` | PowerPoint for Windows (Parallels) | `extend` | Windows PowerPoint profile for Parallels-based rooms. |
| `libreoffice_win_extend` | `odpwin` | LibreOffice for Windows | `extend` | Windows LibreOffice profile for Parallels-based rooms. |
| `acrobat_win_mirror` | `pdfwin` | Adobe Acrobat for Windows (Parallels) | `mirror` | Windows PDF profile for mirrored display rooms. |
| `url_web` | `url` | Browser / Event File web presentation | `extend` | Web-based presentations are handled as Event File URLs rather than cached local files. |
Versioning is handled automatically: when a presenter uploads an updated file, the new Versioning is handled automatically: when a presenter uploads an updated file, the new
hash is cached separately and the old one remains intact. hash is cached separately and the old one remains intact.

View File

@@ -41,7 +41,8 @@ The integration is built on a decoupled three-layer communication model to ensur
- **Role:** Provides a clean, typed API for Svelte components. - **Role:** Provides a clean, typed API for Svelte components.
- **Responsibilities:** - **Responsibilities:**
- Mapping `camelCase` UI triggers to `snake_case` IPC calls. - Mapping `camelCase` UI triggers to `snake_case` IPC calls.
- Resolving a Launch Profile to a single `native_template` string before crossing IPC. - Resolving an extension alias to a canonical Launch Profile, then to a single
`native_template` string before crossing IPC.
The reason for this split is simple: Launch Profiles are policy, while Native Templates are The reason for this split is simple: Launch Profiles are policy, while Native Templates are
executable strings. Keeping that distinction explicit prevents the bridge from mixing config executable strings. Keeping that distinction explicit prevents the bridge from mixing config
@@ -199,6 +200,11 @@ resolved a template yet, it should stop before IPC and surface a missing-profile
This keeps all fallback logic in Svelte, where it can be edited without rebuilding Electron. This keeps all fallback logic in Svelte, where it can be edited without rebuilding Electron.
The native layer should not invent or guess a default launch path. The native layer should not invent or guess a default launch path.
The built-in defaults are organized as canonical profile names plus extension aliases. That
lets multiple file types share one profile without repeating the same app/script details.
URL-based presentations remain a special pseudo-extension handled separately from the cache
open flow.
### Native Template Formats ### Native Template Formats
| Format | Example | | Format | Example |

View File

@@ -22,11 +22,15 @@ guessing defaults.
- [x] `get_launch_profile()` in `launcher_file_cont.svelte` reads from device config then event config; resolves to a `native_template` string and passes it to `launch_from_cache` - [x] `get_launch_profile()` in `launcher_file_cont.svelte` reads from device config then event config; resolves to a `native_template` string and passes it to `launch_from_cache`
**Svelte-side migration — remaining before May 26:** **Svelte-side migration — remaining before May 26:**
- [ ] **[Launcher] Built-in Svelte default profiles** — move the "known good" pptx/key/pdf - [ ] **[Launcher] Built-in Svelte default profiles** — move the built-in presentation/media
policy objects into a Svelte constants file (e.g. `ae_launcher__default_launch_profiles.ts`). policy objects into a Svelte constants file (e.g. `ae_launcher__default_launch_profiles.ts`).
Priority: `get_launch_profile()` already checks device config and event config; add a 3rd Use canonical profile names plus extension aliases so the media family does not repeat the
fallback to these Svelte defaults before returning `null`. Keep the fallback in Svelte, not same VLC config for every file type. Cover the core macOS set (`pptx`, `ppt`, `key`, `odp`,
in Electron. `pdf`), the media set (`mp4`, `mkv`, `mp3`, and related media types), the Windows /
Parallels variants (`pptxwin`, `pptwin`, `odpwin`, `pdfwin`), and the URL/web-based
presentation path. Priority: `get_launch_profile()` already checks device config and event
config; add a 3rd fallback to these Svelte defaults before returning `null`. Keep the
fallback in Svelte, not in Electron.
- [ ] **[Launcher] Composable open flow** — refactor `handle_open_file()` to use - [ ] **[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 `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

View File

@@ -8,7 +8,7 @@
* 1. event_file.cfg_json.display_override — per-file, display_mode only * 1. event_file.cfg_json.display_override — per-file, display_mode only
* 2. event_device.data_json.launch_profiles[ext] — full profile, per device (API) * 2. event_device.data_json.launch_profiles[ext] — full profile, per device (API)
* 3. $events_loc.launcher.launch_profiles[ext] — local persistent override * 3. $events_loc.launcher.launch_profiles[ext] — local persistent override
* 4. DEFAULT_LAUNCH_PROFILES[ext] — these built-ins * 4. DEFAULT_LAUNCH_PROFILES[ext] — extension aliases to canonical built-ins
* 5. DEFAULT_LAUNCH_PROFILES['default'] — catch-all * 5. DEFAULT_LAUNCH_PROFILES['default'] — catch-all
* *
* Keys are lowercase file extensions without the dot: "pptx", "key", "pdf", etc. * Keys are lowercase file extensions without the dot: "pptx", "key", "pdf", etc.
@@ -18,9 +18,12 @@
* - Plain string → run as AppleScript via run_osascript() (macOS only) * - Plain string → run as AppleScript via run_osascript() (macOS only)
* - "shell:..." prefix → run as shell command via run_cmd() * - "shell:..." prefix → run as shell command via run_cmd()
* *
* Reserved for future use (not yet read anywhere): * Reserved for future use:
* - speed_factor: number — delay multiplier for slower machines (1.0 = normal) * - speed_factor: number — delay multiplier for slower machines (1.0 = normal)
* - url: string — for URL-type presentations (e.g. Google Slides) *
* Special pseudo-extension:
* - url — web-based presentations. Handled by the launcher URL branch rather
* than a cache-to-temp open flow.
*/ */
export interface LaunchProfile { export interface LaunchProfile {
@@ -46,18 +49,29 @@ export interface LaunchProfile {
// url?: string; // url?: string;
} }
export const DEFAULT_LAUNCH_PROFILES: Record<string, LaunchProfile> = { function make_vlc_mirror_profile(): LaunchProfile {
return {
// ------------------------------------------------------------------------- app: 'VLC',
// macOS presentation formats display_mode: 'mirror',
// ------------------------------------------------------------------------- open_cmd: 'open -a "VLC" "{{path}}"',
pptx: {
app: 'Microsoft PowerPoint',
display_mode: 'extend',
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"',
post_delay_ms: 2000, post_delay_ms: 2000,
post_script: `tell application "Microsoft PowerPoint" post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
};
}
const POWERPOINT_MAC_EXTEND_PROFILE: LaunchProfile = {
app: 'Microsoft PowerPoint',
display_mode: 'extend',
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "Microsoft PowerPoint"
activate activate
end tell end tell
tell application "System Events" tell application "System Events"
@@ -65,40 +79,25 @@ tell application "System Events"
keystroke return using command down keystroke return using command down
end tell end tell
end tell` end tell`
}, };
ppt: { const KEYNOTE_MAC_EXTEND_PROFILE: LaunchProfile = {
app: 'Microsoft PowerPoint', app: 'Keynote',
display_mode: 'extend', display_mode: 'extend',
open_cmd: 'open -a "Microsoft PowerPoint" "{{path}}"', open_cmd: 'open -a "Keynote" "{{path}}"',
post_delay_ms: 2000, post_delay_ms: 2000,
post_script: `tell application "Microsoft PowerPoint" post_script: `tell application "Keynote"
activate
end tell
tell application "System Events"
tell process "Microsoft PowerPoint"
keystroke return using command down
end tell
end tell`
},
key: {
app: 'Keynote',
display_mode: 'extend',
open_cmd: 'open -a "Keynote" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "Keynote"
activate activate
start (front document) start (front document)
end tell` end tell`
}, };
odp: { const LIBREOFFICE_MAC_EXTEND_PROFILE: LaunchProfile = {
app: 'LibreOffice', app: 'LibreOffice',
display_mode: 'extend', display_mode: 'extend',
open_cmd: 'open -a "LibreOffice" "{{path}}"', open_cmd: 'open -a "LibreOffice" "{{path}}"',
post_delay_ms: 2000, post_delay_ms: 2000,
post_script: `tell application "LibreOffice" post_script: `tell application "LibreOffice"
activate activate
end tell end tell
tell application "System Events" tell application "System Events"
@@ -106,14 +105,14 @@ tell application "System Events"
key code 96 key code 96
end tell end tell
end tell` end tell`
}, };
pdf: { const ACROBAT_MAC_MIRROR_PROFILE: LaunchProfile = {
app: 'Adobe Acrobat Reader DC', app: 'Adobe Acrobat Reader DC',
display_mode: 'mirror', display_mode: 'mirror',
open_cmd: 'open -a "Adobe Acrobat Reader DC" "{{path}}"', open_cmd: 'open -a "Adobe Acrobat Reader DC" "{{path}}"',
post_delay_ms: 2000, post_delay_ms: 2000,
post_script: `tell application "Adobe Acrobat Reader DC" post_script: `tell application "Adobe Acrobat Reader DC"
activate activate
end tell end tell
tell application "System Events" tell application "System Events"
@@ -121,198 +120,29 @@ tell application "System Events"
keystroke "l" using command down keystroke "l" using command down
end tell end tell
end tell` end tell`
}, };
// ------------------------------------------------------------------------- const VLC_MIRROR_PROFILE: LaunchProfile = make_vlc_mirror_profile();
// Media (VLC) — mirror display
// -------------------------------------------------------------------------
mp4: { const POWERPOINT_WIN_EXTEND_PROFILE: LaunchProfile = {
app: 'VLC', app: 'Microsoft Office PowerPoint (Windows)',
display_mode: 'mirror', display_mode: 'extend',
open_cmd: 'open -a "VLC" "{{path}}"', open_cmd: 'open -a "Microsoft Office PowerPoint" "{{path}}"',
post_delay_ms: 2000, post_delay_ms: 3000,
post_script: `tell application "VLC" post_script: `tell application "Microsoft Office PowerPoint"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
mkv: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
mov: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
mpeg: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
avi: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
flv: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
ogg: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
ogv: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
mp3: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
wmv: {
app: 'VLC',
display_mode: 'mirror',
open_cmd: 'open -a "VLC" "{{path}}"',
post_delay_ms: 2000,
post_script: `tell application "VLC"
activate
end tell
tell application "System Events"
tell process "VLC"
keystroke "f" using command down
end tell
end tell`
},
// -------------------------------------------------------------------------
// Windows / Parallels variants — longer post_delay_ms (Parallels needs more time)
// -------------------------------------------------------------------------
pptxwin: {
app: 'Microsoft Office PowerPoint (Windows)',
display_mode: 'extend',
open_cmd: 'open -a "Microsoft Office PowerPoint" "{{path}}"',
post_delay_ms: 3000,
post_script: `tell application "Microsoft Office PowerPoint"
activate activate
end tell end tell
tell application "System Events" tell application "System Events"
key code 96 key code 96
end tell` end tell`
}, };
pptwin: { const LIBREOFFICE_WIN_EXTEND_PROFILE: LaunchProfile = {
app: 'Microsoft Office PowerPoint (Windows)', app: 'LibreOffice (Windows)',
display_mode: 'extend', display_mode: 'extend',
open_cmd: 'open -a "Microsoft Office PowerPoint" "{{path}}"', open_cmd: 'open -a "LibreOffice" "{{path}}"',
post_delay_ms: 3000, post_delay_ms: 3000,
post_script: `tell application "Microsoft Office PowerPoint" post_script: `tell application "LibreOffice"
activate
end tell
tell application "System Events"
key code 96
end tell`
},
odpwin: {
app: 'LibreOffice (Windows)',
display_mode: 'extend',
open_cmd: 'open -a "LibreOffice" "{{path}}"',
post_delay_ms: 3000,
post_script: `tell application "LibreOffice"
activate activate
end tell end tell
tell application "System Events" tell application "System Events"
@@ -320,46 +150,110 @@ tell application "System Events"
key code 96 key code 96
end tell end tell
end tell` end tell`
}, };
pdfwin: { const ACROBAT_WIN_MIRROR_PROFILE: LaunchProfile = {
app: 'Acrobat Reader (Windows)', app: 'Acrobat Reader (Windows)',
display_mode: 'mirror', display_mode: 'mirror',
open_cmd: 'open -a "Acrobat Reader Windows" "{{path}}"', open_cmd: 'open -a "Acrobat Reader Windows" "{{path}}"',
post_delay_ms: 3000, post_delay_ms: 3000,
post_script: `tell application "Acrobat Reader Windows" post_script: `tell application "Acrobat Reader Windows"
activate activate
end tell end tell
tell application "System Events" tell application "System Events"
key code 108 using control down key code 108 using control down
end tell` end tell`
},
// -------------------------------------------------------------------------
// Catch-all — OS default handler, no display change
// Works on macOS (open) and Linux (xdg-open via open_local_file_v2)
// -------------------------------------------------------------------------
default: {
app: 'OS Default',
display_mode: 'none'
// No open_cmd — execution falls through to open_local_file_v2(path)
// No post_script
},
// -------------------------------------------------------------------------
// URL-type files: event_file.filename IS the URL (https://...)
// Opened via native.open_external({ url, app: 'chrome' }) — no local file involved.
// display_mode 'extend' is the default for URL presentations (e.g. Google Slides).
// -------------------------------------------------------------------------
url: {
app: 'Chrome',
display_mode: 'mirror'
// No open_cmd or post_script — URL branch in handle_open_file() handles this
}
}; };
const URL_WEB_PROFILE: LaunchProfile = {
app: 'Chrome',
display_mode: 'extend',
// No open_cmd or post_script — URL branch in handle_open_file() handles this
};
const DEFAULT_OS_PROFILE: LaunchProfile = {
app: 'OS Default',
display_mode: 'none',
// No open_cmd — execution falls through to open_local_file_v2(path)
// No post_script
};
type DefaultLaunchProfileDefinition = {
name: string;
aliases: string[];
profile: LaunchProfile;
};
export const DEFAULT_LAUNCH_PROFILE_DEFS: DefaultLaunchProfileDefinition[] = [
{
name: 'powerpoint_mac_extend',
aliases: ['pptx', 'ppt'],
profile: POWERPOINT_MAC_EXTEND_PROFILE
},
{
name: 'keynote_mac_extend',
aliases: ['key'],
profile: KEYNOTE_MAC_EXTEND_PROFILE
},
{
name: 'libreoffice_mac_extend',
aliases: ['odp'],
profile: LIBREOFFICE_MAC_EXTEND_PROFILE
},
{
name: 'acrobat_mac_mirror',
aliases: ['pdf'],
profile: ACROBAT_MAC_MIRROR_PROFILE
},
{
name: 'vlc_mirror',
aliases: ['mp4', 'mkv', 'mov', 'mpeg', 'avi', 'flv', 'ogg', 'ogv', 'mp3', 'm4v', 'm4a', 'webm', 'wmv', 'wav', 'aac', 'flac'],
profile: VLC_MIRROR_PROFILE
},
{
name: 'powerpoint_win_extend',
aliases: ['pptxwin', 'pptwin'],
profile: POWERPOINT_WIN_EXTEND_PROFILE
},
{
name: 'libreoffice_win_extend',
aliases: ['odpwin'],
profile: LIBREOFFICE_WIN_EXTEND_PROFILE
},
{
name: 'acrobat_win_mirror',
aliases: ['pdfwin'],
profile: ACROBAT_WIN_MIRROR_PROFILE
},
{
name: 'url_web',
aliases: ['url'],
profile: URL_WEB_PROFILE
},
{
name: 'os_default',
aliases: ['default'],
profile: DEFAULT_OS_PROFILE
}
];
export const DEFAULT_LAUNCH_PROFILE_LIBRARY: Record<string, LaunchProfile> = Object.fromEntries(
DEFAULT_LAUNCH_PROFILE_DEFS.map(({ name, profile }) => [name, profile])
);
export const DEFAULT_LAUNCH_PROFILE_ALIASES: Record<string, string> = Object.fromEntries(
DEFAULT_LAUNCH_PROFILE_DEFS.flatMap(({ name, aliases }) =>
aliases.map((alias) => [alias, name])
)
);
export const DEFAULT_LAUNCH_PROFILES: Record<string, LaunchProfile> = Object.fromEntries(
DEFAULT_LAUNCH_PROFILE_DEFS.flatMap(({ name, aliases, profile }) => [
[name, { ...profile }],
...aliases.map((alias) => [alias, { ...profile }])
])
);
/** /**
* Returns a shallow copy of the built-in profile for the given extension, * Returns a shallow copy of the built-in profile for the given extension,
* with a display_override applied if provided. * with a display_override applied if provided.
@@ -373,15 +267,16 @@ export function resolve_launch_profile(
local_profiles?: Record<string, LaunchProfile> | null local_profiles?: Record<string, LaunchProfile> | null
): LaunchProfile { ): LaunchProfile {
const ext = (extension || '').toLowerCase().replace(/^\./, ''); const ext = (extension || '').toLowerCase().replace(/^\./, '');
const default_profile_name = DEFAULT_LAUNCH_PROFILE_ALIASES[ext] ?? ext;
// Priority: device config → local config → built-ins → default // Priority: device config → local config → canonical built-ins → default
const source = const source =
device_profiles?.[ext] ?? device_profiles?.[ext] ??
device_profiles?.['default'] ?? device_profiles?.['default'] ??
local_profiles?.[ext] ?? local_profiles?.[ext] ??
local_profiles?.['default'] ?? local_profiles?.['default'] ??
DEFAULT_LAUNCH_PROFILES[ext] ?? DEFAULT_LAUNCH_PROFILES[default_profile_name] ??
DEFAULT_LAUNCH_PROFILES['default']; DEFAULT_LAUNCH_PROFILE_LIBRARY.os_default;
const profile = { ...source }; const profile = { ...source };

View File

@@ -65,7 +65,7 @@ export async function launch_from_cache({
temp_root, temp_root,
filename, filename,
hash_prefix_length = 2, hash_prefix_length = 2,
script_template = null native_template = null
}: { }: {
cache_root: string; cache_root: string;
hash: string; hash: string;
@@ -73,17 +73,17 @@ export async function launch_from_cache({
filename: string; filename: string;
hash_prefix_length?: number; hash_prefix_length?: number;
/** /**
* Optional data-driven launch script. If provided, Electron runs this instead of * Resolved native launch template. If provided, Electron executes this string
* its hardcoded extension-based logic — no app rebuild needed for script changes. * after the file is copied to temp.
* *
* Two formats: * Two formats:
* - AppleScript: multi-line string with {{path}} placeholder (macOS only) * - AppleScript: multi-line string with {{path}} placeholder (macOS only)
* - Shell command: prefix with "shell:" → e.g. "shell:open \"{{path}}\"" * - Shell command: prefix with "shell:" → e.g. "shell:open \"{{path}}\""
* *
* Configure via event_device.data_json.launch_profiles or $events_loc.launcher.launch_profiles. * Configure via event_device.data_json.launch_profiles or $events_loc.launcher.launch_profiles.
* If null, Electron falls through to its built-in hardcoded defaults. * If null, Electron should treat that as a missing profile error.
*/ */
script_template?: string | null; native_template?: string | null;
}) { }) {
if (!native) if (!native)
return { success: false, error: 'Native bridge not available' }; return { success: false, error: 'Native bridge not available' };
@@ -93,7 +93,7 @@ export async function launch_from_cache({
temp_root, temp_root,
filename, filename,
hash_prefix_length, hash_prefix_length,
script_template native_template
}); });
} }