# Phase 1: ghostty-web Swap Implementation Plan > **Execution:** Use the subagent-driven-development workflow to implement this plan. **Goal:** Replace xterm.js with ghostty-web as the terminal emulator frontend — same backend, better VT parsing. **Architecture:** ghostty-web is a WASM-compiled port of Ghostty's VT100 parser with xterm.js API compatibility. It ships a UMD build (`ghostty-web.umd.cjs`) that exposes globals via ` ``` **After:** ```html ``` Note: We keep WebLinksAddon, SearchAddon, and ImageAddon for now. They use the xterm.js addon interface (`loadAddon`). Task 1 research will determine if they're compatible. If not, Task 4 will handle fallbacks. FitAddon is dropped because ghostty-web bundles it natively. Also update the CSS link if ghostty-web has its own CSS. If ghostty-web does NOT have a CSS file (it uses canvas rendering, not DOM), remove the xterm.css link: **Before:** ```html ``` **After (if ghostty-web has CSS):** ```html ``` **After (if ghostty-web has NO CSS — canvas renderer needs no CSS):** ```html ``` **Step 4: Run tests to verify they pass** Run: ```bash .venv/bin/python -m pytest muxplex/tests/test_frontend_js.py -k "ghostty" -xvs ``` Expected: PASS **Step 5: Commit** ```bash git add muxplex/frontend/index.html muxplex/tests/test_frontend_js.py git commit -m "feat: update index.html to load ghostty-web instead of xterm.js" ``` --- ### Task 4: Update terminal.js for ghostty-web API **Files:** - Modify: `muxplex/frontend/terminal.js` **Depends on:** Task 1 (research results), Task 3 (script tags updated) **Context:** ghostty-web is API-compatible with xterm.js, but has two key differences: 1. Requires `await init()` before creating Terminal — loads WASM asynchronously 2. FitAddon is built-in (import path differs) 3. The global name exposed by the UMD build may differ from `window.Terminal` The existing code uses: - `new window.Terminal({...})` (line 349) - `new window.FitAddon.FitAddon()` (line 369) - `_term.loadAddon(...)` for WebLinksAddon, SearchAddon, ImageAddon (lines 387-406) - `_term.write(string)` for terminal output - `_term.onData(callback)` for terminal input - `_term.onResize(callback)` for resize events - `_term.dispose()` for cleanup - `_term.parser.registerOscHandler()` for OSC 52 clipboard - `_term.attachCustomKeyEventHandler()` for key interception - `_term.getSelection()` and `_term.onSelectionChange()` for clipboard - `_term.focus()`, `_term.cols`, `_term.rows` **Step 1: Add WASM initialization at module load** Add a WASM initialization block near the top of `terminal.js` (after the module-level state declarations, around line 38). ghostty-web's UMD build exposes an `init()` function that must be called once before creating any Terminal instance: ```javascript // --- ghostty-web WASM initialization --- // ghostty-web requires WASM to be loaded before creating Terminal instances. // The UMD build exposes init() on the global (e.g., window.GhosttyWeb.init()). // Call init() once at module load; createTerminal() awaits the result. var _ghosttyReady = null; // Promise that resolves when WASM is loaded (function _initGhosttyWasm() { // The exact global depends on the UMD build — Task 1 research identifies this. // Common patterns: window.GhosttyWeb.init(), window.ghosttyWeb.init() // The init() call may accept a WASM URL: init('/vendor/ghostty-vt.wasm') if (typeof window !== 'undefined' && window.GhosttyWeb && window.GhosttyWeb.init) { _ghosttyReady = window.GhosttyWeb.init('/vendor/ghostty-vt.wasm'); } else if (typeof window !== 'undefined' && window.init) { _ghosttyReady = window.init('/vendor/ghostty-vt.wasm'); } else { _ghosttyReady = Promise.resolve(); // Fallback for test environments } })(); ``` **Important:** The exact global name and init() signature depend on Task 1 research findings. Adjust the code above based on what the UMD build actually exposes. **Step 2: Update `createTerminal()` — Terminal constructor** In the `createTerminal()` function (line 335), update the Terminal constructor reference. ghostty-web's UMD build may expose the Terminal class under a different global: **Before (line 349):** ```javascript _term = new window.Terminal({ ``` **After (adjust global name based on Task 1 research):** ```javascript // ghostty-web UMD exposes Terminal on the same global or on GhosttyWeb var TerminalClass = window.GhosttyWeb ? window.GhosttyWeb.Terminal : window.Terminal; _term = new TerminalClass({ ``` **Step 3: Update FitAddon instantiation** ghostty-web bundles FitAddon. The import path changes: **Before (line 369):** ```javascript _fitAddon = new window.FitAddon.FitAddon(); ``` **After (adjust based on Task 1 research — FitAddon may be on GhosttyWeb or still on window.FitAddon):** ```javascript var FitAddonClass = (window.GhosttyWeb && window.GhosttyWeb.FitAddon) ? window.GhosttyWeb.FitAddon : (window.FitAddon && window.FitAddon.FitAddon); _fitAddon = new FitAddonClass(); ``` **Step 4: Guard addon loading for compatibility** The WebLinksAddon, SearchAddon, and ImageAddon use the xterm.js addon interface. If they fail to load with ghostty-web, wrap them in try/catch so the terminal still works: **Before (lines 387-406):** ```javascript var WebLinksAddon = window.WebLinksAddon && window.WebLinksAddon.WebLinksAddon; if (WebLinksAddon) { _term.loadAddon(new WebLinksAddon(function(event, uri) { window.open(uri, '_blank'); })); } var SearchAddon = window.SearchAddon && window.SearchAddon.SearchAddon; if (SearchAddon) { _searchAddon = new SearchAddon(); _term.loadAddon(_searchAddon); } var ImageAddon = window.ImageAddon && window.ImageAddon.ImageAddon; if (ImageAddon) { _term.loadAddon(new ImageAddon()); } ``` **After:** ```javascript // WebLinksAddon — try loading xterm.js addon; ghostty-web may or may not support it try { var WebLinksAddon = window.WebLinksAddon && window.WebLinksAddon.WebLinksAddon; if (WebLinksAddon) { _term.loadAddon(new WebLinksAddon(function(event, uri) { window.open(uri, '_blank'); })); } } catch (e) { console.warn('WebLinksAddon not compatible with ghostty-web:', e.message); } // SearchAddon — try loading xterm.js addon try { var SearchAddon = window.SearchAddon && window.SearchAddon.SearchAddon; if (SearchAddon) { _searchAddon = new SearchAddon(); _term.loadAddon(_searchAddon); } } catch (e) { console.warn('SearchAddon not compatible with ghostty-web:', e.message); _searchAddon = null; } // ImageAddon — try loading xterm.js addon (Sixel, iTerm2 IIP, Kitty graphics) try { var ImageAddon = window.ImageAddon && window.ImageAddon.ImageAddon; if (ImageAddon) { _term.loadAddon(new ImageAddon()); } } catch (e) { console.warn('ImageAddon not compatible with ghostty-web:', e.message); } ``` **Step 5: Handle the async init() in openTerminal()** The `openTerminal()` function (line 453) and `switchTerminal()` function (line 642) call `createTerminal()` synchronously. Since ghostty-web requires WASM to be loaded first, ensure `_ghosttyReady` is resolved before terminal creation. The simplest approach: `init()` fires at page load (module execution), so by the time the user clicks a session tile, WASM is loaded. But add a guard: In `openTerminal()`, before `createTerminal(fontSize)` (line 479), add: ```javascript // Ensure ghostty-web WASM is loaded before creating terminal if (_ghosttyReady && typeof _ghosttyReady.then === 'function') { _ghosttyReady.then(function() { _ghosttyReady = null; // Only wait once }); } ``` Note: Since `init()` is called at module load and terminal opening happens after user interaction (seconds later), the WASM will already be loaded. This guard is defensive only. **Step 6: Verify the terminal.js changes don't break the existing test suite** Run: ```bash .venv/bin/python -m pytest muxplex/tests/test_frontend_js.py -xvs ``` Expected: All existing tests PASS (they test app.js structure, not terminal.js internals). **Step 7: Commit** ```bash git add muxplex/frontend/terminal.js git commit -m "feat: update terminal.js for ghostty-web API (FitAddon built-in, addon guards)" ``` --- ### Task 5: Remove old xterm.js vendor files **Files:** - Delete: `muxplex/frontend/vendor/xterm.js` - Delete: `muxplex/frontend/vendor/xterm.css` - Delete: `muxplex/frontend/vendor/xterm-addon-fit.js` **Depends on:** Task 3, Task 4 (new vendor files are loaded, terminal.js updated) **Important:** Do NOT delete `xterm-addon-web-links.js`, `xterm-addon-search.js`, or `addon-image.js` yet. These addons are still loaded (with try/catch guards) and may work with ghostty-web's `loadAddon()` interface. They'll be removed later once ghostty-web has native equivalents or Phase 2 eliminates the need. **Step 1: Delete the replaced files** ```bash rm muxplex/frontend/vendor/xterm.js rm muxplex/frontend/vendor/xterm.css rm muxplex/frontend/vendor/xterm-addon-fit.js ``` **Step 2: Verify no remaining references to deleted files** ```bash grep -r "xterm\.js" muxplex/frontend/ --include="*.html" --include="*.js" grep -r "xterm\.css" muxplex/frontend/ --include="*.html" grep -r "xterm-addon-fit" muxplex/frontend/ --include="*.html" --include="*.js" ``` Expected: No references to `xterm.js`, `xterm.css`, or `xterm-addon-fit.js` in HTML or JS files. (Comments referencing "xterm.js" in code are fine — they're documentation, not imports.) **Step 3: Verify remaining vendor files** ```bash ls -la muxplex/frontend/vendor/ ``` Expected contents: - `ghostty-web.js` (new) - `ghostty-vt.wasm` (new) - `ghostty-web.css` (new, if applicable) - `xterm-addon-web-links.js` (kept) - `xterm-addon-search.js` (kept) - `addon-image.js` (kept) - `lit/` directory (unrelated, kept) **Step 4: Commit** ```bash git add -u muxplex/frontend/vendor/ git commit -m "chore: remove xterm.js, xterm.css, xterm-addon-fit.js (replaced by ghostty-web)" ``` --- ### Task 6: Manual smoke test **Files:** None (testing only) **Depends on:** Tasks 2-5 complete **Step 1: Restart the service** ```bash systemctl --user restart muxplex ``` **Step 2: Test terminal rendering** Open the muxplex web UI in a browser. Click a session tile to open a terminal. Verify: - [ ] Terminal renders and shows a shell prompt - [ ] Typing works (keystrokes reach the shell) - [ ] Colors render correctly (run `ls --color` or `htop`) - [ ] Terminal resizes when the browser window resizes (FitAddon working) - [ ] Scrollback works (scroll up to see history) **Step 3: Test search (Ctrl+F)** Press Ctrl+F in the terminal. If the search bar appears and search works, SearchAddon loaded successfully. If not, note this as a known limitation. **Step 4: Test links** Run `echo "https://example.com"` in the terminal. Hover over the URL. If it becomes clickable, WebLinksAddon loaded successfully. Note: ghostty-web may handle OSC 8 hyperlinks natively even without WebLinksAddon. **Step 5: Test session switching** Open one session, then click a different session in the sidebar. Verify the terminal switches and renders the new session content. **Step 6: Document any issues** If anything doesn't work, note it. Common issues to watch for: - WASM not loading: check browser console for 404 on `ghostty-vt.wasm` or MIME type errors - Terminal blank: check if `init()` resolved before Terminal creation - Addons failing: check console for the try/catch warning messages - CSS issues: ghostty-web uses canvas rendering so xterm.css classes like `.xterm` may not exist --- ### Task 7: Run the full test suite **Files:** None (testing only) **Depends on:** Task 6 (smoke test passed or issues documented) **Step 1: Run all tests** Run: ```bash .venv/bin/python -m pytest muxplex/tests/ -x -q --timeout=30 ``` Expected: All 1306 tests pass. The frontend structural tests (`test_frontend_js.py`) verify app.js structure — they don't execute JavaScript, so they're unaffected by the terminal swap. The new tests from Task 3 verify index.html loads ghostty-web. **Step 2: If tests fail, fix before proceeding** Most likely failure: a test that greps for "xterm" in terminal.js comments. These are documentation tests (the test file checks for patterns in JS source). If a test assumes `xterm.js` is present in terminal.js comments, update the comment to reference ghostty-web instead. Check specifically: ```bash grep -n "xterm" muxplex/tests/test_frontend_js.py | head -20 ``` If any tests reference xterm.js presence in terminal.js, update them. --- ### Task 8: Final commit **Files:** Any remaining uncommitted changes **Depends on:** Task 7 (all tests pass) **Step 1: Check for uncommitted changes** ```bash git status git diff --stat ``` **Step 2: Commit any remaining changes** ```bash git add -A git commit -m "feat: Phase 1 complete — ghostty-web replaces xterm.js as terminal frontend" ``` **Step 3: Verify the commit history** ```bash git log --oneline -10 ``` Expected: Clean commit history showing the incremental swap: 1. Research doc 2. Vendor ghostty-web files 3. Update index.html script tags 4. Update terminal.js for API differences 5. Remove old xterm.js files 6. Final commit (if any remaining changes)