fix(site/src/pages/AIBridgePage/SessionThreadsPage/SessionTimeline): hide "Session completed" until every thread has loaded (#26955)

> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

The "Session completed" marker at the bottom of an AI Gateway session
timeline was rendered unconditionally, so on long sessions it appeared
below still-loading threads while the user scrolled. That is misleading:
users read it as the end of the session even when more threads are about
to stream in.

Only render the session end marker (rows 7 and 8 of the grid: the
connecting vertical line, the success dot, and the "Session completed"
text) once every thread has loaded, that is, once both `hasNextPage` and
`isFetchingNextPage` are false. The dashed timeline box still closes
cleanly at the bottom, and the infinite-scroll spinner keeps rendering
inside row 5 while more pages fetch.

| Old | New |
| --- | --- |
| <img width="1099" height="338" alt="preview-old-behaviour"
src="https://github.com/user-attachments/assets/f86c9ce1-f4ca-4088-a1ed-9cdcf8fb940c"
/> | <img width="1099" height="323" alt="preview-new-heaviour"
src="https://github.com/user-attachments/assets/b4a1e703-10d2-4378-9f25-48cac46249a1"
/> |


## Verification

Rendered each SessionTimeline story via a headless Chromium and asserted
whether "Session completed" is present:

| Story | `hasNextPage` | `isFetchingNextPage` | "Session completed" |
| --- | --- | --- | --- |
| OneThread | false | false | visible |
| MultipleThreads | false | false | visible |
| FetchingNextPage | true | true | hidden |
| HasMoreThreadsToLoad (new) | true | false | hidden |

All checks pass locally:

- `pnpm format` (no changes)
- `pnpm lint:check`
- `pnpm lint:types`
- `make pre-commit` via githooks

<details>
<summary>Implementation notes</summary>

-
`site/src/pages/AIBridgePage/SessionThreadsPage/SessionTimeline/SessionTimeline.tsx`:
wrap the row 7 spacer and row 8 status dot/text in `!hasNextPage &&
!isFetchingNextPage`.
- `SessionTimeline.stories.tsx`: add `HasMoreThreadsToLoad` to cover the
between-fetches state.
- No prop signature or public API change; `SessionTimelineSkeleton.tsx`
is untouched because the skeleton is only shown before any threads have
loaded.

</details>
This commit is contained in:
Jake Howell
2026-07-13 10:38:55 +10:00
committed by GitHub
parent 9af3d31036
commit 0f11673a74
2 changed files with 34 additions and 16 deletions
@@ -142,3 +142,13 @@ export const MultipleThreads: Story = {
export const FetchingNextPage: Story = {
args: { hasNextPage: true, isFetchingNextPage: true },
};
// The "Session completed" marker should stay hidden while more threads
// remain to be loaded so it cannot be misread as the end of the session.
export const HasMoreThreadsToLoad: Story = {
args: {
threads: [mockThread, mockThreadLong],
hasNextPage: true,
isFetchingNextPage: false,
},
};
@@ -563,23 +563,31 @@ export const SessionTimeline: FC<SessionTimelineProps> = ({
{/* bottom right rounded corner */}
</div>
{/* row 7: sized intentionally to create the visual space below the timeline border */}
<div className="row-start-7 col-start-3 border-0 border-l border-t border-solid h-4">
{/* vertical line */}
</div>
{/* rows 7-8: session end marker. Only rendered once every thread
has loaded so "Session completed" cannot appear below
still-loading threads and be mistaken for the end of the
session. */}
{!hasNextPage && !isFetchingNextPage && (
<>
{/* row 7: sized intentionally to create the visual space below the timeline border */}
<div className="row-start-7 col-start-3 border-0 border-l border-t border-solid h-4">
{/* vertical line */}
</div>
{/* row 8: session completed */}
<div className="row-start-8 col-start-2 relative">
<StatusIndicatorDot
variant="success"
className="absolute right-0 translate-x-1/2 translate-y-1/2"
/>
</div>
<div className="row-start-8 col-start-4 flex items-center">
<span className="text-content-success font-normal ml-4 text-sm py-1">
Session completed
</span>
</div>
{/* row 8: session completed */}
<div className="row-start-8 col-start-2 relative">
<StatusIndicatorDot
variant="success"
className="absolute right-0 translate-x-1/2 translate-y-1/2"
/>
</div>
<div className="row-start-8 col-start-4 flex items-center">
<span className="text-content-success font-normal ml-4 text-sm py-1">
Session completed
</span>
</div>
</>
)}
</div>
</div>
);