P1: Poller no longer overwrites nextCursor/hasMore — those belong
to the "Load older" flow. Both poller and loadOlder now dedup
messages by messageId or timestamp+from fingerprint.
P1: Cursor is now compound (timestamp|messageId) with stable
tie-breaking sort. Messages sharing the same timestamp at page
boundaries are no longer lost.
P2: getMessagesPage now applies the same enrichment as getTeamData:
leadSessionId propagation and slash-command-result annotation.
P3: Added 3 tests for getMessagesPage covering pagination, cursor
stability with same-timestamp messages, and slash command annotation.
Two different interval sets with the same length would produce
the same cache key, returning stale results. Serialize each
interval's startedAt~completedAt into the key.
Returning messages: [] broke the slash command annotation test and
any code relying on getTeamData.messages (notifications, dedup).
Keep a small batch (50 newest) in getTeamData for compatibility.
Full message history is available via getMessagesPage() API.
New cursor-based IPC endpoint for the messages timeline panel:
- team:getMessagesPage(teamName, { beforeTimestamp?, limit? })
returns { messages, nextCursor, hasMore }
- Cursor is timestamp-based — stable under live message insertion
- Default page size 50, max 200
getTeamData no longer includes messages in its response, eliminating
the ~1MB messages payload from every team refresh. Messages are now
fetched independently by MessagesPanel.
MessagesPanel changes:
- Fetches initial page on mount via getMessagesPage API
- Auto-refreshes newest page every 5s when team is alive
- "Load older messages" button for pagination
- Falls back to prop messages if API fails (graceful degradation)
The TeamDataWorkerClient logged console.warn when the worker file
was not found, which is expected during tests (no build output).
The test setup treats unexpected warnings as failures.
Downgrade to logger.debug for the "not found" message and remove
the eager warning from resolveWorkerPath().
Critical:
- findLogsForTask: call worker directly instead of wrapping in
wrapTeamHandler, so failures propagate to catch and trigger the
main-thread fallback correctly.
- TeamDataWorkerClient: scope error/exit handlers to the specific
worker instance to prevent a stale worker's exit event from
rejecting pending requests on a replacement worker (race condition).
Major:
- TeamDataWorkerClient: validate teamName and taskId before forwarding
to worker thread (input sanitization).
- team-data-worker: include status, since, and intervals length in
cache key to prevent stale results after filter changes.
- team-data-worker: move logsInFlight.delete() into .finally() so
rejected lookups don't poison the in-flight map permanently.
- MemberLogsTab: reset refreshCountRef and refreshing state in effect
cleanup to prevent the refresh indicator from latching on permanently
when the effect tears down mid-refresh (e.g. on tab switch).