Newer version has improved Sixel rendering and image lifecycle management.
UMD global (window.ImageAddon) is compatible — no JS code changes needed.
Should improve image replacement behavior (stacking issue with yazi).
Add 4 direct unit tests for buildStatusTileHTML to match coverage
pattern of sibling functions buildAuthTileHTML and buildOfflineTileHTML:
- is exported as a function
- returns article element with correct statusClass
- escapes XSS in deviceName
- renders statusText in badge span
Search: xterm-addon-search (0.13.0) with slide-down search bar. Ctrl+F
opens bar; type to search (live highlight); Enter/Shift+Enter for
next/prev; Escape to close. Bar sits above terminal-container inside
a new .terminal-wrapper flex column.
Image: xterm-addon-image (0.5.0) for inline graphics. Enables Sixel,
iTerm2 IIP, and Kitty graphic protocols — needed for tools like yazi
file manager. Auto-detects escape sequences, no configuration needed.
Negative Python list indices bypass the intended 404 guard — e.g.
remote_id=-1 on a non-empty list silently proxied to the last remote
instead of returning 404.
Add an explicit 'idx < 0' check before the list lookup, consistent
with the sibling federation_terminal_ws_proxy endpoint (line 728).
Also adds test_federation_connect_returns_404_for_negative_remote_id
to document the expected behavior and prevent regression.
Loads xterm-addon-web-links (0.9.0) from CDN. URLs in terminal output
are auto-detected and underlined on hover. Ctrl+Click (Linux/Windows)
or Cmd+Click (macOS) opens the URL in a new browser tab. Plain click
preserved for normal terminal text selection.
- Add _log.warning() to bare except clause in fetch_remote so unexpected
errors (JSON parse failures, AttributeError, etc.) leave a diagnostic
trail instead of silently surfacing as 'unreachable'
- Map HTTPStatusError (non-401/403 HTTP errors like 500, 503, 429) to
'unreachable' instead of 'auth_failed' — semantically correct since
these are connectivity/server errors, not authentication failures
- Fix stale task comment in test_api.py: task-8 → task-5
- Use getattr(app.state, 'federation_client', None) in shutdown to guard
against the theoretical case where AsyncClient constructor raised before
setting app.state.federation_client
- Rename 'Shutdown:' comment to 'Cleanup:' in the finally block to better
describe that the block covers both the poll-task cancellation and the
overall cleanup guarantee
Update 9 test_app.mjs tests that regressed when task-17 migrated
from sourceUrl to remoteId throughout app.js:
- buildTileHTML: assert data-remote-id instead of data-source-url
- buildTileHTML data-session-key: assert data-remote-id
- buildSidebarHTML: assert data-remote-id instead of data-source-url
- buildSidebarHTML empty: assert data-remote-id="" not data-source-url=""
- _previewClickHandler: assert remoteId forwarded not sourceUrl
- openSession with remoteId: assert federation proxy URL instead of remote URL
(no credentials: include - same-origin calls don't need it)
- openSession passes remoteId to _openTerminal: assert fed-abc123
- closeSession remote: use remoteId to set _viewingRemoteId state
- cycleViewMode state pollution fixed as side-effect of above fixes
(failing tests no longer leave document.getElementById mocked)
- connectWebSocket() now accepts remoteId instead of sourceUrl; when
remoteId is set the WebSocket URL is ws://host/federation/{remoteId}/terminal/ws
(same-origin, no cross-origin connections)
- openTerminal() signature updated to accept remoteId parameter
- openSession() in app.js routes remote connect POST to
/api/federation/{remoteId}/connect/{name} instead of the remote URL
- window._openTerminal() call passes remoteId instead of sourceUrl
- _viewingSourceUrl state replaced with _viewingRemoteId
- Tile and sidebar click handlers updated to pass remoteId
- HTML attributes changed from data-source-url to data-remote-id
- Test suite updated: removed cross-origin sourceUrl tests, added
federation proxy path tests
subprocess.run with check=True raised CalledProcessError then
KeyboardInterrupt on Ctrl+C, printing an ugly traceback. Fix: remove
check=True, catch KeyboardInterrupt, exit silently.
Bug 1: killSession() now checks if _viewingSession === name and calls
closeSession() before pollSessions(). Previously the user was stuck in
the expanded terminal view for a dead session after deleting it.
Bug 2: Sidebar item click handler now ignores clicks on .sidebar-delete
buttons. Tile click handler now ignores clicks on .tile-delete buttons.
Previously stopPropagation() in the document-level delete handler fired
too late (after the sidebar-item/tile handlers had already called
openSession), causing the session to be navigated to before being killed.
Tests added:
- killSession closes active session and returns to dashboard
- sidebar click handler ignores clicks on delete button
- tile click handler ignores clicks on tile-delete button
321 JS tests pass, 760 Python tests pass.
test_css_session_grid, test_css_tile_height, and test_css_bell_indicator
each declared a css=None parameter that was immediately overwritten with
read_css(). The parameter was dead API surface — never passed by any caller.
Removed the parameter from all three signatures.
Adds try/except around the outbound http_client.post() call to match the
error-handling pattern established by the federation_sessions sibling endpoint:
- httpx.HTTPStatusError → 502 with upstream status code in detail
- Any other exception (ConnectError, etc.) → 503 with remote URL in detail
Also adds raise_for_status() to surface non-2xx responses explicitly.
Tests added:
- test_federation_connect_returns_503_when_remote_unreachable
- test_federation_connect_returns_502_when_remote_returns_error_status
- Extract shared auth logic into _ws_auth_check(websocket) async helper,
used by both terminal_ws_proxy and federation_terminal_ws_proxy. Eliminates
verbatim duplication of the cookie + bearer auth block.
- Fix URL scheme conversion in federation_terminal_ws_proxy: add elif branch
for http:// so non-http/https URLs fall through to a safe passthrough
(ws_url = remote_url + "/terminal/ws") instead of silently producing a
malformed URL like ws://s://host/terminal/ws.
No behavior change for the supported http/https URL inputs.
All 15 tests pass.
- Add test_instance_info_federation_enabled_true_when_key_exists: writes a
key file to tmp_path, patches FEDERATION_KEY_PATH, and asserts that
federation_enabled is True — completing the positive/negative pair for
task-9's coverage gap (suggested by code quality review).
- Replace 'with TestClient(app) as _:' with 'with TestClient(app):' — the
discard binding was unnecessary (ruff-clean but stylistically noisy).
Bug 1: amplifier-dev --destroy prompts 'Are you sure? [y/N]'. subprocess.run
had no stdin input, so the prompt hung until timeout. Fix: input='y\n' auto-
confirms interactive prompts in delete_session().
Bug 2: No request-level logging — only startup messages visible in journalctl.
Added INFO-level log lines for session create, delete (command + success/fail),
and connect operations. Set uvicorn log_level='info' so application logs
appear in service logs (journalctl / launchd).
Tests: 5 new tests covering stdin confirmation kwarg, INFO-level logs for
delete/create/connect, and uvicorn log level assertion.
README now documents all features, 14 config keys, full CLI reference,
keyboard shortcuts, platform support, and project structure. Previous
README covered ~40% of actual functionality. Added docs/plans/README.md
noting these are historical ADRs from the initial build.
Added 9 new README tests verifying all settings keys are documented,
clipboard features, keyboard shortcuts, auth modes, platform support,
ANSI previews, hover preview, and view modes.
main.py: Wrapped `app.state.federation_client.aclose()` in a `try/finally` block so the poll task cleanup always runs even if `aclose()` raises unexpectedly.
test_api.py: Extended `test_federation_client_exists_on_app_state` to capture the client reference and assert `client_ref.is_closed` after the `TestClient` context exits, verifying the lifespan shutdown closes the client.
🤖 Generated with Amplifier
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
httpx is imported unconditionally in muxplex/main.py (module-level import),
so it must be a production dependency — not dev-only. Move httpx>=0.27.0
from [project.optional-dependencies].dev to [project.dependencies] to
prevent ModuleNotFoundError on startup in non-dev deployments.
Tests were asserting elements that don't exist in the current HTML
(notifications panel, device-name input, view-scope select). Updated
tests to match the actual settings dialog structure.
- Notification settings (bell-sound, status-text, request-btn) live in
the sessions panel, not a dedicated notifications panel
- Settings dialog has 4 tabs (display, sessions, new-session, devices),
not 5 — no notifications tab
- #setting-device-name is in the display panel, not the devices panel
- #setting-view-scope was never implemented; removed stale test
- .tile-bell-count was removed from CSS; dropped that single assertion
- Add docstring to load_federation_key() for module consistency
- Strengthen test_load_federation_key_uses_default_path to call the
function and assert it returns "" when file absent
- Add test_load_federation_key_uses_env_var_override to cover the
MUXPLEX_FEDERATION_KEY_FILE env var override path
All 31 tests pass.