Implement new public endpoint that returns device name and version without authentication required. The auth exemption was already in place from task 4.
- Add 'remote_instances': [] to DEFAULT_SETTINGS for peer instance configs
- Add 'device_name': '' to DEFAULT_SETTINGS (empty for clean JSON serialization)
- Add import socket to settings.py
- In load_settings(), fill empty device_name with socket.gethostname() so the
hostname is always current even if the machine is renamed
- Add 7 new tests covering defaults, hostname fallback, round-trips, and
mutation isolation
- Update 2 existing tests that compared result == DEFAULT_SETTINGS to account
for the hostname fill-in behavior
Co-authored-by: Amplifier <amplifier@amplified.dev>
<dialog> is hidden by default via UA stylesheet (display:none). Our CSS
set display:flex unconditionally, overriding the UA rule and making the
dialog always visible. Fix: only apply display:flex when the dialog has
the [open] attribute (added by showModal()).
python-pam imports six but doesn't declare it. On Linux, six is usually
present system-wide. On macOS uv tool environments (clean/isolated),
import pam fails with 'No module named six'. Adding six explicitly to
our deps ensures PAM auth works on macOS.
python-multipart is required by FastAPI for form POST handling (login).
Was missing from pyproject.toml — crash on macOS first run.
launchd plist now uses the muxplex entry point script directly instead
of python -m muxplex, so macOS shows 'muxplex' in Activity Monitor,
launchctl list, and login items instead of 'python3'.
- Expand Prerequisites section with bullet format (was a sparse table)
- Add tmux and ttyd per-platform install commands (macOS, Ubuntu/WSL)
- Note that muxplex prints install instructions if either dependency is missing
- Update 'Install as a Service' section with macOS launchd instructions
- Update Usage table to reflect platform-aware install-service behavior
- Add _check_dependencies() that checks shutil.which() for tmux and ttyd
- Print clear error with per-platform install hints when either is missing
- Call _check_dependencies() in main() before serve — not for install-service,
show-password, or reset-secret (those don't need the binary dependencies)
- Tests: exits on missing ttyd, exits on missing tmux, passes when both present,
called for serve but not for install-service
- Add _install_launchd() that writes ~/Library/LaunchAgents/com.muxplex.plist
- Refactor existing systemd code into _install_systemd()
- install_service() now detects sys.platform == 'darwin' and delegates accordingly
- Update install-service help text to mention both platforms
- Tests: launchd plist on macOS, systemd on Linux, no cross-contamination
Hidden sessions were still appearing in the mobile bottom-sheet session
switcher because renderSheetList() read _currentSessions directly,
bypassing the getVisibleSessions() helper used by renderGrid() and
renderSidebar().
- app.js: sortByPriority now receives getVisibleSessions(_currentSessions)
instead of _currentSessions directly in renderSheetList()
- test_frontend_js.py: add test_render_sheet_list_filters_hidden_sessions
to assert getVisibleSessions is called in renderSheetList body
Completes consistent hidden-sessions filtering across all three render
paths (grid, sidebar, mobile bottom sheet).
Co-authored-by: Amplifier <amplifier@amplified.dev>
- Extract getVisibleSessions() helper to consolidate hidden-session
filter used by renderGrid() and renderSidebar() (DRY reduction)
- Add comment to terminal.js magic literal 600 noting it matches
MOBILE_THRESHOLD in app.js
- Move TERMINAL_JS_PATH and _TERMINAL_JS module fixtures to the top
of the test file alongside JS_PATH and _JS for consistency
- Strengthen test_render_grid_uses_visible_for_empty_state_check to
assert visible.length specifically, distinguishing it from
test_render_grid_creates_visible_array
- Update renderGrid/renderSidebar filter tests to assert getVisibleSessions()
call; add test_get_visible_sessions_filters_hidden_sessions to cover
the extracted helper directly
- terminal.js createTerminal(): read fontSize from localStorage 'muxplex.display',
default 14, apply Math.min(storedFontSize, 12) mobile cap
- app.js renderGrid(): filter hidden sessions via _serverSettings.hidden_sessions,
create visible array, apply alphabetical sort via localeCompare when sort_order is
'alphabetical'; 'recent'/'manual' use server-provided order
- app.js renderSidebar(): apply same hidden sessions filter, use visible array
- app.js DOMContentLoaded: call loadServerSettings() after startPolling() in restoreState().then()
- tests: 13 new tests in test_frontend_js.py covering all spec requirements
Co-authored-by: Amplifier <amplifier@example.com>
Three suggestions from the code review applied:
1. Extract _createSessionInput() factory to eliminate five-line
duplication between showNewSessionInput and showFabSessionInput.
Both functions now call the shared factory. Updated 4 existing
tests to check _createSessionInput's body; added 7 new tests
covering factory behavior and factory usage in each consumer.
2. Fix .new-session-fab:focus-visible outline color from var(--accent)
to var(--bg) — the FAB background is already var(--accent), so
using the same color for the outline created zero contrast.
Added 1 new test to guard this going forward.
3. Add early-return guard in showFabSessionInput against duplicate
overlays: if (document.querySelector('.fab-input-overlay')) return;
Prevents a second overlay being created if called programmatically
while one is already open. Added 1 new test.
532 tests pass (9 new tests added, 523 unchanged).
- Add div.sidebar-footer with button#sidebar-new-session-btn.sidebar-new-btn
('+ New') to #session-sidebar in index.html, positioned after #sidebar-list
- Add CSS for .sidebar-footer (padding: 8px, border-top, flex-shrink: 0)
- Add CSS for .sidebar-new-btn (width: 100%, dashed border, dim text, hover
effects with muted text and border color)
- Bind #sidebar-new-session-btn click to showNewSessionInput() in
bindStaticEventListeners()
- Add 15 tests covering HTML structure, CSS rules, and JS binding
🤖 Co-authored-by: Amplifier <amplifier@example.com>
- Extract shared _updateNotificationUI(statusEl, reqBtn, permission) helper
to remove duplicated notification status UI update logic from openSettings
and the requestPermission click handler
- Replace var with const for local bindings in new Notifications handlers
- Use this.checked in bell sound change handler instead of re-querying
the DOM element that was already looked up for addEventListener
switchSettingsTab() in app.js reads panel.dataset.tab (data-tab attribute).
The four .settings-panel elements in index.html were using data-panel instead,
causing all panels to get hidden on every tab click — the settings dialog rendered
as a blank content area after any tab interaction.
Fix: Change data-panel="..." to data-tab="..." on all four panel divs.
Tests added:
- test_html_settings_panels_use_data_tab: verifies each .settings-panel uses
data-tab and not data-panel
- test_html_settings_tab_panel_data_tab_alignment: cross-checks that every tab
button data-tab value has a matching .settings-panel data-tab value, preventing
this class of HTML/JS attribute mismatch from recurring
- Replace DEFAULT_SETTINGS.copy() with copy.deepcopy(DEFAULT_SETTINGS) in
load_settings() and save_settings() to prevent mutation of module-level
mutable defaults (hidden_sessions list was shared via shallow copy)
- Replace except (json.JSONDecodeError, Exception) with
except (FileNotFoundError, json.JSONDecodeError) to only suppress expected
errors and let unexpected exceptions (PermissionError etc.) propagate
- Add two regression tests:
test_load_does_not_mutate_default_settings: verifies deepcopy isolation
test_load_propagates_non_json_errors: verifies narrow exception handling
Remove tile-body::before and sidebar-item-body::before gradient overlays
that obscured ANSI colors at the bottom of previews. Replace box-shadow
inset with border-left on sidebar-item--active so the cyan accent stripe
spans the full item height (was only showing on the header).