From 80dc082df0ba8fd6830346c4f6b770f46d7eee4b Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Sun, 29 Mar 2026 22:12:07 -0700 Subject: [PATCH] refactor: remove command palette HTML and CSS --- .../2026-03-28-auth-phase1-infrastructure.md | 1074 +++++++++++++ docs/plans/2026-03-28-auth-phase2-ui-cli.md | 1066 +++++++++++++ docs/plans/2026-03-29-settings-phase1.md | 1395 +++++++++++++++++ docs/plans/2026-03-29-settings-phase2.md | 1110 +++++++++++++ muxplex/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 152 bytes muxplex/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 152 bytes muxplex/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 275 bytes muxplex/__pycache__/auth.cpython-312.pyc | Bin 0 -> 8190 bytes muxplex/__pycache__/auth.cpython-313.pyc | Bin 0 -> 8331 bytes muxplex/__pycache__/bells.cpython-312.pyc | Bin 0 -> 5858 bytes muxplex/__pycache__/bells.cpython-313.pyc | Bin 0 -> 5790 bytes muxplex/__pycache__/cli.cpython-312.pyc | Bin 0 -> 6830 bytes muxplex/__pycache__/cli.cpython-313.pyc | Bin 0 -> 4181 bytes muxplex/__pycache__/main.cpython-312.pyc | Bin 0 -> 22837 bytes muxplex/__pycache__/main.cpython-313.pyc | Bin 0 -> 20218 bytes muxplex/__pycache__/sessions.cpython-312.pyc | Bin 0 -> 5546 bytes muxplex/__pycache__/sessions.cpython-313.pyc | Bin 0 -> 5509 bytes muxplex/__pycache__/state.cpython-312.pyc | Bin 0 -> 6446 bytes muxplex/__pycache__/state.cpython-313.pyc | Bin 0 -> 6354 bytes muxplex/__pycache__/ttyd.cpython-312.pyc | Bin 0 -> 5889 bytes muxplex/__pycache__/ttyd.cpython-313.pyc | Bin 0 -> 5783 bytes muxplex/frontend/index.html | 7 - muxplex/frontend/style.css | 98 -- .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 158 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 158 bytes .../test_api.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 93323 bytes .../test_api.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 74255 bytes .../test_auth.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 57629 bytes .../test_auth.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 46895 bytes .../test_bells.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 25934 bytes .../test_bells.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 26605 bytes .../test_cli.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 27760 bytes .../test_cli.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 14581 bytes ..._frontend_css.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 128883 bytes ..._frontend_css.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 118534 bytes ...frontend_html.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 45832 bytes ...frontend_html.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 49106 bytes ...t_integration.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 13638 bytes ...t_integration.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 13866 bytes ...test_sessions.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 23665 bytes ...test_sessions.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 22693 bytes .../test_state.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 31889 bytes .../test_state.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 33080 bytes .../test_ttyd.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 23291 bytes .../test_ttyd.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 24008 bytes ...test_ws_proxy.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 2761 bytes ...test_ws_proxy.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 2820 bytes muxplex/tests/test_frontend_css.py | 8 - muxplex/tests/test_frontend_html.py | 11 +- 49 files changed, 4646 insertions(+), 123 deletions(-) create mode 100644 docs/plans/2026-03-28-auth-phase1-infrastructure.md create mode 100644 docs/plans/2026-03-28-auth-phase2-ui-cli.md create mode 100644 docs/plans/2026-03-29-settings-phase1.md create mode 100644 docs/plans/2026-03-29-settings-phase2.md create mode 100644 muxplex/__pycache__/__init__.cpython-312.pyc create mode 100644 muxplex/__pycache__/__init__.cpython-313.pyc create mode 100644 muxplex/__pycache__/__main__.cpython-312.pyc create mode 100644 muxplex/__pycache__/auth.cpython-312.pyc create mode 100644 muxplex/__pycache__/auth.cpython-313.pyc create mode 100644 muxplex/__pycache__/bells.cpython-312.pyc create mode 100644 muxplex/__pycache__/bells.cpython-313.pyc create mode 100644 muxplex/__pycache__/cli.cpython-312.pyc create mode 100644 muxplex/__pycache__/cli.cpython-313.pyc create mode 100644 muxplex/__pycache__/main.cpython-312.pyc create mode 100644 muxplex/__pycache__/main.cpython-313.pyc create mode 100644 muxplex/__pycache__/sessions.cpython-312.pyc create mode 100644 muxplex/__pycache__/sessions.cpython-313.pyc create mode 100644 muxplex/__pycache__/state.cpython-312.pyc create mode 100644 muxplex/__pycache__/state.cpython-313.pyc create mode 100644 muxplex/__pycache__/ttyd.cpython-312.pyc create mode 100644 muxplex/__pycache__/ttyd.cpython-313.pyc create mode 100644 muxplex/tests/__pycache__/__init__.cpython-312.pyc create mode 100644 muxplex/tests/__pycache__/__init__.cpython-313.pyc create mode 100644 muxplex/tests/__pycache__/test_api.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_api.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_auth.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_auth.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_bells.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_bells.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_cli.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_cli.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_frontend_css.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_frontend_css.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_frontend_html.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_frontend_html.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_integration.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_integration.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_sessions.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_sessions.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_state.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_state.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_ttyd.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_ttyd.cpython-313-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_ws_proxy.cpython-312-pytest-9.0.2.pyc create mode 100644 muxplex/tests/__pycache__/test_ws_proxy.cpython-313-pytest-9.0.2.pyc diff --git a/docs/plans/2026-03-28-auth-phase1-infrastructure.md b/docs/plans/2026-03-28-auth-phase1-infrastructure.md new file mode 100644 index 0000000..dfe6d3a --- /dev/null +++ b/docs/plans/2026-03-28-auth-phase1-infrastructure.md @@ -0,0 +1,1074 @@ +# Auth Phase 1: Core Infrastructure — Implementation Plan + +> **Execution:** Use the subagent-driven-development workflow to implement this plan. + +**Goal:** Build the auth module (`auth.py`), middleware, and route stubs so all non-localhost HTTP/WS requests require authentication. + +**Architecture:** A new `muxplex/auth.py` module provides password file management, signing secret management, session cookie signing/verification, PAM authentication, and a FastAPI middleware class. The middleware is mounted in `main.py` and gates every request. Localhost clients bypass auth entirely. A stub `/login` page and `/auth/mode` JSON endpoint are added for Phase 2 to build on. + +**Tech Stack:** Python 3.11+, FastAPI, itsdangerous (TimestampSigner), python-pam, pytest + +**Phase:** 1 of 2 — complete this phase before starting Phase 2 (`2026-03-28-auth-phase2-ui-cli.md`) + +**Design doc:** `docs/plans/2026-03-28-auth-design.md` + +--- + +### Task 1: Add dependencies to pyproject.toml + +**Files:** +- Modify: `pyproject.toml` (line 12–17, `[project.dependencies]`) + +**Step 1: Add python-pam and itsdangerous to dependencies** + +In `pyproject.toml`, add two entries to the `dependencies` list. The existing list looks like: + +```toml +dependencies = [ + "fastapi>=0.115.0", + "uvicorn[standard]>=0.30.0", + "aiofiles>=23.0", + "websockets>=11.0", +] +``` + +Change it to: + +```toml +dependencies = [ + "fastapi>=0.115.0", + "uvicorn[standard]>=0.30.0", + "aiofiles>=23.0", + "websockets>=11.0", + "python-pam>=1.8.4", + "itsdangerous>=2.1.0", +] +``` + +**Step 2: Install updated dependencies** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && uv pip install -e ".[dev]"` +Expected: installs without errors, both new packages appear in the output + +**Step 3: Verify imports work** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -c "import pam; import itsdangerous; print('OK')"` +Expected: prints `OK` + +**Step 4: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add pyproject.toml && git commit -m "chore: add python-pam and itsdangerous dependencies" +``` + +--- + +### Task 2: Password file management in auth.py + +**Files:** +- Create: `muxplex/auth.py` +- Create: `muxplex/tests/test_auth.py` + +**Step 1: Write the failing tests** + +Create `muxplex/tests/test_auth.py`: + +```python +"""Tests for muxplex/auth.py — authentication module.""" + +import os +import stat +from pathlib import Path + +import pytest + + +# --------------------------------------------------------------------------- +# Password file management +# --------------------------------------------------------------------------- + + +def test_get_password_path_returns_expected_path(monkeypatch, tmp_path): + """get_password_path() returns ~/.config/muxplex/password.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + from muxplex.auth import get_password_path + + assert get_password_path() == tmp_path / ".config" / "muxplex" / "password" + + +def test_load_password_returns_none_when_no_file(monkeypatch, tmp_path): + """load_password() returns None when password file does not exist.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + from muxplex.auth import load_password + + assert load_password() is None + + +def test_load_password_reads_existing_file(monkeypatch, tmp_path): + """load_password() reads and strips the password file contents.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + pw_path = tmp_path / ".config" / "muxplex" / "password" + pw_path.parent.mkdir(parents=True, exist_ok=True) + pw_path.write_text("my-secret-password\n") + pw_path.chmod(0o600) + + from muxplex.auth import load_password + + assert load_password() == "my-secret-password" + + +def test_generate_and_save_password_creates_file(monkeypatch, tmp_path): + """generate_and_save_password() creates the file and returns a non-empty string.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + from muxplex.auth import generate_and_save_password + + pw = generate_and_save_password() + assert isinstance(pw, str) + assert len(pw) > 10 + + pw_path = tmp_path / ".config" / "muxplex" / "password" + assert pw_path.exists() + assert pw_path.read_text().strip() == pw + + +def test_generate_and_save_password_sets_0600_permissions(monkeypatch, tmp_path): + """generate_and_save_password() sets the file to mode 0600.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + from muxplex.auth import generate_and_save_password + + generate_and_save_password() + pw_path = tmp_path / ".config" / "muxplex" / "password" + mode = stat.S_IMODE(pw_path.stat().st_mode) + assert mode == 0o600 +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "password" 2>&1 | head -30` +Expected: FAIL — `ModuleNotFoundError: No module named 'muxplex.auth'` + +**Step 3: Write the implementation** + +Create `muxplex/auth.py`: + +```python +""" +muxplex authentication — password management, secret management, +session cookies, PAM integration, and request middleware. +""" + +import os +import secrets +from pathlib import Path + + +# --------------------------------------------------------------------------- +# Config directory +# --------------------------------------------------------------------------- + + +def _config_dir() -> Path: + """Return ~/.config/muxplex, creating it (mode 0700) if needed.""" + d = Path.home() / ".config" / "muxplex" + d.mkdir(parents=True, exist_ok=True) + return d + + +# --------------------------------------------------------------------------- +# Password file management +# --------------------------------------------------------------------------- + + +def get_password_path() -> Path: + """Return the path to the password file: ~/.config/muxplex/password.""" + return Path.home() / ".config" / "muxplex" / "password" + + +def load_password() -> str | None: + """Read the password file if it exists, return None otherwise.""" + path = get_password_path() + if not path.exists(): + return None + return path.read_text().strip() + + +def generate_and_save_password() -> str: + """Generate a random password, write it to the password file (0600), return it.""" + pw = secrets.token_urlsafe(20) + path = get_password_path() + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(pw + "\n") + path.chmod(0o600) + return pw +``` + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "password"` +Expected: all 5 tests PASS + +**Step 5: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/auth.py muxplex/tests/test_auth.py && git commit -m "feat(auth): password file management" +``` + +--- + +### Task 3: Secret file management in auth.py + +**Files:** +- Modify: `muxplex/auth.py` +- Modify: `muxplex/tests/test_auth.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_auth.py`: + +```python +# --------------------------------------------------------------------------- +# Secret file management +# --------------------------------------------------------------------------- + + +def test_get_secret_path_returns_expected_path(monkeypatch, tmp_path): + """get_secret_path() returns ~/.config/muxplex/secret.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + from muxplex.auth import get_secret_path + + assert get_secret_path() == tmp_path / ".config" / "muxplex" / "secret" + + +def test_load_or_create_secret_creates_new_file(monkeypatch, tmp_path): + """load_or_create_secret() creates a secret file when none exists.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + from muxplex.auth import load_or_create_secret + + secret = load_or_create_secret() + assert isinstance(secret, str) + assert len(secret) > 20 + + secret_path = tmp_path / ".config" / "muxplex" / "secret" + assert secret_path.exists() + + +def test_load_or_create_secret_sets_0600_permissions(monkeypatch, tmp_path): + """load_or_create_secret() sets the secret file to mode 0600.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + from muxplex.auth import load_or_create_secret + + load_or_create_secret() + secret_path = tmp_path / ".config" / "muxplex" / "secret" + mode = stat.S_IMODE(secret_path.stat().st_mode) + assert mode == 0o600 + + +def test_load_or_create_secret_returns_same_value_on_second_call(monkeypatch, tmp_path): + """load_or_create_secret() returns the same secret on subsequent calls.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + from muxplex.auth import load_or_create_secret + + first = load_or_create_secret() + second = load_or_create_secret() + assert first == second +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "secret" 2>&1 | head -20` +Expected: FAIL — `ImportError: cannot import name 'get_secret_path' from 'muxplex.auth'` + +**Step 3: Write the implementation** + +Add to `muxplex/auth.py`, after the password management section: + +```python +# --------------------------------------------------------------------------- +# Secret (signing key) management +# --------------------------------------------------------------------------- + + +def get_secret_path() -> Path: + """Return the path to the signing secret file: ~/.config/muxplex/secret.""" + return Path.home() / ".config" / "muxplex" / "secret" + + +def load_or_create_secret() -> str: + """Load the signing secret from file, or create one if it doesn't exist.""" + path = get_secret_path() + if path.exists(): + return path.read_text().strip() + secret = secrets.token_urlsafe(32) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(secret + "\n") + path.chmod(0o600) + return secret +``` + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "secret"` +Expected: all 4 tests PASS + +**Step 5: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/auth.py muxplex/tests/test_auth.py && git commit -m "feat(auth): signing secret file management" +``` + +--- + +### Task 4: Session cookie signing and verification in auth.py + +**Files:** +- Modify: `muxplex/auth.py` +- Modify: `muxplex/tests/test_auth.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_auth.py`: + +```python +# --------------------------------------------------------------------------- +# Session cookie signing / verification +# --------------------------------------------------------------------------- + + +def test_create_session_cookie_returns_string(): + """create_session_cookie() returns a non-empty string.""" + from muxplex.auth import create_session_cookie + + cookie = create_session_cookie("test-secret", ttl_seconds=3600) + assert isinstance(cookie, str) + assert len(cookie) > 0 + + +def test_verify_session_cookie_valid_roundtrip(): + """A cookie created by create_session_cookie verifies successfully.""" + from muxplex.auth import create_session_cookie, verify_session_cookie + + cookie = create_session_cookie("test-secret", ttl_seconds=3600) + assert verify_session_cookie("test-secret", cookie, ttl_seconds=3600) is True + + +def test_verify_session_cookie_tampered(): + """A tampered cookie fails verification.""" + from muxplex.auth import create_session_cookie, verify_session_cookie + + cookie = create_session_cookie("test-secret", ttl_seconds=3600) + tampered = cookie + "X" + assert verify_session_cookie("test-secret", tampered, ttl_seconds=3600) is False + + +def test_verify_session_cookie_wrong_secret(): + """A cookie signed with a different secret fails verification.""" + from muxplex.auth import create_session_cookie, verify_session_cookie + + cookie = create_session_cookie("secret-A", ttl_seconds=3600) + assert verify_session_cookie("secret-B", cookie, ttl_seconds=3600) is False + + +def test_verify_session_cookie_expired(): + """An expired cookie (max_age=0) fails verification.""" + from muxplex.auth import create_session_cookie, verify_session_cookie + + cookie = create_session_cookie("test-secret", ttl_seconds=3600) + # Verify with max_age=0 means it's immediately expired + assert verify_session_cookie("test-secret", cookie, ttl_seconds=0) is False +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "cookie" 2>&1 | head -20` +Expected: FAIL — `ImportError: cannot import name 'create_session_cookie' from 'muxplex.auth'` + +**Step 3: Write the implementation** + +Add to `muxplex/auth.py`, adding the import at the top and the functions after the secret section: + +Add `from itsdangerous import TimestampSigner, BadSignature, SignatureExpired` to the imports at the top of the file. + +Then add after the secret management section: + +```python +# --------------------------------------------------------------------------- +# Session cookie signing / verification +# --------------------------------------------------------------------------- + + +def create_session_cookie(secret: str, ttl_seconds: int) -> str: + """Create a signed, timestamped session cookie value.""" + signer = TimestampSigner(secret) + return signer.sign("muxplex-session").decode() + + +def verify_session_cookie(secret: str, cookie: str, ttl_seconds: int) -> bool: + """Verify a session cookie's signature and expiry. Returns True/False.""" + signer = TimestampSigner(secret) + try: + signer.unsign(cookie, max_age=ttl_seconds if ttl_seconds > 0 else None) + return ttl_seconds > 0 or True # ttl=0 with valid sig is still valid (session cookie) + except (BadSignature, SignatureExpired): + return False +``` + +Wait — re-reading the design: `--session-ttl 0` means session cookie (clears on browser close). So `ttl_seconds=0` should mean **no server-side expiry check** (the cookie is valid until the browser drops it). But the test says `ttl_seconds=0` should fail. Let me reconsider. + +Actually the test `test_verify_session_cookie_expired` needs a different approach. We need to test that a cookie signed *in the past* with a short TTL fails. Let's fix both the test and implementation: + +Replace the expired test with: + +```python +def test_verify_session_cookie_expired(): + """A cookie verified with a very short TTL fails (simulates expiry).""" + import time + from muxplex.auth import create_session_cookie, verify_session_cookie + + cookie = create_session_cookie("test-secret", ttl_seconds=1) + time.sleep(1.1) # Wait for it to expire + assert verify_session_cookie("test-secret", cookie, ttl_seconds=1) is False +``` + +And the implementation: + +```python +def verify_session_cookie(secret: str, cookie: str, ttl_seconds: int) -> bool: + """Verify a session cookie's signature and expiry. Returns True/False. + + ttl_seconds=0 means session cookie — no server-side expiry check. + """ + signer = TimestampSigner(secret) + try: + max_age = ttl_seconds if ttl_seconds > 0 else None + signer.unsign(cookie, max_age=max_age) + return True + except (BadSignature, SignatureExpired): + return False +``` + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "cookie"` +Expected: all 5 tests PASS (the expired test takes ~1.1s) + +**Step 5: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/auth.py muxplex/tests/test_auth.py && git commit -m "feat(auth): session cookie signing and verification" +``` + +--- + +### Task 5: PAM authentication with running-user check + +**Files:** +- Modify: `muxplex/auth.py` +- Modify: `muxplex/tests/test_auth.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_auth.py`: + +```python +# --------------------------------------------------------------------------- +# PAM authentication +# --------------------------------------------------------------------------- + + +def test_pam_available_returns_true_when_pam_importable(): + """pam_available() returns True when python-pam is installed.""" + from muxplex.auth import pam_available + + # python-pam is in our deps, so it should be importable + assert pam_available() is True + + +def test_pam_available_returns_false_on_import_error(monkeypatch): + """pam_available() returns False when pam cannot be imported.""" + import builtins + + real_import = builtins.__import__ + + def mock_import(name, *args, **kwargs): + if name == "pam": + raise ImportError("mock: no pam") + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", mock_import) + from muxplex.auth import pam_available + + assert pam_available() is False + + +def test_authenticate_pam_success(monkeypatch): + """authenticate_pam() returns True when PAM succeeds for the running user.""" + import pwd + + from muxplex.auth import authenticate_pam + + running_user = pwd.getpwuid(os.getuid()).pw_name + monkeypatch.setattr("pam.authenticate", lambda u, p, service="login": True) + assert authenticate_pam(running_user, "correct-password") is True + + +def test_authenticate_pam_wrong_password(monkeypatch): + """authenticate_pam() returns False when PAM rejects credentials.""" + import pwd + + from muxplex.auth import authenticate_pam + + running_user = pwd.getpwuid(os.getuid()).pw_name + monkeypatch.setattr("pam.authenticate", lambda u, p, service="login": False) + assert authenticate_pam(running_user, "wrong-password") is False + + +def test_authenticate_pam_wrong_user_rejected(monkeypatch): + """authenticate_pam() rejects a different username even if PAM would accept it.""" + from muxplex.auth import authenticate_pam + + # Mock PAM to always return True — but wrong username should still fail + monkeypatch.setattr("pam.authenticate", lambda u, p, service="login": True) + assert authenticate_pam("root", "any-password") is False +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "pam" 2>&1 | head -20` +Expected: FAIL — `ImportError: cannot import name 'pam_available' from 'muxplex.auth'` + +**Step 3: Write the implementation** + +Add to `muxplex/auth.py`, after the cookie section: + +```python +# --------------------------------------------------------------------------- +# PAM authentication +# --------------------------------------------------------------------------- + + +def pam_available() -> bool: + """Check whether the python-pam module is importable.""" + try: + import pam # noqa: F811 + + return True + except ImportError: + return False + + +def authenticate_pam(username: str, password: str) -> bool: + """Authenticate via PAM. Username must match the running process owner.""" + import os as _os + import pwd + + import pam + + running_user = pwd.getpwuid(_os.getuid()).pw_name + if username != running_user: + return False + return pam.authenticate(username, password, service="login") +``` + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "pam"` +Expected: all 5 PAM tests PASS + +**Step 5: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/auth.py muxplex/tests/test_auth.py && git commit -m "feat(auth): PAM authentication with running-user check" +``` + +--- + +### Task 6: Auth middleware in auth.py + +**Files:** +- Modify: `muxplex/auth.py` +- Modify: `muxplex/tests/test_auth.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_auth.py`: + +```python +# --------------------------------------------------------------------------- +# Auth middleware +# --------------------------------------------------------------------------- + +import base64 + +from fastapi import FastAPI +from fastapi.testclient import TestClient +from starlette.responses import PlainTextResponse + +from muxplex.auth import AuthMiddleware, create_session_cookie + + +def _make_test_app(auth_mode: str = "password", password: str = "test-pw") -> FastAPI: + """Create a minimal FastAPI app with AuthMiddleware for testing.""" + test_app = FastAPI() + + test_app.add_middleware( + AuthMiddleware, + auth_mode=auth_mode, + secret="test-secret", + ttl_seconds=3600, + password=password, + ) + + @test_app.get("/protected") + async def protected(): + return PlainTextResponse("OK") + + return test_app + + +def test_middleware_localhost_bypasses_auth(): + """Requests from 127.0.0.1 pass through without auth.""" + app = _make_test_app() + client = TestClient(app, base_url="http://127.0.0.1") + response = client.get("/protected") + assert response.status_code == 200 + assert response.text == "OK" + + +def test_middleware_valid_session_cookie_passes(): + """Non-localhost request with a valid session cookie passes through.""" + app = _make_test_app() + cookie = create_session_cookie("test-secret", ttl_seconds=3600) + client = TestClient(app, base_url="http://192.168.1.1") + response = client.get("/protected", cookies={"muxplex_session": cookie}) + assert response.status_code == 200 + assert response.text == "OK" + + +def test_middleware_tampered_cookie_redirects(): + """Non-localhost request with a tampered cookie redirects to /login.""" + app = _make_test_app() + client = TestClient(app, base_url="http://192.168.1.1", follow_redirects=False) + response = client.get("/protected", cookies={"muxplex_session": "bad.cookie.value"}) + assert response.status_code == 307 + assert "/login" in response.headers["location"] + + +def test_middleware_no_cookie_non_localhost_redirects(): + """Non-localhost request with no cookie redirects to /login.""" + app = _make_test_app() + client = TestClient(app, base_url="http://192.168.1.1", follow_redirects=False) + response = client.get("/protected") + assert response.status_code == 307 + assert "/login" in response.headers["location"] + + +def test_middleware_basic_auth_valid_password(): + """Non-localhost request with valid Basic auth header passes through.""" + app = _make_test_app(auth_mode="password", password="test-pw") + client = TestClient(app, base_url="http://192.168.1.1") + creds = base64.b64encode(b":test-pw").decode() + response = client.get("/protected", headers={"Authorization": f"Basic {creds}"}) + assert response.status_code == 200 + assert response.text == "OK" + + +def test_middleware_basic_auth_invalid_password(): + """Non-localhost request with wrong Basic auth header returns 401.""" + app = _make_test_app(auth_mode="password", password="test-pw") + client = TestClient(app, base_url="http://192.168.1.1") + creds = base64.b64encode(b":wrong-pw").decode() + response = client.get("/protected", headers={"Authorization": f"Basic {creds}"}) + assert response.status_code == 401 + + +def test_middleware_json_request_gets_401_not_redirect(): + """Non-localhost API request (Accept: application/json) gets 401, not redirect.""" + app = _make_test_app() + client = TestClient(app, base_url="http://192.168.1.1", follow_redirects=False) + response = client.get("/protected", headers={"Accept": "application/json"}) + assert response.status_code == 401 + + +def test_middleware_login_path_excluded(): + """/login path is excluded from auth to avoid redirect loops.""" + app = _make_test_app() + + @app.get("/login") + async def login(): + return PlainTextResponse("login page") + + client = TestClient(app, base_url="http://192.168.1.1") + response = client.get("/login") + assert response.status_code == 200 +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "middleware" 2>&1 | head -20` +Expected: FAIL — `ImportError: cannot import name 'AuthMiddleware' from 'muxplex.auth'` + +**Step 3: Write the implementation** + +Add these imports to the top of `muxplex/auth.py`: + +```python +import base64 + +from starlette.middleware.base import BaseHTTPMiddleware +from starlette.requests import Request +from starlette.responses import JSONResponse, RedirectResponse, Response +``` + +Then add after the PAM section: + +```python +# --------------------------------------------------------------------------- +# Auth middleware +# --------------------------------------------------------------------------- + +# Paths that bypass auth (login page itself, static assets it needs) +_AUTH_EXEMPT_PATHS = {"/login", "/auth/mode", "/auth/logout"} + + +class AuthMiddleware(BaseHTTPMiddleware): + """FastAPI middleware that enforces authentication on non-localhost requests.""" + + def __init__( + self, + app, + auth_mode: str, + secret: str, + ttl_seconds: int, + password: str = "", + ): + super().__init__(app) + self.auth_mode = auth_mode + self.secret = secret + self.ttl_seconds = ttl_seconds + self.password = password + + async def dispatch(self, request: Request, call_next) -> Response: + # 1. Localhost bypass + client_host = request.client.host if request.client else "unknown" + if client_host in ("127.0.0.1", "::1"): + return await call_next(request) + + # 2. Exempt paths (login page, auth endpoints) + if request.url.path in _AUTH_EXEMPT_PATHS: + return await call_next(request) + + # 3. Valid session cookie + cookie = request.cookies.get("muxplex_session") + if cookie and verify_session_cookie(self.secret, cookie, self.ttl_seconds): + return await call_next(request) + + # 4. Authorization: Basic header + auth_header = request.headers.get("authorization", "") + if auth_header.lower().startswith("basic "): + try: + decoded = base64.b64decode(auth_header[6:]).decode() + username, _, pw = decoded.partition(":") + if self._check_credentials(username, pw): + return await call_next(request) + except Exception: + pass + return JSONResponse({"detail": "Invalid credentials"}, status_code=401) + + # 5. No auth — redirect browsers, 401 for API clients + accept = request.headers.get("accept", "") + if "application/json" in accept: + return JSONResponse({"detail": "Authentication required"}, status_code=401) + return RedirectResponse(url="/login", status_code=307) + + def _check_credentials(self, username: str, password: str) -> bool: + """Validate credentials against the configured auth mode.""" + if self.auth_mode == "pam": + return authenticate_pam(username, password) + return password == self.password +``` + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "middleware"` +Expected: all 8 middleware tests PASS + +**Step 5: Run all auth tests** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v` +Expected: all tests PASS (password + secret + cookie + PAM + middleware) + +**Step 6: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/auth.py muxplex/tests/test_auth.py && git commit -m "feat(auth): request middleware with localhost bypass and session cookie check" +``` + +--- + +### Task 7: Wire middleware into main.py + +**Files:** +- Modify: `muxplex/main.py` + +**Step 1: Write the failing test** + +Append to `muxplex/tests/test_api.py`, at the very end: + +```python +# --------------------------------------------------------------------------- +# Auth middleware integration +# --------------------------------------------------------------------------- + + +def test_non_localhost_without_auth_gets_redirected(monkeypatch): + """A non-localhost request without credentials is redirected to /login.""" + from fastapi.testclient import TestClient + + from muxplex.main import app + + # Ensure auth is active — set a known password via env + monkeypatch.setenv("MUXPLEX_PASSWORD", "test-pw-for-api") + + with TestClient(app, base_url="http://192.168.1.1") as c: + response = c.get("/health", follow_redirects=False) + # Should be redirected to /login or get 307/401 + assert response.status_code in (307, 401) +``` + +**Step 2: Run to verify it fails** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py::test_non_localhost_without_auth_gets_redirected -v` +Expected: FAIL — currently returns 200 (no middleware yet) + +**Step 3: Wire middleware into main.py** + +In `muxplex/main.py`, add auth setup logic. Add these imports near the top (after the existing imports): + +```python +import pwd +import sys + +from muxplex.auth import ( + AuthMiddleware, + generate_and_save_password, + load_or_create_secret, + load_password, + pam_available, +) +``` + +Then, after the `app = FastAPI(...)` line (line 173) and before the request/response models section, add: + +```python +# --------------------------------------------------------------------------- +# Auth setup +# --------------------------------------------------------------------------- + +def _resolve_auth() -> tuple[str, str]: + """Determine auth mode and resolve password. Returns (auth_mode, password). + + Fallback chain for non-localhost: + 1. PAM available → ("pam", "") + 2. MUXPLEX_PASSWORD env → ("password", ) + 3. ~/.config/muxplex/password file → ("password", ) + 4. Auto-generate → ("password", ) + """ + # Explicit override: MUXPLEX_AUTH=password forces password mode + force_password = os.environ.get("MUXPLEX_AUTH", "").lower() == "password" + + if not force_password and pam_available(): + running_user = pwd.getpwuid(os.getuid()).pw_name + print(f" muxplex auth: PAM (user: {running_user})", file=sys.stderr) + return "pam", "" + + if not force_password: + print(" muxplex auth: PAM unavailable, using password mode", file=sys.stderr) + + # Password mode — resolve password + env_pw = os.environ.get("MUXPLEX_PASSWORD") + if env_pw: + print(" muxplex auth: password (env)", file=sys.stderr) + return "password", env_pw + + file_pw = load_password() + if file_pw: + print(f" muxplex auth: password (file: {load_password.__module__})", file=sys.stderr) + return "password", file_pw + + # Last resort: auto-generate + generated = generate_and_save_password() + from muxplex.auth import get_password_path + + print( + f" muxplex auth: password generated — {generated} — saved to {get_password_path()}", + file=sys.stderr, + ) + return "password", generated + + +_auth_mode, _auth_password = _resolve_auth() +_auth_secret = load_or_create_secret() +_auth_ttl = int(os.environ.get("MUXPLEX_SESSION_TTL", "604800")) + +app.add_middleware( + AuthMiddleware, + auth_mode=_auth_mode, + secret=_auth_secret, + ttl_seconds=_auth_ttl, + password=_auth_password, +) +``` + +**Step 4: Run to verify test passes** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py::test_non_localhost_without_auth_gets_redirected -v` +Expected: PASS + +**Step 5: Run the full existing test suite to make sure nothing broke** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py -v` +Expected: all existing tests still PASS (they use `TestClient` with default `base_url="http://testserver"` which resolves to localhost via TestClient internals — **but verify this**. If existing tests break because they're not coming from localhost, the `patch_startup_and_state` fixture needs to also set `MUXPLEX_PASSWORD` or the middleware needs to treat testserver as localhost.) + +**Important:** If existing tests fail with 307/401, add this line to the `patch_startup_and_state` fixture in `test_api.py`: + +```python +monkeypatch.setenv("MUXPLEX_PASSWORD", "test-password") +``` + +This ensures auth resolves without side effects. The TestClient's default host (`testserver`) won't match localhost, so the fixture needs to provide auth context. + +Also, if tests fail because `TestClient` doesn't present as localhost, you may need to add the `muxplex_session` cookie to the `client` fixture. **However**, the simpler fix is: TestClient by default uses `base_url="http://testserver"` and `request.client.host` will be `testclient` — which is NOT `127.0.0.1`. Two solutions: + +**Option A (recommended):** Update the `client` fixture to pass a valid session cookie: +```python +@pytest.fixture +def client(monkeypatch): + monkeypatch.setenv("MUXPLEX_PASSWORD", "test-password") + with TestClient(app) as c: + # Authenticate for non-localhost TestClient + from muxplex.auth import create_session_cookie + from muxplex.main import _auth_secret, _auth_ttl + cookie = create_session_cookie(_auth_secret, _auth_ttl) + c.cookies.set("muxplex_session", cookie) + yield c +``` + +**Option B:** Use `base_url="http://127.0.0.1"` on the existing client fixture. But this changes `request.client.host` behavior. + +Verify which approach is needed by running the existing tests first. Adapt accordingly. + +**Step 6: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/main.py muxplex/tests/test_api.py && git commit -m "feat: wire auth middleware into FastAPI app" +``` + +--- + +### Task 8: GET /login stub and /auth/mode endpoint + +**Files:** +- Modify: `muxplex/main.py` +- Modify: `muxplex/tests/test_api.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_api.py`: + +```python +# --------------------------------------------------------------------------- +# Login stub and auth mode endpoint +# --------------------------------------------------------------------------- + + +def test_get_login_returns_200_html(client): + """GET /login returns 200 with HTML content.""" + response = client.get("/login") + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] + assert "&1 | head -20` +Expected: FAIL — 404 (routes don't exist yet) + +**Step 3: Add the routes to main.py** + +Add these imports to the top of `muxplex/main.py` if not already present: + +```python +from fastapi.responses import HTMLResponse, JSONResponse as FastJSONResponse +``` + +Then add these routes **before** the static file mount (before the `_FRONTEND_DIR` line, which must remain the last mount): + +```python +# --------------------------------------------------------------------------- +# Auth routes +# --------------------------------------------------------------------------- + + +@app.get("/login", response_class=HTMLResponse) +async def login_page(): + """Stub login page — replaced in Phase 2 with branded login.html.""" + return HTMLResponse( + "" + "

