3.0 KiB
Context
The server log SSE route currently replays historyLimit entries independently for every stream, then sends live events. The management terminal stores a bounded client buffer but does not own a scroll container or follow state, so the browser viewport remains at its initial top position after history is appended. Durable log bodies are stored by the platform LogBodyStore as memory indexes backed by per-stream JSONL segment files; this change does not rewrite or delete that durable history.
Goals / Non-Goals
Goals:
- Make initial server log replay bounded across all streams and limited to the newest entries.
- Keep replay chronological and preserve live SSE delivery.
- Keep at most 10,000 terminal lines in the browser and automatically follow the newest line until the operator scrolls away.
- Make returning to the bottom restore follow mode.
Non-Goals:
- No full-history fetch, log retention deletion, database migration, or change to Run ingest.
- No browser-to-Run transport or game-specific log selection.
Decisions
- Treat
historyLimitas a server-wide budget for the SSE endpoint, with a platform cap of 10,000. This prevents stream-count multiplication while retaining the existing query parameter and compatibility for existing clients. - Read bounded tails from each stream using its latest sequence, merge by timestamp/sequence/stream ID, and emit only the newest budgeted entries in chronological order. This keeps the UI output coherent without adding a new cross-stream database query API.
- Give the terminal output element a ref and track
followLatestfrom scroll position. The terminal requests only a 500-entry recent replay, then locks to the bottom afterreadywith two animation frames; layout-driven scroll events during replay cannot unlock follow mode. After initialization, a user scroll above a small bottom threshold unlocks, and a later scroll to the threshold locks again. - Keep the rendered buffer capped at 10,000 through the existing merge helper. System and command-result lines use the same cap, so browser memory remains bounded even when the stream is noisy.
Risks / Trade-offs
- [Risk] Reading a tail from every stream still does bounded work proportional to stream count. -> Mitigation: each stream read is capped by the global budget and the final response is capped at 10,000; existing per-stream cursor storage remains unchanged.
- [Risk] Timestamp ties across streams can reorder entries relative to ingest order. -> Mitigation: use sequence and stream ID tie-breakers and retain each stream's sequence ordering.
- [Risk] Scroll events can race with React rendering. -> Mitigation: defer bottom scrolling with
requestAnimationFrameand re-check the element's current scroll metrics.
Migration Plan
Deploy the backend and frontend together. Existing clients sending historyLimit continue to work, but receive a server-wide bounded replay. Rollback is code-only: reverting the endpoint selection and terminal follow logic restores the prior behavior without data migration.