diff --git a/muxplex/frontend/index.html b/muxplex/frontend/index.html index f155d96..a74223a 100644 --- a/muxplex/frontend/index.html +++ b/muxplex/frontend/index.html @@ -246,6 +246,7 @@ + diff --git a/muxplex/frontend/terminal.js b/muxplex/frontend/terminal.js index 49527c6..c142b25 100644 --- a/muxplex/frontend/terminal.js +++ b/muxplex/frontend/terminal.js @@ -243,6 +243,18 @@ function createTerminal() { _fitAddon = new window.FitAddon.FitAddon(); _term.loadAddon(_fitAddon); + + // Clickable URLs — Ctrl+Click (Windows/Linux) or Cmd+Click (macOS) opens in new tab. + // xterm-addon-web-links auto-detects URLs and adds hover underlines. + // Plain click is preserved for normal terminal text selection. + var WebLinksAddon = window.WebLinksAddon && window.WebLinksAddon.WebLinksAddon; + if (WebLinksAddon) { + _term.loadAddon(new WebLinksAddon(function(event, uri) { + if (event.ctrlKey || event.metaKey) { + window.open(uri, '_blank'); + } + })); + } } // ─── Open / close ───────────────────────────────────────────────────────────── diff --git a/muxplex/frontend/tests/test_terminal.mjs b/muxplex/frontend/tests/test_terminal.mjs index 726dd3c..08ef771 100644 --- a/muxplex/frontend/tests/test_terminal.mjs +++ b/muxplex/frontend/tests/test_terminal.mjs @@ -949,4 +949,16 @@ test('terminal.js registers OSC 52 handler for tmux clipboard bridge', () => { ); }); +// --- Clickable URLs via xterm-addon-web-links --- + +test('terminal.js loads xterm-addon-web-links for clickable URLs', () => { + const source = fs.readFileSync(new URL('../terminal.js', import.meta.url), 'utf8'); + assert.ok(source.includes('WebLinksAddon'), 'must reference WebLinksAddon'); + assert.ok( + source.includes('ctrlKey') || source.includes('metaKey'), + 'must check modifier key for link clicks', + ); + assert.ok(source.includes('window.open'), 'must open URLs in new tab'); +}); + diff --git a/muxplex/tests/test_frontend_html.py b/muxplex/tests/test_frontend_html.py index ecdfb95..d30aa8b 100644 --- a/muxplex/tests/test_frontend_html.py +++ b/muxplex/tests/test_frontend_html.py @@ -1325,3 +1325,21 @@ def test_html_display_panel_no_view_scope() -> None: assert el is None, ( "#setting-view-scope must NOT be in the display panel (moved to Multi-Device tab)" ) + + +# ============================================================ +# Clickable URLs — xterm-addon-web-links (task: clickable URLs) +# ============================================================ + + +def read_html() -> str: + """Read raw HTML content of index.html.""" + return HTML_PATH.read_text() + + +def test_html_loads_web_links_addon() -> None: + """index.html must load the xterm-addon-web-links CDN script.""" + html = read_html() + assert "web-links" in html.lower() or "weblinks" in html.lower(), ( + "Must load xterm-addon-web-links from CDN" + )