# Native App Agent Task List
> Use this file to track steps for complex features or bug fixes.
> **Status:** Stable - ongoing development.

## Current Investigation
- This started as an API contract review for the native Electron bootstrap path and expanded into a packaging/runtime issue after the deploy step stopped producing bundles.
- We now know the API side was not the root cause. The bootstrap request shape in `src/main/api_client.ts` was wrong and has been corrected.
- The packaging blocker has been diagnosed and fixed (see below).

## Launcher Terminology Cleanup
- Align the native docs/comments with the Svelte-side terminology: **Launch Profile** = the
   Svelte config object keyed by extension; **Native Template** = the AppleScript or shell
   string Electron actually executes after the file lands in temp.
- Update `src/main/file_handlers.ts` comments and any bridge-facing wording so they describe the
   resolved `native_template` string accurately. `launch_profiles` is only the Svelte-side map;
   the IPC payload is the single executable string. Do not reintroduce `launch_scripts` as the
   public term.
- Keep the source of truth in Svelte. Electron should remain a thin executor/copy layer.
- After source/docs updates, rebuild/regenerate `dist/main/file_handlers.js` from source; do not
   hand-edit the generated file.
- If any parameter names remain awkward in source, preserve runtime behavior first and rename
   only when the signature ripple is understood.

## What Was Fixed
- Updated the native bootstrap flow to use the direct `site_domain/search` request body expected by API V3.
- Standardized the account-bypass header to `x-no-account-id: bypass` where that narrow bypass is intended.
- Removed a redundant `x-no-account-id` header from file download calls.
- Rewrote the device lookup smoke test so it validates the real two-step bootstrap path end to end.
- Upgraded Electron from 34.x to 42.0.1.
- Replaced deprecated `electron-packager` with `@electron/packager` 20.0.0.
- Added a `package:linux` smoke test path so packaging failures can be isolated from macOS-specific behavior.
- **Fixed packaging hang on Node 26:** `yauzl` 2.10.0 (used by `extract-zip` in `@electron/packager`) emits no `data` events on Node 26 streams, causing zip extraction to hang indefinitely. Fix: patched `@electron/packager/dist/unzip.js` to use `bsdtar` (libarchive) instead of `extract-zip`. `bsdtar` was chosen over `7z` because `7z` refuses macOS `.app` bundles with chained symlinks inside framework bundles. Patch is re-applied on every `npm install` via the `postinstall` script at `scripts/patch-packager-unzip.js`.

## Verified So Far
- `npm run dev` works once the Electron binary is present locally.
- Manual Electron cache extraction restored a runnable checkout on this machine.
- API validation confirmed the backend responds correctly for:
	- `event_device/{id}` lookup
	- `site_domain/search?limit=1` with the direct `SearchQuery` body
- The returned `site_domain.account_id` matches the device account context in the verified bootstrap flow.
- The SvelteKit frontend bootstrap path already follows the correct API contract and does not need the same fix.
- **`npm run package:linux` now produces `builds/aether_launcher-linux-x64/`** with a complete bundle (confirmed 2026-05-11).
- **`npm run package:mac` now produces `builds/aether_launcher-darwin-x64/` and `builds/aether_launcher-darwin-arm64/`** with `aether_launcher.app` inside each (confirmed 2026-05-11). Initial fix used `7z` but it refused chained symlinks inside macOS framework bundles; switched to `bsdtar` (libarchive) which handles both Linux and macOS zips correctly.
- `deploy/deploy.sh` output directory names (`aether_launcher-darwin-x64`, `aether_launcher-darwin-arm64`) match packager output — no script changes needed.

## Remaining Items
1. Test that the packaged Linux binary runs end-to-end against the dev API.

## Root Cause Summary (Packaging Hang)
- **Tool chain:** Node 26.1.0 + `@electron/packager` 20.0.0 + `extract-zip` 2.0.1 + `yauzl` 2.10.0
- **Symptom:** `npm run package:linux` exits 0 but produces no output. Debug log shows it starts extraction but never finishes.
- **Root cause:** `yauzl` opens a read stream for the first zip entry, but on Node 26, no `data` events are ever emitted on that stream. The `pipeline(readStream, writeStream)` call in `extract-zip` blocks forever.
- **Fix:** Replace the one-liner `extractElectronZip` function in `node_modules/@electron/packager/dist/unzip.js` with a `child_process.execSync` call to `bsdtar -xf`. `bsdtar` was chosen over `7z` because `7z` refuses macOS `.app` bundles with chained symlinks (e.g. `Electron Framework.framework/Libraries → Versions/Current/Libraries`). A `postinstall` npm script re-applies this patch after each `npm install`.
- **Build-time dependency:** `libarchive` (provides `bsdtar`) must be installed on the build host. On Arch: `pacman -S libarchive`; macOS: included in Xcode CLT or `brew install libarchive`; Ubuntu/Debian: `apt install libarchive-tools`.

