148 lines
6.5 KiB
Markdown
148 lines
6.5 KiB
Markdown
---
|
|
created: 2026-08-31
|
|
status: completed
|
|
requirements:
|
|
- REQ-UI-TASK-PROMPT-TRANSCRIPT-VISIBILITY-001
|
|
system_design:
|
|
- ../../specs/ui/system-design/task-prompt-transcript-visibility.md
|
|
legacy_specs: []
|
|
---
|
|
|
|
# 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 `#1` visible-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 `LazyLoadMessagesOptions` in
|
|
`apps/web/hooks/use-lazy-load-messages.ts` with 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-`#1` boundary, 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 `loadMoreRaw` and callers without a target on their current single-page
|
|
path. Leave Prompt History's `minUserPromptsPerLoad` behavior unchanged.
|
|
|
|
### Native transcript wiring
|
|
|
|
- Pass a 20-text-part target from
|
|
`apps/web/components/task/chat/message-list-native.tsx` when 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.ts` with 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`, `chromium`
|
|
project.
|
|
- Mobile: `apps/web/e2e/tests/chat/mobile-message-pagination.spec.ts`,
|
|
`mobile-chrome` project.
|
|
- 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.tsx` and the current
|
|
`mobile-message-pagination.spec.ts` flow.
|
|
- 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-chrome` performs 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](task-01-count-transcript-text.md)
|
|
|
|
## 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 --check` passed.
|
|
|
|
## 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.
|
|
- `content` and 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.
|