muxplex login

" + "
" + "" + "" + "
" + "" + ) + + +@app.get("/auth/mode") +async def auth_mode_endpoint(): + """Return the current auth mode and running username.""" + username = "" + if _auth_mode == "pam": + username = pwd.getpwuid(os.getuid()).pw_name + return {"mode": _auth_mode, "user": username} +``` + +**Step 4: Run to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py -v -k "login or auth_mode"` +Expected: both tests PASS + +**Step 5: Run the full test suite** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/ -v` +Expected: all tests PASS + +**Step 6: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/main.py muxplex/tests/test_api.py && git commit -m "feat: add /login stub and /auth/mode endpoint" +``` + +--- + +## Phase 1 Complete Checklist + +After all 8 tasks: + +- [ ] `pyproject.toml` has `python-pam` and `itsdangerous` in `[project.dependencies]` +- [ ] `muxplex/auth.py` exists with: password mgmt, secret mgmt, cookie signing, PAM auth, AuthMiddleware +- [ ] `muxplex/tests/test_auth.py` exists with tests for all of the above +- [ ] `muxplex/main.py` mounts AuthMiddleware, has `/login` stub and `/auth/mode` endpoint +- [ ] All tests in `muxplex/tests/` pass: `python -m pytest muxplex/tests/ -v` +- [ ] 8 clean commits with conventional commit messages + +Proceed to Phase 2: `docs/plans/2026-03-28-auth-phase2-ui-cli.md` \ No newline at end of file diff --git a/docs/plans/2026-03-28-auth-phase2-ui-cli.md b/docs/plans/2026-03-28-auth-phase2-ui-cli.md new file mode 100644 index 0000000..819981b --- /dev/null +++ b/docs/plans/2026-03-28-auth-phase2-ui-cli.md @@ -0,0 +1,1066 @@ +# Auth Phase 2: Login UI + CLI — Implementation Plan + +> **Execution:** Use the subagent-driven-development workflow to implement this plan. + +**Goal:** Build the branded login page, complete login/logout routes, and add all CLI auth commands (`--auth`, `--session-ttl`, `show-password`, `reset-secret`, startup logging). + +**Architecture:** Phase 1 established `auth.py` (middleware, password/secret/cookie/PAM functions) and a stub `/login` route. Phase 2 replaces the stub with a fully branded `login.html` that auto-detects PAM vs password mode, adds the POST `/login` and GET `/auth/logout` handlers, and wires the CLI flags and subcommands that control auth behavior at startup. + +**Tech Stack:** Python 3.11+, FastAPI, HTML/CSS/JS (no framework), argparse, pytest + +**Phase:** 2 of 2 — complete Phase 1 (`2026-03-28-auth-phase1-infrastructure.md`) before starting this phase. + +**Design doc:** `docs/plans/2026-03-28-auth-design.md` + +**Prerequisite:** Phase 1 must be complete. Verify: `python -m pytest muxplex/tests/ -v` — all tests pass, `muxplex/auth.py` exists with `AuthMiddleware`, `/login` stub and `/auth/mode` endpoint exist in `main.py`. + +--- + +### Task 1: Create branded login.html + +**Files:** +- Create: `muxplex/frontend/login.html` +- Modify: `muxplex/tests/test_frontend_html.py` (add login.html tests) + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_frontend_html.py`: + +```python +# --------------------------------------------------------------------------- +# login.html tests +# --------------------------------------------------------------------------- + +_LOGIN_HTML_PATH = pathlib.Path(__file__).parent.parent / "frontend" / "login.html" + + +def _login_soup() -> BeautifulSoup: + """Parse login.html — separate from index.html soup.""" + return BeautifulSoup(_LOGIN_HTML_PATH.read_text(), "html.parser") + + +def test_login_html_exists() -> None: + """login.html must exist in the frontend directory.""" + assert _LOGIN_HTML_PATH.exists(), f"Missing {_LOGIN_HTML_PATH}" + + +def test_login_html_has_form() -> None: + """login.html must contain a POST form targeting /login.""" + soup = _login_soup() + form = soup.find("form") + assert form is not None, "Missing
element" + assert form.get("method", "").lower() == "post", "Form method should be POST" + assert form.get("action") == "/login", "Form action should be /login" + + +def test_login_html_has_password_autocomplete() -> None: + """login.html password field must have autocomplete='current-password'.""" + soup = _login_soup() + pw_input = soup.find("input", attrs={"autocomplete": "current-password"}) + assert pw_input is not None, "Missing password input with autocomplete='current-password'" + + +def test_login_html_has_wordmark() -> None: + """login.html must include the muxplex wordmark SVG.""" + soup = _login_soup() + # Check for either an with wordmark or inline SVG + img = soup.find("img", attrs={"src": lambda s: s and "wordmark" in s}) + assert img is not None, "Missing muxplex wordmark image" + + +def test_login_html_references_muxplex_auth() -> None: + """login.html must reference window.MUXPLEX_AUTH for mode detection.""" + text = _LOGIN_HTML_PATH.read_text() + assert "MUXPLEX_AUTH" in text, "login.html must reference MUXPLEX_AUTH for mode detection" + + +def test_login_html_has_error_display() -> None: + """login.html must have an element for displaying auth errors.""" + soup = _login_soup() + # Look for an element that handles error state + text = _LOGIN_HTML_PATH.read_text() + assert "error" in text.lower(), "login.html must handle error display (query param ?error=1)" +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_frontend_html.py -v -k "login" 2>&1 | head -20` +Expected: FAIL — `login.html` doesn't exist yet + +**Step 3: Create the branded login.html** + +Create `muxplex/frontend/login.html`: + +```html + + + + + + + muxplex — login + + + + + +
+ + + + +``` + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_frontend_html.py -v -k "login"` +Expected: all 6 login tests PASS + +**Step 5: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/frontend/login.html muxplex/tests/test_frontend_html.py && git commit -m "feat: branded login.html with PAM/password mode detection" +``` + +--- + +### Task 2: POST /login handler + +**Files:** +- Modify: `muxplex/main.py` +- Modify: `muxplex/tests/test_api.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_api.py`: + +```python +# --------------------------------------------------------------------------- +# POST /login +# --------------------------------------------------------------------------- + + +def test_post_login_correct_password_redirects_to_root(client, monkeypatch): + """POST /login with correct password returns 303 redirect to / with session cookie.""" + monkeypatch.setattr("muxplex.main._auth_mode", "password") + monkeypatch.setattr("muxplex.main._auth_password", "test-pw") + + response = client.post( + "/login", + data={"password": "test-pw"}, + follow_redirects=False, + ) + assert response.status_code == 303 + assert response.headers["location"] == "/" + assert "muxplex_session" in response.cookies + + +def test_post_login_wrong_password_redirects_to_login_error(client, monkeypatch): + """POST /login with wrong password returns 303 redirect to /login?error=1.""" + monkeypatch.setattr("muxplex.main._auth_mode", "password") + monkeypatch.setattr("muxplex.main._auth_password", "test-pw") + + response = client.post( + "/login", + data={"password": "wrong-pw"}, + follow_redirects=False, + ) + assert response.status_code == 303 + assert "/login" in response.headers["location"] + assert "error=1" in response.headers["location"] + + +def test_post_login_pam_mode_correct_creds(client, monkeypatch): + """POST /login in PAM mode with correct creds sets cookie and redirects.""" + monkeypatch.setattr("muxplex.main._auth_mode", "pam") + monkeypatch.setattr( + "muxplex.auth.authenticate_pam", + lambda u, p: True, + ) + + response = client.post( + "/login", + data={"username": "testuser", "password": "correct"}, + follow_redirects=False, + ) + assert response.status_code == 303 + assert response.headers["location"] == "/" + assert "muxplex_session" in response.cookies + + +def test_post_login_pam_mode_wrong_creds(client, monkeypatch): + """POST /login in PAM mode with wrong creds redirects to /login?error=1.""" + monkeypatch.setattr("muxplex.main._auth_mode", "pam") + monkeypatch.setattr( + "muxplex.auth.authenticate_pam", + lambda u, p: False, + ) + + response = client.post( + "/login", + data={"username": "testuser", "password": "wrong"}, + follow_redirects=False, + ) + assert response.status_code == 303 + assert "error=1" in response.headers["location"] +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py -v -k "post_login" 2>&1 | head -20` +Expected: FAIL — 405 Method Not Allowed (no POST handler for `/login` yet) + +**Step 3: Add the POST /login handler** + +In `muxplex/main.py`, add `from fastapi import Request` to the existing FastAPI imports if not already present. Then add this route right after the existing `GET /login` route: + +```python +@app.post("/login") +async def login_submit(request: Request): + """Handle login form submission.""" + from muxplex.auth import authenticate_pam, create_session_cookie + + form = await request.form() + username = form.get("username", "") + password = form.get("password", "") + + # Validate credentials + if _auth_mode == "pam": + ok = authenticate_pam(str(username), str(password)) + else: + ok = str(password) == _auth_password + + if not ok: + from starlette.responses import RedirectResponse + + return RedirectResponse("/login?error=1", status_code=303) + + # Success — set session cookie and redirect to / + cookie = create_session_cookie(_auth_secret, _auth_ttl) + from starlette.responses import RedirectResponse + + response = RedirectResponse("/", status_code=303) + response.set_cookie( + "muxplex_session", + cookie, + httponly=True, + samesite="strict", + max_age=_auth_ttl if _auth_ttl > 0 else None, + ) + return response +``` + +Note: The `RedirectResponse` import may already be available from `starlette.responses` (used in `auth.py`). Use whatever import pattern is cleanest — either add to the top-level imports or keep the local imports. Prefer adding `from starlette.responses import RedirectResponse` to the module-level imports at the top. + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py -v -k "post_login"` +Expected: all 4 tests PASS + +**Step 5: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/main.py muxplex/tests/test_api.py && git commit -m "feat: POST /login handler for PAM and password modes" +``` + +--- + +### Task 3: GET /auth/logout + +**Files:** +- Modify: `muxplex/main.py` +- Modify: `muxplex/tests/test_api.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_api.py`: + +```python +# --------------------------------------------------------------------------- +# GET /auth/logout +# --------------------------------------------------------------------------- + + +def test_logout_redirects_to_login(client): + """GET /auth/logout returns 303 redirect to /login.""" + response = client.get("/auth/logout", follow_redirects=False) + assert response.status_code == 303 + assert "/login" in response.headers["location"] + + +def test_logout_clears_session_cookie(client): + """GET /auth/logout deletes the muxplex_session cookie (max-age=0).""" + response = client.get("/auth/logout", follow_redirects=False) + # Check Set-Cookie header clears the cookie + set_cookie = response.headers.get("set-cookie", "") + assert "muxplex_session" in set_cookie + # Cookie should be expired (max-age=0 or empty value) + assert 'max-age=0' in set_cookie.lower() or '=""' in set_cookie or "=''" in set_cookie +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py -v -k "logout" 2>&1 | head -20` +Expected: FAIL — 404 or 307 (no `/auth/logout` route yet) + +**Step 3: Add the logout route** + +In `muxplex/main.py`, add this route after the POST `/login` handler (and before the static file mount): + +```python +@app.get("/auth/logout") +async def logout(): + """Clear the session cookie and redirect to login.""" + from starlette.responses import RedirectResponse + + response = RedirectResponse("/login", status_code=303) + response.delete_cookie("muxplex_session") + return response +``` + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py -v -k "logout"` +Expected: both tests PASS + +**Step 5: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/main.py muxplex/tests/test_api.py && git commit -m "feat: GET /auth/logout clears session cookie" +``` + +--- + +### Task 4: Replace /login stub with branded login.html serving + +**Files:** +- Modify: `muxplex/main.py` +- Modify: `muxplex/tests/test_api.py` + +**Step 1: Write the failing test** + +Append to `muxplex/tests/test_api.py`: + +```python +# --------------------------------------------------------------------------- +# GET /login serves branded page with injected auth mode +# --------------------------------------------------------------------------- + + +def test_get_login_injects_muxplex_auth(client): + """GET /login HTML must contain window.MUXPLEX_AUTH with the auth mode.""" + response = client.get("/login") + assert response.status_code == 200 + assert "MUXPLEX_AUTH" in response.text + assert '"mode"' in response.text +``` + +**Step 2: Run to verify it fails** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py::test_get_login_injects_muxplex_auth -v` +Expected: FAIL — current stub doesn't have `MUXPLEX_AUTH` + +**Step 3: Replace the GET /login handler** + +In `muxplex/main.py`, replace the existing `login_page()` function with: + +```python +@app.get("/login", response_class=HTMLResponse) +async def login_page(): + """Serve the branded login page with auth mode injected.""" + import json + + html = (_FRONTEND_DIR / "login.html").read_text() + + username = "" + if _auth_mode == "pam": + username = pwd.getpwuid(os.getuid()).pw_name + + mode_data = json.dumps({"mode": _auth_mode, "user": username}) + # Inject auth mode before so the inline script can read it + html = html.replace( + "", + f"\n", + ) + return HTMLResponse(html) +``` + +Note: `_FRONTEND_DIR` is already defined at the bottom of `main.py` as `pathlib.Path(__file__).parent / "frontend"`. It's used for the StaticFiles mount. You need to move this variable definition **above** the routes section so `login_page()` can reference it, or define it separately near the top. The simplest change: move the `_FRONTEND_DIR = pathlib.Path(__file__).parent / "frontend"` line to just after the imports/config section (around line 50), keeping the `app.mount(...)` line at the bottom. + +**Step 4: Run to verify it passes** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_api.py -v -k "login"` +Expected: all login tests PASS (including the old `test_get_login_returns_200_html`) + +**Step 5: Optionally remove the /auth/mode endpoint** + +Since `login.html` now reads `window.MUXPLEX_AUTH` instead of fetching `/auth/mode`, the endpoint is redundant. However, it's harmless and could be useful for API clients. **Keep it** but it's no longer required for the login flow. + +**Step 6: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/main.py muxplex/tests/test_api.py && git commit -m "feat: /login GET serves branded login.html with injected auth mode" +``` + +--- + +### Task 5: CLI: update host default and add auth flags + +**Files:** +- Modify: `muxplex/cli.py` +- Modify: `muxplex/tests/test_cli.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_cli.py`: + +```python +# --------------------------------------------------------------------------- +# Auth CLI flags +# --------------------------------------------------------------------------- + + +def test_main_default_host_is_localhost(): + """Default --host must be 127.0.0.1 (changed from 0.0.0.0).""" + from muxplex.cli import main + + with patch("muxplex.cli.serve") as mock_serve: + with patch("sys.argv", ["muxplex"]): + main() + call_kwargs = mock_serve.call_args + assert call_kwargs[1]["host"] == "127.0.0.1" or call_kwargs[0][0] == "127.0.0.1" + + +def test_main_passes_auth_flag(): + """main() with --auth password must forward auth='password' to serve().""" + from muxplex.cli import main + + with patch("muxplex.cli.serve") as mock_serve: + with patch("sys.argv", ["muxplex", "--auth", "password"]): + main() + _, kwargs = mock_serve.call_args + assert kwargs.get("auth") == "password" + + +def test_main_passes_session_ttl_flag(): + """main() with --session-ttl 3600 must forward session_ttl=3600 to serve().""" + from muxplex.cli import main + + with patch("muxplex.cli.serve") as mock_serve: + with patch("sys.argv", ["muxplex", "--session-ttl", "3600"]): + main() + _, kwargs = mock_serve.call_args + assert kwargs.get("session_ttl") == 3600 +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_cli.py -v -k "default_host or auth_flag or session_ttl" 2>&1 | head -20` +Expected: FAIL — default host is still `0.0.0.0`, no `--auth` or `--session-ttl` flags + +**Step 3: Update cli.py** + +In `muxplex/cli.py`, make these changes: + +1. Change the `serve()` signature to accept auth params: + +```python +def serve(host: str = "127.0.0.1", port: int = 8088, auth: str = "pam", session_ttl: int = 604800) -> None: + """Start the muxplex server.""" + import uvicorn # noqa: PLC0415 + + os.environ.setdefault("MUXPLEX_PORT", str(port)) + if auth: + os.environ.setdefault("MUXPLEX_AUTH", auth) + os.environ.setdefault("MUXPLEX_SESSION_TTL", str(session_ttl)) + + from muxplex.main import app # noqa: PLC0415 + + print(f" muxplex → http://{host}:{port}") + uvicorn.run(app, host=host, port=port, log_level="warning") +``` + +2. Change the `--host` default: + +```python + parser.add_argument( + "--host", default="127.0.0.1", help="Bind host (default: 127.0.0.1)" + ) +``` + +3. Add new arguments after the `--port` argument: + +```python + parser.add_argument( + "--auth", + choices=["pam", "password"], + default="pam", + help="Auth mode: pam (default) or password", + ) + parser.add_argument( + "--session-ttl", + type=int, + default=604800, + help="Session cookie TTL in seconds (default: 604800 = 7 days, 0 = browser session)", + ) +``` + +4. Update the `serve()` call in `main()` to pass the new args: + +```python + if args.command == "install-service": + install_service(system=args.system) + else: + serve(host=args.host, port=args.port, auth=args.auth, session_ttl=args.session_ttl) +``` + +**Step 4: Update the existing test that checks the old default** + +The existing test `test_main_calls_serve_by_default` asserts `host="0.0.0.0"`. Update it: + +In `muxplex/tests/test_cli.py`, change: +```python + mock_serve.assert_called_once_with(host="0.0.0.0", port=8088) +``` +to: +```python + mock_serve.assert_called_once_with(host="127.0.0.1", port=8088, auth="pam", session_ttl=604800) +``` + +**Step 5: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_cli.py -v` +Expected: all tests PASS (including updated existing tests) + +**Step 6: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/cli.py muxplex/tests/test_cli.py && git commit -m "feat(cli): add --auth and --session-ttl flags, change --host default to 127.0.0.1" +``` + +--- + +### Task 6: CLI: show-password subcommand + +**Files:** +- Modify: `muxplex/cli.py` +- Modify: `muxplex/tests/test_cli.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_cli.py`: + +```python +# --------------------------------------------------------------------------- +# show-password subcommand +# --------------------------------------------------------------------------- + + +def test_show_password_prints_password_from_file(tmp_path, monkeypatch, capsys): + """show-password prints the password when the file exists.""" + from muxplex.cli import main + + # Set up a fake password file + fake_home = tmp_path / "home" + fake_home.mkdir() + monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) + pw_path = fake_home / ".config" / "muxplex" / "password" + pw_path.parent.mkdir(parents=True, exist_ok=True) + pw_path.write_text("my-test-password\n") + pw_path.chmod(0o600) + + # Force password mode + monkeypatch.setenv("MUXPLEX_AUTH", "password") + + with patch("sys.argv", ["muxplex", "show-password"]): + main() + + captured = capsys.readouterr() + assert "my-test-password" in captured.out + + +def test_show_password_no_file(tmp_path, monkeypatch, capsys): + """show-password prints a helpful message when no password file exists.""" + from muxplex.cli import main + + fake_home = tmp_path / "home" + fake_home.mkdir() + monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) + monkeypatch.setenv("MUXPLEX_AUTH", "password") + + with patch("sys.argv", ["muxplex", "show-password"]): + main() + + captured = capsys.readouterr() + assert "no password" in captured.out.lower() or "not found" in captured.out.lower() + + +def test_show_password_pam_mode(monkeypatch, capsys): + """show-password in PAM mode prints that PAM is active.""" + from muxplex.cli import main + + monkeypatch.delenv("MUXPLEX_AUTH", raising=False) + monkeypatch.setattr("muxplex.auth.pam_available", lambda: True) + + with patch("sys.argv", ["muxplex", "show-password"]): + main() + + captured = capsys.readouterr() + assert "pam" in captured.out.lower() +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_cli.py -v -k "show_password" 2>&1 | head -20` +Expected: FAIL — `show-password` subcommand doesn't exist + +**Step 3: Add the show-password subcommand** + +In `muxplex/cli.py`, add the function: + +```python +def show_password() -> None: + """Show the current muxplex password.""" + from muxplex.auth import load_password, pam_available + + auth_mode = os.environ.get("MUXPLEX_AUTH", "").lower() + if auth_mode != "password" and pam_available(): + print("Auth mode: PAM — no password file used") + return + + pw = load_password() + if pw: + print(f"Password: {pw}") + else: + print("No password file found. Start muxplex to auto-generate one.") +``` + +Then register it as a subcommand in `main()`. Add after the `install-service` subparser: + +```python + sub.add_parser("show-password", help="Show the current muxplex password") +``` + +And in the command dispatch: + +```python + if args.command == "install-service": + install_service(system=args.system) + elif args.command == "show-password": + show_password() + else: + serve(host=args.host, port=args.port, auth=args.auth, session_ttl=args.session_ttl) +``` + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_cli.py -v -k "show_password"` +Expected: all 3 tests PASS + +**Step 5: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/cli.py muxplex/tests/test_cli.py && git commit -m "feat(cli): add show-password subcommand" +``` + +--- + +### Task 7: CLI: reset-secret subcommand + +**Files:** +- Modify: `muxplex/cli.py` +- Modify: `muxplex/tests/test_cli.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_cli.py`: + +```python +# --------------------------------------------------------------------------- +# reset-secret subcommand +# --------------------------------------------------------------------------- + + +def test_reset_secret_writes_new_secret(tmp_path, monkeypatch, capsys): + """reset-secret writes a new secret file.""" + from muxplex.cli import main + + fake_home = tmp_path / "home" + fake_home.mkdir() + monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) + + with patch("sys.argv", ["muxplex", "reset-secret"]): + main() + + secret_path = fake_home / ".config" / "muxplex" / "secret" + assert secret_path.exists() + content = secret_path.read_text().strip() + assert len(content) > 20 + + +def test_reset_secret_sets_0600_permissions(tmp_path, monkeypatch, capsys): + """reset-secret sets the secret file to mode 0600.""" + from muxplex.cli import main + + fake_home = tmp_path / "home" + fake_home.mkdir() + monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) + + with patch("sys.argv", ["muxplex", "reset-secret"]): + main() + + secret_path = fake_home / ".config" / "muxplex" / "secret" + mode = stat.S_IMODE(secret_path.stat().st_mode) + assert mode == 0o600 + + +def test_reset_secret_prints_warning(tmp_path, monkeypatch, capsys): + """reset-secret prints a warning about invalidated sessions.""" + from muxplex.cli import main + + fake_home = tmp_path / "home" + fake_home.mkdir() + monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) + + with patch("sys.argv", ["muxplex", "reset-secret"]): + main() + + captured = capsys.readouterr() + assert "invalid" in captured.out.lower() or "warning" in captured.out.lower() +``` + +**Step 2: Run tests to verify they fail** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_cli.py -v -k "reset_secret" 2>&1 | head -20` +Expected: FAIL — `reset-secret` subcommand doesn't exist + +**Step 3: Add the reset-secret subcommand** + +In `muxplex/cli.py`, add the function: + +```python +def reset_secret() -> None: + """Regenerate the signing secret, invalidating all active sessions.""" + import secrets as _secrets + + from muxplex.auth import get_secret_path + + path = get_secret_path() + path.parent.mkdir(parents=True, exist_ok=True) + new_secret = _secrets.token_urlsafe(32) + path.write_text(new_secret + "\n") + path.chmod(0o600) + print(f"New signing secret written to {path}") + print("Warning: all active sessions are now invalid.") +``` + +Register it as a subcommand. Add after the `show-password` subparser: + +```python + sub.add_parser("reset-secret", help="Regenerate signing secret (invalidates sessions)") +``` + +And in the command dispatch: + +```python + if args.command == "install-service": + install_service(system=args.system) + elif args.command == "show-password": + show_password() + elif args.command == "reset-secret": + reset_secret() + else: + serve(host=args.host, port=args.port, auth=args.auth, session_ttl=args.session_ttl) +``` + +Also add `import stat` to the test file imports if not already present. + +**Step 4: Run tests to verify they pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_cli.py -v -k "reset_secret"` +Expected: all 3 tests PASS + +**Step 5: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/cli.py muxplex/tests/test_cli.py && git commit -m "feat(cli): add reset-secret subcommand" +``` + +--- + +### Task 8: Startup auth logging + +**Files:** +- Modify: `muxplex/main.py` (refine `_resolve_auth` logging) +- Modify: `muxplex/tests/test_auth.py` + +**Step 1: Write the failing tests** + +Append to `muxplex/tests/test_auth.py`: + +```python +# --------------------------------------------------------------------------- +# Startup auth logging (via _resolve_auth) +# --------------------------------------------------------------------------- + + +def test_resolve_auth_pam_mode_logs_pam(monkeypatch, capsys, tmp_path): + """_resolve_auth() prints PAM auth line when PAM is available.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + monkeypatch.delenv("MUXPLEX_AUTH", raising=False) + monkeypatch.delenv("MUXPLEX_PASSWORD", raising=False) + monkeypatch.setattr("muxplex.auth.pam_available", lambda: True) + + # Import after patching + from muxplex.main import _resolve_auth + + mode, pw = _resolve_auth() + assert mode == "pam" + captured = capsys.readouterr() + assert "PAM" in captured.err + + +def test_resolve_auth_env_password_logs_env(monkeypatch, capsys, tmp_path): + """_resolve_auth() prints env password line when MUXPLEX_PASSWORD is set.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + monkeypatch.setenv("MUXPLEX_AUTH", "password") + monkeypatch.setenv("MUXPLEX_PASSWORD", "from-env") + + from muxplex.main import _resolve_auth + + mode, pw = _resolve_auth() + assert mode == "password" + assert pw == "from-env" + captured = capsys.readouterr() + assert "env" in captured.err.lower() + + +def test_resolve_auth_file_password_logs_file(monkeypatch, capsys, tmp_path): + """_resolve_auth() prints file password line when password file exists.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + monkeypatch.setenv("MUXPLEX_AUTH", "password") + monkeypatch.delenv("MUXPLEX_PASSWORD", raising=False) + + pw_path = tmp_path / ".config" / "muxplex" / "password" + pw_path.parent.mkdir(parents=True, exist_ok=True) + pw_path.write_text("file-password\n") + pw_path.chmod(0o600) + + from muxplex.main import _resolve_auth + + mode, pw = _resolve_auth() + assert mode == "password" + assert pw == "file-password" + captured = capsys.readouterr() + assert "file" in captured.err.lower() or "password" in captured.err.lower() + + +def test_resolve_auth_generates_password_as_last_resort(monkeypatch, capsys, tmp_path): + """_resolve_auth() auto-generates a password when nothing else is available.""" + monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path)) + monkeypatch.setenv("MUXPLEX_AUTH", "password") + monkeypatch.delenv("MUXPLEX_PASSWORD", raising=False) + + from muxplex.main import _resolve_auth + + mode, pw = _resolve_auth() + assert mode == "password" + assert len(pw) > 10 + captured = capsys.readouterr() + assert "generated" in captured.err.lower() + # The generated password should be printed so the user can see it + assert pw in captured.err +``` + +**Step 2: Run tests to verify they fail or pass** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "resolve_auth" 2>&1 | head -30` +Expected: These tests verify the `_resolve_auth` function added in Phase 1 Task 7. They may pass already if the logging was correctly implemented. If they fail, fix the `_resolve_auth` function. + +**Step 3: Refine _resolve_auth logging if needed** + +Verify the `_resolve_auth()` function in `muxplex/main.py` prints exactly these lines to stderr: + +- PAM available: `muxplex auth: PAM (user: {username})` +- Env password: `muxplex auth: password (env)` +- File password: `muxplex auth: password (file: ~/.config/muxplex/password)` +- Auto-generated: `muxplex auth: password generated — {password} — saved to ~/.config/muxplex/password` + +Update the function if the format doesn't match. Fix the `file_pw` logging line — the Phase 1 plan had a bug (it printed `load_password.__module__` instead of the file path). It should be: + +```python + file_pw = load_password() + if file_pw: + from muxplex.auth import get_password_path + print(f" muxplex auth: password (file: {get_password_path()})", file=sys.stderr) + return "password", file_pw +``` + +**Step 4: Run all tests** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/test_auth.py -v -k "resolve_auth"` +Expected: all 4 tests PASS + +**Step 5: Run the full test suite** + +Run: `cd /home/bkrabach/dev/web-tmux/muxplex && python -m pytest muxplex/tests/ -v` +Expected: ALL tests pass across all test files + +**Step 6: Commit** + +```bash +cd /home/bkrabach/dev/web-tmux/muxplex && git add muxplex/main.py muxplex/tests/test_auth.py && git commit -m "feat: startup auth mode logging with auto-generated password display" +``` + +--- + +## Phase 2 Complete Checklist + +After all 8 tasks: + +- [ ] `muxplex/frontend/login.html` exists — branded dark theme, wordmark, PAM/password mode detection +- [ ] POST `/login` works — correct creds → cookie + redirect to `/`, wrong creds → redirect to `/login?error=1` +- [ ] GET `/auth/logout` works — clears cookie, redirects to `/login` +- [ ] GET `/login` serves `login.html` with `window.MUXPLEX_AUTH` injected +- [ ] `--host` defaults to `127.0.0.1` +- [ ] `--auth` and `--session-ttl` flags work +- [ ] `muxplex show-password` prints the password or PAM message +- [ ] `muxplex reset-secret` regenerates the signing key with warning +- [ ] Startup prints one clear auth mode line to stderr +- [ ] All tests pass: `python -m pytest muxplex/tests/ -v` +- [ ] 8 clean commits with conventional commit messages + +## End-to-End Smoke Test + +After both phases are complete, manually verify: + +1. `cd /home/bkrabach/dev/web-tmux/muxplex && python -m muxplex --host 0.0.0.0` — should print auth mode line +2. Open `http://localhost:8088` — should load the dashboard (localhost bypass) +3. Open from another device on the LAN — should redirect to `/login` +4. Log in with the displayed password — should redirect to dashboard +5. `muxplex show-password` — prints the password +6. `muxplex reset-secret` — prints warning, old browser session should fail + +## Deferred + +- HTTPS/TLS support +- Rate limiting on login endpoint +- Remember-me longer TTL +- Admin reset flow +- `install-service` auth-aware unit files (systemd EnvironmentFile, launchd plist) \ No newline at end of file diff --git a/docs/plans/2026-03-29-settings-phase1.md b/docs/plans/2026-03-29-settings-phase1.md new file mode 100644 index 0000000..f12d098 --- /dev/null +++ b/docs/plans/2026-03-29-settings-phase1.md @@ -0,0 +1,1395 @@ +# Settings Panel — Phase 1: Backend + Palette Removal + Settings Infrastructure + +> **Execution:** Use the subagent-driven-development workflow to implement this plan. + +**Phase 1 of 2** — complete this phase before starting [Phase 2](./2026-03-29-settings-phase2.md). + +**Design doc:** [`docs/plans/2026-03-29-settings-design.md`](./2026-03-29-settings-design.md) + +**Goal:** Remove the dead command palette, add the backend settings API + new-session endpoint, and build the settings modal shell with the Display tab wired up for immediate visual feedback. + +**Architecture:** Server-side settings live in `~/.config/muxplex/settings.json` (loaded/saved via a new `muxplex/settings.py` module). Three new endpoints: `GET/PATCH /api/settings` and `POST /api/sessions` (create). The frontend settings modal is a `` element with tabbed navigation. Display tab fields (font size, hover delay, grid columns) use localStorage and apply immediately. + +**Tech Stack:** Python 3.11+ / FastAPI / Pydantic, vanilla JS, CSS custom properties. + +--- + +### Task 1: Remove command palette — HTML + CSS + +**Files:** +- Modify: `muxplex/frontend/index.html` (lines 35, 50-58) +- Modify: `muxplex/frontend/style.css` (lines 356-364, 690-778) +- Modify: `muxplex/tests/test_frontend_html.py` (lines 60-76, 243) +- Modify: `muxplex/tests/test_frontend_css.py` (lines 69-74) + +**Step 1: Remove palette trigger button from expanded header** + +In `muxplex/frontend/index.html`, remove the `#palette-trigger` button from the expanded header (line 35): + +```html + + +``` + +The expanded header should now end after ``: + +```html +
+ + + +
+``` + +**Step 2: Remove command palette HTML block** + +In `muxplex/frontend/index.html`, remove the entire command palette block (lines 50-58): + +```html + + + +``` + +**Step 3: Remove palette CSS** + +In `muxplex/frontend/style.css`, remove the `.palette-trigger` block (lines 356-364): + +```css +/* REMOVE this block: */ +.palette-trigger { + background: none; + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text-dim); + font-size: 12px; + padding: 4px 10px; + cursor: pointer; +} +``` + +Remove the entire "Command palette overlay" CSS section (lines 690-778 — from the comment block through `.palette-item__time`): + +```css +/* REMOVE everything from this comment through .palette-item__time { ... } */ +/* ============================================================ + Command palette overlay (desktop session switching) + ============================================================ */ + +.command-palette { ... } +.command-palette__backdrop { ... } +.command-palette__dialog { ... } +.command-palette__input { ... } +.command-palette__input::placeholder { ... } +.command-palette__list { ... } +.palette-item { ... } +.palette-item:hover, +.palette-item--selected { ... } +.palette-item__index { ... } +.palette-item__name { ... } +.palette-item__bell { ... } +.palette-item__time { ... } +``` + +**Step 4: Update HTML tests** + +In `muxplex/tests/test_frontend_html.py`, update `test_html_expanded_view_elements` (line 60-69) to remove `palette-trigger`: + +```python +def test_html_expanded_view_elements() -> None: + """id=back-btn, expanded-session-name, reconnect-overlay.""" + soup = _SOUP + for id_ in ( + "back-btn", + "expanded-session-name", + "reconnect-overlay", + ): + assert soup.find(id=id_), f"Missing element with id='{id_}'" +``` + +Remove `test_html_command_palette` entirely (lines 72-76): + +```python +# DELETE this entire function: +def test_html_command_palette() -> None: + """id=command-palette, palette-input, palette-list, palette-backdrop.""" + ... +``` + +In the `test_html_elements_carry_their_css_classes` test (around line 243), remove the palette-trigger entry: + +```python +# REMOVE this tuple from the cases list: + ("palette-trigger", "palette-trigger", "needs border and hover styles"), +``` + +**Step 5: Update CSS tests** + +In `muxplex/tests/test_frontend_css.py`, remove `test_css_command_palette` (lines 69-74): + +```python +# DELETE this entire function: +def test_css_command_palette(): + css = read_css() + assert ".command-palette__dialog" in css + assert ".command-palette__input" in css + assert ".palette-item" in css + assert ".palette-item--selected" in css +``` + +**Step 6: Run tests to verify nothing breaks** + +```bash +cd muxplex && python -m pytest muxplex/tests/test_frontend_html.py muxplex/tests/test_frontend_css.py -v +``` + +Expected: All tests pass (palette tests removed, no other test depends on palette HTML/CSS). + +**Step 7: Commit** + +```bash +git add -A && git commit -m "refactor: remove command palette HTML and CSS" +``` + +--- + +### Task 2: Remove command palette — JavaScript + +**Files:** +- Modify: `muxplex/frontend/app.js` (lines 901-1086, 1169-1170, 1219-1247, 1274-1332) + +**Step 1: Remove palette state variables** + +In `muxplex/frontend/app.js`, remove the command palette state block (lines 901-906): + +```javascript +// REMOVE this entire block: +// ─── Command palette state ──────────────────────────────────────────────── +const PALETTE_MAX_ITEMS = 9; +let _paletteSelectedIndex = 0; +let _paletteFilteredSessions = []; +let _paletteOpen = false; +let _paletteInputListener = null; +``` + +**Step 2: Remove palette functions** + +Remove the entire "Command palette functions" section (lines 908-1062): +- `renderPaletteList` +- `highlightPaletteItem` +- `openPalette` +- `closePalette` +- `onPaletteInput` +- `handlePaletteKeydown` + +**Step 3: Simplify handleGlobalKeydown** + +Replace the current `handleGlobalKeydown` function (lines 1071-1086) with a version that removes palette references. Keep only the Escape-to-close-session behavior: + +```javascript +/** + * Global keydown handler. + * In fullscreen: Escape returns to grid. + * @param {KeyboardEvent} e + */ +function handleGlobalKeydown(e) { + if (_viewMode === 'fullscreen') { + if (e.key === 'Escape') { + e.preventDefault(); + closeSession(); + } + } +} +``` + +**Step 4: Remove palette event bindings from bindStaticEventListeners** + +In `bindStaticEventListeners` (around lines 1164-1210), remove these two lines: + +```javascript +// REMOVE these two lines: + on($('palette-trigger'), 'click', openPalette); + on($('palette-backdrop'), 'click', closePalette); +``` + +**Step 5: Remove palette test-only helpers** + +Remove these test-only helpers (lines 1219-1247): + +```javascript +// REMOVE all of these: +/** Test-only: set _paletteFilteredSessions directly. */ +function _setPaletteFilteredSessions(sessions) { ... } +/** Test-only: get _paletteFilteredSessions. */ +function _getPaletteFilteredSessions() { ... } +/** Test-only: set _paletteSelectedIndex directly. */ +function _setPaletteSelectedIndex(index) { ... } +/** Test-only: get _paletteSelectedIndex. */ +function _getPaletteSelectedIndex() { ... } +/** Test-only: set _paletteOpen directly. */ +function _setPaletteOpen(val) { ... } +/** Test-only: get _paletteOpen. */ +function _isPaletteOpen() { ... } +``` + +**Step 6: Remove palette exports from module.exports** + +In the `module.exports` block (starting around line 1274), remove these entries: + +```javascript +// REMOVE these exports: + // Command palette + renderPaletteList, + highlightPaletteItem, + openPalette, + closePalette, + onPaletteInput, + handlePaletteKeydown, + // Test-only helpers (palette) + _setPaletteFilteredSessions, + _getPaletteFilteredSessions, + _setPaletteSelectedIndex, + _getPaletteSelectedIndex, + _setPaletteOpen, + _isPaletteOpen, +``` + +Keep `handleGlobalKeydown` and `bindStaticEventListeners` in exports — they still exist. + +**Step 7: Run full test suite** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass. + +**Step 8: Commit** + +```bash +git add -A && git commit -m "refactor: remove command palette JavaScript" +``` + +--- + +### Task 3: Server-side settings module + +**Files:** +- Create: `muxplex/settings.py` +- Create: `muxplex/tests/test_settings.py` + +**Step 1: Write the failing tests** + +Create `muxplex/tests/test_settings.py`: + +```python +"""Tests for muxplex/settings.py — settings file management.""" + +import json + +import pytest + +from muxplex import settings as settings_mod + + +@pytest.fixture(autouse=True) +def redirect_settings_path(tmp_path, monkeypatch): + """Redirect SETTINGS_PATH to tmp_path so tests don't touch real config.""" + tmp_settings = tmp_path / "settings.json" + monkeypatch.setattr(settings_mod, "SETTINGS_PATH", tmp_settings) + + +def test_load_returns_defaults_when_no_file(): + """load_settings() returns DEFAULT_SETTINGS when file doesn't exist.""" + result = settings_mod.load_settings() + assert result == settings_mod.DEFAULT_SETTINGS + + +def test_load_returns_saved_values(tmp_path, monkeypatch): + """load_settings() reads values from existing settings.json.""" + path = settings_mod.SETTINGS_PATH + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps({"sort_order": "alphabetical"})) + result = settings_mod.load_settings() + assert result["sort_order"] == "alphabetical" + # Missing keys filled with defaults + assert result["default_session"] is None + + +def test_save_creates_file_and_dirs(): + """save_settings() creates parent dirs and writes JSON.""" + settings_mod.save_settings({"sort_order": "recent"}) + assert settings_mod.SETTINGS_PATH.exists() + data = json.loads(settings_mod.SETTINGS_PATH.read_text()) + assert data["sort_order"] == "recent" + + +def test_save_merges_with_defaults(): + """save_settings() merges partial data with defaults.""" + settings_mod.save_settings({"sort_order": "alphabetical"}) + data = json.loads(settings_mod.SETTINGS_PATH.read_text()) + assert data["sort_order"] == "alphabetical" + assert data["new_session_template"] == "tmux new-session -d -s {name}" + + +def test_load_handles_corrupt_json(tmp_path): + """load_settings() returns defaults if JSON is corrupt.""" + path = settings_mod.SETTINGS_PATH + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("not json {{{") + result = settings_mod.load_settings() + assert result == settings_mod.DEFAULT_SETTINGS + + +def test_patch_settings_merges_single_field(): + """patch_settings() merges a single field into existing settings.""" + settings_mod.save_settings(settings_mod.DEFAULT_SETTINGS.copy()) + result = settings_mod.patch_settings({"sort_order": "alphabetical"}) + assert result["sort_order"] == "alphabetical" + assert result["default_session"] is None # unchanged + + +def test_patch_settings_ignores_unknown_keys(): + """patch_settings() ignores keys not in DEFAULT_SETTINGS.""" + settings_mod.save_settings(settings_mod.DEFAULT_SETTINGS.copy()) + result = settings_mod.patch_settings({"bogus_key": "ignored"}) + assert "bogus_key" not in result +``` + +**Step 2: Run tests to verify they fail** + +```bash +cd muxplex && python -m pytest muxplex/tests/test_settings.py -v +``` + +Expected: FAIL — `ModuleNotFoundError: No module named 'muxplex.settings'` + +**Step 3: Write the implementation** + +Create `muxplex/settings.py`: + +```python +""" +muxplex settings — server-side configuration file management. + +Settings are stored in ~/.config/muxplex/settings.json. +""" + +import json +import logging +from pathlib import Path + +_log = logging.getLogger(__name__) + +# --------------------------------------------------------------------------- +# Path +# --------------------------------------------------------------------------- + +SETTINGS_PATH: Path = Path.home() / ".config" / "muxplex" / "settings.json" + +# --------------------------------------------------------------------------- +# Defaults +# --------------------------------------------------------------------------- + +DEFAULT_SETTINGS: dict = { + "default_session": None, + "sort_order": "manual", + "hidden_sessions": [], + "window_size_largest": False, + "auto_open_created": True, + "new_session_template": "tmux new-session -d -s {name}", +} + +# --------------------------------------------------------------------------- +# Load / save +# --------------------------------------------------------------------------- + + +def load_settings() -> dict: + """Load settings from disk, returning defaults for missing keys or corrupt files.""" + defaults = DEFAULT_SETTINGS.copy() + if not SETTINGS_PATH.exists(): + return defaults + try: + data = json.loads(SETTINGS_PATH.read_text()) + if not isinstance(data, dict): + return defaults + except (json.JSONDecodeError, OSError): + _log.warning("Corrupt settings file at %s, using defaults", SETTINGS_PATH) + return defaults + # Merge: file values override defaults, unknown keys ignored + for key in defaults: + if key in data: + defaults[key] = data[key] + return defaults + + +def save_settings(data: dict) -> None: + """Write settings to disk, merging with defaults for any missing keys.""" + merged = DEFAULT_SETTINGS.copy() + for key in merged: + if key in data: + merged[key] = data[key] + SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True) + SETTINGS_PATH.write_text(json.dumps(merged, indent=2) + "\n") + + +def patch_settings(patch: dict) -> dict: + """Load current settings, merge patch (known keys only), save, and return result.""" + current = load_settings() + for key in DEFAULT_SETTINGS: + if key in patch: + current[key] = patch[key] + save_settings(current) + return current +``` + +**Step 4: Run tests to verify they pass** + +```bash +cd muxplex && python -m pytest muxplex/tests/test_settings.py -v +``` + +Expected: All 8 tests pass. + +**Step 5: Commit** + +```bash +git add -A && git commit -m "feat: add server-side settings module" +``` + +--- + +### Task 4: Settings API endpoints + +**Files:** +- Modify: `muxplex/main.py` +- Modify: `muxplex/tests/test_api.py` + +**Step 1: Write the failing tests** + +Add to the bottom of `muxplex/tests/test_api.py`: + +```python +# --------------------------------------------------------------------------- +# GET /api/settings +# --------------------------------------------------------------------------- + + +def test_get_settings_returns_defaults(client, monkeypatch): + """GET /api/settings returns default settings when no file exists.""" + import muxplex.settings as settings_mod + + monkeypatch.setattr(settings_mod, "SETTINGS_PATH", client.app.state._tmp_path / "settings.json") + response = client.get("/api/settings") + assert response.status_code == 200 + data = response.json() + assert data["sort_order"] == "manual" + assert data["new_session_template"] == "tmux new-session -d -s {name}" + + +def test_get_settings_returns_saved_values(client, tmp_path, monkeypatch): + """GET /api/settings returns previously saved settings.""" + import json + import muxplex.settings as settings_mod + + settings_path = tmp_path / "settings.json" + settings_path.write_text(json.dumps({"sort_order": "alphabetical"})) + monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_path) + + response = client.get("/api/settings") + assert response.status_code == 200 + assert response.json()["sort_order"] == "alphabetical" + + +# --------------------------------------------------------------------------- +# PATCH /api/settings +# --------------------------------------------------------------------------- + + +def test_patch_settings_updates_field(client, tmp_path, monkeypatch): + """PATCH /api/settings merges a single field and returns updated settings.""" + import muxplex.settings as settings_mod + + monkeypatch.setattr(settings_mod, "SETTINGS_PATH", tmp_path / "settings.json") + + response = client.patch("/api/settings", json={"sort_order": "alphabetical"}) + assert response.status_code == 200 + data = response.json() + assert data["sort_order"] == "alphabetical" + assert data["default_session"] is None # unchanged default + + +def test_patch_settings_ignores_unknown_keys(client, tmp_path, monkeypatch): + """PATCH /api/settings ignores keys not in the schema.""" + import muxplex.settings as settings_mod + + monkeypatch.setattr(settings_mod, "SETTINGS_PATH", tmp_path / "settings.json") + + response = client.patch("/api/settings", json={"unknown_key": "value"}) + assert response.status_code == 200 + assert "unknown_key" not in response.json() +``` + +**Step 2: Run tests to verify they fail** + +```bash +cd muxplex && python -m pytest muxplex/tests/test_api.py::test_get_settings_returns_defaults -v +``` + +Expected: FAIL — 404 (route doesn't exist yet). + +**Step 3: Add the endpoints to main.py** + +In `muxplex/main.py`, add the import at the top (after existing imports): + +```python +from muxplex.settings import load_settings, patch_settings +``` + +Add the route handlers after the `setup_hooks` endpoint and before the WebSocket proxy section: + +```python +# --------------------------------------------------------------------------- +# Settings +# --------------------------------------------------------------------------- + + +@app.get("/api/settings") +async def get_settings() -> dict: + """Return server-side settings.""" + return load_settings() + + +@app.patch("/api/settings") +async def update_settings(request: Request) -> dict: + """Partial update of server-side settings. Merges known keys only.""" + body = await request.json() + return patch_settings(body) +``` + +**Step 4: Run tests to verify they pass** + +```bash +cd muxplex && python -m pytest muxplex/tests/test_api.py -k "settings" -v +``` + +Expected: All 4 settings tests pass. + +**Step 5: Commit** + +```bash +git add -A && git commit -m "feat: add GET/PATCH /api/settings endpoints" +``` + +--- + +### Task 5: POST /api/sessions (create new session) + +**Files:** +- Modify: `muxplex/main.py` +- Modify: `muxplex/tests/test_api.py` + +**Step 1: Write the failing tests** + +Add to the bottom of `muxplex/tests/test_api.py`: + +```python +# --------------------------------------------------------------------------- +# POST /api/sessions (create new session) +# --------------------------------------------------------------------------- + + +def test_create_session_returns_200_with_name(client, tmp_path, monkeypatch): + """POST /api/sessions returns 200 with the session name.""" + import muxplex.settings as settings_mod + + monkeypatch.setattr(settings_mod, "SETTINGS_PATH", tmp_path / "settings.json") + + # Mock subprocess so nothing actually runs + import subprocess + monkeypatch.setattr(subprocess, "Popen", lambda *a, **kw: None) + + response = client.post("/api/sessions", json={"name": "my-project"}) + assert response.status_code == 200 + assert response.json()["name"] == "my-project" + + +def test_create_session_substitutes_name_in_template(client, tmp_path, monkeypatch): + """POST /api/sessions substitutes {name} in the template command.""" + import json + import subprocess + import muxplex.settings as settings_mod + + settings_path = tmp_path / "settings.json" + settings_path.write_text(json.dumps({ + "new_session_template": "echo {name}" + })) + monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_path) + + captured_cmd = [] + original_popen = subprocess.Popen + + def mock_popen(*args, **kwargs): + captured_cmd.append(args[0] if args else kwargs.get("args")) + return None + + monkeypatch.setattr(subprocess, "Popen", mock_popen) + + client.post("/api/sessions", json={"name": "test-proj"}) + assert len(captured_cmd) == 1 + assert "test-proj" in captured_cmd[0] + + +def test_create_session_rejects_empty_name(client): + """POST /api/sessions with empty name returns 422.""" + response = client.post("/api/sessions", json={"name": ""}) + assert response.status_code == 422 + + +def test_create_session_rejects_missing_name(client): + """POST /api/sessions without name field returns 422.""" + response = client.post("/api/sessions", json={}) + assert response.status_code == 422 +``` + +**Step 2: Run tests to verify they fail** + +```bash +cd muxplex && python -m pytest muxplex/tests/test_api.py::test_create_session_returns_200_with_name -v +``` + +Expected: FAIL — 405 or 404 (route doesn't exist yet). + +**Step 3: Add the endpoint to main.py** + +Add to the imports at the top of `muxplex/main.py`: + +```python +import subprocess +``` + +Add a new Pydantic model near the existing models: + +```python +class CreateSessionPayload(BaseModel): + name: str + + @property + def validated_name(self) -> str: + if not self.name or not self.name.strip(): + raise ValueError("name must not be empty") + return self.name.strip() +``` + +Add the route handler after the settings endpoints: + +```python +# --------------------------------------------------------------------------- +# New session creation +# --------------------------------------------------------------------------- + + +@app.post("/api/sessions") +async def create_session(payload: CreateSessionPayload) -> dict: + """Create a new session by executing the configured template command. + + Substitutes {name} in the template, runs it as a fire-and-forget subprocess. + No existence check — handles create-or-reattach patterns. + Returns 200 with {"name": "..."} regardless of outcome. + """ + name = payload.validated_name + template = load_settings().get( + "new_session_template", "tmux new-session -d -s {name}" + ) + command = template.replace("{name}", name) + try: + subprocess.Popen(command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except Exception: + _log.warning("Failed to execute new session command: %s", command) + return {"name": name} +``` + +**Note:** The Pydantic validator on `name` gives us automatic 422 for empty/missing `name`. We need to add a custom validator. Actually, Pydantic v2 with `str` type accepts empty strings, so use a `field_validator`: + +```python +from pydantic import BaseModel, field_validator + +class CreateSessionPayload(BaseModel): + name: str + + @field_validator("name") + @classmethod + def name_must_not_be_empty(cls, v: str) -> str: + if not v or not v.strip(): + raise ValueError("name must not be empty") + return v.strip() +``` + +**Step 4: Run tests to verify they pass** + +```bash +cd muxplex && python -m pytest muxplex/tests/test_api.py -k "create_session" -v +``` + +Expected: All 4 create_session tests pass. + +**Step 5: Commit** + +```bash +git add -A && git commit -m "feat: add POST /api/sessions endpoint for new session creation" +``` + +--- + +### Task 6: Settings modal HTML + CSS skeleton + +**Files:** +- Modify: `muxplex/frontend/index.html` +- Modify: `muxplex/frontend/style.css` + +**Step 1: Add gear icon and `+` button to the overview header** + +In `muxplex/frontend/index.html`, replace the overview header (lines 21-24): + +```html +
+

