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]
