fix: three mobile/WSL bugs reported by tester

- Mobile idle tiles: display:none → height:36px so session content is
  visible (all sessions are 'idle' with zero bells — was blank on iPhone)
- .tile-pre dead CSS: merge font/color rules into .tile-body pre (the
  actual HTML selector) so snapshot text renders with monospace + muted
  color; update mobile active-tier selector to match
- systemd PATH: strip /mnt/ entries from PATH before writing service
  unit file — WSL paths with spaces caused systemd to reject the line
This commit is contained in:
Brian Krabach
2026-03-28 09:03:33 -07:00
parent 38c78fb575
commit 4ffdc1bd99
4 changed files with 105 additions and 4 deletions
+5 -1
View File
@@ -25,6 +25,10 @@ def install_service(*, system: bool = False) -> None:
"""Install muxplex as a systemd service.""" """Install muxplex as a systemd service."""
executable = sys.executable executable = sys.executable
_raw_path = os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin")
_safe_path = ":".join(p for p in _raw_path.split(":") if not p.startswith("/mnt/"))
_safe_path = _safe_path or "/usr/local/bin:/usr/bin:/bin"
unit = f"""\ unit = f"""\
[Unit] [Unit]
Description=muxplex — web-based tmux session dashboard Description=muxplex — web-based tmux session dashboard
@@ -35,7 +39,7 @@ Type=simple
ExecStart={executable} -m muxplex ExecStart={executable} -m muxplex
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
Environment=PATH={os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin")} Environment=PATH={_safe_path}
[Install] [Install]
WantedBy={"multi-user.target" if system else "default.target"} WantedBy={"multi-user.target" if system else "default.target"}
+10 -3
View File
@@ -228,6 +228,13 @@ body {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
padding: 6px 8px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-dim);
white-space: pre;
overflow: hidden;
margin: 0;
} }
.tile-pre { .tile-pre {
@@ -678,15 +685,15 @@ body {
.session-tile--tier-active { .session-tile--tier-active {
min-height: 60px; min-height: 60px;
} }
.session-tile--tier-active .tile-pre { .session-tile--tier-active .tile-body pre {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* Tier 3 — idle: name-only (~44px total) */ /* Tier 3 — idle: compact preview (36px body) */
.session-tile--tier-idle .tile-body { .session-tile--tier-idle .tile-body {
display: none; height: 36px;
} }
.session-tile--tier-idle { .session-tile--tier-idle {
min-height: 44px; min-height: 44px;
+37
View File
@@ -84,6 +84,43 @@ def test_install_service_system_mode_target(tmp_path, monkeypatch):
assert "multi-user.target" in content assert "multi-user.target" in content
def test_install_service_strips_wsl_mnt_paths_from_environment(tmp_path, monkeypatch):
"""Fix 3: install_service() must strip /mnt/ paths from Environment=PATH.
WSL mounts Windows at /mnt/c/, /mnt/d/ etc. Paths like
'/mnt/c/Program Files/dotnet/' contain spaces, causing systemd to
truncate and reject the Environment= line.
"""
from muxplex.cli import install_service
fake_home = tmp_path / "home"
fake_home.mkdir()
monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home))
wsl_path = (
"/usr/local/bin:/usr/bin:/bin:/mnt/c/Program Files/dotnet:/mnt/d/tools/bin"
)
monkeypatch.setenv("PATH", wsl_path)
install_service(system=False)
unit_path = fake_home / ".config" / "systemd" / "user" / "muxplex.service"
content = unit_path.read_text()
# Find the Environment=PATH line
env_line = next(
(line for line in content.splitlines() if line.startswith("Environment=PATH=")),
None,
)
assert env_line is not None, "Environment=PATH line must be present"
assert "/mnt/" not in env_line, (
f"WSL /mnt/ paths must be stripped from Environment=PATH; got: {env_line!r}"
)
# Safe paths must still be present
assert "/usr/local/bin" in env_line
assert "/usr/bin" in env_line
def test_dunder_main_calls_main(): def test_dunder_main_calls_main():
"""python -m muxplex must call cli.main().""" """python -m muxplex must call cli.main()."""
import importlib.util import importlib.util
+53
View File
@@ -534,3 +534,56 @@ def test_css_reduced_motion_sidebar_after_toast():
assert toast_idx < sidebar_idx, ( assert toast_idx < sidebar_idx, (
".session-sidebar must come after .toast in reduced-motion block" ".session-sidebar must come after .toast in reduced-motion block"
) )
# ── Bug-fix regression tests ─────────────────────────────────────────────────
def test_idle_tile_body_not_display_none():
"""Fix 1: .session-tile--tier-idle .tile-body must NOT use display:none.
All sessions are 'idle' when zero bell notifications — display:none caused
a blank screen on mobile (iPhone).
"""
css = read_css()
# Locate the idle rule block
marker = ".session-tile--tier-idle .tile-body"
assert marker in css, "idle .tile-body rule must exist"
idx = css.index(marker)
block_start = css.index("{", idx)
block_end = css.index("}", block_start)
block = css[block_start:block_end]
assert "display: none" not in block, (
".session-tile--tier-idle .tile-body must not use display:none "
"(hides all tile content on mobile)"
)
def test_tile_body_pre_has_typography():
"""Fix 2a: .tile-body pre must carry font-family and color declarations.
Previously those rules were on dead .tile-pre class which was never in HTML.
"""
css = read_css()
marker = ".tile-body pre"
assert marker in css, ".tile-body pre rule must exist"
idx = css.index(marker)
block_start = css.index("{", idx)
block_end = css.index("}", block_start)
block = css[block_start:block_end]
assert "font-family" in block, ".tile-body pre must declare font-family"
assert "color" in block, ".tile-body pre must declare color"
def test_mobile_active_tier_targets_tile_body_pre_not_tile_pre():
"""Fix 2b: mobile active-tier selector must be .tile-body pre, not .tile-pre.
.tile-pre is never applied in HTML; the real element is <pre> inside .tile-body.
"""
css = read_css()
assert ".session-tile--tier-active .tile-body pre" in css, (
"mobile active-tier must target .tile-body pre"
)
assert ".session-tile--tier-active .tile-pre" not in css, (
".tile-pre is a dead class — selector must be removed"
)