# 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. --- ## set_display_layout — Setup & Status (updated 2026-05-20) **Primary approach: `display_control` (native CoreGraphics — no Homebrew required)** - Source: `scripts/display_control.m` (derived from OSIT MasterKey app, Ian Kohl 2019) - Build: run `scripts/build-display-control.sh` on a Mac (requires Xcode CLT only) - Output: `resources/bin/display_control` — commit this binary to the repo - Uses `CGConfigureDisplayMirrorOfDisplay` — same CoreGraphics API macOS uses internally - Supports 3+ displays; auto-detects all connected displays; no config string needed **Fallback approach: `displayplacer` (requires `brew install displayplacer` on each venue Mac)** - Reference: [jakehilborn/displayplacer](https://github.com/jakehilborn/displayplacer) - Still used when `display_control` binary is not present - Also used for per-device `configStr` overrides (displayplacer-format strings in `event_device.data_json`) **Current state (2026-05-20):** - ✅ Correct `mirror_of_display:` syntax used in displayplacer fallback (was `mirror:` — wrong, now fixed) - ✅ Failures logged to Electron console (`[Launcher] set_display_layout:`) instead of silently swallowed - ✅ **Display Mode toggle** added to Launcher config (Native OS section) — Extend/Mirror buttons always visible, no Technical Mode required - ⏳ `display_control` binary not yet built — must be compiled on a Mac and committed **To build `display_control` (do this on a Mac):** ```bash # One-time: install Xcode Command Line Tools if not already installed xcode-select --install # Then: ./scripts/build-display-control.sh # Test it with a second display connected: ./resources/bin/display_control status ./resources/bin/display_control extend ./resources/bin/display_control mirror # Commit the binary: git add resources/bin/display_control git commit -m "build: add display_control binary (macOS CoreGraphics)" ``` **Optional per-device override (displayplacer format, for edge cases):** For rooms where auto-detection produces the wrong result, store the raw configStr in `event_device.data_json`: ```json { "displayplacer_config_extend": "", "displayplacer_config_mirror": "" } ``` `configStr` is passed from the Svelte call site and uses the displayplacer fallback path directly.