## References
- Electron 42 release notes: https://www.electronjs.org/blog/electron-42-0
- Related Electron packaging discussion: https://github.com/aaddrick/claude-desktop-debian/pull/587
- Electron packaging/runtime change reference: https://github.com/electron/electron/pull/49328
- yauzl Node 26 stream issue: `yauzl` 2.10.0 uses legacy Node streams (streams1 style); Node 26 changed stream internal behavior so `openReadStream` returns a stream that never emits `data` without a proper pipeline consumer.

## Notes
- Was on Electron 34.
- The problem is not the backend API keys or the frontend site bootstrap flow.
- The packaging fix is a node_modules patch, not upstream. If `@electron/packager` or `extract-zip` releases a Node 26-compatible version, the `postinstall` script should be removed.

---

## Pending Feature: set_display_layout — displayplacer Per-Device Config

**Background (added 2026-05-12, from Svelte-side LaunchProfile work):**

The Svelte Events Launcher (`launcher_file_cont.svelte`) now resolves a `LaunchProfile` per file extension and calls `native.set_display_layout({ mode })` before opening a presentation. The underlying handler in `src/main/system_handlers.ts` uses a bundled `displayplacer` macOS binary. The wiring is complete on both ends — but it **silently no-ops on every device** because `displayplacer` requires a per-machine `configStr` (the output of `displayplacer list` on *that specific Mac*) to identify the exact display UUIDs and pixel positions. Without it, `displayplacer` cannot apply any layout.

**What's needed:**

1. **Capture `configStr` per room Mac.** On each presentation Mac, run:
   ```
   displayplacer list
   ```
   This prints the current display configuration. Copy the full output for both the "extend" and "mirror" layouts (they differ in which display is primary and how they're positioned).

2. **Store configs in the API.** The Svelte side reads device config from `event_device.data_json`. Store the captured strings there:
   ```json
   {
     "displayplacer_config_extend": "<full configStr for extended layout>",
     "displayplacer_config_mirror": "<full configStr for mirrored layout>"
   }
   ```
   Admin UI to set these values already exists via the normal event_device edit flow. You can also set them directly via the V3 CRUD API (`PATCH /v3/crud/event_device/{id}/`).

3. ✅ **`set_display_layout` in `src/main/system_handlers.ts` already accepts `configStr`.** Handler signature is `{ mode, configStr }` and uses it correctly — no change needed.

4. ✅ **Electron relay (`src/preload/index.ts`) already forwards `configStr`** — args passed as-is. `AetherNativeBridge` type in `src/shared/types.ts` updated to include `set_display_layout` with correct signature (2026-05-12).

5. **Pass `configStr` from Svelte.** The Svelte call site in `launcher_file_cont.svelte` (Step 3 in `handle_open_file`) currently calls:
   ```ts
   await native.set_display_layout({ mode: profile.display_mode }).catch(() => {});
   ```
   It needs to be updated to thread `configStr` from `$ae_loc.native_device.data_json`:
   ```ts
   const cfg_key = profile.display_mode === 'mirror'
       ? 'displayplacer_config_mirror'
       : 'displayplacer_config_extend';
   const configStr = ($ae_loc as any).native_device?.data_json?.[cfg_key] ?? null;
   await native.set_display_layout({ mode: profile.display_mode, configStr }).catch(() => {});
   ```

**Contract already in place (Svelte side — no action needed):**
- `$ae_loc.native_device` is the `event_device` object loaded during native app bootstrap
- `data_json` is an open JSON field on that object
- `handle_open_file()` already calls `set_display_layout` at the right point — it just needs the configStr threaded through

**Resources:**
- displayplacer GitHub (usage + examples): https://github.com/jakehilborn/displayplacer
- `displayplacer list` — prints current layout as a re-runnable config string
- `displayplacer <configStr>` — applies layout; the configStr from `list` is what you pass back
- Current Electron handler: `src/main/system_handlers.ts` — find the `set_display_layout` IPC handler
- Svelte call site: `src/routes/events/[event_id]/(launcher)/launcher_file_cont.svelte` — Step 3 comment in `handle_open_file()`
