Database::messages() on the #34 branch (307bdcd) returns every row a channel has, and GetState calls it once per channel on every invocation. That was fine when the buffer lived in memory and died with the session; now the store persists, and rows only accumulate (every reconnect writes a fresh self-JOIN, scroll-back writes pages down). Reads that scale with lifetime channel history instead of with what the UI can show will get slower every week the client is used, and the failure is gradual enough that nobody notices until a busy channel takes seconds to open.
Give the trait a bounded read: messages() takes a limit and returns the newest N for the channel (the tail), reading the server-channel-timestamp index backwards with a cursor rather than materializing the whole range. Add a before-anchored page read for scroll-back so older history comes out in CACHE_PAGE_SIZE chunks instead of arriving implicitly via the full scan. Callers in handle_command (GetState, GetChannelState) pass a live-window-sized limit. The local-cache spec's port shape (seed, pageBefore) is the naming and semantics to converge on, so the core interface doesn't need renaming when the platform cache port lands.
Spec links:
Acceptance criteria:
Database::messages() (or its successor) takes a limit and returns at most that many messages, the newest for the channel, still in ascending server_time order.
- A before-anchored read exists that returns the page of messages older than a given msgid, bounded by the same kind of limit.
- The IndexedDB implementation serves both via index cursors and never materializes the full channel range for a bounded read.
GetState and GetChannelState request a bounded tail rather than the whole channel.
- A channel with more rows than the limit opens with exactly the newest window; older pages arrive only when explicitly requested.
Out of scope: the platform-side HistoryCachePort and its adapters (epic #10, #54-#58 own that surface), retention or pruning of old rows (nothing deletes yet, and that's a separate decision), and the app-side scroll listener that triggers paging.
Blocked by #34, which introduces the Database trait and the IndexedDB store this bounds. #134 (core test harness) isn't a blocker but is where the limit and paging behaviors should get their assertions.
Database::messages()on the #34 branch (307bdcd) returns every row a channel has, andGetStatecalls it once per channel on every invocation. That was fine when the buffer lived in memory and died with the session; now the store persists, and rows only accumulate (every reconnect writes a fresh self-JOIN, scroll-back writes pages down). Reads that scale with lifetime channel history instead of with what the UI can show will get slower every week the client is used, and the failure is gradual enough that nobody notices until a busy channel takes seconds to open.Give the trait a bounded read:
messages()takes a limit and returns the newest N for the channel (the tail), reading theserver-channel-timestampindex backwards with a cursor rather than materializing the whole range. Add a before-anchored page read for scroll-back so older history comes out inCACHE_PAGE_SIZEchunks instead of arriving implicitly via the full scan. Callers inhandle_command(GetState,GetChannelState) pass a live-window-sized limit. The local-cache spec's port shape (seed,pageBefore) is the naming and semantics to converge on, so the core interface doesn't need renaming when the platform cache port lands.Spec links:
seed/pageBeforewith limits and theCACHE_PAGE_SIZE/MAX_LIVE_MESSAGESbudgets this read path should respect.Acceptance criteria:
Database::messages()(or its successor) takes a limit and returns at most that many messages, the newest for the channel, still in ascending server_time order.GetStateandGetChannelStaterequest a bounded tail rather than the whole channel.Out of scope: the platform-side
HistoryCachePortand its adapters (epic #10, #54-#58 own that surface), retention or pruning of old rows (nothing deletes yet, and that's a separate decision), and the app-side scroll listener that triggers paging.Blocked by #34, which introduces the
Databasetrait and the IndexedDB store this bounds. #134 (core test harness) isn't a blocker but is where the limit and paging behaviors should get their assertions.