Commit Graph

305 Commits

Author SHA1 Message Date
Brian Krabach b310b7f8a6 fix: self-host xterm.js vendor libs — eliminates Edge Tracking Prevention console noise 2026-04-01 20:40:14 -07:00
Brian Krabach 463cbf4cb3 fix: federation remoteId must be integer index, not URL string
- fetch_remote now accepts index i from enumerate() and sets remote_id: int = i
  instead of remote.get("id", url), which fell back to the URL string
- asyncio.gather uses enumerate(remote_instances) to pass the integer index
- federation_connect route: remote_id type changed from str to int; FastAPI
  now validates path param at framework level, simplifying the handler body
- Update test_federation_sessions_includes_remote_failure_status to expect
  integer 0 (not the legacy 'remote-1' id field string)
- Update test_federation_connect_returns_404_for_non_integer_remote_id: now
  expects 422 (FastAPI schema validation) instead of 404
- Add test_federation_sessions_remote_id_is_integer_index verifying remoteId
  is 0 for first remote and 1 for second
2026-04-01 20:22:45 -07:00
Brian Krabach a07e5b5383 merge: integrate upstream (clipboard, URL click, search addon, delete fixes) with federation proxy rewrite 2026-04-01 18:55:54 -07:00
Brian Krabach e4ba201cd2 refactor: use module-level socket import in _ttyd_is_listening 2026-04-01 18:34:12 -07:00
Brian Krabach dae5cea16b chore: remove unused websockets.exceptions import, add .gitignore, untrack __pycache__
- Remove unused 'import websockets.exceptions' from main.py (no reference
  to websockets.exceptions.* anywhere in the file; all WS handlers use
  broad Exception catches)
- Add .gitignore with __pycache__/, *.pyc, .venv/, and other standard
  Python excludes so bytecode no longer shows as modified in git status
