c142b3a025
NATS delivers live only, so reloading the page lost a room's history. Add the
client half of the new history endpoint:
- ControlPlane.fetchHistory(roomID, limit): signed GET /rooms/{id}/history?limit=N,
decoding each base64-std frame to the raw bytes the live subscription delivers.
- BusClient.history(roomID, limit): opens each replayed frame (verify + decrypt)
exactly like subscribe, dropping any that fail, oldest -> newest.
- Extract BusClient.openFrame as the shared envelope-opening core for subscribe
and history (no duplication; subscribe behavior unchanged).
- ulidTime(id): decode the ms-epoch a ULID encodes in its first 10 Crockford
chars (inverse of newULID), so a frame's timestamp comes from its id (the wire
carries none). Covered by ulid.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>