Skip to content

chat-runtime: windowed projection undercounts totalMessages when items split into multiple ChatMsgs #344

Description

@kimbotai1337

Background

Surfaced by CodeRabbit during review of #335 (chat timeline windowing). Filed as a follow-up so #335 can land with the perf win while the issue stays tracked.

Problem

In `src/features/chat/runtime/projection.ts`, the windowed branch of `projectMessages` uses `countProjectedItemMessages(item)` (which assumes 1 ChatMsg per timeline item) to advance `totalMessages`, but `projectItem` can split a single timeline item into multiple ChatMsgs via `splitToolCallMessage` — e.g. an assistant message containing thinking, text, and tool_use blocks is emitted as separate entries.

```ts
// projection.ts ~L130-141
const messageCount = countProjectedItemMessages(item); // always 1 for user/assistant/thinking/etc.
totalMessages += messageCount;
if (messageCount > 0 && visibleMessages.length < visibleCount) {
const remaining = visibleCount - visibleMessages.length;
const projected = projectItem(item, options); // can be >1
visibleMessages.unshift(...projected.slice(-remaining));
}
```

Effect

  • `totalMessages` undercounts when any item splits.
  • `hasMore = totalMessages > visibleMessages.length` can incorrectly read `false` when there are actually more messages to load → "Show more" / "Load more" affordances disappear early.
  • The slice assumption `projected.length ≤ messageCount` does not hold during splits.

Repro shape

A long session with at least one assistant turn whose message contains a mix of thinking/text/tool_use blocks, viewed with windowing enabled (the default after #335 lands).

Suggested fix

Two viable directions:

  1. Make `countProjectedItemMessages` split-aware — run the same split logic as `splitToolCallMessage` and count its output. Preserves the perf win (no full projection) but duplicates split logic.
  2. Refactor the windowed branch to project every item (use `projectItem().length` for the count) and only avoid the `unshift` cost for off-window items. Cleaner but partially negates the windowing perf benefit.

Option (1) is preferred; option (2) is a safer fallback.

Severity

Minor (per CodeRabbit). UX bug — no data loss, no crash — surfaces as a missing "load more" affordance in some long mixed-content sessions.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions