Skip to content

fix(web): server-side global ordering for sessions list - #109

Open
Poxel2 wants to merge 4 commits into
offendingcommit:mainfrom
Poxel2:fix/sessions-list-sort-order
Open

Poxel2 wants to merge 4 commits into
offendingcommit:mainfrom
Poxel2:fix/sessions-list-sort-order

Conversation

@Poxel2

@Poxel2 Poxel2 commented Sep 21, 2026

Copy link
Copy Markdown

Problem

The Sessions list showed the oldest sessions on page 1 even with sort direction "Newest": in a workspace with ~965 sessions, the top cards dated May 31 – June 2 while the workspace had sessions created minutes ago.

Root cause

POST /v3/workspaces/{workspace_id}/sessions/list returns pages in ascending created_at order by default. Unlike conclusions/list and messages/list, the web client did not send the server's reverse query parameter (it was also missing from the bundled openapi.json for this endpoint), so useSessions() could not request newest-first pagination.

The page then applied a client-side sort (sortDir: "desc") within the fetched page only — which just re-sorted the 20 oldest sessions. Page 1 stayed stuck at the beginning of the timeline; newly created sessions landed on the last page and were never shown first.

Fix

Server-side global ordering (bd77a2d, 6d50fa0)

  • useSessions() accepts a reverse parameter (default true) and forwards it as the reverse query parameter, so server-side pagination starts at the newest session. The reverse flag is part of the QK.sessions cache key.
  • SessionList maps the sort direction (desc = Newest, asc = Oldest) to reverse and no longer re-sorts client-side — a client sort only shuffles the current page slice and hides the global order. Changing the direction resets to page 1.
  • schema.d.ts: expose reverse on the sessions/list query params (the server already accepted it; the generated types did not).
  • Dropped the unused page_size query param in favor of size (the Honcho server silently ignores page_size and falls back to 50/page).

The server orders sessions by created_at only (ORDER BY created_at, id, direction via reverse) — this is a global sort across all pages, but by creation time, not by last activity. For long-lived reused sessions "Newest" still means "newest created"; a last-activity ordering would need a honcho-side change and is documented as an open point in SessionList.

Removed the page-local Active/ID sort options (df9265e)

The previous Active and ID sort options had no server-side equivalent, so they could never sort globally — as page-local client sorts they silently showed the extreme of the loaded slice instead of the workspace, which is exactly the half-correct behavior this PR removes for created_at. They were removed rather than kept; the sort control now offers a single honest Newest/Oldest toggle backed by the server order.

Testing

  • pnpm --filter @openconcho/web typecheck ✓, biome lint ✓, 118/118 vitest tests pass (mutation-checked: a mock that ignores the reverse flag fails the suite).
  • New regression tests (sessions-global-sort.test.tsx + shared sessions-api-mock.ts), mocked >1-page workspace:
    • Newest default: page 1 requests reverse=true and renders the globally newest session (not the globally oldest).
    • Toggling to Oldest re-requests page 1 with reverse=false and renders the globally oldest session first.
    • Page 2 under Oldest still rides reverse=false and surfaces the globally newest session on the last page — the order spans the whole result set, not the loaded slice.
  • Verified against a live self-hosted Honcho (965-session workspace): without reverse, page 1 starts at the oldest row (2026-05-25); with reverse: true, page 1 starts at the newest row.

Notes

  • useSessionMessages intentionally keeps ascending order (chat transcript order).
  • Open point (out of scope, documented in code): global active/id sorting or last-activity ordering needs a honcho-side sort-field parameter.

The sessions list page showed the oldest sessions (May/June 2026 in a
workspace with 965 sessions) as the newest, because page 1 of
POST /v3/workspaces/{workspace_id}/sessions/list arrives in ascending
created_at order while the UI applies its own client-side sort per page.