- Untrack all pre-existing __pycache__/*.pyc files via git rm --cached
2026-04-01 18:30:10 -07:00
Brian Krabach e738701cdd chore: final Phase 2 cleanup — remove stale references and unused imports
Remove unused JSONResponse import from starlette.responses in main.py.
All other stale references (CORSMiddleware, federation_tokens, X-Muxplex-Token,
window.opener, etc.) verified absent. Federation proxy endpoints confirmed present.
2026-04-01 18:17:55 -07:00
Brian Krabach ebbf03118b fix: reorganize settings tabs — merge notifications into sessions, move device-name to display, remove view-scope 2026-04-01 18:02:07 -07:00
Brian Krabach 805d3d7898 fix: restore notifications tab and revert out-of-scope settings dialog reorganization
Revert index.html to state at 046d123 (task-7 completion), undoing the
out-of-scope settings dialog reorganization from 9f51d1b that:
- Removed the notifications tab button and panel
- Moved #setting-bell-sound into sessions panel
- Moved #setting-device-name into display panel
- Moved #notification-status-text and #notification-request-btn into sessions panel
- Removed #setting-view-scope from devices panel

This restores the 5-tab structure (display, sessions, notifications,
new-session, devices) and correct element placement that the Python
HTML tests assert.

Also remove 2 stale test_frontend_js.py tests that expected buildSources
and _sources to exist after task-1 removed them:
- test_save_remote_instances_rebuilds_sources
- test_build_sources_checks_multi_device_enabled

All 770 Python tests now pass (was 10 failures).
2026-04-01 17:50:16 -07:00
Brian Krabach 9f51d1bd51 fix: reorganize settings dialog — remove notifications tab, move bell/device-name fields
🤖 Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
2026-04-01 17:40:05 -07:00
Brian Krabach 046d123149 refactor: remove all cross-origin browser-direct federation code
- Delete storeFederationToken function from app.js
- Delete window.addEventListener('message',...) handler from app.js
- Delete buildAuthTileHTML function from app.js
- Delete formatLastSeen function from app.js
- Delete buildOfflineTileHTML function from app.js
- Delete openLoginPopup function from app.js
- Simplify api() to same-origin only (no baseUrl/credentials/X-Muxplex-Token)
- Remove federation auth relay <script> block from index.html
- Remove /api/auth/token endpoint from main.py
- Remove CORSMiddleware from main.py
- Remove X-Muxplex-Token header check from auth.py
- Delete corresponding tests and add absence verification tests
2026-04-01 17:30:18 -07:00
Brian Krabach c48456a14a fix: include .settings-remote-key in remote instance debounced input selector 2026-04-01 17:08:14 -07:00
Brian Krabach 4ccaaf153e fix: parse JSON response in federation generate-key handler
api() returns a Response object; chain .then(res => res.json()) before
accessing data.key so the returned key is displayed in #federation-key-display
and settings-key-display--visible is applied.
2026-04-01 16:57:15 -07:00
Brian Krabach 63e7ef2a1c feat: federation key management UI in Settings > Multi-Device 2026-04-01 16:50:01 -07:00
Brian Krabach 77e2b8476b fix: revert sidebar to single-line header (name + badge + ×) 2026-04-01 16:36:56 -07:00
Brian Krabach c5df6511c1 style: fix stale comment, merge split tile-meta CSS block, clarify test variable 2026-04-01 16:29:33 -07:00
Brian Krabach f18caf5d27 fix: move device badge inside tile-meta to prevent × overlap 2026-04-01 16:21:57 -07:00
Brian Krabach adb0f8c39c feat: upgrade xterm-addon-image 0.5.0 → @xterm/addon-image 0.9.0
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).
2026-04-01 16:18:58 -07:00
Brian Krabach d7bcf49283 fix: use var(--border) for default left border on tiles and sidebar items 2026-04-01 16:12:23 -07:00
Brian Krabach d7d40f1f0d refactor: remove sourceUrl property from session objects 2026-04-01 16:04:53 -07:00
Brian Krabach b7cf45c621 test: add dedicated unit tests for buildStatusTileHTML
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
2026-04-01 15:56:27 -07:00
Brian Krabach 84b277854b refactor: remove _sources state and buildSources/tagSessions/mergeSources 2026-04-01 15:43:51 -07:00
Brian Krabach 7132587847 feat: add search (Ctrl+F) and image rendering (Sixel/Kitty) addons
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.
2026-04-01 14:59:43 -07:00
Brian Krabach 235b4aadfe fix: guard negative remote_id in federation_connect endpoint
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.
2026-04-01 14:38:09 -07:00
Brian Krabach 2c30ad39a2 feat: clickable URLs in terminal — Ctrl/Cmd+Click opens in new tab
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.
2026-04-01 14:34:18 -07:00
Brian Krabach 745e58bae6 style: add 503 warning log and regroup 502 test with federation_connect suite 2026-04-01 14:33:27 -07:00
Brian Krabach b1639826ab fix: improve fetch_remote error handling and diagnostics
- 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
2026-04-01 14:22:29 -07:00
Brian Krabach 489a127056 refactor: defensive getattr guard and clarified cleanup comment in lifespan
- 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
2026-04-01 14:13:45 -07:00
Brian Krabach 43cc75efc8 test: add dispatch test for generate-federation-key subcommand 2026-04-01 14:03:07 -07:00
Brian Krabach 939a20e8b5 fix: migrate test_app.mjs sourceUrl tests to remoteId API
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)
2026-04-01 13:35:26 -07:00
Brian Krabach 38300fcf79 feat: route remote terminal connections through federation proxy
- 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
2026-04-01 13:18:10 -07:00
Brian Krabach 7e28438953 fix: update pollSessions JSDoc and remove zombie backoff test 2026-04-01 13:08:31 -07:00
Brian Krabach dc7c077b6a test: remove stale multi-source pollSessions tests superseded by task-16 2026-04-01 13:01:09 -07:00
Brian Krabach 2de19bd988 fix: clean exit on Ctrl+C in muxplex service logs
subprocess.run with check=True raised CalledProcessError then
KeyboardInterrupt on Ctrl+C, printing an ugly traceback. Fix: remove
check=True, catch KeyboardInterrupt, exit silently.
2026-04-01 13:00:00 -07:00
Brian Krabach 442cfcc051 feat: simplify pollSessions to use federation proxy endpoint 2026-04-01 12:54:08 -07:00
Brian Krabach bdb3f069e5 fix: delete active session returns to dashboard, sidebar/tile delete doesn't navigate
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.
2026-04-01 12:48:38 -07:00
Brian Krabach 0e3ed96c16 refactor: drop unused css=None parameters in 3 CSS test functions
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.
2026-04-01 12:48:00 -07:00
Brian Krabach 56694fde61 style: add debug logging to ws relay exceptions; format test_frontend_css 2026-04-01 12:44:31 -07:00
Brian Krabach 2dda544893 chore: remove untracked planning doc swept up in phase 1 commit 2026-04-01 12:37:53 -07:00
Brian Krabach d0fe9cda67 chore: Phase 1 complete — all backend proxy tests pass 2026-04-01 12:33:17 -07:00
Brian Krabach e726ef9926 feat: redact federation/remote keys from GET /api/settings 2026-04-01 12:19:24 -07:00
Brian Krabach 7e8892c926 feat: add POST /api/federation/generate-key endpoint 2026-04-01 12:12:37 -07:00
Brian Krabach a2763e26f6 fix: add HTTP error handling to federation_connect endpoint
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
2026-04-01 12:07:57 -07:00
Brian Krabach e420b84b9f feat: add POST /api/federation/{remote_id}/connect proxy 2026-04-01 11:54:40 -07:00
Brian Krabach 536b487f52 refactor: extract _ws_auth_check helper and fix URL scheme conversion
- 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.
2026-04-01 11:49:08 -07:00
Brian Krabach 086ef19aa4 feat: add WS /federation/{remote_id}/terminal/ws proxy endpoint 2026-04-01 11:42:19 -07:00
Brian Krabach 2dad066a5e style: move import hmac to module-level per PEP 8 2026-04-01 11:37:55 -07:00
Brian Krabach d07800c8c5 feat: add Bearer token auth to WebSocket proxy for federation 2026-04-01 11:32:17 -07:00
Brian Krabach 4e23935b94 chore: add pyrightconfig.json to resolve Pyright venv path 2026-04-01 11:27:36 -07:00
Brian Krabach 5f0fcf3e71 test: add True-path coverage and clean up TestClient binding for instance-info
- 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).
2026-04-01 11:23:04 -07:00
Brian Krabach 98d552557a fix: delete command auto-confirms prompts + add request-level logging
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.
2026-04-01 11:18:57 -07:00