muxplex

+
+ + + +
+
+``` + +**Step 2: Add the same buttons to the expanded header** + +Update the expanded header to include the gear icon (after `expanded-session-name`): + +```html +
+ + + + +
+``` + +**Step 3: Add the settings dialog HTML** + +Add after the toast element (before the `` comment): + +```html + + + +
+ +
+ +
+

Display

+ + + +
+ + + + + + +
+
+
+``` + +**Step 4: Add settings CSS** + +Append to `muxplex/frontend/style.css` (at the bottom, before any final closing comments): + +```css +/* ============================================================ + Header action buttons (+ and gear) + ============================================================ */ + +.header-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.header-btn { + background: none; + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text-dim); + font-size: 16px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: color var(--t-fast), border-color var(--t-fast); +} + +.header-btn:hover { + color: var(--text); + border-color: var(--text-muted); +} + +/* ============================================================ + Settings dialog + ============================================================ */ + +.settings-backdrop { + position: fixed; + inset: 0; + background: var(--bg-overlay); + backdrop-filter: blur(2px); + z-index: 299; +} + +.settings-dialog { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: min(600px, 90vw); + height: min(480px, 80vh); + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 8px; + color: var(--text); + padding: 0; + overflow: hidden; + z-index: 300; + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5); +} + +.settings-dialog::backdrop { + background: transparent; /* we use our own backdrop for blur */ +} + +.settings-layout { + display: flex; + height: 100%; +} + +.settings-tabs { + width: 140px; + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 2px; + padding: 16px 0; + border-right: 1px solid var(--border); + background: var(--bg); +} + +.settings-tab { + background: none; + border: none; + border-left: 2px solid transparent; + color: var(--text-muted); + font-size: 13px; + font-family: var(--font-ui); + padding: 8px 16px; + text-align: left; + cursor: pointer; + transition: color var(--t-fast); +} + +.settings-tab:hover { + color: var(--text); +} + +.settings-tab--active { + color: var(--accent); + border-left-color: var(--accent); +} + +.settings-content { + flex: 1; + overflow-y: auto; + padding: 24px; +} + +.settings-panel-title { + font-size: 16px; + font-weight: 600; + margin: 0 0 20px; + color: var(--text); +} + +.settings-field { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 0; + border-bottom: 1px solid var(--border-subtle); +} + +.settings-label { + font-size: 13px; + color: var(--text); +} + +.settings-select { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text); + font-size: 13px; + font-family: var(--font-ui); + padding: 6px 10px; + cursor: pointer; +} + +.settings-select:focus { + outline: 1px solid var(--accent); + border-color: var(--accent); +} + +/* Mobile: bottom half-sheet */ +@media (max-width: 599px) { + .settings-dialog { + top: auto; + bottom: 0; + left: 0; + right: 0; + transform: none; + width: 100%; + height: 85vh; + border-radius: 12px 12px 0 0; + } + + .settings-layout { + flex-direction: column; + } + + .settings-tabs { + width: 100%; + flex-direction: row; + border-right: none; + border-bottom: 1px solid var(--border); + padding: 0; + overflow-x: auto; + } + + .settings-tab { + border-left: none; + border-bottom: 2px solid transparent; + padding: 12px 16px; + white-space: nowrap; + min-height: 48px; + } + + .settings-tab--active { + border-bottom-color: var(--accent); + border-left-color: transparent; + } +} +``` + +**Step 5: Run tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/test_frontend_html.py muxplex/tests/test_frontend_css.py -v +``` + +Expected: All tests pass (new elements don't conflict with existing tests). + +**Step 6: Commit** + +```bash +git add -A && git commit -m "feat: add settings dialog HTML structure and CSS" +``` + +--- + +### Task 7: Settings JS infrastructure — open/close, tabs, localStorage + +**Files:** +- Modify: `muxplex/frontend/app.js` + +**Step 1: Add settings state variables** + +In `muxplex/frontend/app.js`, add after the existing app state block (after `let _previewSessionName = null;`): + +```javascript +// ─── Settings state ────────────────────────────────────────────────────────── +let _settingsOpen = false; +const DISPLAY_SETTINGS_KEY = 'muxplex.display'; +const DISPLAY_DEFAULTS = { + fontSize: 14, + hoverPreviewDelay: 1500, + gridColumns: 'auto', + bellSound: false, + notificationPermission: 'default', +}; +``` + +**Step 2: Add settings open/close functions** + +Add after the existing `closeSession` function: + +```javascript +// ─── Settings dialog ───────────────────────────────────────────────────────── + +/** + * Load display settings from localStorage, merged with defaults. + * @returns {object} + */ +function loadDisplaySettings() { + try { + const raw = localStorage.getItem(DISPLAY_SETTINGS_KEY); + if (raw) { + const parsed = JSON.parse(raw); + return Object.assign({}, DISPLAY_DEFAULTS, parsed); + } + } catch (_) { /* blocked or corrupt — use defaults */ } + return Object.assign({}, DISPLAY_DEFAULTS); +} + +/** + * Save display settings to localStorage. + * @param {object} settings + */ +function saveDisplaySettings(settings) { + try { + localStorage.setItem(DISPLAY_SETTINGS_KEY, JSON.stringify(settings)); + } catch (_) { /* blocked — ok */ } +} + +/** + * Open the settings dialog. Loads current values into form controls. + */ +function openSettings() { + _settingsOpen = true; + const dialog = $('settings-dialog'); + const backdrop = $('settings-backdrop'); + if (dialog && typeof dialog.showModal === 'function') { + dialog.showModal(); + } + if (backdrop) backdrop.classList.remove('hidden'); + + // Load current display settings into form controls + const ds = loadDisplaySettings(); + var fontSel = $('setting-font-size'); + var delaySel = $('setting-hover-delay'); + var colsSel = $('setting-grid-columns'); + if (fontSel) fontSel.value = String(ds.fontSize); + if (delaySel) delaySel.value = String(ds.hoverPreviewDelay); + if (colsSel) colsSel.value = String(ds.gridColumns); +} + +/** + * Close the settings dialog. + */ +function closeSettings() { + _settingsOpen = false; + const dialog = $('settings-dialog'); + const backdrop = $('settings-backdrop'); + if (dialog && typeof dialog.close === 'function') { + try { dialog.close(); } catch (_) {} + } + if (backdrop) backdrop.classList.add('hidden'); +} + +/** + * Switch to a settings tab by name. + * @param {string} tabName - one of 'display', 'sessions', 'notifications', 'new-session' + */ +function switchSettingsTab(tabName) { + // Update tab buttons + document.querySelectorAll('.settings-tab').forEach(function(btn) { + if (btn.dataset.tab === tabName) { + btn.classList.add('settings-tab--active'); + btn.setAttribute('aria-selected', 'true'); + } else { + btn.classList.remove('settings-tab--active'); + btn.setAttribute('aria-selected', 'false'); + } + }); + // Show/hide panels + document.querySelectorAll('.settings-panel').forEach(function(panel) { + if (panel.dataset.tab === tabName) { + panel.classList.remove('hidden'); + } else { + panel.classList.add('hidden'); + } + }); +} +``` + +**Step 3: Update handleGlobalKeydown for settings** + +Replace the `handleGlobalKeydown` function to add `,` shortcut and Escape for settings: + +```javascript +/** + * Global keydown handler. + * Comma opens settings. Escape closes settings or returns to grid. + * @param {KeyboardEvent} e + */ +function handleGlobalKeydown(e) { + // Settings dialog + if (_settingsOpen) { + if (e.key === 'Escape') { + e.preventDefault(); + closeSettings(); + } + return; // don't process other shortcuts while settings is open + } + + // Comma opens settings (unless typing in an input) + if (e.key === ',' && !e.ctrlKey && !e.metaKey) { + var tag = e.target && e.target.tagName; + if (tag !== 'INPUT' && tag !== 'TEXTAREA' && tag !== 'SELECT') { + e.preventDefault(); + openSettings(); + return; + } + } + + if (_viewMode === 'fullscreen') { + if (e.key === 'Escape') { + e.preventDefault(); + closeSession(); + } + } +} +``` + +**Step 4: Wire up event listeners in bindStaticEventListeners** + +Add to `bindStaticEventListeners` (after the existing bindings, before the hover preview section): + +```javascript + // Settings + on($('settings-btn'), 'click', openSettings); + on($('settings-btn-expanded'), 'click', openSettings); + on($('settings-backdrop'), 'click', closeSettings); + + // Settings dialog: close on Escape via dialog's built-in cancel event + var settingsDialog = $('settings-dialog'); + if (settingsDialog) { + settingsDialog.addEventListener('cancel', function(e) { + e.preventDefault(); + closeSettings(); + }); + } + + // Settings tab switching + document.querySelectorAll('.settings-tab').forEach(function(tab) { + on(tab, 'click', function() { + switchSettingsTab(tab.dataset.tab); + }); + }); +``` + +**Step 5: Add settings to module.exports** + +In the `module.exports` block, add: + +```javascript + // Settings + loadDisplaySettings, + saveDisplaySettings, + openSettings, + closeSettings, + switchSettingsTab, +``` + +**Step 6: Run tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass. + +**Step 7: Commit** + +```bash +git add -A && git commit -m "feat: add settings dialog open/close, tab switching, localStorage management" +``` + +--- + +### Task 8: Wire Display tab — font size, hover delay, grid columns + +**Files:** +- Modify: `muxplex/frontend/app.js` +- Modify: `muxplex/frontend/style.css` + +**Step 1: Add display settings change handlers** + +In `muxplex/frontend/app.js`, add after the `switchSettingsTab` function: + +```javascript +/** + * Apply display settings to the live UI. + * Called on page load and whenever a display setting changes. + * @param {object} ds - display settings object + */ +function applyDisplaySettings(ds) { + // Font size: update CSS custom property (grid previews use it) + document.documentElement.style.setProperty('--preview-font-size', ds.fontSize + 'px'); + + // Grid columns: set CSS custom property on session-grid + var grid = $('session-grid'); + if (grid) { + if (ds.gridColumns === 'auto') { + grid.style.removeProperty('grid-template-columns'); + } else { + grid.style.gridTemplateColumns = 'repeat(' + ds.gridColumns + ', 1fr)'; + } + } +} + +/** + * Handle changes to display setting + + + + +
+ Hidden sessions +
+
+ + + +``` + +**Step 2: Add server settings load/save functions to app.js** + +In `muxplex/frontend/app.js`, add after the `onDisplaySettingChange` function: + +```javascript +// ─── Server settings ───────────────────────────────────────────────────────── +let _serverSettings = null; + +/** + * Load server settings via GET /api/settings. + * Caches the result in _serverSettings for local reads. + * @returns {Promise} + */ +async function loadServerSettings() { + try { + var res = await api('GET', '/api/settings'); + _serverSettings = await res.json(); + } catch (err) { + console.warn('[loadServerSettings]', err); + _serverSettings = _serverSettings || {}; + } + return _serverSettings; +} + +/** + * Patch a single server setting field. + * @param {string} key + * @param {*} value + * @returns {Promise} + */ +async function patchServerSetting(key, value) { + var patch = {}; + patch[key] = value; + try { + var res = await api('PATCH', '/api/settings', patch); + _serverSettings = await res.json(); + showToast('Setting saved'); + } catch (err) { + console.warn('[patchServerSetting]', err); + showToast('Failed to save setting'); + } +} +``` + +**Step 3: Populate Sessions tab when settings opens** + +Update the `openSettings` function. After the display settings load, add server settings loading: + +```javascript + // Load server settings and populate Sessions tab + loadServerSettings().then(function(ss) { + // Default session dropdown: populate with current session list + var defaultSel = $('setting-default-session'); + if (defaultSel) { + var current = ss.default_session || ''; + defaultSel.innerHTML = '' + + _currentSessions.map(function(s) { + var name = escapeHtml(s.name); + var selected = s.name === current ? ' selected' : ''; + return ''; + }).join(''); + } + + // Sort order + var sortSel = $('setting-sort-order'); + if (sortSel) sortSel.value = ss.sort_order || 'manual'; + + // Hidden sessions checkboxes + var hiddenDiv = $('setting-hidden-sessions'); + if (hiddenDiv) { + var hiddenList = ss.hidden_sessions || []; + hiddenDiv.innerHTML = _currentSessions.map(function(s) { + var name = escapeHtml(s.name); + var checked = hiddenList.indexOf(s.name) !== -1 ? ' checked' : ''; + return ''; + }).join(''); + } + + // Checkboxes + var wslEl = $('setting-window-size-largest'); + if (wslEl) wslEl.checked = !!ss.window_size_largest; + var aoEl = $('setting-auto-open'); + if (aoEl) aoEl.checked = ss.auto_open_created !== false; + }); +``` + +**Step 4: Bind change handlers for Sessions tab in bindStaticEventListeners** + +Add to `bindStaticEventListeners`: + +```javascript + // Sessions tab change handlers + on($('setting-default-session'), 'change', function() { + patchServerSetting('default_session', this.value || null); + }); + on($('setting-sort-order'), 'change', function() { + patchServerSetting('sort_order', this.value); + }); + on($('setting-window-size-largest'), 'change', function() { + patchServerSetting('window_size_largest', this.checked); + }); + on($('setting-auto-open'), 'change', function() { + patchServerSetting('auto_open_created', this.checked); + }); + + // Hidden sessions: delegated handler on container (checkboxes are dynamic) + var hiddenContainer = $('setting-hidden-sessions'); + if (hiddenContainer) { + hiddenContainer.addEventListener('change', function() { + var checkboxes = hiddenContainer.querySelectorAll('input[type="checkbox"]'); + var hidden = []; + checkboxes.forEach(function(cb) { + if (cb.checked) hidden.push(cb.dataset.session); + }); + patchServerSetting('hidden_sessions', hidden); + }); + } +``` + +**Step 5: Add CSS for column-layout fields and checkbox list** + +Append to `muxplex/frontend/style.css`: + +```css +.settings-field--column { + flex-direction: column; + align-items: flex-start; + gap: 8px; +} + +.settings-checkbox-list { + display: flex; + flex-direction: column; + gap: 6px; + padding-left: 4px; +} + +.settings-checkbox-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: var(--text); + cursor: pointer; +} + +.settings-checkbox { + accent-color: var(--accent); + cursor: pointer; +} +``` + +**Step 6: Add exports** + +Add to `module.exports`: + +```javascript + loadServerSettings, + patchServerSetting, +``` + +**Step 7: Run tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass. + +**Step 8: Commit** + +```bash +git add -A && git commit -m "feat: add Sessions settings tab with server-side persistence" +``` + +--- + +### Task 2: Notifications tab + +**Files:** +- Modify: `muxplex/frontend/index.html` +- Modify: `muxplex/frontend/app.js` +- Modify: `muxplex/frontend/style.css` + +**Step 1: Add Notifications tab HTML** + +In `muxplex/frontend/index.html`, replace the empty `#settings-panel-notifications` div: + +```html + + +``` + +**Step 2: Add CSS for notification status and action button** + +Append to `muxplex/frontend/style.css`: + +```css +.settings-notification-status { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 6px; +} + +.settings-status-text { + font-size: 12px; + color: var(--text-muted); +} + +.settings-action-btn { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text); + font-size: 12px; + font-family: var(--font-ui); + padding: 6px 12px; + cursor: pointer; + transition: border-color var(--t-fast); +} + +.settings-action-btn:hover { + border-color: var(--accent); +} + +.settings-action-btn:disabled { + opacity: 0.5; + cursor: default; +} +``` + +**Step 3: Add notification handlers to app.js** + +Update `openSettings` to populate notification controls (add inside the function): + +```javascript + // Notifications tab + var ds2 = loadDisplaySettings(); + var bellSoundEl = $('setting-bell-sound'); + if (bellSoundEl) bellSoundEl.checked = !!ds2.bellSound; + + var notifText = $('notification-status-text'); + var notifBtn = $('notification-request-btn'); + if (notifText && notifBtn) { + var perm = typeof Notification !== 'undefined' ? Notification.permission : 'unavailable'; + if (perm === 'granted') { + notifText.textContent = 'Granted'; + notifBtn.textContent = 'Granted'; + notifBtn.disabled = true; + } else if (perm === 'denied') { + notifText.textContent = 'Denied (change in browser settings)'; + notifBtn.textContent = 'Denied'; + notifBtn.disabled = true; + } else if (perm === 'unavailable') { + notifText.textContent = 'Not supported'; + notifBtn.disabled = true; + } else { + notifText.textContent = 'Not requested'; + notifBtn.textContent = 'Request permission'; + notifBtn.disabled = false; + } + } +``` + +**Step 4: Bind notification change handlers in bindStaticEventListeners** + +Add to `bindStaticEventListeners`: + +```javascript + // Notifications tab + on($('setting-bell-sound'), 'change', function() { + var ds = loadDisplaySettings(); + ds.bellSound = this.checked; + saveDisplaySettings(ds); + }); + + on($('notification-request-btn'), 'click', function() { + if (typeof Notification === 'undefined') return; + Notification.requestPermission().then(function(perm) { + _notificationPermission = perm; + var ds = loadDisplaySettings(); + ds.notificationPermission = perm; + saveDisplaySettings(ds); + // Update UI + var notifText = $('notification-status-text'); + var notifBtn = $('notification-request-btn'); + if (perm === 'granted') { + if (notifText) notifText.textContent = 'Granted'; + if (notifBtn) { notifBtn.textContent = 'Granted'; notifBtn.disabled = true; } + } else if (perm === 'denied') { + if (notifText) notifText.textContent = 'Denied (change in browser settings)'; + if (notifBtn) { notifBtn.textContent = 'Denied'; notifBtn.disabled = true; } + } + }); + }); +``` + +**Step 5: Run tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass. + +**Step 6: Commit** + +```bash +git add -A && git commit -m "feat: add Notifications settings tab" +``` + +--- + +### Task 3: New Session tab — template textarea + +**Files:** +- Modify: `muxplex/frontend/index.html` +- Modify: `muxplex/frontend/app.js` +- Modify: `muxplex/frontend/style.css` + +**Step 1: Add New Session tab HTML** + +In `muxplex/frontend/index.html`, replace the empty `#settings-panel-new-session` div: + +```html + + +``` + +**Step 2: Add CSS for textarea and helper text** + +Append to `muxplex/frontend/style.css`: + +```css +.settings-textarea { + width: 100%; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text); + font-size: 13px; + font-family: var(--font-mono); + padding: 10px; + resize: vertical; + min-height: 60px; +} + +.settings-textarea:focus { + outline: 1px solid var(--accent); + border-color: var(--accent); +} + +.settings-helper { + font-size: 12px; + color: var(--text-muted); + font-style: italic; +} +``` + +**Step 3: Populate template on settings open** + +Inside `openSettings`, in the `loadServerSettings().then(...)` callback, add: + +```javascript + // New Session tab + var templateEl = $('setting-template'); + if (templateEl) templateEl.value = ss.new_session_template || 'tmux new-session -d -s {name}'; +``` + +**Step 4: Bind template change handlers in bindStaticEventListeners** + +Add to `bindStaticEventListeners`: + +```javascript + // New Session tab: save template on blur (not every keystroke) + var templateEl = $('setting-template'); + if (templateEl) { + var _templateDebounce = null; + templateEl.addEventListener('input', function() { + clearTimeout(_templateDebounce); + _templateDebounce = setTimeout(function() { + patchServerSetting('new_session_template', templateEl.value); + }, 500); + }); + } + + on($('setting-template-reset'), 'click', function() { + var templateEl = $('setting-template'); + if (templateEl) templateEl.value = 'tmux new-session -d -s {name}'; + patchServerSetting('new_session_template', 'tmux new-session -d -s {name}'); + }); +``` + +**Step 5: Run tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass. + +**Step 6: Commit** + +```bash +git add -A && git commit -m "feat: add New Session settings tab with template textarea" +``` + +--- + +### Task 4: Header `+` button with inline name input + +**Files:** +- Modify: `muxplex/frontend/app.js` +- Modify: `muxplex/frontend/style.css` + +**Step 1: Add new session creation function** + +In `muxplex/frontend/app.js`, add after the server settings functions: + +```javascript +// ─── New session creation ──────────────────────────────────────────────────── + +/** + * Show an inline name input replacing the given button element. + * On Enter: call createNewSession(). On Escape: cancel and restore button. + * @param {HTMLElement} btn - the button to replace with input + */ +function showNewSessionInput(btn) { + if (!btn) return; + var container = btn.parentElement; + if (!container) return; + + // Create inline input + var input = document.createElement('input'); + input.type = 'text'; + input.className = 'new-session-input'; + input.placeholder = 'Session name\u2026'; + input.setAttribute('autocomplete', 'off'); + input.setAttribute('spellcheck', 'false'); + + // Replace button with input + btn.style.display = 'none'; + container.insertBefore(input, btn); + input.focus(); + + function cleanup() { + input.remove(); + btn.style.display = ''; + } + + input.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { + e.preventDefault(); + var name = input.value.trim(); + if (name) { + cleanup(); + createNewSession(name); + } + } else if (e.key === 'Escape') { + e.preventDefault(); + cleanup(); + } + }); + + // Cancel on blur (click away) + input.addEventListener('blur', function() { + // Delay to allow click handlers to fire first + setTimeout(cleanup, 150); + }); +} + +/** + * Create a new session by POSTing to /api/sessions. + * Shows toast, triggers poll refresh, auto-opens if setting enabled. + * @param {string} name + * @returns {Promise} + */ +async function createNewSession(name) { + try { + var res = await api('POST', '/api/sessions', { name: name }); + var data = await res.json(); + showToast("Session '" + data.name + "' created"); + + // Trigger immediate poll refresh to pick up the new session + await pollSessions(); + + // Auto-open if setting enabled + var ss = _serverSettings || await loadServerSettings(); + if (ss.auto_open_created !== false) { + // Small delay to let tmux create the session + setTimeout(function() { + openSession(data.name); + }, 500); + } + } catch (err) { + showToast(err.message || 'Failed to create session'); + } +} +``` + +**Step 2: Add CSS for inline input** + +Append to `muxplex/frontend/style.css`: + +```css +/* ============================================================ + New session inline input + ============================================================ */ + +.new-session-input { + background: var(--bg); + border: 1px solid var(--accent); + border-radius: 4px; + color: var(--text); + font-size: 13px; + font-family: var(--font-ui); + padding: 4px 10px; + width: 180px; + outline: none; +} + +.new-session-input::placeholder { + color: var(--text-dim); +} +``` + +**Step 3: Bind the header `+` button** + +Add to `bindStaticEventListeners`: + +```javascript + // New session: header + button + on($('new-session-btn'), 'click', function() { + showNewSessionInput($('new-session-btn')); + }); +``` + +**Step 4: Add exports** + +Add to `module.exports`: + +```javascript + showNewSessionInput, + createNewSession, +``` + +**Step 5: Run tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass. + +**Step 6: Commit** + +```bash +git add -A && git commit -m "feat: add header + button with inline name input for session creation" +``` + +--- + +### Task 5: Sidebar `+ New` sticky footer + +**Files:** +- Modify: `muxplex/frontend/index.html` +- Modify: `muxplex/frontend/app.js` +- Modify: `muxplex/frontend/style.css` + +**Step 1: Add sidebar footer HTML** + +In `muxplex/frontend/index.html`, add a footer inside the `#session-sidebar` div, after `#sidebar-list`: + +```html +
+ + + +
+``` + +**Step 2: Add sidebar footer CSS** + +Append to `muxplex/frontend/style.css`: + +```css +.sidebar-footer { + padding: 8px; + border-top: 1px solid var(--border-subtle); + flex-shrink: 0; +} + +.sidebar-new-btn { + width: 100%; + background: none; + border: 1px dashed var(--border); + border-radius: 4px; + color: var(--text-dim); + font-size: 12px; + font-family: var(--font-ui); + padding: 8px; + cursor: pointer; + transition: color var(--t-fast), border-color var(--t-fast); +} + +.sidebar-new-btn:hover { + color: var(--text); + border-color: var(--text-muted); +} +``` + +**Step 3: Bind sidebar button** + +Add to `bindStaticEventListeners`: + +```javascript + // New session: sidebar footer button + on($('sidebar-new-session-btn'), 'click', function() { + showNewSessionInput($('sidebar-new-session-btn')); + }); +``` + +**Step 4: Run tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass. + +**Step 5: Commit** + +```bash +git add -A && git commit -m "feat: add sidebar + New sticky footer with inline input" +``` + +--- + +### Task 6: Mobile FAB + +**Files:** +- Modify: `muxplex/frontend/index.html` +- Modify: `muxplex/frontend/app.js` +- Modify: `muxplex/frontend/style.css` + +**Step 1: Add FAB HTML** + +In `muxplex/frontend/index.html`, add before the toast element: + +```html + + +``` + +**Step 2: Add FAB CSS** + +Append to `muxplex/frontend/style.css`: + +```css +/* ============================================================ + Mobile FAB (new session) + ============================================================ */ + +.new-session-fab { + display: none; /* hidden by default, shown on mobile */ + position: fixed; + bottom: 16px; + right: 16px; + width: 56px; + height: 56px; + border-radius: 50%; + background: var(--accent); + color: var(--bg); + border: none; + font-size: 28px; + font-weight: 300; + line-height: 1; + cursor: pointer; + z-index: 100; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + transition: transform var(--t-fast), box-shadow var(--t-fast); +} + +.new-session-fab:active { + transform: scale(0.95); +} + +@media (max-width: 959px) { + .new-session-fab { + display: flex; + align-items: center; + justify-content: center; + } + + /* Hide the header + button on mobile since FAB replaces it */ + #new-session-btn { + display: none; + } +} +``` + +**Step 3: Bind FAB click** + +Add to `bindStaticEventListeners`: + +```javascript + // New session: mobile FAB + on($('new-session-fab'), 'click', function() { + showNewSessionInput($('new-session-fab')); + }); +``` + +**Step 4: Hide FAB when in fullscreen view** + +In `openSession`, add after switching views: + +```javascript + var fab = $('new-session-fab'); + if (fab) fab.classList.add('hidden'); +``` + +In `closeSession`, restore the FAB: + +```javascript + var fab = $('new-session-fab'); + if (fab) fab.classList.remove('hidden'); +``` + +**Step 5: Run tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass. + +**Step 6: Commit** + +```bash +git add -A && git commit -m "feat: add mobile FAB for new session creation" +``` + +--- + +### Task 7: Apply settings effects — font size, grid columns, hover delay, hidden sessions, sort order + +**Files:** +- Modify: `muxplex/frontend/app.js` +- Modify: `muxplex/frontend/terminal.js` + +**Step 1: Font size affects terminal.js** + +In `muxplex/frontend/terminal.js`, update the `createTerminal` function to read font size from localStorage: + +Replace the hardcoded fontSize in `createTerminal()`: + +```javascript + // Read font size from display settings (localStorage) + var storedFontSize = 14; + try { + var raw = localStorage.getItem('muxplex.display'); + if (raw) { + var parsed = JSON.parse(raw); + if (parsed.fontSize) storedFontSize = parsed.fontSize; + } + } catch (_) {} + + const mobile = window.innerWidth < 600; + + _term = new window.Terminal({ + cursorBlink: true, + fontSize: mobile ? Math.min(storedFontSize, 12) : storedFontSize, + fontFamily: "'SF Mono', 'Fira Code', Consolas, monospace", + // ... rest unchanged + }); +``` + +**Step 2: Hidden sessions filter in renderGrid** + +In `muxplex/frontend/app.js`, update `renderGrid` to filter hidden sessions. At the top of the function, before computing `ordered`: + +```javascript + // Filter hidden sessions (server settings) + var hidden = (_serverSettings && _serverSettings.hidden_sessions) || []; + var visible = sessions; + if (hidden.length > 0) { + visible = sessions.filter(function(s) { + return hidden.indexOf(s.name) === -1; + }); + } +``` + +Then use `visible` instead of `sessions` for the rest of the function. Update the empty-state check and the `ordered` computation: + +```javascript + if (!visible || visible.length === 0) { + if (grid) grid.innerHTML = ''; + if (emptyState) emptyState.classList.remove('hidden'); + return; + } + + if (emptyState) emptyState.classList.add('hidden'); + + const mobile = isMobile(); + const ordered = mobile ? sortByPriority(visible) : visible; +``` + +**Step 3: Sort order support in renderGrid** + +Update `renderGrid` to apply sort order from server settings. After the hidden filter, before the mobile check: + +```javascript + // Apply sort order from server settings + var sortOrder = (_serverSettings && _serverSettings.sort_order) || 'manual'; + if (sortOrder === 'alphabetical') { + visible = visible.slice().sort(function(a, b) { + return (a.name || '').localeCompare(b.name || ''); + }); + } + // 'recent' and 'manual' use the server-provided order (which is already the default) +``` + +**Step 4: Hidden sessions filter in renderSidebar** + +In `renderSidebar`, apply the same hidden filter: + +```javascript + var hidden = (_serverSettings && _serverSettings.hidden_sessions) || []; + var visible = sessions; + if (hidden.length > 0) { + visible = sessions.filter(function(s) { + return hidden.indexOf(s.name) === -1; + }); + } +``` + +Use `visible` instead of `sessions` for the sidebar rendering. + +**Step 5: Load server settings on page init** + +In the `DOMContentLoaded` handler, after `startPolling()`, add a call to load server settings: + +```javascript + restoreState() + .then(() => { + startPolling(); + startHeartbeat(); + requestNotificationPermission(); + loadServerSettings(); // <-- add this line + bindStaticEventListeners(); + }) +``` + +**Step 6: Run tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass. + +**Step 7: Commit** + +```bash +git add -A && git commit -m "feat: apply settings effects — font size, grid columns, hidden sessions, sort order" +``` + +--- + +### Task 8: Frontend tests for settings + +**Files:** +- Modify: `muxplex/tests/test_frontend_html.py` +- Modify: `muxplex/tests/test_frontend_css.py` + +**Step 1: Add HTML tests for settings elements** + +Add to `muxplex/tests/test_frontend_html.py`: + +```python +def test_html_settings_dialog() -> None: + """id=settings-dialog, settings-backdrop, settings-btn.""" + soup = _SOUP + for id_ in ("settings-dialog", "settings-backdrop", "settings-btn"): + assert soup.find(id=id_), f"Missing element with id='{id_}'" + + +def test_html_settings_tabs() -> None: + """Settings dialog contains four tab buttons: display, sessions, notifications, new-session.""" + soup = _SOUP + tabs = soup.select(".settings-tab") + tab_names = [t.get("data-tab") for t in tabs] + assert "display" in tab_names + assert "sessions" in tab_names + assert "notifications" in tab_names + assert "new-session" in tab_names + + +def test_html_display_tab_controls() -> None: + """Display tab has font-size, hover-delay, and grid-columns selects.""" + soup = _SOUP + for id_ in ("setting-font-size", "setting-hover-delay", "setting-grid-columns"): + assert soup.find(id=id_), f"Missing element with id='{id_}'" + + +def test_html_new_session_btn() -> None: + """id=new-session-btn, new-session-fab exist.""" + soup = _SOUP + assert soup.find(id="new-session-btn"), "Missing element with id='new-session-btn'" + assert soup.find(id="new-session-fab"), "Missing element with id='new-session-fab'" + + +def test_html_sidebar_new_session_btn() -> None: + """id=sidebar-new-session-btn exists inside the sidebar.""" + soup = _SOUP + assert soup.find(id="sidebar-new-session-btn"), "Missing element with id='sidebar-new-session-btn'" + + +def test_html_sessions_tab_controls() -> None: + """Sessions tab has default-session, sort-order selects and checkboxes.""" + soup = _SOUP + for id_ in ("setting-default-session", "setting-sort-order", "setting-window-size-largest", "setting-auto-open"): + assert soup.find(id=id_), f"Missing element with id='{id_}'" + + +def test_html_notifications_tab_controls() -> None: + """Notifications tab has bell-sound checkbox and permission button.""" + soup = _SOUP + assert soup.find(id="setting-bell-sound"), "Missing id='setting-bell-sound'" + assert soup.find(id="notification-request-btn"), "Missing id='notification-request-btn'" + + +def test_html_new_session_tab_controls() -> None: + """New Session tab has template textarea and reset button.""" + soup = _SOUP + assert soup.find(id="setting-template"), "Missing id='setting-template'" + assert soup.find(id="setting-template-reset"), "Missing id='setting-template-reset'" +``` + +**Step 2: Add CSS tests for settings styles** + +Add to `muxplex/tests/test_frontend_css.py`: + +```python +def test_css_settings_dialog(): + css = read_css() + assert ".settings-dialog" in css + assert ".settings-tabs" in css + assert ".settings-tab--active" in css + assert ".settings-content" in css + assert ".settings-field" in css + assert ".settings-select" in css + + +def test_css_header_btn(): + css = read_css() + assert ".header-btn" in css + assert ".header-actions" in css + + +def test_css_new_session_fab(): + css = read_css() + assert ".new-session-fab" in css + + +def test_css_new_session_input(): + css = read_css() + assert ".new-session-input" in css + + +def test_css_settings_textarea(): + css = read_css() + assert ".settings-textarea" in css + + +def test_css_sidebar_footer(): + css = read_css() + assert ".sidebar-footer" in css + assert ".sidebar-new-btn" in css +``` + +**Step 3: Run all tests** + +```bash +cd muxplex && python -m pytest muxplex/tests/ -v +``` + +Expected: All tests pass (both old and new). + +**Step 4: Commit** + +```bash +git add -A && git commit -m "test: add frontend tests for settings dialog and new session UI" +``` + +--- + +## Phase 2 Complete + +After completing all 8 tasks, the full settings feature is done: + +1. **Sessions tab** — default session, sort order, hidden sessions, window-size largest, auto-open +2. **Notifications tab** — bell sound toggle, desktop notification permission button +3. **New Session tab** — command template textarea with reset button +4. **Header `+` button** — inline name input, creates session via API +5. **Sidebar `+ New`** — sticky footer with same inline input +6. **Mobile FAB** — 56px floating action button, <960px only +7. **Settings effects applied** — font size, grid columns, hover delay, hidden sessions, sort order +8. **Frontend tests** — HTML structure and CSS coverage for all new elements + +### Open questions (from design doc — decide during implementation): +1. Hidden sessions: completely removed from grid/sidebar (current implementation) +2. Session name validation: accepts anything the user types (tmux will reject invalid names) +3. Settings pre-auth: not accessible from login page (v1) \ No newline at end of file diff --git a/muxplex/__pycache__/__init__.cpython-312.pyc b/muxplex/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55769f66d6f96cf688db5c0771dba324c5dc8cfc GIT binary patch literal 152 zcmX@j%ge<81ZS5V&jitrK?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^%UnMrKQ~oBDZ3~! zDKR-iKP9zHzdSWbw#Ej R*;5Q+d}L;1WGrF^vH)&@C2Rly literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/__main__.cpython-312.pyc b/muxplex/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08d56345ac147a7b94eaa2d67741cd9a3fb915da GIT binary patch literal 275 zcmX@j%ge<81ZS5V&x`=lk3k$5V1hC}D*+kP8B!Rc7*ZHhm~t3%nWC5&8JHMSnN~xU zFfvp!YqC^nJLcr%mn#&N=H+GPr7Pr?RutrjCs9%OKQyk0@&FAkgB{FKt1RJ$ThpgD{{Tr2=2J}@&fGTvk` Re99p6kb$L@t%w~c2msygO7j2! literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/auth.cpython-312.pyc b/muxplex/__pycache__/auth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..852348cdc58e1d2790ea139f7d29b5f34476144c GIT binary patch literal 8190 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 literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/auth.cpython-313.pyc b/muxplex/__pycache__/auth.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d16c0e4e3b27718dda785158afe523a96934fddd GIT binary patch literal 8331 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 JFYr;A{y!}5c|-sJ literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/bells.cpython-313.pyc b/muxplex/__pycache__/bells.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b45198dd12131ee1aa452e64492c406ae951a3ee GIT binary patch literal 5790 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 OUCaEv*gx@AsQy2pkUpaT literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/cli.cpython-312.pyc b/muxplex/__pycache__/cli.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b969f47d5577172da8d67ce70f677b098ca5815 GIT binary patch literal 6830 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 g9@e^_J8MZJo}V__4Eudnb&c=L$=2R|!qDgXcg literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/cli.cpython-313.pyc b/muxplex/__pycache__/cli.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfe2d327d01f214ffc330f871df2eb2fd589392d GIT binary patch literal 4181 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 Md@y(iANl$8-&Op;CIA2c literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/sessions.cpython-313.pyc b/muxplex/__pycache__/sessions.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a60a228866ed8cbea7822794b795326c80c030ac GIT binary patch literal 5509 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 literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/state.cpython-313.pyc b/muxplex/__pycache__/state.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bc1f1194574d00f3f4a3ef818315222a67ff930 GIT binary patch literal 6354 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 literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/ttyd.cpython-312.pyc b/muxplex/__pycache__/ttyd.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc1047de004b80a96cfd5f567aa088283e8ce06a GIT binary patch literal 5889 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 DWBB&+ literal 0 HcmV?d00001 diff --git a/muxplex/__pycache__/ttyd.cpython-313.pyc b/muxplex/__pycache__/ttyd.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aeba78fc9cb1f5579a92fb2cdebf642f21aefaf3 GIT binary patch literal 5783 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 literal 0 HcmV?d00001 diff --git a/muxplex/frontend/index.html b/muxplex/frontend/index.html index eb98212..5641ad0 100644 --- a/muxplex/frontend/index.html +++ b/muxplex/frontend/index.html @@ -32,7 +32,6 @@ -
@@ -47,13 +46,7 @@
- - diff --git a/muxplex/frontend/style.css b/muxplex/frontend/style.css index bfbc857..c50ad97 100644 --- a/muxplex/frontend/style.css +++ b/muxplex/frontend/style.css @@ -353,15 +353,6 @@ body { white-space: nowrap; } -.palette-trigger { - background: none; - border: 1px solid var(--border); - border-radius: 4px; - color: var(--text-dim); - font-size: 12px; - padding: 4px 10px; - cursor: pointer; -} .view-body { display: flex; @@ -687,95 +678,6 @@ body { } } -/* ============================================================ - Command palette overlay (desktop session switching) - ============================================================ */ - -.command-palette { - position: fixed; - inset: 0; - z-index: 200; - display: flex; - align-items: flex-start; - justify-content: center; - padding-top: 15vh; -} - -.command-palette__backdrop { - position: absolute; - inset: 0; - background: var(--bg-overlay); - backdrop-filter: blur(2px); -} - -.command-palette__dialog { - position: relative; - width: min(440px, 90vw); - background: var(--bg-header); - border: 1px solid var(--border); - border-radius: 8px; - overflow: hidden; - box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5); -} - -.command-palette__input { - width: 100%; - padding: 14px 16px; - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - color: var(--text); - font-size: 14px; - font-family: var(--font-ui); - outline: none; -} - -.command-palette__input::placeholder { - color: var(--text-dim); -} - -.command-palette__list { - list-style: none; - max-height: 320px; - overflow-y: auto; -} - -.palette-item { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 16px; - cursor: pointer; - font-size: 13px; - color: var(--text-muted); - transition: background var(--t-fast); -} - -.palette-item:hover, -.palette-item--selected { - background: var(--accent-dim); - color: var(--text); -} - -.palette-item__index { - font-size: 11px; - color: var(--text-dim); - width: 16px; -} - -.palette-item__name { - flex: 1; -} - -.palette-item__bell { - color: var(--bell); - font-size: 11px; -} - -.palette-item__time { - font-size: 11px; - color: var(--text-dim); -} /* ─── Mobile Bottom Sheet ─────────────────────────────────────── */ diff --git a/muxplex/tests/__pycache__/__init__.cpython-312.pyc b/muxplex/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d78fbbb516d4e792b227e991ef6bf2e9b0ee44f8 GIT binary patch literal 158 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≪=T@H;QkDF;;2E^j}>crac9~{yYzQRhMChE~7g6 zU>E%6BLf4Ri?|TEjJ@E~(X(gZc>?i1gE~UJ0eBCGO$ex!-Wa*x45?PYAzRMf`PlI% z2t?&-Q4<5OLrjw~=Qs8VgbLW}|&k$@T7?|UVhxJFV48U|4SB+jl)~}x7AX2vH zU7pWRIRx;>K=|oUU9MWw9netFO(;*$*)vpZq)U-(Eaz0OLy$Ws4MY+bxK2QK%I~DM z4^g2j6gp{;qjc;TijdbZG#k~bC{I!o0`NS=XTCDP6vQuQAYNP3u(k$amYhDWJcj{) z2~Z>ceGE!TJRkVJ6ohEKuJP5@S6U%huZ3Ve@LgZyXk=_dX3efl%kC-Pp09WQ=VSlj z*f;tn4s?-F-3y_5t#jl!gzB}bP<{AC!5ON?NJGULFz)zLQQp&M<5z$S2aLwby?M9=-0>`obIo1Kh$;?`jMJOiGCWv+Y`)A55OXb2sbiB#xWS#li3^g;H1s~ z5bX9kdiu}D`c!K@V&1ueHi%lXn!e#J14CP2O|}KpTC(m!A~CSPqhs5??d{uk?`_}K zz71KL-De}P-iM8tvJJhC!LhLBTz^kb{}4>Y_@&|@TZ|ODu~!I=kiryt+#RYlpP5-7 zMIIZB!yZqLLY1p#yaB0fN^`?6;z1f0LI(pdR5sA=f=OS1Kc**TgAw>swop-$^fd*5 zT;Yi)6BbVHW4uMkN5BV3Rao?8ecB7QtF7B^+3rc84v zv+2N;bnq`d^`nO~t9DO$_S~}A9oxUYZMMGQ)m^Xb8f&@vz<+sa~C} zUY&7o8&9IhRIkQw^|l!|{Y|R}_?{@U?rm4E@V!aBI>j3E!xJ=|XfG;geb^O(jx);9|s&qnu;j#p29-k7ZrW@L`CkpDXx34Wg)1~a@B%Jfa8>_4g*E0XRXYn@8)CvBhT&AvPh6M%JkoXQQB9A~ zKIi0!uo4-Ma*VJ|NzFmhaeyqaZ&;I?Rg)wG5;GC}z#`~M!kfNfSMtR;$lSJI45l2% z^bm;!BbLxztYGvxa1hWF2ms;R4$&CbuSPM-?-Dwq8`Y&S8z!q)-xDc}?|o3PLI=y- z>sF6m;3A&Y@U!d9v(m=CZjlW~JJo=jS76pnLL52at*yVGauhieibmDu(_Bk$T0- z0He~vL>Zu)$dpxY2$cmGt$;9;$q&cw(1m@5`!^Hu3s%e^=3(L3v3gibW*+1Qrp?c9 ztSexOuW22Nv1Zd^tQqEEBCymW8 z@&_C0S7Zei#D&3Hl^As#MNus2r;dCWCq&m32#E9@%0-O&p9uMo|B{Y;?wty~{^f($ z4!-yx1%a&1NG(@)k9Ls|s0Dx1e3$P`(hjeR675Hk*PYtpv{n_T`C$qe?Ywq?a$+uR zM|t%C{rcK@y31+xfXIwVenNC^g0<&$V>u`NVuF?JYpzr6g<`CRrQS;d73}1EfKq-dr)RWIV_UO@P z!ktfcJpy*@BA0<8;8}l=c)@lhLb$8~C4bIu9ExMiv(%R)`$^|2!e^^O@4|#ukud;5 zD|ZkAEh%#384pM!W0vB8C)L)OJASkAuo?=E$sd(oTJV?Tk4j$`i~gV&OgHjX{> zYoGk`CsBd+=93v8!cfxRG~eetleELDqGUbA>9J>cbqeEsQnV2j3^40M`tLQPS6)rO zl1Am~-qAFYTl0(y(|lj#X1Izx(&{N*VLip^(JQ<`7bG*iEemf(1d&Z=f@1%FUxOu_evc*ccUu$S}aYOxqbGEuhc zoQr{vV5z>qM>ttLlb|+|{;Ouj_8=yiGlkqY8^%G`X zhOy1oPFJ@rQQaeb6h|zj^H!Cvi5YK2YeJl*H|>;{g1fX|T&1svR>OKBrjY7{`i5b? zW^1vGIiW769t&R!yh%ciVCIN^&);PI4Q&%aW9#JxL)M~7pN5cE6uhDQX*h;$h00Di ztgcbjohy=Rq!KRF<$kCLoCaJT)OgbU!5v$72sI}KC+XlZOEmPSS_VS++kYt*<+{() zN)QiSS&1U&DnNrjREy?^hP-yN{Uzni~?f^IMC

^Vn zP#rT80f!%n^>M{01&rbvQ>NscqTNGOl(rQ)N==SYK~iRA6KXdCOkLRw7Q!=^04+qgdQ~>PV~dtraz%PkNHg!dGq_I zkUDi8i_ofSQm;QaB|RYGlZ^SsKXG&alyt~&B(rwc&7Yr=9{y`j{TB~kUpwX5O3GI6 z%}!Fb?tW$WtiR#9^XnhaE`9Wt-O|u{$6{;nUUlEKqYc8*`5Uw-BkRTyfO{Q6h>Y*Z z`qs@z^f#>@;CrIXO6!Q&pca#Q6=^qF01`SpAz?VtUR2N;6wQopX%?|SPGo)SAwal} z%R+##{yHwJOA*)sE-08}r+FP>gzFerZ6_*dGV5EGk+zPzXM$U^!L6ufZ~ztG))|TZ zrnMt{carV$x+wVotU1nOELKGVEes?Yi3*P2t;Q;n8rjR$$Zys(pYYhf?Qxu_FQP_% z4Ft`Hnvg zEfZ7>Qn3S%O(|x`Kj%`fs!&cW&f=ShxC1M{%c!&dwMWpzC4D}tTe6|(tY;fc`~%y|)E z0NDJ`Su{*xwdeuV6J$-wHre&Mh?b?Y@ILLF{!5Xvz{Hnl;Z0P8rYyW}^(d_74Oc71 z!s|Bd@69??Fy>{gLq$u*!t1j(6Ab8>r*u1=LDEtZ6|(Sp#3X$rGwaY|43eHz!-VlK z!NTiTGk+>(EWF;wY)y0)zI40tEPSPFQW*;$)ZaF(R=~pR^EF!wa~59jQ*tf9!hf*e zWc>{-#==+nG>W~U?}CMgaqnZDr-H<6b1q%w?U>neoD&6=vha)`xB%=nw_N4*+leV{ zCoMBsK!eea0#WvMtD;ox9kI}Jy91>tc!*)E@@-TUVnxB%TL*L2fjG5SBN-`QLOr>+ z5D8;c;(d{YP-RWY>N?(jfEoYB51|t2Ju62J{R4)&L&LpbVRGR@swL-A2Urlf!l{(< z0t96Ui^D|%M#^Pzh!zqCD5WTub(Jle@_9`1M_8lyZ{`sy%~`3Laq7F^eJ(il%#uCX zeJ7@*lSW3}>Uhd3IQ6j&H_v@5lnr%_?w+iFl6Z7F4a@5LOgdx4Ip@7_Cf!HNNU@uq zvd*yp34hppe2@K4_Bc*hi_k$=D&*4d(?R%@3bwDQ`%IOgV)|010VY7vSI#C$X;GAhNpWvHN%9Qd;2aUSHG zbKvvvF6YJ>+NzWl7x-2g0i-U2Rf&gSSXKE98qY_myNhB6&fpe7s9wMn<)2|hb9Cxp zofn2o(==LXzQ4x6_b(3iEQL6E&Ps#)TTmN@rBPnQv>jND_%>8@{=Nj-qzq;GXtS#T zZLZITo*=Y&hNI1{dxSRMCq~|5b#yTd)+I35u@3t;I~>RM6~SQp83qGijwLSK$g|$S zm7^u!C>jpmWJ;kz#2+?r7UjI>U5iF!W26arbzI{XMXnJ zcpMcZOmRSQ3kjw%aMU6uOtESA030MF4`wm{Qlcez@p2h11>as~Ou@6uTEY}3%W&yb z&2nA3FTP|vkm<+@X^9U1(wVK(|0kdOLAd~TTxvZ8o%=jDr zqU$!tZ0H-Ev3P`qJJ+A-{$r@qyt zz>UeRx(PGz5V@!Fm@M)6N?V4f+La(C#q_eARf5MoGi&C(Pgfwz<#t`A;dvF8-qM%K z1MwcD+JY6L@YiA9qwiAvNhor@_Op#4Mt;BficYpd$}a(0EGM{M#o+!tHD^AAN1_7F zU9@eoeglW=p?ky)A+0d%r<&Jio7Z3WW}8{6;=LLC`k;-ZziH$prUQJBvJ^u>N_!K? zQmh^3M|g?s#diVLL&TSaI9u}7x??r=Z`L@DwH5*DNjymdLuoo%~?uFo0t~W3pCJap;yQq(8Vz;DQ zjFo+=0@(mn!}dooGmS_Lps5p`6{A+c78M{850XhVVKXFCzYL;RaJYF@dnRQa*(-b95eJU9uG` zaED5Idc_2+$s+}>zI(J7Z>Xp--ilajAy!6I;X&R)Qh^IFiz}ye(ak-H0>Lhrp835} zDme$cD6(i*O0bM#Jq<=%Jv5`IsdyGe(Jb04jl>Ioy4q9K*7RRs;0;8-{Pu^8eyMGl zsa=_^T{%;`He0)PyyxaaleK$BPB4?Hjj)p%_ZltO*#Tjrjy++J&MUBCANPv$TGiNy z8Lq<(dh^&u9%mX=M4O&$rATxXYkG4=3rB}YaO{AH8%MFG^J2yEVYIMv7?D*etX!*F zM=&}JSxJ*x4vq*YQ1VX zXOpd;bCY@b8B55PoOpr>;ul(V%Fb4 z{63rI8uDqStRTzO>wMNCTb@V#OSV_-zu<7-3d5E?mX{oF*oUkk$7uuwK-Bo0N30@q zG>JX-Unm|A3p5aNcFBZ-UOFc5eO*DPs z-k2=+M28|uG`T5EEm^pmFn>g0dOW%|%+R5u=;^5YT9tgq(feb@GlJ~fH& z$2Z~IG~2LybTHGfd+fsXhUb zzh&Je-E{7;GFfBDuKWtYEt#0CfN1gz!z4MULFGRrkNp1I3cTh6hQ|Oapfb zJdaj(Bt}EXL}L)J!u?@KAv^w(8aiqv4)jCK;geKrD;wCVWjY1 z5MLO*0rCP5nouqpEmwhJL)?2=#fE4JO4VbSwV6Oeh}+7v6ZER=)xv)ndYFlTeCA=* ze#P>uLg*#W4~eK7DC+756D5af5HSiMrUnsv7Vkz{`Z;OT`Vs;kQ*FpPWZ|?|;X5(x zP-(KN|8((7zRMgP3c*Rrr)6EkKlSd z8`BA@ouuMX6sAGQLX7%C@I`&dzJ8VN3ZES&(?~?$C3%`c80R>H#i)?>VDAr|mc~u5 z@3`rkY&bA-3R0xeQU6ZX{Gyb!awL6h>DbVugN} zH0*)KyACvDt?x?MWoQ54?dnyxEe=l=VhdD_b+Mn(-D6#^9J)(K{+ILv z%N++Qbc`LjO^5&16?ozDT{_Nosnsn9Mq(}AVJ)P<3&-_iyq9*Ym9=Bn`djr_+bVYK z@3o7*tau5Jt@Hi2et%|kQLJ@5>iRzn6(Tdz~g!pSmfs+xziq=T*bv1)-<9Aq1A>V)Sw zfXM(gIn08dKu>{Jhc=Ai1Zr~8KTSM>c@^vry9&Y3Jk$h)V&)75QHz@d1S!uYLQTZ{ zXA;E6u+3B&?_tcpm-^_!7DT8Qz}75#W*%zN#}BF(GW_HPOwn7eGBi=EdP|@tpJCQ! zZH2(4Ogmjn;a5?UapX1gXF(}L%vLrJHB}|i5ssIdXvd!*&X{e6`4Br=u%(2ML3<;h zHDP|*BpOJ2fSO!s*Uz~aYGSn#sL7?GrkdnQ9kw&nlppqz`GQ4)mm@tfSrZrlWl>yr z4fb=P!OpE)Rbz7H*9fB0!Yi~!3S;~kQc}J`XMUZEuTsH4FWxmv^3YRs0aadLh$&nl zwo%BYi6fXsq;gb+uel-;=n(tbwt)qg_3r94YsCJ!t+@PYgpH=~83Gl113+Q9F z(}+w}Q2}}>;+nNn!!T^!HPn(H_L8EzYmXpItFOF)JNN`48wxDUkj?j`?SCQd{L8?q z@dGzoCIb(Q90U6CHhhug@&jN+F2D6#u3BIAT=Qh=R%hL7ZrNPr5Rgj)$akIj2uSi& zz0JM2K8Wb&ack!BCuSahI{Wz3na6;Fo~;B0IdD&NcYWF&&&40c!+oRX=w|yL)^@hr z-mJEEu5!Ft>!$iDN9QKbo9kUvZ+9FGIo{j>B(%oyR+|&;-&#Y5-U`{NzS(hfS3VLV z6LUN{%bh8plk&Wd1^|E(47`q6nE8~&(AEakIK6QN8#pJtFtn-Mj|2~Np;8BmDYFmt zxk0%%3$XH3p` z%{d_Ssa6qg>e;yNXCFwcHsWN={!v0;+>j|7!_4UT!i@n zd>sV$1J7Q@1Rdl|5V)0`J9?!%9P5LK>&zCH&FlbC$D`H0kkFq9f0tj#+!RfvnqD9A=lkTN)+4h z@}X;pBEJq%Mtp5Z%Zlpw8s@{>Hohj`hZhHg6E3e5_2H$MC8`T=hElz1?!1Kq;LLZV zb$1=WSkPey8s`B0J*c8U6;mX?*v7f|KHUA6nvO2Fzg~B=#_@;NqjuMu7B{MIy4^VX zmfdkQ;Cl=88pm5Tj-#!fw;I{;<&LB4i$L_-3x?>j9dMd3Lcu0W#3&dvQ6~DhdGKCH zJM_VHQz#YQ7nnm8nnxAx1Kyj#bqaTHq6r1Ocjc!a0lY66l>xFYl6WEFL!QLTYaowJ zpm-izp^t9{aRn5I#7l2!CJhoT0q?zK+UoGW^q4xlFWpXu_dWsd;m~Y=_bat$YM7ZB z!y_0O4bQ9QRz`SlW?=MQ;C)r{R9ScrV4sNeLLGs$9b6Si;;?d1Ke=4%)E?_&1hN{K zp`C-tarx3}ZQE zSQZ5(O2^1VmjyXvp+FS!erBFU%ubpa%_*dL&e`J$h0AjZ<}lV&AsR!W*O(iu6VJeC zl{xnT9^mAV@}F@Qf0p<&C~w)VR_i-9n^n5&wpgv7vwSx2Jxl2OmSwkUEVi1Frq8#% z&^9JbS(m?KTVg$Iy<@l7+HSGxt&bsbi<{2BV+&Yo=uFKms@>jbx30hKw_7`IH@U6L zZ#Oxt8*jHZS|7N*m7Vb5#3~P(ENQj2-rgUu?!O&eYTb7GfXlk_c3Yiw?d^^A*7n<* z>#VzPZ*-ttYnye`?MJQE)>XIHG+OuF-ns(C5vvcUmibX^a9TsR>)hzrp%ujutAv?UNw_MCEw`(D))Us->-^U3w>LPfYi`##F}*qm zhG?zEq}Ehfcimp)N3G3=+EzD)ZSq>T+*;$eHec_)W5Lhuwi;{I?R9l1aK-g^FoE?< H)%O1bD0~-2 literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_api.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_api.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99d03c0f71ba3a85b0a27b1f08c174364f95279e GIT binary patch literal 74255 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 x1 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 Q9oBVs>zyzdrFH**0Vn>9UjP6A literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_auth.cpython-312-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_auth.cpython-312-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d661c30cc5630a24a9d20a05d0f531fedbcae944 GIT binary patch literal 57629 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 X*W*~E0bu1q??LbF?ndu&{!0E|U2YoN literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_auth.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_auth.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db2d8bb83448c281b16312439bb47003253ff5d3 GIT binary patch literal 46895 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 F{{cBmeMJBO literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_bells.cpython-312-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_bells.cpython-312-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93960cc14c64227a09f7d03d2fe6a924bd82ce52 GIT binary patch literal 25934 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 H*+cvvR2i*5 literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_bells.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_bells.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ec9567be432d0f6dcaefe163c0fa594fa52ff5d GIT binary patch literal 26605 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 literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_cli.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_cli.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab51c7d7e6c983fd91c2ef3b8b845721d561920e GIT binary patch literal 14581 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 IAp4#Fe|&soN&o-= literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_frontend_css.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_frontend_css.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0578297279f97ff69ad589f8c65241fa282c6e6f GIT binary patch literal 118534 zcmeHw3ve7qdfqNDc+TQM0HpZl5a0t#Vwd1s1YeRU>Li_xCv$FPUZ}eDVs`-Saj}b= zS@1FRB~0wtlx6!U=TySkjsqul4s<0h*>N0|li0qp%jGy#sa-7YfWS^7U#?=G%dV3l zos~kloP2-JOwY_>cR|XWJ?S)q!N1)--90`3^mPCI_y2geqoX~5*90fco9|Alk4+9>T z4+EZ%j{u&Oj{-g}9|Jta+I^h2`vhzEh)ho|Q)c&&j6&&&y{3pODW2 zJ}I9Ad`dnKctL&w@M-x;z>D%zfX|FKgrA;1hxhjxMayf_SWcD3)Lb^NWK)N<{H=@< zO=_C-%{yO~Qi_%y&r12+H6^P_iEK(rCDdzDenOd4qTi+-aA~>KDyz5g!pqI7lAl(y zNvo64uYmtX{J%R0@Ot1XpmAKL)Utzm>8aqJK)CUe-ZY&bi##3N64yBaqhi)jNk^JQJ z%|rN~$|&}4o{(^eVBC6=IG#T`b!)km)r^Q5Lba{<*ERv%3H*uJyD07`iaTa+6vQ0` z@lZiHq|(>udf*l$um_e9!ULNhQ6O*}7wE|1)5~p$1x_QJ>nF6(k^1F8;3_^_b9OD0ZgsiB3|m#L0OQSq74zWJliNY%Pq0S;UJ3%SG%m+IqIu8P{jnR z71pm3lmlmjQ$2F4k7vT7=Xh@O@mv=?xBGam3!X(E&-K9bcpx0YL?4Naj*r|AE;ppJ z_k()R-odF`hr^nb&Po(K8rGH@F&p1+SZ>4x0!LRn6Pl)|d6wZgWth5kTyH@|N+vU+ zha!*xlTT;0<>pKdnYHE3acv?uok_7$@$pP< zG?9sC6O+nv=aa0R>0I`rs^-+?-Z<#4$wWS`fE-C=6M0;@-=uB;wwFb!+jy>FXXJgYWW@~{E^g3p>Rw!=8C)xZ8UZ2G-t+2#I~SJ3o`SID zUh>$64w0lV&E4pGlxg=~^68j3mwwcDFxNT7|&v{|n-BU(v z!4C4vNSkTov7#%+lS{(3C2>nZkY?nfC@l)+IWI_e@eCAOu!HMka#><#Z8WcEj98SvBbK1zKK^f+~#xv%U zwo7_jVmhCTjHNRfwG+2hX~DooSG9*i8wt>2Zn=3fot;eFR3D=h37~u^-N=VhP0=(A z)A4aNolY$6J3|AjU`uH$HG-%wQ zLDd83ci^uCw66g;Mv<)rVfRe3DDGYq%yVAYeHULo#TM)!zl^jQ5M*ZNqASLeOTz9Y zaaTduH+!@w?pqYhb6(h25O*2T1xJ`?K;rCC9xJ+HJQFu=8k4#Nxzy+vD=;^(#q21sR=)7exy znaJljM{ZaRMI)YQ$|W7Trg<1ACvHK|QZj$)7U+Egx`&1>o~5Jrm_ z?LPxJh)M65d!Ez`-eQgM!Y3pz`{TvOW7FCvB-sp5A3}0-8F`3(83?>U*rfJQt#5KN z3?dPun3B>r@%o5e7>T6Pli)OtSEn*Jb1IXzq~dusk=4?)Eun(~yi-LL13qu8QuMu5vgn0%e&h_$G(G}y#C1Geu94H6}X0ZcuU{Ns7dEr1o#5M#) z7aU=p0g1EVH=5att{6`)2?xBZ94)9Zb@*~P{?u{VpE}r}>dTcq61g&84yP_twCu~_ z)CJFm98R~*6pJKt)7kubBv3S&%Vw1%5i1cbpU6*Zkx1^EzPVERjf9%jAFFglQPtJv zXg-(F^3|!N9h^#Hyg1gpI0!WexKKF~t)+i$(M>AtC*OiJjrc)WE`pjp`Ax-8+NrZq}p{XbFwn>?%oH^j>(7vxAMCKsD7+Dm|b6$uP zNTh(G3yv_)fW$d456tXESBxi@gvgTURm(AR)nE?11qoVTK*5|)`HwH4;0q`ioxxW+ ztE?ScL+Q+C4zR9M2mZZyi7hrKC$SQYOeHc(KCi^%sdOTfTaSNFOVP4&>Fm^WUhjyS z6-Cl{WfDtPXEYMglnhq9N~-#VZ8P-EJH*Wz)KkFAMf_{G-K*4(?pn`7dByJyIpD7( z{gtG@k~F4%eCn~Z1^ijt3GO2{hfsHo^0%gRh(mdhx*7-e;^>8biG zWG@?y8$&}8H&#ig>QgiY?Q~B;y9>hLtXvca7X|a27X}L?hfdK2N0?_o;;hVLMOTa` zmxRG3vA-aMXCc)dUKGr8UI-UR$&R86jxf)F#MvZ|6ksL?};)2lHM17(e|^kbnfI=T{y`*oN zQWYrqYY|mRO(&HUj0Z3&teV;d)w{K=JYd!rH|sl(=llow*Tw)G9os9xtJ<EO6BrR~S{+Ly6$$!<>BMM8ktV0LyrkSr1Hu@_h?>jgqxwc?-Vtfw z@RNt1Ir3EPEs0CjJL-0OXMH?wwjge`Kz$OOG;a6SZ&`*Tydvp9_th9HS6ltD5-+3d zsMA=6F2Bbekgi9t=x!==2EwQ$RH0V64w=J45m#bHFrbGByQ!V^U{=> zn}T_?w4y~<+wCIj5Q)pd+snZ(u6Cr-8jO?O8e!EgH;ux|*|pVPx(C!*R72o%gl@Om zLWv{NVZ8%NE|!5cX?V3ecU@7(GPxTg(nLCyQnL5knWZ&#Gf<^&A+VJI*}zb@6WBpu zCxKl6%T3U>R&Ivd4BI`sDZQV-0D(P}qk|9;Cq%^4shi6pYh@f_sA_(>&4|FVt@;V% z)!G2aNE$l~OKCef*=yHB4+C}uVNiV$MHDJK4yxr&fD^vn=t@J!`|ZBR-gxe{=Vmgc zzJqr@wbZlmjqq#XnG3T!NsK+GYl3ZkM_b-g#~*)HU;5A++z##U&9e zk%1YdNIE*^IWG*5(GZF)*g<|7X)_9s6bE0*+VmfPE%(yO*}^S(CipbW+sz7L3jvr*ykJ%F54&_guOT_VF2I{%;k$tsH zhIJ3cI;<5YnQALLvQ4m=t>Aq(TdSzt)a6R~TJfG{kdGU$&wVx4K7uhk_aCiw*C&Fh z&$_Hs3uQM$6k<>9F%h=5#VfkKe*AD+(016zuB;;7WZ`VxoE*FB=4`R*{D|U&udCO6 zQr?r+&DqXz5{kAmE9N1&L+*@)Oq=E%53!%zVbx{*!lpUCk8BhEoAKX*e-Z!m&HOBA z!!G@TP3KAKw8cG4Oid{XRT@=bK30*Uc|`@KmdHTCC!0^Cvx-WbMOgNiU^7vPsu$4r z*P#+udn?oe>%GA<;h=t(EcTuVYwfqqfOMAG=sjX3Az&sAhqWl9^4h;RNSTj?wQ&0v zr86x5VZ@9&C}fs6j3Q^x~;6?2t3X?EjUpn>5se*9v z-pc@YPu+XDM4|^5zxvr<{49P7hn@zU{VaYQxN_-Dmu#3 zigt`HfIg+nD0uH>9$Z_;0`3Yy3A0|*VZ~6#>}TzQhn}{>dtI&Ig;7-v_V~|0A*wQw z2!<<>k!6R2Z5ynTqXD)dEw*h#l{}5H2A-$!N<*v>Y+FGVa!y#Y>_FYz zxrFr;%*BOWs&b2XDUeGoa;wR>)|5Go? zAs^3#LeKHs;p4e3c<%J^To*id`FO4ep2x*-xBhemirK-vCEn?cbSghFA`Qdq7Pz;{ zbtRiOxVjc6HL6}ld)222kP^Xa$XU!mvlGX&%-Z=S^*Ks>9zgGgZ>($MD)jqP5FQvF z9@aaj5-GBHM0YNV8+Mf_`BE(UGyrZ-N#cB-)BNU8Veofm92>h@Akv^EVr9M` z$gPCw`oRivrw-)#A;x3m`N59yWO#mfo19^I#p(2l9a$3gEy2f`H@r?(IecyGnVDW= zn*qKy$o2xW&0vPP*##udO!F9QGdN;A8JQT3Y&cbEH57JoYSJWQ(PbP?>GFuY4nJm> zu>vYTY_sTDB7H05kb_Rrw@Mxm#GE`9t~AC3AwUl9^n({~!q`_b&sN`XYbUWy3+9(E zA&o;>I4fCgkJ_JoIH=Nx88&>zs2PNp8(kk;xg&#K_?lriiW65L&hDb-Q0_JSYeWa% z3B29j@ul!9;d{@P+IPS&4Bvd{E%ofJ@YRT&GpAs1tRU>Iu^RD3%POyMY^Ql%vr?I8 z*$@}U)zT?t6ucmtImAWFhyjEy7+viy&+V94XtlCtVySAYbQd4SExBIv{^*VnXwy^` z)qRD!;27(Ie22{|n=RJ3ki_lyffci{!9uX|+W9J9ImbxQ$yyD=Q)R9{7Gx{jqt@E+ zfmXPUu|}+Lg)6%sVujmat^4GrN4~=4!duo;JkSc4mqIUYlAFyHu0X5Zm|(7On;v3? z%h&MMlY-&dh86CB-0tI<`m5J?9&Zba`q}dD-$ACDBmg@3$8b`KPSv~1>$C(1Xu^6>YV>}y!LqGNbvC0igKuiEO$VxuRWRQPAGk1ZrKvYi z=wIMp`)>f8Tly!VU9*jay{Af{5!#FQjEMA(7^}GP%*VwUkUjpmh&PVo!!<7Adu`WG zt@+@X%=om%q{!i=hQ!Jhte@b)`Bqr6*M3ozNZ-|6cFG9JotKwb=X+IY`#`-8pgs6E zW#3ZOQSz%)S_7!|O$`-<<8MMvmwVnX zL$IOg;~8Dr>;8;cv_pTY@+?Z^BNHi4<}%ZhSxc(?Mmj$sS^E*DV7D=as?VUs>&%R+ zo604~joR6^#A+wIs7@{pe)F3SPR9&nw`E9e(J800q$G>(L3z%w@rH5j|Q0E<* z1$Vp5NdrW(DTO&{FvHyc0upDZc??b(95J5EoHTr-`SO+dZxbLd{dFcfERGM?I@2dG zjJM+Tfb0_(^}#bHzL5Tz$`c=3KH4sLq+C{F+)FpRlkL%Ap2;MpFi|G5>B$6{iZEF! zqFsJuohec8HfkS^9yzW_N{wVRhD}-3;|dfZ5-{zjm<3WC%k_yJsK~ zY5Ed|_h9lp3`0tm?_o2@-3pWMVTdU@d=J|(p3Hm?dpcjiY|DXJEQrfI7NJ?(W04u= z&WOlk5f;P_k41KjC)YNdQGLFGqQZt4lP~;$3hUjDeIdSSU1cwkFEc-!V)$MvtY77Z z2weMeO-xaziS5hvTRU28U(UuQXJr{94nc>V+l?Ox|Lu?5U;y6{vz~&{1@==A$F=Nm z%EDs4YWEWV%rJwGXIiLvif8QNhxBL4=c}l>n;wBChcOYQjIBCN$yc1SAXhdVT^)FU zdK{Bhr7eHG)!fht3X;9>Gb9HR`l>@KSML;P!D=RCJ04i|)zZ^B04$wk3D=Y^96aoC71IKn&w65qVQV?|etCzphi z=KkOFrO+p7|8FC?fZkZ{0(!)D0c{4kcV}_|y^+Vr1+*RG$;<_`H(QGkVynUS{ngfL zt1^Fw{CAafw z6lMGDcWi|~X?I(1O5PAbvfLAEGbP5_A3{ZztJ_+&k&0}#^~(6)uq`Q?m;VW)a7y-mr1;826=4iLnp>SE=mrKY1r1Tp(Gtu zzkq(a4q=%Xd`l5Bl{<&f;0UX(s~EhtOKg6IR6YPDB@rH$5=P> zZ6GF0=bd9n&uj#Ft3O@I08wFk)UZEPrdK0wW)^V3HluT1h!LaEj2VHiyV8oTn33Zf zLTu(VFNGYA7-}pGy6QtlRJkN>DvEpG4>UA2zf0gd!nXtOH6d{KG)r8u65pr9?^>~U z99jORQX;RcLb;jktZH{TGm%}PMzA|9wVr>!5%+cHx2vqN(wg;GT9x@(0c*bSdZp#! zC$@1p|0e8zZHU#M&m}(cdH(OuPZT{}>4tnfW2PdGg12}kL$7{+_8!k<=+(!w_jo2l zuRfkV$1~Pe-TD)J-(f;YK*ZWuRE;_;kTM}LZ3=em;VL(OLxCDAt*uNQ^QhicR>L}r zDt$v)e(AO$Y{ry(p2c{%y7Tielb;^D#cWzZZ+=8_-Cp0o_1hnZWcT4CQ#bVvQ{vn3 zjimkOFD46HGaI8SS=qbA32=fd`oSWvc zqASLeOTw`wac4o;KfAXm?q3wlb6(hA5O*5U1xJ`?K;rCP9xJ+HJh>$7_sY;JUcbLC zz?W2}ZnEA!x4$k3T;{f}82T9a0Dg=8b%BXKr`m@6@mqiS>jKsfJYRqL>w@~=*|jG zfQ1s*oo?%0W3Xi#(bBpC-AyQOGU=h!ZkBpOAr;UO=>+UtZOOwBPK1fY(*DAT-4wlx zxeiw&v~VCxSgV^MT9i{Qm1tIQavE;G!!_0lo7U`0FkGl)3z|BO~< zaH=4}=#go0iieF9Kr%U{@few$vSU0MCa27$!pTzTaat;1QL=Xy8q^?j&2wJZ%N8OO zU2ueX1|-fl@mSFnw*f==np7f_cshhYBK0?NfBY5#||?IETkF zvlm@4o?H?Rd5jiVM~cot5-hqXnCH9@h0!DeSD3q9;SnK$%d8mQ*4B zEWy!IKAi+*b&K&?Va7irsmdg5`;*m+2}b`!^$u4B>(E%MHjkFw?zUk*lsK+B_v{pb zf2hQCKBxCs8o?2Gf-<~4)gY2=zw{PEm2RQ_JnH_x_}6~SExGNPyJwR^I#CLpBns)H zYN2yiK^UApUK9rx1@oL21`FaYBMQ&cJj^p7arQWm6}T^0q-3m#bi1u55Oni~G8S675nDmnlE$if7*%2t&W;{n@t$QWrckYamx#M1nzO zFpX$)1Jxif$kimcBjt8M#uHP9UUWi*B(16>@)JaWz-RenRQ)+TgdTG4!#YS1wv*d~ z9O^~_n*c5ud%XXUinXFZ42!Cx)oLWd0b-4Tf;%>YTOUCC)xqb>WXTsjNoPPbS9B!Bg8zW#&= zFfAxodo07xiBx)88zJvkHCWec7nv5S;y{)9OK3%i7MXwR-sXo3((r7uD25jW^PCsL z%u6Um7aU=p0g1Co9xJ+HJh>!<%@ypqQs_J}u{|}j<>B(!tF7{q5cPPI|D!8d@x9qN zsP1LQU)8hs zl?irX6O#*enPD!oMS@*0$>k92vSU0Mf?ejU_;e|Bk!Hp3%}DnIwzYgVru7|Lzu2Pl z^)`IH4YNC3=G(be|9Av;I6r>uHD3^mbsw*n$+FMJtPh@jHfDYB?6WcJgJ++OSsy(2 z`0QdrwCD3+uaD&JTt*z_-}$a z3?dq@{~jP{W1 z7*EC?(vrBRAVlV%9U55_%yV9d6vRD7bionk8IU;F#A8KQj3<|bh<65A@WIs(BJ}qe zs`!iXxo;m6?i<@@I9p-PckVJwPo>~C!}6BQb^s*!2}vYrs*SF0sakwJ!ol9Cun7nA z#+U)1t}&mRI}YSQ`ACHw2dI)q<_6Xbb5JFxQI8FCW|)s)G|Ua~ z7!7lFj3;B5GiUXuN}&rht7C8D@Ek_#!;6A>&I^YNq@zXA1xJ`?K;j(6XES@z730as zRAbD$gJ<@49Psj8M?H5O@XfX9VgAt&JSK5^RRYl9joYw`ZCJ$KzUHf-3ghgKn)++Ve4ajtF$5f2a9)|Jt8LJ%`o@^AJBasF zcGOj;t=;#$Oy~y##akUsUw4r8a?kPX>kifj&mI1_QI~O})5mjN@Z9C&xh{C__VHX7 zJa6#vTo*j|_;{`do?)w?SO1iInawOB!3_Q3Bg4?`$ip1ckYwzDF-rwQQf@k*NoTP~ zmL&TGY=_KtkRDavM8owCYio+t;#iGWv`}yhMb!HQXp>9*mjs?C@C*SmU9j3SmDAGX z8h!*OnKEz^5Bc7ILRYI>GB{XyDW6JDDp?IRX>nN7k7p9M@IdNspstH+Hn2=?lx5n; z!)*yqcbGh_+$*7Awte-{)e7;juXt*x!qeC3&2Hp^u)78`ccv;c*XNR9&?`IY$tA<= z)2{zOh1rK$%2}&WC`~5vNoEQsKcPsegf=mnqbVq#hMDAqq9GOb`-~aLm`TVWmfB6A z;=EIR?pUjk!Pvk?o8l@{a#Vj46~}1K&=BC<;IcWxtM93{S-AYPycSMY#STy z4ZXX!P{GSI%trwDFz>AcS=#vaSgCKM6gvIA+hVV7_taNkrcc;cUpBB{!S6ryW!bl` z&R(w96O`;L_0AOt$~&$|eLNF9tY z9$qk|+?YisWR$Udgh{(IIlAk)bP8^6h*u|R6X~&hRJTRyX|lHNwH~ct+sAR1l5Xoe zY-+^P-5(u~Xw&K#9B`V_bVN(&`|akBm=AOmoYG z7ljCppKuc#^)r8SpdcKbQ;OozMZrAhg`)-WfDv7Agn0%e&M7=rbj5gbNjU1AOkYVj zyd)kg2xs4d1oqiQ!93@Mvjy^yNznyIm}fxZTadsuvlm@4o{UU2cucKuALuW)*)Zb( z^;~ZIzG57RH-q(D2Kv;Dfpnjmf#p~7N&w4k#&J1hh3sEv&vbbuuuC=Dxvh}1l;?Q% zO%yP+dyZ${L_vMe#1St-|b3;_`t5olL3!Y7t3Q4yO*$TET_AD@?ei=aT<90ov3rzaJ zWXIE?XAEnfrW1+U_C2etZwy4J{|UFs(fa1+Fx)#Bs=LVplf1?Q({6_cCNu1Gt~4w2 z7}s>1m>pP*;42n@jN};ocC0}^k6|aHaA_Q<&1gG3&#rLK_j~02=fd`oSWvc zqASLeOTw`w5$HcK8!OT(&phXa0|gN%r|5zs%rhWyHpXK`SBxi@gab>Whswg=3)dP; zAZq*t9~)5}z+dnYGA@G|P=5IAYXc+w0pGPodEa3BwZd^sh;&%LN>FY+8=UI#+1CbM zJYR_T3qFQu&++Up`09gaf5BHDJo`53>Vs$BCS6_d3BR7`bc1=s9+Dyv+p_wQq+~MbDJ`v;S|f*t$lr;%lRF&Md)8LH9tWe0?ZH}ed$nsU zm(54Cw62UmR|Tfa7%$@uAW8k#0QzIpsEFlJdxW=(X_n}{Hzu(E8(|z6@_lkcO-vcS zxzkj`j}iEB0FC(25<3h75nC&3l~AM9-$C9VCdS4Ox~-r=26*4>(V_^WDCRjY>|^ev zDZ1bY^9)Fw#n!8tz37VZVjA3p>3zDyye;5r!Y^<@g{f@daE_z5?~IGRh{a_yyo z{2PL+Izv5}YX?J587jhK8ORt;q*Ey+yN2|niXM{mWcR*p(20!P{C7Zup{o4Q6eGL+ z*HHddq8RT2IJUy-M=^Rx19(c(c|~=B@UB5(%ys$uKJ3eP0e|0z(Bbd<7@flRMRXOT zgmE~;;7AR*?7(;9@B0vgrR)G+>ZZTw`yw)s@9+D3p9rq{`#!8gd)}Y@eV_W^+28l6 z51xHrMD@XQpYQXD5bgE&4A#*`Jz0TuG^N^mnTc$2BB#QX9b7ex=JNU6q#=ZTm`vYI z=CV1qKPb^oqcp9-`4jd!*|sBHL!oQLRw@&)W;`O?WIfi><|=KwtvhVpB}O$^-Gk#P z;x_4RMjJ<@;nfz>k3~RlRny}WEach{#RKTwWTr2|E%8NBI6zuch0%v+@Qhp(Rde`F*tEbz*sS2`-iwtePE|)z*j~ z!%YMiKQ_Z$vY5n=H6A1JV>`x^A%1LblAbSxo}f)qPx(@t3c{|Ly+v`?qF|o$!mhig zjo5-6g=4_S5I$WWJrogd{b>AeLZI=8VXMi&M1oEM@65ig6P z3yv_)fW*0tJXUnYcydXIE{R_8FCi1)X%Bx}^h!A#8ss%NuaCF*oL7vTWrxpsC4~5# zSEF0_oL8UoY7G!h9;&Zm{c#ow!Ahu0AK|MFWpXNSZ=f5VV3pJmlcBU^IU|!;bHFHH zE|XKEdZ)YWdJCIwcXpW8vDX)ev9xeVYtni|{cQlfhe>NP@3ofS+8RQbo7Su`*i)&0 zfU3SqYl}Z~Z>Mi72>mlyTl6mq<~c9)7sPEw6jEh8%rhWy2CE4(d(jo+$t9uRTw9zh zg&y~OZQ&_F>Z$0cS4*ziR&WR5+|Ae0v}OeBx1KbkZw1%DI=Ee)TEUG4F85mR*Z!5c zC}g&&N{%*5-!R*J<)snmZ&kB}I}2lpO)iX8-r44*&?h?N&S*mf`LeAJNr65Kd-g9d-DTZP?vCJs zydl;)9AstN9)jxS@7t=4^h~oYpb>iXC-}rortz4+P0f(BOH55E2~|prffFh5T;&dF zqw2pw!`34YI(T`yiz-u*p8!~HH~q<{Q#Y4e=~^5i^(S#jKj|uCv?-Z`Qp!3lQh$nS z|3Lyj4G?Zunf>71l+aILfWRICe@NiJ5cnK{djwts&`6uxPyiulD1i*Ep+8uAS!?3v zV~pmrrW@TqLJhx8RLRe~sS*#}=rR)*yJwO`GT&gH^TO`C=ZzRVCRjm!8EG>~9xJ+H zJh>$7UfR(2#+KK%6t@`NVp>hs67;E4b${Xc|tPpF& z$lT;~L}7iGBRzpY;#?qbrHMS$%E6H8<89fn7s>b`7p=QLi93 zuvBo(t~8WDQ7fjxj``C9kIvwsTrzDl=dhujnkb_R|%Sglin;b#59#hY;a16wuZ zjv<}RVr)oarv$by$&P12dSX&Zr4!O4UQ%<*ltl$kx`VvmwO|j!rr&~H0oCg z*WV_TsuuzD!3(+RObS|Ad1)*SjJa!_CHr{$HueEahm=O)1$pE4kd1Ql|0~%Z-p3UZdXC2UoxBq2jFZan^XXgeray z?bCi1fjfb>+dIA#ekFYGVyS)WoeSm%F>`dbv9w_izKBocC4flTFmFHi%xYW`8%9WYkSL0^N-HS_IeKWyV^`*zQ&IP}6Ztgn!ELAg-~abnY|6Xagw*m@M`A{#cOp^`pG6|MCO$^~P8u`nqss_Xp|ga?@2J zpV?8_4(M_7Dz%K+4Yj{La^6&Sphxp1t@RXa(Pfva{PpotSaf;nk^JgLtH~H)Y+|oj zCXevVe-Hfc`#|^?A36T{i1JYQ=cRyuk&kNv8`lJLTx+^YpENrh;~L1?^252c;TYH2 zFs2Q_4zZ7CAk1?-lc#dOKYNd7@>K5Q*?T;br*a?9-s71(mHT-19?#^d+{d%`cqUKf zKAyeDbFYtQqJurB>-u~=*8|V+eY{bJWeY_kSM)GrmQqHs!v_CY2uWv7onRUWjDOC! za27Mh*!2Qi{3hgcOm1D2v0crWVWO#Tr#2j-Wd<@N8?dAsF%oJS$3ms3A$D@rH zVdB3?!_gzhHAzWmioTiUpj0zMCXrXJ3=W6&eNOjUuiOkm* zXRk&5CMx(-Vi#=&TY|HT{-kH$tX3HMM5*W5JD+4C+R@q5YlO?8Gr)z*%`lf6hj95U z6bVdObO@I_Vmukba6V5}Ogx-R%{T__nZ(kJ5b6(hf_i-b(U_u0M zCnHl0?(Uys+qLrEhR@x_&{}pldBQ$-*NL18P6up0d1dZ8=hOP!U5IL64yOZl1RwP- z#)loxK6jVBd9PPnSOs+Im&;cHT-YrF0sKs2N&~6yUKm*EnsglkHjI=u=;z2`5bKbt zp?z5OpAw+&TqWB>2Ekjl5K#XoT_K8B{a*n3_R3_rQLXwm<@-~Bwd65OKw~2|scG>F z(gYTHY&AQjehZbnOC<8^0M$t3;ev4f%}*9Z2(6jtyl}oC4ja)0N0?_o;+vo3v7#%+ zlS{&RlSIB;3duwwdq}i-N?C;q!jZXHQ9QCJnCHB3q#%Zk=z=56Gazv;#$!cSj3*;g z4f0(%KXRuE+#^ zRx!uzSs-(raeKAHRnQp0-==bvhv!4JhG(mpaVE0NjHRl7hPwWa2IxOVpxOWphvbK6 zuNB3^i-LL13x^A&Wkt~iN0?_o;_NjZE4pGl8J@h&0s84u=pqf!-Z<=A5C;5V4)51} zI2Tk8wG8luYyyu+$mv7AJYd7z%a-6EjJk{|suqDAooSNqp3E71Un3qiIM?ZHGBXX% zb~&vp-FEScX%svyeKESa{{d@PzktrW&S0r`JK526i-#I7*;B=hr;1yotX*TARrnWZ zMhm3vG-4~4kC(Ah&r8Jf1uZxX%&JyffBDYOdW$d+ib z1Je~n^<=JbCbu)aHPIo+%!MT#wa^At*?}2}kBC;!fFN@yD~yN29r{bpWnuQ+IrtdD z^h0Y$8%B0HHOKNf5^TOLC9-MA2ebVuYYbAqjb^CF2(UrM)>La_rI>o!t8|&^sGVOk zv{ktoq@gRd*49R+L=`Kb3>5a9 zDD|AA@vsjX`Aip!t9Kc?Vy9SQOvQYtiY4*%{2+{CWgK`{5~>Y@axfdb(#@oR`>l`ffz)gpVhxbOZoJZMO99J*a0Dz{ss^xN3_vn)1nlLq6HYWW_ivYD^xR3oxto1ae)7-CE_)Eb7y8{pCt>p*QjXPuQ z=y6?FX6zp4C{wv7*h78yBku*R)_bv@;(_`mFNI#vEpIRRg3kDNvj=F>OVW)9^N?pAAt2|(lCcIV$V}~Xw$%*URqHj=~M>fGD~14 zCE09p>C>NqAxfKVKyy{Jz5U5dMlwZ7Gzof@(tawvU%D}&WF=iuVcimL`ADHMJ(f!2RHA zGpUNEv$)wNOXtW)VY)>}p2NXwEk4iv|I0Y7-)Kj9@ssLGu>A85?%N-0rr%;Db!DZ%d}`6 zh(deaJ`CljJ#5Y=%*W|r;><`V9y@MwmyET|`zX88FmfmGUML{;lJ4Kz9Y^jw zvm|bKZ1iz}H=AMOVzo@eSehJ(-t64o3_%7Ph+TLq=2y-CCxD z`%I~Q8`KbcHog&lEj)8!wzt%?7qmrp?;CBewapC7+%|}hP}j_Jh0s36OL`~J*wj1= z8=d>#r_j3uzS9gv^%ex~o@a@FZ6&@_De-+u{H`m@>|R#xZz?6SD|81|**guvw#fU9 zsKH%=Rm#fOU`qXFRoMWawy3b*ZO_w{DIQKGJk+ehOZl_~XbX%E4@p}zFxsN?1JV{< zA41xqOYVMP+G2x64ahx=w&;Ddw1wQqC_ewKfO>t1x3baOw*rc9lm2BQ_l_h$@7ZJ> zxTglSkxZ!SEoN#wVp!b z;n?ZU$nLrxg{F%RJK}~Mf=!C;FRPGId&aXWPE4rpQ5%SeT5iH-RZi7+*DA@fsKl92 z{|2BM@w1x|KQ`S1T*W66T0DPiDmM;y&=a@RRTMu$l+RBalux@!`7Cwp;FQnEqCn@o zFhZ1%88ZT3ccm3wF(bz}gb|{3%u>kVh@r;9R#$zC|VdWtKo^?5MEdkdkVr6_mm=;SA4=a=Y=Qko-~3B zc9362+C7EGimn)n7Iv7`@GJ<_ppl-gDxV++;d~)b#(#N`)o=y=i(?JR8M^2$=HcXv zfK$_IrB~*>)Zo-qUTJd*qzc_qxkbE`!KpE4to3;!ocSuZuoYJIZ>59JSf0EUCY$!p*^D(hVzB^V?FnXL7=r=~hm4ZU!wM4{ zwjs;l9+QWd{U##~Lo22&9+uM9ECj=HnKTn`W3$m|W=cv`5*Z1VPm(1mWIco8=PdaN z`zDyW!j;zYMjOjo@N$B^aV6%>f0LKmhWBHfy4%|Bcs)Q06ZLF~czp>UI zVbh)Q#Bt+^vFKOSxbd)<_c#ZDW~vMjYyD|;)o z(nu_C^@oft#Bqf-;jBvZv_jpj>KjRYnPRAh%S>h z<_L2Fm79iP!`yUoB0_^DQl}Ex$Stty4OPuZCXJ2b8@cTMJaNh)sO{jCvvQd6K4r`= zqiXI3EM3v0hq*41m6V&9_LAv}H*l==$SoBZ&AA)xSh3~7xX(yvM3($eVVQ?|EEngI zTO)~SWVR+v#`jO8J`Y2Sm_jY1jDwlM?KZowE_qtF)&4@Uir`v0~i*5w5>2xA-U8#giGT~@W!TbRYT-qzX zOZs+tN+UTks-UiPPKAwlSei_vv-+iK_p4OOEJS$4v_=nTy}RTDD#OEDT>2ay`r+o> z>nO3toa7*zbSN3U>llm2#gmc+Aa2da){y!9dk*3&DV~XuGmzpHl(^%Mhq7*tw z%xj6wWV2Yrg)7cvhV9kz`L-;z!b~=MvBFGdCYH}+pRP8OF?+5w-yUGCWRmF>TWPet zyp?6C6x`SuU+0v=9^siJW;e`H^8WM8}0{-{TGQI{(X>th+^ zW<;A%)7fhy!x#tElqMNb8X3mY`5}y?X^e&7xM4iBhIb4@V;GF1>YFq;{38OidtA=d zI>SdKG74?1j@St3zO7>nfkQOf_f-5?x3SdnkF_kd!pAx@UEyOj6U#r=XV(8%!@*18 zPb{~@$I(xc15iwG>Y#-YEDP0;vq^Az^bvw5|JYHozMld%NbOYegiF6sdSLj;Bi93enF z9Q7oDQv`^~q@E>kp1_j?UL-J1;2Hro$EXy#K|m+)!vyXS_z?nk3H%I!*9m-uz|Rx- z1p>cB;Hw0Fg}|>8_%#B*LEtwDyhVWCzKZcEp#BbluMzlt0)If@j|h|qe1pIr6Zj^9 zZxQ%20)Ii^uL#gyFk_3dH6fd|ST9rC=t4UIvQE0(dS)`00$2K+8bTk$8PI+T@SVnB zF!pW5w{923=kK(>(;N&A z-y3)@fS;At=HSlvdK!b<-U~Gb_rDhsg1c5a8-rU{5IVTh*&N)q(jx?iRyvyix`m)b zSHmlz23&1#3?5hsbp-dXNKL_Kf-Bvj;NEuzn}YjhDva>C?XJvC+@X*TU7Tl|^1#RjR5!&7a z(A^Onq#GVt+1iMFxPSkOB;W!Xdt{}jC3tS7uN@_}hEU(m7Tm6{D|l>WPb;!)Y6~{M z)883p-yCdauj~H$%!J!5iZ1;d9 z#)B1nBn4KK5K1-{9Iq49<|4gvOgoOvN>Yjbh%0iOihFiIf;-u`wl;~^PX36zTzj*u zN>zT}d)=>lW*P@YaHXwc*UanpzJB-lUGKd=s;v#V@cf?dKc#+ar_1%<$zeW{VR@vx zU9JyY=Us~HEtld}JZbk^9`^PQOM@P_YuNXe-_5_&yj6oQ-gIC%_*QT@^j662^0<;U zS67*lTJL1!TZosQt8M&kH=SULlnc9DijT~8&Qb!+1C29l^T=?^?W9;T^?$HQsf2ufe+>?^e7U@NUDq z5%0BlH{rbw?`FK$l3G@iFK1NwvYHvmCP$Rs)QFP2(Q_p`oR+^o`E~hP zQcYb>B{g|eO=`)J!K9qIEGN=wSxtU*ETtwD`Pi9Lax$G9PL5=?oC8}Sgr)d6OzHNFCR8n{mVEKEXU@M~@~g@0 zm^v~@F`G{S7BBvfzFtFpGi$ofgd^_w>9tw?5XReU*s6@NBpe!a@Zkm=%Yv#(rpX)U&xBjvuxm3Nsjt9GB! z#e9yqk-n8*C%2hG&Rzim(LI<+XVfm)F`$-BWXH7bO9{1G%Z{g$JtZlf8hNJ_FeAC+E_o4Y zZvCm0rlm%PtSuE6v5~CrLj~x zn;Owd{&Z$Ak=9CU;@XwWSXzmz$x$^vl+IjAq~lccrC?l3Tuu_W6n%-slFE!6SJjMK zYBFO_-WW|MMiNQX!T{<;=0mf__jGG6hFU+>hg}Y!cguCXYQxBw5de13X z?7yOPYHBwJ*sODa9^lY#Q>4N=+a|0!i{L>fMYxd>ar}U14%ubnt zeJ*^6ISb)KKO&$1NBVQuCtg>u>&ITW1n9>;xPB-+_*{|tACv1ZszGJGT9_}E0*l$_ zTD7hUwE0jKDALbB%n%W>OfV2j_LBf|L6yy-7e&;&PUs!4*~<9ZT==9>#*?BzRK|gy zW*JYqs|$IG!#-Up&*~lLY^6Lm7d~&4@_bn-Uv~dAi+SFmm=_i=CTrhE7)P!$3YF$1 zMxkB;AEk;yr7sl^aa0s<%Kf1vs1|qLt^K?WS979E1P3-L#-Psnidh+h8t4+K0Rsu@UtS2+ngcl1mX!*SN8mP{^Px~EP z-L0|0nl>ez_g!`?5ejkH^DTqlWec-7ZX8&#dKR?xlJzWnqMidv?KeEErtQ1{r#=$R zdw!OyGI@VL_^M0eHYL?wnHD+C#Z&2tBpvL;ZE!-+OfQ{>AJ>Q6N*XvWe1%zXaJM4JjPP^aTcE?_g7@!otMKvNXv6fj54zs(ni;>{UTo|xMtdf|_%OVIE%JJJ!)&J> zjxp4MFsny|pfRY0AQ>1}WMes~3YY8HUhd%&pUVJ==z%VUdv9OA{f-_u$j}M)<*!Wa zk45>H8yxyXicP*oP&c}eZ)rU4SsQx7hs)wz%KRV-5-kOVZxjDYJX;12A!4^ zZ{GcDE=Brvu^|17u*F3wm6{b>z6J8Lo0o9?gz}A(O}^dt$y^EsMa6?=g3p@#P}+_#T5Mt&A@hw#CQR z2<36Bx%?P1Ci!|QdqqapJ0H6{AHP<%_+aMe+#Fbz#mzx9v;bk%0S|}M@7|OWzaz0V zOs&UIQ+JcG7shFokjOheKAFEl;C2}HDSNki7!Zv_Gi-L|Q}Y^YYBr^2ajGayJqeG! z6yie|##H=?$6xP%$Mnqu_Xh8s z|7ermehd)%uNXKsmE{1Nbq>&RyEoVo;mlFt)3m~;&XTeF2JkQHC}+=SAvBo%vBY@G z5Y9cHg{)aNBf~iN*X6kV=UMAy>!kLS?mZajx6XV1qoTFw4EAJ6vMAbWm!SVe-yb0(Y3faJWAOhUH9Uft;w z$$7Y)2CkxJMlB3uG?h-5ySmefOUX(YAnBf%KNqp5kPBHUHurj5Q>4qMa0ekI1N@+t1m7+-g<1X~l#OyaXtJYZbFX7K`(ndvjnQt&zqon>sj8^=8_@i%( zM`LxlwU}+QnFLL(2{o0#EPE|EFq%oHK#!Gt>5K~DbjQDm3a$8uI z1jkQg$0%5rJe0|T>(@|5`}Ki~Sdzdv>^mKcJUIhT-A{Qq03#OS7~w?%93+FNJhg+2 z%`oir-hOopL6~US;OXrsbM4efIgrtUHiEFuS|WH8De!b2f9;};B?s}~t2bYrSv~b? zF|uj$_;U4UTg zUu45()K?u;N);c34lbL(#}Ex>i80TftHjx;c+7O6vf65k-$79`OB_e`n6;tWLpZE( z)gc%M%qoRL7^}>-+&K9YGrkh|hSv~?36Y^Ktb+(JFXnEuB$#W7Q$_-<(2BK3_<4Jn zJ?1IeBeG)cQTx0-p1wWCgS7c$&@hZKQAbZ(xRQ@`t*0*) zs}NheZ=ZEfU7y+XUcM0BZWK!Bma-&e-twX(cevmf`&^)o7>5v8-G`dsnB^$hmMKh* z)o@WnjnPNdGy+4zR2|eTT+}WP-lGw=!4OPS9NbXV(^Vo!;`nPmw7MzxjW*sIzBxSe zN-??x^4S-5J_zrcO}uw>+Wmp=ecyC)=FMWgd_S^D5AT{h2ID&z4cWP97em?J+oeal ziec`6YM@YY4R(IXqeRr!VcNJgCrMOnUmdff*mZh?+a?oN~ePQ<8Ty(oG zy-9Vty%;8IA-sKFdh>zwrrz_~$HzY2t9QK#xBXWn{~Tbm&LKpUg5vjW*jY3c!G7|? zI#*;jV{m_?{Y0xYh<+g+ffmc?D9b~O6_0H{)u%y?CB?T0YV2RJrN5?P=?^e6J@`38 zje|Bc$qD7Pm?4X!&=zVOS`pMZ%u(aA?)E4Ww;)C?j~*|h&v*iNfsFz*;MyN~J!T1shbVDM2@*d}&$ zoKmlv(3>2xuKo>prDa?DUogxm9ejgRH&4xMolQ@jDn@oq9)H-k_d#Ue?T+`(PB(q9 z`u){2t7aRE4V&-RZqXzACXd7TPPF%S%UrZq%$2>xFn1Kfy<$cr9hL33x7=Q>2l`fK zTHjGUS8|ZUT*}$CI}y!ZWs{bg zM<-Khwo)g^SP^0+WTj>xtnVW3MJFnjy#*#J%A$Ru+A;gaRDR~G&=M&`cNi0uY$D=T z(#x2s)L)>e7n_AdzSNuWdY!yK^(?gG_LjNm4lxVuD2BPC5Z+NX3%ziA&+SelLG zqK%>y=4Yslg!I)`t4?hxYGw(4V%xwH{;Za+Gh}ihTL`DVQNO_Ukg(a#Wiq)2)_)rF zA*26L4kCtgqIP^=$?_x;9IDLfDh9&8{=;<`~!9 z?=|OZuL>!Y`5gzxw3;P>p4Ylr*VbEUf3~%rk6xucZ55BwW~MsPuz(LiCrmUh z;A4}Bqbc8zZ+r)AWl%Vc`6gz=VO**=@0oUM+iWwG(<<8m4LRUI%4Uis-*nk)ZrWA0 ztk{gzVCHqn8B6%YjI~ACx?(vHhuAEc1K|@n*s64{SPq(&T?fJ^a?q)4GnmZHwsv&F z+GF_!>>k^mZ)9c@bX5c;p}fFw^saoffupx98%J+3b8Ws&9DTww?llV`q zNUghbU+K_NN|Fwh$!3Oza83<&A@KdP$s15urfsRFs(<&DWI{=*eE-p4M#biWtf6#A zn#ycj?Ulsvr7?8~zEaq*&;xzrs@R!2-f3vm`BR!alF34=ZWU?OVdJdKA?8Ndk{6i) zH=9^|Y!^~)t;x!9$hEqMywN_TT!?GTQLb`L=UBPStz6*zV590=gJgsQ9VN4cKGvs2mc)H4)yUx%nFQ*E7g$Sjgh%=6gYul@=GLsz1~ z>}maGC*vS&fRi~0RaasgYfzjUr4Sz#%pRbG$QCp76Lup`l~gx#Z=6wSWSDg+O5ZP6 z_kX10SOnIOP)|x~5T{M%JY90Gd}ledO_`2ptiDpale9J+w`a3`+cJIKL#L_Br@{yvQqx(xw-#f$GD+-Qk^j5>`QZF6Z1n=MAv zSwz5iwg<3snxddk^j7RSyFVWME@TFZ|K_&Ons?XdvNmQhpp>%>B!XHnPxq_eqKT= z9J$Zk+#$>YLAk4FS?8o9GtI(}zLCP)e-ET155o<&4&CJEBI#{CybTV)HfMK3RL0q*CgC)rydc#Izd z#DbUa+h-tN-yxU0^&h?atMC4m*R$6DuyyV4Zol{De`vbD^Z1nKmj9-ID*Lx1pU@{= zT8&(-rQQCW2OqQ@nQi#jZ~TKkz3m7-z z-!Wgetx&h^c3ZJ-_v9-N8#d23Y%ery*IzhNY^eSgd;qKEh4YR}0}6 zSfQq`&hCeuB`G@XkLVPNsr~e={6keCBF*Kcq!GYDtS3?vR)ty3=Nfy#4bZ}2Hg{E} zV}KZ(4J!!c-*)S7EBcp{dU8m=qUuAMu8rx}zNU}8`-#gHe95y=T^b_w^%x63`Jvyn z_C>cwtgk=b-V(2obXOCZtu^t8q;Ha7I?K9#1stpAvM$G?a-W0#j$)M(U3gF10LpS*4AOuF(J@ES=D z>0dxTQ9n_`S(;wihL`Z2pCiu9^MJN2L^RT|Y zZ!Wx(0q<=#2&0|%HtXTT0u?tl%mT_lg083{1tNSateq4vKq3rL6m2XTmX<+4Z*bUL zu&c_~p(6}bMnE50hz4L@O@2kB#a42Ib%2tuAnV{$m;6(+4)!lva;8>Ty)#&7@;#?+ zFb%k#Q#bkN)D8N94ZnI)Ez5jnAnY8mnckV!Xm(I-IfFcCeI z_tC9&<>Wz^eEQ5e$Qv0EWYH4N~nXxQoil3uMG@kmKi0XGKx}-k_fYtkSV?W-GC-Fq zd@J)%MtE_Ms4ybD_<7!T!mE7`3up&LxM28ch|CQfF%!+VSDR{*5QyeTNV> ziAw#7wT$3ZFDdmBJ|Xq0U-6a^TCUHgWpHf_cGNlUzzag^6*NyJ105L1hm741OWjV; z#CAV4QwXbVmZ%v^^7?siLqWeSQKFDVwHY@EEG5NSg_dyGvtqVE*j+?O!SFc1|e)M^O^5KTJe_I@N8QK6!FH~sIy$kxUoa-AzF*H-MQ|vyp3I^ zA)wh*Hl5^xKGP$vWKAbVHKrIJEB{2QFnBF9EKOaWk}n6B20m6fbyI^)cl>#mB9SAfYBFaz^MN`bTdIuwm5 zfrEzsB2nMA%d7U8$GgE|9Pj3EBMEe^dvGvrJe#~hr{h9VJtnDi=$&=b2pxmFFSk>a zl4pwoaQ24ToSkNlOfCEhm1*J({58bEH}Ru@nqHT{bKN@<2NHy_R(!y#4;$dhEdT+r^fHlcyhU+M-Jrrw-3TA$iNZbP>P% z+|3=rED)5tik5Xwx;Wb`{OFw&-u`pxF5Z;emT!nIAY?7&u!T zIIs6#)B|5ylzqZ@8MsUqmE5$o(6m+G{-WNrwb=C1!i+7J5Q@1H%4tpQCtSx!min&*>iNt4Dk z+;){|*r7^2)z4w1gl$;oq;oc(`j#?9Ywo*R#2dz7WGt3HfmzOC8FDG^Z@T#r&$vGA zzqwp*!w+4bg*z@Ck9f#?YzUAK2oO8EAIf=jaR0xwp%PBp8YOmcKW;27JGg)4#1i80 zlO<78;S;FoQU|#RWlN>T*sN_oK))WfTk+t60uMI)1#Oqcg%}YLwjGkc+yh+%5o97& zhO!*HG{K38Q3Mm_hFilHz#$SXdKpPcZL}0gSui5N~Lba$Oj?hS+b3UPqa}T z2!1-Pkv&g&42U2Xm&}3iNel<2!D#)Zh)dr3jdcDZZbg+~Hs?dw`e_l~D=ajcsKI4- z-@{44-q`Bg@uO-go5IbU42Mk((?zL?k*sW9-DDtE2~{1JvsaQSRX%$5tW0NIkun_H zwwTm4rriai88O99Eqt*;1pIdvncP^io4Od<=f7s)Fbxpqgdbm41a zBsI+MsrT}YjHhi;U&^rkd*ts5WrdYHsUd3GbjWueuH#W>frPS4xRChugSadwHL5|N zSJvKZdL#hO!5^H7?fkldm5nk;G_qV7_WaQTE z+IwTyUYQpkb;`HWS*0g2luAZ!?ckNWqLoTM)*VeMgk7P_b`SF_ zba9e&DGJ3)Y)?)qapo15@zTCqw5YM0m}ci(6K#+kHm=oW9^}q3TAw=Q?@xYR&einD zuOzgCmHRNULzM0M{M7h5+lPruc8PP0vLukNk}sLY)>8G~6UeV76B>3jvKn#1;y)r^ zwtIAU@gp-AP;_jyuybRu^GZo-D&gZ;++Er)xdl%CLr+_+fAaVzdtKXmAG!R#P$95o z$~X13nQISg8)!G*%of_pCr^23D_@|0N}Xw*`r3S;AHVzD$sNKh5R|)$mUS-BkDYuh z7|hv***&-tH+D$hND*#vgV93Df zf*~BjO`Gn5A>e1h&=0)9U)T$z&Tx`1plkIc+zN((Ho}K1_zOV`21(pPq1bh`6`>56 z$O1N%)zwe?rmpL&Vz+xhVcc8wk^kO}kFI}w{o|A#xd@yv7-GJ$?}+D`7hIQSf^JLDRf8qJv3*xW6|VV4+k9(x zp|!i%+Ix3>F^oNlN2;rZQfAfiO@y`Mu8ztxE-=!qYene#t>0M!BXwty0DsCZ67ev6 z&AV)zqt%uUoupauSgDv;94-ngt`{E3516v;Tq!n5p4n*^iQ zED7*Yx~K^^7mWLGW8Ds0M`Nnu;(Q5U8}el=Em@!phR#Yx9q-8rZFyVBL{`onUFu2! zECBqL0a&bC25`m#m@gXVIH~^;EunrD#%Wx4M2?c5e_!?)^m`SOt{~4Yl)ZaB&sK2+&hu0Ot>vZWj%wibq4_LtZIQ~F7uD9?1w{7<# zx_lgN`>!Y+e-FG3g4wKd2oYY#!QUo3MPvWLZ}m{BK}Oh6+KrB9pJB{7WCDZ7Q5qq- z3ofU?Ef`O5awksrgubFwadPKpe#6}o!$Wj&#kb7UxojjNn>Ye1baE$2i6E`b3Rx9S zfow%PRy;~j37PZ1IV`WznGh9^XlXyr{KP)Iyoa6dNoTMwHBm-bOV(Cs2M3ouLAKcH z++l*W?k8H3C=+#!2{CAkb#eLpa$7Q*_m@fkYCeH&T+A_vvMVn12gb~)f^&M1@m57ZN@((AoS27Aaog3HxWsK8}aH^7^i;PPL zA$Q4bdKqcTJc!T~?W9nq1f}Fp3=(;s^YeV>I#>`6w1V)DW}sM_X~TzBih%>&aHxNG%VT{=3odm7B*we!+Z{O)r%cL=jUQ0^*P);a0u zG}yHi3_pFnVG)oA($VkM)=xKou;%?WGuvjrP;BTd)^3}x?J3mu6l-_jXp|PD|Ju~v z=?eu)|26#Xb2oPgvp`VpDq7Y#>9y$#!jHc3#gA5f6w=pIH1^*E={1LJQ0h3CT4<&P%11jQsB2>iB7O+YpsR~bfa;_T1y}iOSYEqiPmad_R_W7)@rIi zH=aqGfi7##9e5p#vFqS}g3mSzwxMnL0^4pdGd!A3W|IS*gJU@GZzS72YIyI&^|~xX zNy?6oCQAYHgGwiP*_O4EXY@K>+Jw46KTnFn4Ro5}Iu8@4&vA{55C_rB3R~%N^_BVI zXZjV8cFB_%%pLi(#WIfb1AC5G6Hl|E8Mm0J-=TP%ma_kf4;qnQ#wx}av6#~$W>!+X ztR}vTuoZB zy`iH}C4?m?SYkfEkf>HmVWZ4Lna(-~i7;T{HC9Vu!zbJyw|~4!54^sRG?tzJQ@tLM zM2V5ao55szvh_&JZJ*{2u;^rgAfHy!UP)+HG97v*&ciEN}|4E?ap?kZ6^dOLq>D z2gq0|ZwoW$-)mGMf=J-kiM0`o*cgsyUdr@g6AbefNeLCJseeM?8)UST(Lu&IjOqz^ z-C}j3eji~vsTRz;ek@92;O4+gOEJ6=bVcv32jShfy|_ox`$5h7H8Z{$wOHSNKhmLx zchfzRyT23dzAeo~yP1v--Fetu408u=ATPV>=SBBN(!FbX;AJ+6iBILz-b&b_Dwqx4sw`AfOwcYN-IBa( znTPq~z<;m{y%fSykhS4lLP1D(5L&0hR=2 zH<*-9oxk|{$>SH}FP%U4io6YUvwY%g_a%rYAuk%kP#(@;I#YlRG6q4crxdY=VB%PQ zGO$EKa()!>@r9GSj?-o{*L&=7U`|k-(z5s+r(!8jpS;qb2B82qhOttGk~ZgEj~PU0~qlQLd349KS1ChKIU>+!=6E$uS^tI-;M;EBL=%Evg6oDJ<&VGIz1GYKb7wtRiT z!$KE8c*HRNcPLDmIBk((yqMf^qI!r^8tl31FklqtA#R1iF2KsCY>1QWn>|iKv$7#> z#py5vEm0YUxaHF+^@WDG6{^w9ll4{>8{#ddKU>N~OjSJjrH1&)$}wj{MR2wehM1Dl zMxiERD*wpaXu!&y>|-ZKUvLUmGNEFS`xw+3-Fy`7M5c1DK4n5e$0KGR;1SyZFCv*I z8s+MrB1GkQ-c&iBi7&{C6*p=^rPZ9MD2cnYIH{UDa;Fx=+$eh!WbYCJJbq-=aw;|q1!?u>vU{esU{v*GK29rPBI@$rL$07 zXLDnzH9k5{b;e&7%*1xrJB)1_DVW+38(REyP2$?9~LgX z4tOaLkCR#elJY~J01Sdskd$?p9)PVKX;75v;_<`?+MH=VY4Nza4N&z687IkjgNzGg zC}a?uie2!^?pIW2$@v>(&<1&?POkm|*%+Npa#hCVRf#BOSQ?w1S;JOnAv_NBV<{U; zCl9MXhG(?8OZ!V0pLpGFcggj0KXAEz+4W1&AGq4T7g;m)#akzDo}5`zjI_^3VueU- zKC+__*>U$^Au{k$q!2kd8GKmVI`wn6-oE+v%(h}}$9(PfLhbhX+P*?<-`x|1+QT2M zE7YEv4B)rlGTf;Ech1|#8kJw2AvmkS4D|ni z$5`5hVplYjHzL-NypSwMu$*#{bPn2aVwNpet&~)0W8QedUybwO?fTaehAz!oC5X0U~cZ5h}i#I_u4wZygpY<0xe0=9Z$TM4!X zVp|2aMq*nHwkBd*1GZ*jdk$>Nh;1#{mJ^!@wiU#-PV!&y?{G;i#MKJ^D~YWQY^#WE zJ=j(gTRYg+5ZeZ@Jx6REU|UOU8wXv{btA7rL(is_f+AkZ%i<+DpDUzusjf^emA)3c zTo}rVKN|g-cqJ`oE@je+I4q}?bZ#Im<}ZoKY*v)hpC8G{>6Ccr%qcOQO%J7W1tsSKiz19~aNKsG;^$&nHt1+m=Na8g#%a@CWN z&ct7-dgXLsM9vLh&g}Dn#SQ-t1+abHah~}*a*nv8Pa9G036`8wa^_r;E5EhX(cdPy z_qy_jHfj-4f8}tTXDh_f=QXCI_Ev`^uo(_>7Ca^Id8`BZ`pCLtF8bT*U^8qVc%)XD zN(hFf6_;P&=+kiCX&O$*>vYn{$d7xuBI<` zf=A~-ayWT0o8AogO15_>naf-PlxMS8xC{tO=RiK2mp6;H36(;!Frsu`Ov;@~;d(Y5 zs|wjn?sHWi%jCAR>V`~@UUx`;!WwGX=4IP^7>YH)7l!QH9nY9lN4RH1Nf$pa5DG^;F%%*SDat4UHXW z>_TJD>8dc4E+nf$E;*Dg1`X|ndV7+Ef~*wR@tR{)5o7jt7-B;0KeI%&Ck6KE^)Ifr z?R0IliregJHdhL$YP)T?br_5L7M!&;X~+KC0l5Ztzp8+PQw_W@e7yj(DUp<`^$9?t zhmv3?V?Cq{Rznb+xHyu@7BV@d>doc{l3As?BB5N)k7QE`IXx^V2DAB#$!r39zUogX z$xCVUu0~!Yxn%OW!?K*0tBox8^tIt^GM6ksNYpDMOs)p87+UI9qhq_#vD4_-O&zqt zdlkfYMe5l;2kS-;eGsg@x%b9iRX8SCxm9KP@(dmbr?jek=x}+wW9P`v z#LKr%scqfFPHVM~*e8SgKE`N&BmBhip$9C}zUP1HhF|XcQ}q2iQ=HO{pQ8K!*m-_r z_S{QilA?GKW9Mu

lSl9>z*@1Oap0+79DGtZ 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 S#9v0zdz{|Cs{@13!WV literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_integration.cpython-312-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_integration.cpython-312-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..767907bc1ec505cb82051a8e459eba0360e4e4cb GIT binary patch literal 13638 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 literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_integration.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_integration.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23d2c1fe983c64e75173c4efa612e2ec43a323b5 GIT binary patch literal 13866 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 literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_sessions.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_sessions.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4838237cb8ec7fd2d300f469e4ff2f12bf51ad01 GIT binary patch literal 22693 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 literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_state.cpython-312-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_state.cpython-312-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..870950b38a853d49973356a91ada0975f89a7130 GIT binary patch literal 31889 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 Yr__O1X^^_^wKYil?zgR#j_{lQ{}*iG*Z=?k literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_state.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_state.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e198dd18be0856271f8ce227622f43a8724c6fc6 GIT binary patch literal 33080 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 s>umzRti$m$Ds=g77o)SeIlvZu}=4FA0jE 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 SZS>jv@933-@}f~z!hZq5cf1z> literal 0 HcmV?d00001 diff --git a/muxplex/tests/__pycache__/test_ws_proxy.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_ws_proxy.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12d7aac73dd0e409466fcd67b9e7313de0e17a93 GIT binary patch literal 2820 zcmd@WOOF&qxVq=vyR%`}KoAwnNg&&1nE_!*1Vk}G69^c0b|cBgbZWY5XWH%Q>QvRR zJ8Mix^uQ00gdMHzOc%pTu(*esh@+;_G_M=n)N}4yUYHnNEAS#tFeP3BOzwL5T7_2t zmOqBL)i&+I-eqfzwmB8TXCanUNP)K)$Ff85XSCI1?lzS;;%v8%{SbG1yHP-Q8(rdu z^{8LB9xGLD+D5!6Dew9r37j3_XnHr*QcooqbMOy681JFYF>Wn0W(+~7hBm?E|9t*%^}=tS@G#HA|-e(JXJ|P8*sxzj-*LoRR{%y!e66`AkYSQ=?gV&xn?qmW~Rr z{L84!>8Q;APoq-J{P^EQrCqTXdVj!GnOyD*m{XVfx2ex+17uz_r>} zmPj3i7J;jjt%->@Hk#`g?99#w z|0@p97ZH(QCs+uM7o-ht??*5&+yR1s;uez~s|DWTea#~2*b&%e9L|Io)~&cY6JOi- zs~9o~D%-~1krT^N|BNkg0>Nr~))sM5upW1*ZN@pktKn>~2Upn*e6Y`v%+ZeP_Jq`| zQ~HmyNV)liOq$m4pAzZioY^~MT_!?MF13EEpdTo-dUMaJrCcHKI>KJci>7DhSa@D_S^Gu$qz*Yj*TmADibGh)xKiugGk*T=@1?-Ixb|P z!)e4_){O|Kam5j2iz-Mw50p-;=c~*CPX`Q$MSRo|9o7px&E>QM)`E^1!kq(Y(0988 zBx@gZMLS-&qL+}^S2<@qekCKJ(1l4zRLa{0u6k=cr;bZlPfu^&Nj8b>bJLsiq5zCo zees8l4(rlJYnzi6aXSr<-fryB)+q@MzcFt5Y4g{wgogG)*6ZOd5TDVItT>FH0glJ0 za3Jv<2nCouRfG8Jpn&Rj;&UifchJ)#XyNcL)#E=_kN^DU>7S}+?tJuU?$`k33dIK} zAI>e0)M}`PC&kC;X$cm0-`BwX3^+IlI8wl8Gb9gAYQEav&bA None: def test_html_expanded_view_elements() -> None: - """id=back-btn, expanded-session-name, palette-trigger, reconnect-overlay.""" + """id=back-btn, expanded-session-name, reconnect-overlay.""" soup = _SOUP for id_ in ( "back-btn", "expanded-session-name", - "palette-trigger", "reconnect-overlay", ): assert soup.find(id=id_), f"Missing element with id='{id_}'" -def test_html_command_palette() -> None: - """id=command-palette, palette-input, palette-list, palette-backdrop.""" - soup = _SOUP - for id_ in ("command-palette", "palette-input", "palette-list", "palette-backdrop"): - assert soup.find(id=id_), f"Missing element with id='{id_}'" - - def test_html_bottom_sheet() -> None: """id=bottom-sheet, sheet-list, sheet-backdrop, session-pill, session-pill-label, session-pill-bell.""" soup = _SOUP @@ -240,7 +232,6 @@ def test_html_element_classes() -> None: ("session-pill", "session-pill", "needs position:fixed to float"), ("toast", "toast", "needs position:fixed and animation"), ("back-btn", "back-btn", "needs border and hover styles"), - ("palette-trigger", "palette-trigger", "needs border and hover styles"), ( "expanded-session-name", "expanded-session-name",