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:
+5
-1
@@ -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"}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user