diff --git a/src/webterm/local_server.py b/src/webterm/local_server.py index abc9ae3..5615e66 100644 --- a/src/webterm/local_server.py +++ b/src/webterm/local_server.py @@ -8,6 +8,7 @@ import hashlib import json import logging import signal +import time from pathlib import Path from typing import TYPE_CHECKING @@ -84,7 +85,10 @@ class LocalClientConnector(SessionConnector): class LocalServer: def mark_route_activity(self, route_key: str) -> None: - now = asyncio.get_event_loop().time() + try: + now = asyncio.get_running_loop().time() + except RuntimeError: + now = time.monotonic() self._route_last_activity[route_key] = now # Throttle SSE notifications - max once per 250ms per route last_notified = self._route_last_sse_notification.get(route_key, 0.0) @@ -738,8 +742,9 @@ class LocalServer: h1 {{ margin-bottom: 8px; }} .subtitle {{ color: #64748b; font-size: 14px; margin-bottom: 16px; }} .grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }} - .tile {{ background: #1e293b; border: 1px solid #334155; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 6px rgba(0,0,0,0.4); cursor: pointer; }} + .tile {{ background: #1e293b; border: 1px solid #334155; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 6px rgba(0,0,0,0.4); cursor: pointer; transition: border-color 0.15s; }} .tile:hover {{ border-color: #475569; }} + .tile.selected {{ border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59,130,246,0.3); }} .tile-header {{ padding: 10px 12px; font-weight: bold; border-bottom: 1px solid #334155; display: flex; align-items: center; justify-content: space-between; }} .tile-title {{ display: flex; align-items: center; gap: 8px; }} .sparkline {{ opacity: 0.9; }} @@ -748,18 +753,50 @@ class LocalServer: .meta {{ padding: 8px 12px; color: #94a3b8; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }} a {{ color: inherit; text-decoration: none; }} .empty {{ color: #64748b; text-align: center; padding: 40px; }} + /* Floating search results panel */ + .floating-results {{ position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 400px; max-width: 90vw; max-height: 70vh; overflow-y: auto; background: #1e293b; border: 1px solid #475569; border-radius: 8px; box-shadow: 0 8px 32px rgba(0,0,0,0.5); padding: 16px; z-index: 1000; }} + .floating-results.hidden {{ display: none; }} + .floating-results .search-header {{ margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 8px; }} + .floating-results .search-query {{ font-size: 18px; font-weight: bold; color: #3b82f6; }} + .floating-results .result-item {{ display: flex; align-items: center; gap: 12px; padding: 12px; margin: 6px 0; border: 1px solid #334155; border-radius: 6px; cursor: pointer; transition: all 0.15s; }} + .floating-results .result-item:hover, .floating-results .result-item.active {{ background: #334155; border-color: #3b82f6; }} + .floating-results .result-thumb {{ width: 72px; height: 40px; flex: 0 0 auto; border-radius: 4px; border: 1px solid #334155; background: #0b1220; object-fit: contain; }} + .floating-results .result-content {{ display: flex; flex-direction: column; gap: 2px; }} + .floating-results .result-title {{ font-weight: bold; margin-bottom: 4px; }} + .floating-results .result-meta {{ font-size: 12px; color: #94a3b8; }} + .floating-results .no-results {{ color: #64748b; text-align: center; padding: 20px; }} + /* Keyboard indicator */ + .key-indicator {{ position: fixed; bottom: 16px; left: 16px; display: flex; gap: 4px; z-index: 1000; }} + .key-box {{ display: inline-flex; align-items: center; justify-content: center; background: #334155; color: #e2e8f0; font-size: 12px; font-weight: bold; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.3); opacity: 1; transition: opacity 0.3s; }} + .key-box.square {{ width: 28px; height: 28px; }} + .key-box.rectangle {{ padding: 4px 8px; }} + .key-box.fade-out {{ opacity: 0; }} + /* Help hint */ + .help-hint {{ position: fixed; bottom: 16px; right: 16px; color: #64748b; font-size: 12px; }}