You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
While validating #555, the independent SDE Agent and Agent Org backend streams worked as intended, but a separate sidebar pagination problem became visible:
Clicking Load more sometimes reveals sessions immediately.
Other times, the same button must be clicked two or three times before any new session row appears.
A Load more click shown under one agent section can add sessions to the Pinned section instead.
After startup or a roster refresh, Load more can replay pages that are already present in the local cache before it reaches genuinely new sessions.
The click is usually not lost. The backend page can load successfully while every returned row is already present in the local cache, remains hidden by the frontend's per-section display limit, or is regrouped into a different section.
Steps to Reproduce
Open the session sidebar and group sessions by agent.
Have enough sessions for a backend category to contain multiple pages, with some sessions pinned.
Expand the visible local rows until the agent section is exactly at its current display limit.
Click that section's Load more button while the backend category still has another page.
Observe that the loading request completes, but sometimes no new row appears in the clicked section.
Click Load more again.
The second click expands the already-loaded local rows and the sessions finally become visible.
In some page distributions, the first click increases the Pinned section rather than the section where the button was displayed.
A minimal deterministic fixture is:
The unpinned agent group has 20 loaded rows and a visibleCount of 20.
The pinned group has 10 loaded rows and a visibleCount of 10.
The backend category has more rows.
The next backend page returns 9 unpinned rows and 1 pinned row.
After the backend click, the atom contains 29 unpinned and 11 pinned rows, but the UI still renders only 20 and 10. No newly fetched row is visible until a second local expansion click.
Expected Behavior
One enabled Load more click should produce an immediate, visible result when matching rows exist.
A button presented under a specific section should have clear ownership of the rows it loads.
Loading more unpinned sessions for an agent section should not silently add rows only to Pinned.
The UI should not require a second click merely to reveal rows fetched by the first click.
When no matching rows remain, the button should disappear or transition to an explicit exhausted state.
Actual Behavior
The sidebar currently has two pagination layers that use the same Load more label:
Local reveal pagination increments groupVisibleCounts by the sidebar page size.
Backend category pagination advances a backend cursor and merges another page into the global session atom.
After a backend page is merged, the frontend:
splits sessions into pinned and unpinned arrays;
regroups unpinned sessions by agent, Agent Org, workspace, or date;
applies the old local visibleCount to every destination group.
The backend fetch does not increase those destination groups' visible counts. Therefore, newly fetched rows can be present in state but remain entirely invisible.
Root Cause
1. Backend and local pagination do not share one owner
A backend pager is rendered when a category has no currently hidden local rows. After that pager fetches another page, the same group can immediately acquire hidden local rows because its visibleCount is unchanged. The row then changes from a backend pager into a local reveal pager, even though both controls look identical to the user.
Native sidebar SQL pages include the persisted pinned value but do not exclude pinned rows before LIMIT/OFFSET. After the page reaches the frontend, pinned rows are removed from the clicked agent group and inserted into the global Pinned section.
3. Some backend categories are wider than the visible UI group
A single backend cursor can cover more than one visible group:
standalone_agent can represent SDE, Wingman, and Custom Agent groups.
agent_org_root can represent multiple Agent Org groups.
A button visually placed below one group can therefore fetch rows belonging to another group. This is another way for the clicked section to remain unchanged even though the request succeeded.
4. The local cache and backend cursor can describe different windows
The sidebar restores up to 200 session rows from local storage so it can render immediately after startup. A roster refresh resets each backend pagination cursor to the first page, but the refreshed native rows are merged into the existing session atom instead of replacing the cached native window.
That can leave the frontend in a state such as:
the atom already contains 30 standalone sessions restored from local cache;
standalone_agent.loaded has been reset to 10 after refreshing page 1;
the backend therefore believes the next page starts at offset 10.
The next two Load more clicks request offsets 10 and 20. Those pages contain rows that are already present in the atom, so ID-based merging removes the duplicates and the rendered list does not change. Only the request at offset 30 reaches genuinely new rows.
This is another deterministic reason one click can appear to do nothing. It is separate from the local visibleCount problem: in this case the request may add no new atom rows at all because the backend cursor is catching up with a larger cached window.
Relevant areas:
src/store/session/sessionAtom/atoms.ts
src/store/session/sessionAtom/persistence.ts
src/store/session/sessionAtom/loaders.ts
Affected Session Categories
These root causes do not affect every visible sidebar label in exactly the same way.
Visible sidebar section
Cached pages can be replayed after cursor reset
Newly fetched rows can remain hidden behind the local display limit
Rows can move to Pinned
One backend page can add rows under another visible label
SDE / Wingman / Custom Agent
Yes
Yes
Yes
Yes — they share the standalone_agent backend stream
Individual Agent Org groups
Yes
Yes
Yes
Yes — several Org groups share the agent_org_root backend stream
CLI Agent
Yes
Yes
Yes
No — it has its own cli_agent category
OS Agent
Yes
Yes
Yes
No — it has its own os_agent category
Work Logs / human sessions
Yes
Yes
Possible when the row is pinned
No — it has its own human_session category
Imported history such as Codex App, Claude Code, Cursor, and OpenCode
Not from the exact native cache/cursor mismatch described above: refresh replaces that source's previous top-level rows and pagination is tracked per source/date bucket
Yes
No in the current imported-history sidebar payload
No — each imported source has its own category
In plain language:
The cache/cursor replay bug primarily affects ORGII-managed native categories: standalone agents, Agent Org roots, CLI, OS, and human sessions.
The local display-limit bug can affect every section, including Codex and Claude Code: the frontend may receive new rows but continue showing only the previous number of rows.
The cross-label ownership bug is concentrated in shared backend streams: SDE/Wingman/Custom and multiple Agent Org groups.
Codex, Claude Code, Cursor, OpenCode, and other imported-history sources have independent source pagination. They still need one-click visibility coverage, but they should not be described as having the same native cache/cursor replay mechanism.
Evidence
A read-only inspection of one real local dataset reproduced the issue with this standalone-agent page distribution. No session names, IDs, prompts, or transcript content were inspected.
Backend page
Pinned rows
Unpinned rows
Cumulative pinned
Cumulative unpinned
1
5
5
5
5
2
4
6
9
11
3
1
9
10
20
4
1
9
11
29
After page 3, the local limits can be exactly Pinned=10 and SDE=20. Loading page 4 succeeds, but all 10 returned rows land beyond those limits. The screen shows no new session row until another local expansion click.
A second deterministic fixture covers the cache/cursor mismatch:
restore 30 native rows from local storage;
refresh the roster with a backend page size of 10;
verify that the atom still contains 30 rows while the cursor reports 10 loaded rows;
the first Load more requests offset 10 and adds 0 unique rows;
the second requests offset 20 and adds 0 unique rows;
the third requests offset 30 and finally adds new rows.
The existing focused test suites all pass (27 tests across pagination helpers, menu section builders, and sidebar loaders). They verify the loader and menu builder separately, but do not exercise the full sequence:
backend Load more click -> merge -> regroup -> local visibleCount -> rendered rows
There is also coverage that intentionally keeps an Agent Org backend pager when all loaded roots are pinned, but no assertion that the next click visibly updates the intended section.
Suggested Resolution
The implementation can vary, but pagination ownership should become unambiguous:
Give the Pinned section its own pagination behavior, or exclude pinned rows from unpinned section queries before LIMIT/OFFSET.
Make one visible Load more control correspond to one well-defined data stream.
When a backend page returns, immediately reveal the newly fetched rows in their destination group(s), or remove the second local pagination layer for that path.
If a backend cursor is intentionally shared by several visible groups, place the control at a neutral category-level footer rather than implying that it belongs to one label.
Keep the backend cursor and retained native cache on the same window: either replace the relevant native window on refresh, restore a cursor consistent with the retained rows, or track page membership explicitly instead of inferring it from one loaded count.
This should not require a database schema change. The likely changes are query filtering/stream ownership plus frontend pagination and visibility state.
Acceptance Criteria
Every enabled Load more click either reveals at least one newly available row immediately or clearly transitions the control to loading/exhausted/error state.
A successful backend page cannot be completely hidden solely because the destination group's previous visibleCount was already full.
Loading more for an unpinned agent section cannot produce only a silent increase in the Pinned section.
Pinned pagination cannot consume the visible capacity or cursor of an unpinned section.
A pager displayed under one agent label cannot silently load rows only for another label; shared-category pagers use a neutral placement or exact group filtering.
SDE/Wingman/Custom and multiple Agent Org groups are covered explicitly.
Native cache/cursor coverage includes standalone agents, Agent Org roots, CLI, OS, and human sessions.
Imported-history coverage includes Codex App, Claude Code, and Cursor IDE, proving that one source-local Load more click reveals its newly fetched rows without relying on a second local reveal click.
After cold-start hydration or a forced roster refresh, the next enabled Load more request cannot consist entirely of rows already retained from later cached pages.
Tests cover a cached native list larger than page 1, followed by cursor reset and repeated loads.
Repeated loads do not skip or duplicate rows.
Rapid repeated clicks while loading=true do not start overlapping requests.
Equivalent behavior is checked for by-agent, by-time, and by-workspace sidebar modes.
Tests cover the integrated fetch/merge/regroup/render sequence, not only the loader and menu builder in isolation.
A strict rendered E2E test proves that one click makes the expected new row visible and fails if the row appears only after another click.
This is related to the pagination work in #555 but is a distinct problem. #555 separates normal standalone sessions from Agent Org roots correctly; this issue concerns coordination between backend category pagination, pinned regrouping, local per-section visibility limits, and restored cache state.
Description
While validating #555, the independent SDE Agent and Agent Org backend streams worked as intended, but a separate sidebar pagination problem became visible:
Load moresometimes reveals sessions immediately.Load moreclick shown under one agent section can add sessions to thePinnedsection instead.Load morecan replay pages that are already present in the local cache before it reaches genuinely new sessions.The click is usually not lost. The backend page can load successfully while every returned row is already present in the local cache, remains hidden by the frontend's per-section display limit, or is regrouped into a different section.
Steps to Reproduce
Load morebutton while the backend category still has another page.Load moreagain.Pinnedsection rather than the section where the button was displayed.A minimal deterministic fixture is:
visibleCountof 20.visibleCountof 10.After the backend click, the atom contains 29 unpinned and 11 pinned rows, but the UI still renders only 20 and 10. No newly fetched row is visible until a second local expansion click.
Expected Behavior
Load moreclick should produce an immediate, visible result when matching rows exist.Pinned.Actual Behavior
The sidebar currently has two pagination layers that use the same
Load morelabel:groupVisibleCountsby the sidebar page size.After a backend page is merged, the frontend:
pinnedandunpinnedarrays;visibleCountto every destination group.The backend fetch does not increase those destination groups' visible counts. Therefore, newly fetched rows can be present in state but remain entirely invisible.
Root Cause
1. Backend and local pagination do not share one owner
A backend pager is rendered when a category has no currently hidden local rows. After that pager fetches another page, the same group can immediately acquire hidden local rows because its
visibleCountis unchanged. The row then changes from a backend pager into a local reveal pager, even though both controls look identical to the user.Relevant areas:
src/scaffold/NavigationSidebar/connectors/useWorkstationSidebarHandlers.tssrc/scaffold/NavigationSidebar/connectors/useSessionMenuItems/paginationHelpers.tsxsrc/scaffold/NavigationSidebar/connectors/useSessionMenuItems/menuSectionBuilders.tssrc/scaffold/NavigationSidebar/connectors/useSessionMenuItems/index.tsxsrc/store/session/sessionAtom/loaders.tssrc/store/session/sessionAtom/paginationAtoms.ts2. Pinned rows are included in category pages
Native sidebar SQL pages include the persisted
pinnedvalue but do not exclude pinned rows beforeLIMIT/OFFSET. After the page reaches the frontend, pinned rows are removed from the clicked agent group and inserted into the globalPinnedsection.Relevant backend areas:
src-tauri/crates/agent-core/src/core/coordination/agent_org_runs/store.rssrc-tauri/src/agent_sessions/session_directory/aggregation.rs3. Some backend categories are wider than the visible UI group
A single backend cursor can cover more than one visible group:
standalone_agentcan represent SDE, Wingman, and Custom Agent groups.agent_org_rootcan represent multiple Agent Org groups.A button visually placed below one group can therefore fetch rows belonging to another group. This is another way for the clicked section to remain unchanged even though the request succeeded.
4. The local cache and backend cursor can describe different windows
The sidebar restores up to 200 session rows from local storage so it can render immediately after startup. A roster refresh resets each backend pagination cursor to the first page, but the refreshed native rows are merged into the existing session atom instead of replacing the cached native window.
That can leave the frontend in a state such as:
standalone_agent.loadedhas been reset to 10 after refreshing page 1;The next two
Load moreclicks request offsets 10 and 20. Those pages contain rows that are already present in the atom, so ID-based merging removes the duplicates and the rendered list does not change. Only the request at offset 30 reaches genuinely new rows.This is another deterministic reason one click can appear to do nothing. It is separate from the local
visibleCountproblem: in this case the request may add no new atom rows at all because the backend cursor is catching up with a larger cached window.Relevant areas:
src/store/session/sessionAtom/atoms.tssrc/store/session/sessionAtom/persistence.tssrc/store/session/sessionAtom/loaders.tsAffected Session Categories
These root causes do not affect every visible sidebar label in exactly the same way.
Pinnedstandalone_agentbackend streamagent_org_rootbackend streamcli_agentcategoryos_agentcategoryhuman_sessioncategoryIn plain language:
Evidence
A read-only inspection of one real local dataset reproduced the issue with this standalone-agent page distribution. No session names, IDs, prompts, or transcript content were inspected.
After page 3, the local limits can be exactly
Pinned=10andSDE=20. Loading page 4 succeeds, but all 10 returned rows land beyond those limits. The screen shows no new session row until another local expansion click.A second deterministic fixture covers the cache/cursor mismatch:
Load morerequests offset 10 and adds 0 unique rows;The existing focused test suites all pass (27 tests across pagination helpers, menu section builders, and sidebar loaders). They verify the loader and menu builder separately, but do not exercise the full sequence:
backend Load more click -> merge -> regroup -> local visibleCount -> rendered rowsThere is also coverage that intentionally keeps an Agent Org backend pager when all loaded roots are pinned, but no assertion that the next click visibly updates the intended section.
Suggested Resolution
The implementation can vary, but pagination ownership should become unambiguous:
Pinnedsection its own pagination behavior, or exclude pinned rows from unpinned section queries beforeLIMIT/OFFSET.Load morecontrol correspond to one well-defined data stream.loadedcount.This should not require a database schema change. The likely changes are query filtering/stream ownership plus frontend pagination and visibility state.
Acceptance Criteria
Load moreclick either reveals at least one newly available row immediately or clearly transitions the control to loading/exhausted/error state.visibleCountwas already full.Pinnedsection.Load moreclick reveals its newly fetched rows without relying on a second local reveal click.Load morerequest cannot consist entirely of rows already retained from later cached pages.loading=truedo not start overlapping requests.Environment
Additional Context
This is related to the pagination work in #555 but is a distinct problem. #555 separates normal standalone sessions from Agent Org roots correctly; this issue concerns coordination between backend category pagination, pinned regrouping, local per-section visibility limits, and restored cache state.