feat: implement all P0 UX improvements - visual hierarchy, readable previews, search filter, default session
P0.1: Visual Hierarchy for Tiles
- Backend: Added last_activity_at to GET /api/sessions by querying tmux list-sessions -F '#{session_activity}'
Updated sessions.py with tuple return from enumerate_sessions and activity cache
Updated main.py poll loop to populate both session endpoints with activity data
- Frontend: sessionPriority() now returns 'active' (< 5 min) in addition to 'bell'/'idle'
Added .session-tile--active (accent left border) and .session-tile--stale (dimmed styling) CSS classes
Updated session-tile.js and sidebar-item.js to apply priority-based classes
- Fixed P4 hover fade bug: removed opacity:0 rule on .session-tile:hover .tile-meta
P0.2: Readable Tile Previews
- Wired --preview-font-size CSS variable (was set by JS but never consumed by CSS)
Changed default from 11px to 12px with line-height 1.2 (was 1.0)
- Reduced preview line count from 20 to 12 lines for better readability at larger size
P0.3: Search/Filter Bar
- New <search-filter> Lit component with live input, match count, and clear button
Replaces empty filter-bar div in index.html with functional search component
Integrated into renderGrid() for live filtering and bindStaticEventListeners() for re-render on input
P0.4: Default Session / Quick-Resume
- Updated restoreState() to fall back to default_session setting when no active_session in server state
Added existence check before opening to prevent errors on non-existent sessions
Files changed: sessions.py, main.py, app.js, style.css, session-tile.js, sidebar-item.js, utils.js, index.html
New files: search-filter.js (Lit component)
Test updates: test_api.py, test_frontend_css.py, test_integration.py, test_sessions.py
All 1306 tests pass.
Generated with Amplifier
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This commit is contained in:
+11
-6
@@ -51,6 +51,7 @@ from muxplex.auth import (
|
||||
from muxplex.bells import apply_bell_clear_rule, process_bell_flags
|
||||
from muxplex.sessions import (
|
||||
enumerate_sessions,
|
||||
get_session_activity,
|
||||
get_session_list,
|
||||
get_snapshots,
|
||||
run_tmux,
|
||||
@@ -180,12 +181,12 @@ async def _run_poll_cycle() -> None:
|
||||
global _settings_sync_counter
|
||||
async with state_lock:
|
||||
# 1. Enumerate live tmux sessions
|
||||
names = await enumerate_sessions()
|
||||
names, activity = await enumerate_sessions()
|
||||
name_set = set(names)
|
||||
|
||||
# 2. Capture pane snapshots and update in-memory snapshot cache
|
||||
new_snapshots = await snapshot_all(names)
|
||||
update_session_cache(names, new_snapshots)
|
||||
update_session_cache(names, new_snapshots, activity)
|
||||
|
||||
# 3. Load current persisted state
|
||||
state = load_state()
|
||||
@@ -583,9 +584,10 @@ async def patch_state(patch: StatePatch) -> dict:
|
||||
|
||||
@app.get("/api/sessions")
|
||||
async def get_sessions() -> list[dict]:
|
||||
"""Return list of sessions with name, snapshot, and bell data."""
|
||||
"""Return list of sessions with name, snapshot, bell, and activity data."""
|
||||
names = get_session_list()
|
||||
snapshots = get_snapshots()
|
||||
activities = get_session_activity()
|
||||
state = await read_state()
|
||||
|
||||
result = []
|
||||
@@ -597,6 +599,7 @@ async def get_sessions() -> list[dict]:
|
||||
"name": name,
|
||||
"snapshot": snapshots.get(name, ""),
|
||||
"bell": bell,
|
||||
"last_activity_at": activities.get(name),
|
||||
}
|
||||
)
|
||||
return result
|
||||
@@ -654,7 +657,7 @@ async def create_session(payload: CreateSessionPayload) -> dict:
|
||||
# Some commands (amplifier-workspace) create the session then
|
||||
# try to attach (which fails without a TTY). If the session
|
||||
# exists despite the non-zero exit, treat it as success.
|
||||
sessions = await enumerate_sessions()
|
||||
sessions, _act = await enumerate_sessions()
|
||||
if name in sessions:
|
||||
_log.info(
|
||||
"Session command exited %d but session '%s' exists -- "
|
||||
@@ -699,9 +702,9 @@ async def create_session(payload: CreateSessionPayload) -> dict:
|
||||
# Eagerly refresh the session cache so the next GET /api/sessions
|
||||
# reflects the newly-created session without waiting for the poll loop.
|
||||
try:
|
||||
fresh_names = await enumerate_sessions()
|
||||
fresh_names, fresh_activity = await enumerate_sessions()
|
||||
fresh_snapshots = await snapshot_all(fresh_names)
|
||||
update_session_cache(fresh_names, fresh_snapshots)
|
||||
update_session_cache(fresh_names, fresh_snapshots, fresh_activity)
|
||||
except Exception:
|
||||
pass # non-fatal; poll loop will catch up
|
||||
|
||||
@@ -1349,6 +1352,7 @@ async def federation_sessions(request: Request) -> list[dict]:
|
||||
# Build local sessions with deviceId/deviceName/remoteId/sessionKey tags
|
||||
names = get_session_list()
|
||||
snapshots = get_snapshots()
|
||||
activities = get_session_activity()
|
||||
state = await read_state()
|
||||
local_sessions: list[dict] = []
|
||||
for name in names:
|
||||
@@ -1359,6 +1363,7 @@ async def federation_sessions(request: Request) -> list[dict]:
|
||||
"name": name,
|
||||
"snapshot": snapshots.get(name, ""),
|
||||
"bell": bell,
|
||||
"last_activity_at": activities.get(name),
|
||||
"deviceId": local_device_id,
|
||||
"deviceName": local_device_name,
|
||||
"remoteId": None,
|
||||
|
||||
Reference in New Issue
Block a user