zjncs opened a new pull request, #5520:
URL: https://github.com/apache/rocketmq-dashboard/pull/5520

   ## Problem
   
   `AiConversationService.timeline` fetches `limit + 1` rows precisely so the 
extra probe row can say "there is more" (its own javadoc: *"It fetches `limit + 
1` rows so `nextAfter` can be derived without a second query"*), and the 
controller documents the contract (`AiConversationController`: *"`nextAfter` in 
the answer is the cursor for the following page, or **null at the tail**"*). 
But the `truncated` flag was computed and then **ignored**:
   
   ```java
   boolean truncated = rows.size() > bounded;        // computed, never used
   List<RmqAiEvent> page = truncated ? rows.subList(0, bounded) : rows;
   ...
   Integer nextAfter = page.isEmpty() ? null : page.get(page.size() - 
1).getSeq();  // any non-empty page
   ```
   
   `nextAfter` was non-null for **any** non-empty page, including a provably 
final one. The pre-fix tests pinned exactly this wrong behavior: the service 
test mocked a 3-row conversation with `limit=200` and asserted `nextAfter == 
3`; the integration test read the 50-row tail (seqs 151..200, nothing beyond — 
proven by the immediately following `after=200` call returning an empty page 
with null) and asserted `nextAfter == 200`.
   
   Client impact, per the frontend walk (`useConversationTimeline`, which stops 
only on `cursor === null` — its own test comment documents *"the client stops 
paging on null, not on an empty page"*):
   
   1. every conversation open / reload / post-run refetch issues **one extra, 
always-empty** `GET .../events` request;
   2. a `loadMore` landing exactly on the tail keeps `hasMore` true and renders 
the load-earlier affordance for one empty fetch;
   3. the limit+1 probe row is fetched and discarded without influencing the 
answer — dead computation.
   
   ## Fix
   
   One line — the cursor now comes from the probe row's verdict:
   
   ```java
   Integer nextAfter = truncated ? page.get(page.size() - 1).getSeq() : null;
   ```
   
   Truncated pages still hand back the last returned `seq`, so paging keeps 
advancing past unreadable rows (the skip-rows test keeps its cursor semantics 
where they matter). Only the untruncated tail page changes, from a phantom 
cursor to `null` — which is what the documented contract always said. No 
frontend change needed: `null` is the walk's existing stop signal, and it 
finally becomes accurate.
   
   ## Verification
   
   - **before**: 3 failures — new case 
`timelineShouldReportNoCursorWhenThePageIsTheTailTest` (exactly `limit` rows, 
expected null, got 2) plus the two updated assertions (`expected: null but was: 
3` / `but was: 200`);
   - **after**: **53/53** across `AiConversationServiceTest` (19) + 
`AiConversationPersistenceIntegrationTest` (7) + `AiConversationControllerTest` 
(27), including the unchanged truncated-page case 
(`timelineShouldDeriveTheCursorFromOneExtraRowTest` still asserts cursor == 2);
   - **mutation** (revert the service line, keep the tests): back to 3 
failures; restore: 26/26;
   - **frontend regression**: `useConversationTimeline.test.ts` + 
`ChatThread.test.tsx` — **20/20** unchanged.
   
   Orthogonal to #5343 (same files, different defect: that PR changes the 
repository read's ordering strategy and does not touch the cursor computation).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to