6.5 KiB
created, status, requirements, system_design, legacy_specs
| created | status | requirements | system_design | legacy_specs | ||
|---|---|---|---|---|---|---|
| 2026-08-31 | completed |
|
|
Implementation plan: Text-aware transcript pagination
Overview
Make one upward transcript load target 20 readable text parts instead of 20 arbitrary persisted rows. Extend the existing per-consumer accumulation path, opt only the native transcript into it, then prove the behavior on desktop and mobile with standalone tool activity that currently consumes a page and stops the preload cycle.
Scope
In scope
- Count
message,content, and legacy untyped rows toward the transcript's 20-part lazy-load target. - Keep tool calls and all other activity rows in the loaded transcript without letting them satisfy the text target.
- Preserve prompt
#1, raw exhaustion, no-progress, and bounded-loop stops. - Preserve Prompt History's user-prompt target and raw search/drain semantics.
- Add focused hook coverage and desktop/mobile browser regressions.
Out of scope
- Backend pagination queries, cursors, page limits, persistence, or API fields.
- Initial newest-window size or eager history loading on task open.
- Message filtering, grouping, collapsing, or transcript virtualization.
- New controls, copy, layout, or mobile composition.
- Changing the prompt
#1visible-start boundary or recovery behavior.
Confirmed root cause
useLazyLoadMessages requests OLDER_PAGE_LIMIT (20) raw rows. The native
transcript calls the hook without an accumulation option, so loadMore returns
after the first page and OlderPageResult.count reports every prepended row,
including tool activity.
The committed-geometry sentinel can request another page only after that load returns and React commits it. Standalone tool rows can move the sentinel out of the preload region, so a page containing mostly or entirely tool calls consumes one upward reach without delivering a useful text batch.
The smallest regression is two raw pages: the first contains 19 tool rows and
one text row; the second supplies the remaining text rows. The current
transcript load settles after page one. The corrected load continues until 20
new message/content rows arrive or a defined stop condition applies.
Technical approach
Text-aware hook accumulation
- Extend
LazyLoadMessagesOptionsinapps/web/hooks/use-lazy-load-messages.tswith a text-part target. - Count only message types
message,content, and absent legacy types. - Reuse the current multi-page accumulation loop, joined-flight handling,
prompt-
#1boundary, zero-progress stop, and pages-per-load safety bound. - Keep
loadMore's numeric result as total raw rows prepended so sentinel progress and recovery semantics do not change. - Leave
loadMoreRawand callers without a target on their current single-page path. Leave Prompt History'sminUserPromptsPerLoadbehavior unchanged.
Native transcript wiring
- Pass a 20-text-part target from
apps/web/components/task/chat/message-list-native.tsxwhen constructing the native transcript loader. - Keep the loading state active across the accumulated batch and restore the prepend anchor after each intermediate React commit.
- Keep sentinel continuation and recovery contracts unchanged.
Browser regression
- Add a shared fixture in
apps/web/e2e/tests/chat/message-pagination-helpers.tswith an older text batch behind one raw page of tool calls placed in separate turns. Separate turns keep the tool rows standalone so the current geometry rule stops after the first page. - In desktop Chromium and mobile Chrome, perform one upward reach, assert more than one older-page request, assert the older text marker becomes visible, and retain the existing prepend-anchor tolerance.
Tests
| Acceptance criterion | Test evidence |
|---|---|
AC-UI-TASK-PROMPT-TRANSCRIPT-VISIBILITY-001.13 |
apps/web/hooks/use-lazy-load-messages.test.ts proves mixed tool/text pages accumulate 20 text parts and that message, content, and legacy rows count while activity rows do not. |
AC-UI-TASK-PROMPT-TRANSCRIPT-VISIBILITY-001.3, .7 |
Hook cases preserve prompt #1, exhaustion, zero-progress, and safety-bound stops. |
AC-UI-TASK-PROMPT-TRANSCRIPT-VISIBILITY-001.9, .11, .13 |
Existing desktop and mobile message-pagination specs gain the standalone-tool-page regression and anchor assertion. |
E2E tests
- Desktop:
apps/web/e2e/tests/chat/message-pagination.spec.ts,chromiumproject. - Mobile:
apps/web/e2e/tests/chat/mobile-message-pagination.spec.ts,mobile-chromeproject. - Shared cues: older-request count, older text marker, stable anchor-row top, and absence of the recovery control during successful loading.
Mobile design contract
- Desktop outcome: one upward reach loads a useful text batch even when tool activity occupies the first raw page.
- Mobile entry point: existing Chat tab in the full-height task layout.
- Nearest exemplar:
apps/web/components/task/task-layout.tsxand the currentmobile-message-pagination.spec.tsflow. - Hierarchy and surface: no composition change; transcript remains the only vertical scroll owner.
- Shared logic: desktop and mobile use the same store, text predicate, cursor coordinator, sentinel, and anchor restoration.
- Mobile proof: Pixel 5
mobile-chromeperforms the same one-reach tool-heavy pagination scenario and observes the older text marker.
Work orders
- [completed] Task 01: Count transcript text during lazy loads
Verification results
- Focused Vitest: 50 tests passed across the lazy-load hook and native scroll management suites.
- TypeScript typecheck, changed-file ESLint, production E2E build, i18n new-code ratchet, and E2E sleep ratchet passed.
- Desktop Chromium message pagination: 7 tests passed.
- Mobile Chrome message pagination: 7 tests passed.
- Specification linter tests: 20 passed; all specification files passed.
git diff --checkpassed.
Risks
- Sequential pages increase request volume for tool-heavy histories; the existing per-load safety bound must remain effective.
- Joined requests can use another consumer's page size, so progress must come from the merged store delta rather than assumptions about response size.
contentand legacy untyped rows must count consistently without counting structured activity whose payload also contains text.- Intermediate store commits must not break prepend anchoring or flash the recovery control between successful pages.