Skip to content

Release v0.51.757 — page archived sidebar rows + archived-search refetch (#5200, @santastabber) - #5241

Merged
nesquena-hermes merged 2 commits into
masterfrom
fix/5200-archived-paging
Jun 29, 2026
Merged

nesquena-hermes merged 2 commits into
masterfrom
fix/5200-archived-paging

Conversation

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Release v0.51.757 — page archived sidebar rows + archived-search refetch (#5200, @santastabber)

Ships #5200 by @santastabber. Perf + correctness fix on the session sidebar.

What it does

Archived sidebar rows are now paged (capped per request) instead of all loaded at once — an instance with thousands of archived sessions no longer renders them all. The contributor PR converged from an earlier gate-fail: when "Show archived" is on and you start a search, the sidebar transparently refetches without the archive cap so a title/id match beyond the first archived page is still reachable, then restores normal paging when the search clears.

Gate (converged from gate-fail, re-gated fresh)

  • I earlier bounced this: the archived-paging suppression applied on the renderSessionList path but NOT the filterSessions() search-input path, so an archived title match past page 1 was unreachable while searching (SILENT data-not-shown). The re-push added _syncArchivedSearchPagingRefresh() + search-active detection that refetches without archived_limit on the search-active edge and restores paging on clear.
  • Codex (reproduce): SAFE TO SHIP — verified the archived title match beyond page 1 now appears under archived-search; a render generation/queue prevents a slower capped/uncapped response from overwriting newer state (no thrash/loop); default non-search paging + the 2000-row cap stay intact; backend slices only archived rows with limit/offset-separated cache keys.
  • 48 sidebar/cache tests pass; full suite 11075 passed (only the 2 pre-existing cron-isolation flakes — unrelated).

Closes #5200.

@nesquena-hermes
nesquena-hermes merged commit dd4d4b2 into master Jun 29, 2026
17 checks passed
@nesquena-hermes
nesquena-hermes deleted the fix/5200-archived-paging branch June 29, 2026 22:38
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This release PR adds paginated loading for archived sidebar rows (archived_limit/archived_offset query params on /api/sessions) and a search-active suppression that temporarily removes the limit when the user is typing a title search, ensuring archived matches beyond the first page remain reachable. When the search clears, paging restores automatically.

  • Backend: new _query_positive_int helper; _build_session_list_cache_payload slices archived_scoped_filtered to the requested window while keeping all visible rows unaffected and preserving full-count metadata for the tab badge; the cache key is parameterised by limit and offset so differently-paged requests don't share a cache entry.
  • Frontend: _archivedRowsLoadedLimit state + SESSION_ARCHIVED_PAGE_SIZE / SESSION_ARCHIVED_MAX_LOADED_LIMIT constants drive a "Load N more" affordance; _sessionArchivePagingFilterActive() suppresses archived_limit during any search or project-filter activation; _syncArchivedSearchPagingRefresh triggers a full refetch on search-state edge transitions (inactive→active and back) to bring in the uncapped set without waiting for debounce.

Confidence Score: 4/5

Safe to merge. The archived-paging slice correctly keeps all visible rows intact, full counts for tab badges and the 'N remaining' prompt are computed before slicing, and separately-parameterised cache keys prevent cross-contamination.

Both changed files carry small rough edges: the normalization of archived_limit/archived_offset is repeated three times across the call chain despite _query_positive_int already guaranteeing clean integers, and _syncArchivedSearchPagingRefresh doesn't account for an already-active project filter, firing an extra HTTP request in that state. Neither causes incorrect data, but they warrant a follow-up cleanup pass.

api/routes.py (redundant inner normalization blocks) and static/sessions.js (spurious refetch path when _activeProject is set at search transition time).

Important Files Changed

Filename Overview
api/routes.py Adds archived_limit/archived_offset paging in _build_session_list_cache_payload and _query_positive_int helper; session-count metrics computed before slicing (correct for tab badge). Contains redundant inline normalization of values already validated by _query_positive_int.
api/route_session_list_cache.py Extends _session_list_cache_key with archived_limit/archived_offset so differently-paged requests get separate cache slots. Normalization logic here mirrors what _query_positive_int already guarantees at the call site.
static/sessions.js Adds _archivedRowsLoadedLimit state, _sessionArchivePagingFilterActive(), _syncArchivedSearchPagingRefresh(), and the 'Load more archived' DOM widget. Spurious refetch possible when _activeProject is already set and search transitions, but correctness is unaffected.
tests/test_session_list_long_history_perf.py Adds two new backend tests: one end-to-end paging test confirming visible rows are never hidden and archived_count is unaffected by paging, and a cache-key isolation test.
tests/test_issue4766_sidebar_source_pushdown.py Extends JS query-string tests to cover archived_limit injection, search/project suppression, and the _syncArchivedSearchPagingRefresh edge-transition behaviour via Node.js harness.
tests/test_sidebar_session_partition.py Updates source-count assertions to match the new server-count sentinel path and adds archive load-more DOM assertions.
CHANGELOG.md Release entry added for v0.51.757; correctly maintained by the release process per repo convention.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant U as User
    participant JS as sessions.js
    participant API as /api/sessions

    Note over JS: _showArchived=true, no search/project active

    JS->>API: "GET /api/sessions?include_archived=1&archived_limit=100"
    API-->>JS: "sessions[visible + archived[0:100]], archived_count=N"

    JS->>U: Renders sidebar (visible rows + first 100 archived)
    JS->>U: Load 100 more archived (N-100 remaining) button

    U->>JS: Clicks Load more
    JS->>JS: "_archivedRowsLoadedLimit = 200"
    JS->>API: "GET /api/sessions?include_archived=1&archived_limit=200"
    API-->>JS: sessions[visible + archived[0:200]]
    JS->>U: Renders sidebar (200 archived rows now shown)

    U->>JS: Types search query in sessionSearch
    JS->>JS: filterSessions() → _syncArchivedSearchPagingRefresh(q)
    JS->>JS: _archivedSearchPagingQueryActive: false → true
    JS->>API: "GET /api/sessions?include_archived=1 (no archived_limit)"
    API-->>JS: ALL archived sessions returned
    JS->>U: Client-side title filter finds matches beyond page 1

    U->>JS: Clears search input
    JS->>JS: _syncArchivedSearchPagingRefresh('')
    JS->>JS: _archivedSearchPagingQueryActive: true → false
    JS->>API: "GET /api/sessions?include_archived=1&archived_limit=200"
    API-->>JS: sessions[visible + archived[0:200]]
    JS->>U: Paged view restored (200 archived rows)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant U as User
    participant JS as sessions.js
    participant API as /api/sessions

    Note over JS: _showArchived=true, no search/project active

    JS->>API: "GET /api/sessions?include_archived=1&archived_limit=100"
    API-->>JS: "sessions[visible + archived[0:100]], archived_count=N"

    JS->>U: Renders sidebar (visible rows + first 100 archived)
    JS->>U: Load 100 more archived (N-100 remaining) button

    U->>JS: Clicks Load more
    JS->>JS: "_archivedRowsLoadedLimit = 200"
    JS->>API: "GET /api/sessions?include_archived=1&archived_limit=200"
    API-->>JS: sessions[visible + archived[0:200]]
    JS->>U: Renders sidebar (200 archived rows now shown)

    U->>JS: Types search query in sessionSearch
    JS->>JS: filterSessions() → _syncArchivedSearchPagingRefresh(q)
    JS->>JS: _archivedSearchPagingQueryActive: false → true
    JS->>API: "GET /api/sessions?include_archived=1 (no archived_limit)"
    API-->>JS: ALL archived sessions returned
    JS->>U: Client-side title filter finds matches beyond page 1

    U->>JS: Clears search input
    JS->>JS: _syncArchivedSearchPagingRefresh('')
    JS->>JS: _archivedSearchPagingQueryActive: true → false
    JS->>API: "GET /api/sessions?include_archived=1&archived_limit=200"
    API-->>JS: sessions[visible + archived[0:200]]
    JS->>U: Paged view restored (200 archived rows)
Loading

Reviews (1): Last reviewed commit: "docs(changelog): page archived sidebar r..." | Re-trigger Greptile

Comment thread api/routes.py
Comment on lines +1940 to +1954
if include_archived and archived_limit is not None:
try:
normalized_archived_limit = max(0, int(archived_limit))
except (TypeError, ValueError):
normalized_archived_limit = None
try:
normalized_archived_offset = max(0, int(archived_offset or 0))
except (TypeError, ValueError):
normalized_archived_offset = 0
if normalized_archived_limit is not None:
visible_rows_for_page = [s for s in visible_scoped_filtered if not s.get("archived")]
archived_rows_for_page = [s for s in archived_scoped_filtered if s.get("archived")]
scoped = visible_rows_for_page + archived_rows_for_page[
normalized_archived_offset: normalized_archived_offset + normalized_archived_limit
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant normalization — archived_limit and archived_offset have already been validated to non-negative integers (or None) by _query_positive_int at the call site, so the try/except blocks here can never fire and the max(0, int(...)) guards are no-ops. The same duplication occurs in _session_list_cache_key. Simplifying to direct use of the incoming values would remove ~10 lines of dead code from both sites.

Suggested change
if include_archived and archived_limit is not None:
try:
normalized_archived_limit = max(0, int(archived_limit))
except (TypeError, ValueError):
normalized_archived_limit = None
try:
normalized_archived_offset = max(0, int(archived_offset or 0))
except (TypeError, ValueError):
normalized_archived_offset = 0
if normalized_archived_limit is not None:
visible_rows_for_page = [s for s in visible_scoped_filtered if not s.get("archived")]
archived_rows_for_page = [s for s in archived_scoped_filtered if s.get("archived")]
scoped = visible_rows_for_page + archived_rows_for_page[
normalized_archived_offset: normalized_archived_offset + normalized_archived_limit
]
if include_archived and archived_limit is not None:
visible_rows_for_page = [s for s in visible_scoped_filtered if not s.get("archived")]
archived_rows_for_page = [s for s in archived_scoped_filtered if s.get("archived")]
scoped = visible_rows_for_page + archived_rows_for_page[
archived_offset: archived_offset + archived_limit
]

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread static/sessions.js
Comment on lines 5150 to 5165

function _syncArchivedSearchPagingRefresh(query){
const queryActive=Boolean(String(query||'').trim());
const previous=_archivedSearchPagingQueryActive;
_archivedSearchPagingQueryActive=queryActive;
if(!_showArchived||queryActive===previous) return;
// Archived title/id filtering is client-side. When search becomes active,
// refetch without archived_limit so matches beyond the first archived page are
// reachable; when search clears, refetch again to restore normal archive paging.
if(typeof renderSessionList==='function') void renderSessionList({deferWhileInteracting:false});
}

function filterSessions(){
// Immediate client-side title filter (no flicker)
// Debounced content search via API for message text
syncSessionSearchClear();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Spurious refetch when project filter is already active

_syncArchivedSearchPagingRefresh fires renderSessionList whenever _archivedSearchPagingQueryActive transitions between false and true, but _sessionArchivePagingFilterActive() already returns true when _activeProject is non-null — meaning archived_limit is already omitted from the query. If a user has a project filter active and then starts typing (or clears) a search, the false→true (or true→false) transition in _archivedSearchPagingQueryActive triggers an extra HTTP request even though the resulting query string is identical to the one already in flight. No data-correctness problem, but it is an avoidable extra round-trip in that specific mixed state. A guard checking !_activeProject before deciding to refetch would eliminate it.

@cutter-sh

cutter-sh Bot commented Jun 29, 2026

Copy link
Copy Markdown

🎬 Cutter preview — PR #5241

Load more archived sessions
Load more archived sessions — Sidebar pages through archived conversations, loading 100 more at a time up to 2000.

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