# 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)