fix(dashboard): UI polish — modals, layout, consistency, test fixes

Dashboard UX polish pass — consolidates create forms into modals
triggered from the page header, fixes layout inconsistencies, adds
scroll-to navigation for the Keys page, and aligns the TokenBar with
the design system.

Changes:
- App.tsx: add padding to sidebar header
- resolve-page-title.ts: add missing routes, better fallback title
- en.ts: fix nav labels (Profiles was 'profiles : multi agents')
- ModelsPage: two-col layout, auxiliary tasks modal, TokenBar redesign
- ProfilesPage: create button in header, form in modal, Checkbox component
- CronPage: create button in header, form in modal
- EnvPage: scroll-to sub-nav in header, fix text overflow

Modal and dialog standardization:
- Replace all native confirm()/window.confirm() with ConfirmDialog
  (OAuthProvidersCard, PluginsPage, ModelsPage, ConfigPage)
- Add useModalBehavior hook (Escape-to-close, scroll lock, focus restore)
- Apply hook to ProfilesPage, CronPage, AuxiliaryTasksModal

Component fixes (from PR review):
- Checkbox: fix controlled/uncontrolled mismatch, add focus-visible ring
- TokenBar: add rounded-full to legend dots, remove dead code

CI/test fixes:
- Fix TS unused imports (noUnusedLocals), type-narrow PickerTarget union
- Add windows-footgun suppression on platform-guarded os.killpg
- Fix 19 stale unit tests + 9 e2e tests broken by recent main changes
- Restore minimal example-dashboard plugin for plugin auth test
This commit is contained in:
Austin Pickett 2026-05-12 13:42:14 -04:00
parent dd0923bb89
commit fc3fd6bb6b
27 changed files with 788 additions and 295 deletions

View file

@ -2,10 +2,11 @@
from pathlib import Path
def test_profiles_nav_label_uses_short_multi_agents_copy():
def test_profiles_nav_label_uses_short_copy():
en_i18n = Path(__file__).resolve().parents[2] / "web" / "src" / "i18n" / "en.ts"
content = en_i18n.read_text(encoding="utf-8")
assert 'profiles: "profiles : multi agents"' in content
assert "Profiles: Running Multiple Agents" not in content
# Nav label should be the clean short form, not the old verbose string
assert 'profiles: "Profiles"' in content
assert "profiles : multi agents" not in content

View file

@ -6,6 +6,7 @@ rather than leaving zombie processes or telling users to manually restart
when launchd will auto-respawn.
"""
import os
import subprocess
from types import SimpleNamespace
from unittest.mock import patch, MagicMock
@ -1068,13 +1069,18 @@ class TestFindGatewayPidsExclude:
def test_excludes_specified_pids(self, monkeypatch):
monkeypatch.setattr(gateway_cli, "is_windows", lambda: False)
# Bypass /proc scan so the subprocess (ps) fallback is used
_real_isdir = os.path.isdir
monkeypatch.setattr("os.path.isdir", lambda p: False if p == "/proc" else _real_isdir(p))
monkeypatch.setattr(gateway_cli, "_get_service_pids", lambda: set())
monkeypatch.setattr(gateway_cli, "_get_ancestor_pids", lambda: {999})
def fake_run(cmd, **kwargs):
return subprocess.CompletedProcess(
cmd, 0,
stdout=(
"user 100 0.0 0.0 0 0 ? S 00:00 0:00 python gateway/run.py\n"
"user 200 0.0 0.0 0 0 ? S 00:00 0:00 python gateway/run.py\n"
"100 python gateway/run.py\n"
"200 python gateway/run.py\n"
),
stderr="",
)
@ -1082,19 +1088,24 @@ class TestFindGatewayPidsExclude:
monkeypatch.setattr(gateway_cli.subprocess, "run", fake_run)
monkeypatch.setattr("os.getpid", lambda: 999)
pids = gateway_cli.find_gateway_pids(exclude_pids={100})
pids = gateway_cli.find_gateway_pids(exclude_pids={100}, all_profiles=True)
assert 100 not in pids
assert 200 in pids
def test_no_exclude_returns_all(self, monkeypatch):
monkeypatch.setattr(gateway_cli, "is_windows", lambda: False)
# Bypass /proc scan so the subprocess (ps) fallback is used
_real_isdir = os.path.isdir
monkeypatch.setattr("os.path.isdir", lambda p: False if p == "/proc" else _real_isdir(p))
monkeypatch.setattr(gateway_cli, "_get_service_pids", lambda: set())
monkeypatch.setattr(gateway_cli, "_get_ancestor_pids", lambda: {999})
def fake_run(cmd, **kwargs):
return subprocess.CompletedProcess(
cmd, 0,
stdout=(
"user 100 0.0 0.0 0 0 ? S 00:00 0:00 python gateway/run.py\n"
"user 200 0.0 0.0 0 0 ? S 00:00 0:00 python gateway/run.py\n"
"100 python gateway/run.py\n"
"200 python gateway/run.py\n"
),
stderr="",
)
@ -1102,7 +1113,7 @@ class TestFindGatewayPidsExclude:
monkeypatch.setattr(gateway_cli.subprocess, "run", fake_run)
monkeypatch.setattr("os.getpid", lambda: 999)
pids = gateway_cli.find_gateway_pids()
pids = gateway_cli.find_gateway_pids(all_profiles=True)
assert 100 in pids
assert 200 in pids
@ -1111,6 +1122,10 @@ class TestFindGatewayPidsExclude:
profile_dir.mkdir(parents=True)
monkeypatch.setattr(gateway_cli, "is_windows", lambda: False)
monkeypatch.setattr(gateway_cli, "get_hermes_home", lambda: profile_dir)
# Bypass /proc scan so the subprocess (ps) fallback is used
_real_isdir = os.path.isdir
monkeypatch.setattr("os.path.isdir", lambda p: False if p == "/proc" else _real_isdir(p))
monkeypatch.setattr(gateway_cli, "_get_ancestor_pids", lambda: {999})
def fake_run(cmd, **kwargs):
return subprocess.CompletedProcess(