Unlike conclusions/list and messages/list, the generated API types for
sessions/list did not expose the server's reverse query parameter, so
useSessions could not ask for newest-first pagination. The page-local
desc sort then only shuffled the oldest-20 slice.

- pass reverse: true in useSessions so page 1 starts at the newest session
- add reverse to the QK.sessions cache key
- drop the unused page_size query param in favor of size (the server
  silently ignores page_size and falls back to 50/page)
@Poxel2

Poxel2 commented Sep 21, 2026

Copy link
Copy Markdown
Author

Follow-up (also deployed locally): reverse: true fixed page 1, but the client-side SortControl still re-sorted only the 20 loaded cards of the current page — on a 49-page workspace, "Oldest" showed the oldest entry of the loaded slice, not of the workspace.

Pushed the completion to Poxel2:pox/local-repair (commit b2d37a5):

  • useSessions now takes a reverse parameter and forwards it server-side; SessionList maps created_at desc/asc to reverse true/false and stops client-side re-sorting that field (a page-local sort hides the global order again).
  • active/id have no server equivalent (the endpoint orders by created_at only and filters is_active == true — verified live); they remain page-local sorts, documented in PAGE_LOCAL_SORT_FIELDS.
  • Open point (server semantics, documented not fixed): reverse sorts by created_at, NOT last activity. A last-activity sort needs a honcho-side change.
  • Regression test included (mocked >1-page workspace).

…, not page-oldest)

Follow-up to offendingcommit#109: that PR made page 1 request reverse=true, but the
SortControl still only re-sorted the 20 loaded cards of the CURRENT page.
On a workspace with 49 pages, 'Oldest' showed the oldest entry of the
loaded slice (today 10:55), not the oldest of the workspace.

The honcho fork's sessions/list orders by created_at only —  is
the single server-side sort lever (no sort-field parameter, no
last-activity ordering; see open point below).

- useSessions gains a reverse parameter, forwarded as the server query
  param; SessionList maps created_at desc/asc to reverse true/false and
  stops client-side re-sorting that field (a page-local sort would hide
  the global order again)
- active/id have no server equivalent (the list endpoint returns only
  is_active rows anyway); they remain page-local sorts, documented in
  PAGE_LOCAL_SORT_FIELDS with the server-semantics rationale
- schema.d.ts: expose reverse on sessions/list query params (the server
  already accepted it; the generated types did not)
- regression: mocked >1-page workspace, page 1 with Newest must show the
  globally newest and NOT the globally oldest session

Open point (server semantics, documented not fixed): reverse sorts by
created_at, NOT by last activity. For long-lived reused sessions 'Newest'
still means 'newest created'. A last-activity sort needs a honcho-side
change (out of scope here, per Nicht-Scope).
Follow-up on the global sort fix: Active and ID had no server-side
equivalent (sessions/list orders by created_at only), so keeping them
as page-local client sorts silently showed the extreme of the loaded
20-card slice instead of the workspace — the exact half-correct
behavior the global-sort fix removed for created_at. Removing them
leaves a single honest control: Newest/Oldest, ordered server-side
across all pages.

A global active/id sort or last-activity ordering needs a honcho-side
sort-field parameter (documented as open point in SessionList).
@Poxel2 Poxel2 changed the title fix(web): request server-side reverse ordering for sessions list fix(web): server-side global ordering for sessions list Sep 21, 2026
The first regression round only asserted the Newest default: reverse
flag on the initial request, page 1 rendering the globally newest
session. Direction toggling and pagination had no coverage — the exact
paths the original bug traveled.

- toggling Newest -> Oldest must re-request PAGE 1 with reverse=false
  and render the globally OLDEST session first (not the oldest of a
  stale page slice)
- walking to page 2 under Oldest must keep riding reverse=false and
  surface the globally NEWEST session on the last page — proof the
  order spans the whole result set, not the loaded slice

Mutation-checked: a mock that ignores the reverse flag fails the
Newest-default assertion, so the tests genuinely pin the server order.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant