feat: add live replays and PostgreSQL maintenance tools
Build and Push Docker Container / build-and-push (push) Successful in 7m53s

- Stream compact live replay updates across local and clustered dashboards.
- Render responsive snake bodies as SVG paths with aligned custom icons.
- Add cache-busted assets, replay fallback routes, and live-follow playback.
- Support PostgreSQL benchmark sampling and idempotent SQLite migration.
- Add dry-run cleanup for old low-quality PostgreSQL replay payloads.
- Reward safe perimeter lanes and bump Prism to version 1.5.0.
- Add backend, migration, dashboard, and perimeter regression coverage.
This commit is contained in:
2026-08-02 00:50:46 +02:00
parent 9b99b526e4
commit f14d780f29
29 changed files with 1574 additions and 310 deletions
+9 -4
View File
@@ -4,7 +4,7 @@ from typing import Awaitable, Callable
import asyncio, inspect, json, time
class DashboardEventsService:
def __init__(self, enabled:bool, redis_url:str, channel:str, event_origin:str, shutdown_event:asyncio.Event, on_notice:Callable[[str], Awaitable[None]], logger):
def __init__(self, enabled:bool, redis_url:str, channel:str, event_origin:str, shutdown_event:asyncio.Event, on_notice:Callable[[str, str|None], Awaitable[None]], logger):
self.enabled = enabled
self.redis_url = redis_url
self.channel = channel
@@ -71,18 +71,19 @@ class DashboardEventsService:
except Exception:
pass
async def publish_notice(self, trigger:str) -> None:
async def publish_notice(self, trigger:str, game_id:str|None=None) -> None:
if not self.enabled:
return
if self.redis is None:
return
if trigger not in {'game_saved', 'stale_finalized', 'manual'}:
if trigger not in {'game_started', 'game_turn', 'game_saved', 'stale_finalized', 'manual'}:
return
message = {
'type': 'dashboard_games_update_notice',
'origin': self.event_origin,
'trigger': trigger,
'game_id': game_id,
'sent_at': int(time.time()),
}
try:
@@ -120,7 +121,11 @@ class DashboardEventsService:
continue
notice_trigger = str(payload.get('trigger') or 'game_saved')
await self.on_notice(notice_trigger)
notice_game_id_raw = payload.get('game_id')
notice_game_id = (
None if notice_game_id_raw is None else str(notice_game_id_raw)
)
await self.on_notice(notice_trigger, notice_game_id)
except asyncio.CancelledError:
pass
except Exception as error: