Skip to content

fix(web): keep archived list pagination on unarchive#635

Merged
ColeMurray merged 2 commits into
mainfrom
worktree-fix-unarchive-rollback
May 14, 2026
Merged

fix(web): keep archived list pagination on unarchive#635
ColeMurray merged 2 commits into
mainfrom
worktree-fix-unarchive-rollback

Conversation

@ColeMurray

@ColeMurray ColeMurray commented May 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • The Data Controls archive list optimistically hid rows on Unarchive but never rolled back hiddenIds on failure, and on success the mutate(ARCHIVED_SESSIONS_KEY) revalidation collapsed any "Load more" pagination via the SWR onSuccess callback.
  • Switched to the request-first + scoped cache-update pattern already used by session-sidebar.tsx (handleSessionArchived): on success, remove the row from the first-page SWR cache with revalidate: false and filter it from extraSessions; on failure, leave state alone so the row stays visible.
  • Adds a Vitest + RTL test file covering the affected behavior.

Behavior change

Scenario Before After
Unarchive succeeds Row hidden optimistically, then revalidation collapses page-2 of "Load more" Row removed from page-1 cache + extras filter; pagination preserved
Unarchive returns 500 Row hidden, sometimes flickers back on revalidation, stays hidden if GET also fails Row stays visible; toast.error shown
Unarchive succeeds on a page-2 session Row reappears after revalidation collapse + onSuccess wipe Row removed cleanly from extraSessions
Sidebar refresh on success mutate(SIDEBAR_SESSIONS_KEY) (kept) Same — newly active session reappears in sidebar

Out of scope (flagged for follow-up)

  • The onSuccess callback on the archived-sessions useSWR still resets extraSessions on unrelated revalidations (focus/reconnect). The sidebar handles the same issue with a prevDataRef synchronous-render pattern (session-sidebar.tsx:96-101); worth porting in a separate PR.
  • No tests added for the /api/sessions/[id]/unarchive route handler — would match the route.test.ts pattern from existing API route tests.

Test plan

  • npx vitest run in packages/web — 30 files, 242 tests pass (5 new in data-controls-settings.test.tsx)
  • npx tsc --noEmit in packages/web — clean
  • npx eslint src/ in packages/web — clean
  • Manual: Settings > Data Controls — unarchive a session, confirm row disappears and reappears in sidebar
  • Manual: Settings > Data Controls — force POST /api/sessions/<id>/unarchive to 500 via devtools, confirm row stays visible and an error toast appears
  • Manual: Settings > Data Controls — with >20 archived sessions, click "Load more", then unarchive a session from page 1; confirm page-2 rows remain visible

Summary by CodeRabbit

  • Bug Fixes

    • Session unarchive now shows clear success and error notifications and preserves pagination visibility when unarchiving across pages.
    • Sidebar reliably refreshes to reflect session unarchives.
  • Tests

    • Added test coverage for unarchive success/failure, pagination edge cases, UI row visibility, and sidebar refresh behavior.

Review Change Stack

The Data Controls archive list optimistically hid rows on Unarchive but
never rolled back on failure, and the subsequent mutate(ARCHIVED_SESSIONS_KEY)
revalidation reset the "Load more" pagination via the SWR onSuccess
callback. Switch to the request-first + scoped cache-update pattern used
by the session sidebar: on success, remove the row from the first-page
SWR cache with revalidate: false and filter it from extraSessions; on
failure, leave state alone so the row stays visible.

Adds component-level tests covering: success removes the row, 500 keeps
it visible, page-1 unarchive preserves page-2 rows, page-2 unarchive
removes only that row, and successful unarchive revalidates the sidebar.
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f45862c9-c771-438f-8533-b477fa4fe780

📥 Commits

Reviewing files that changed from the base of the PR and between f5a22f3 and 639ae7f.

📒 Files selected for processing (2)
  • packages/web/src/components/settings/data-controls-settings.test.tsx
  • packages/web/src/components/settings/data-controls-settings.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/components/settings/data-controls-settings.tsx
  • packages/web/src/components/settings/data-controls-settings.test.tsx

📝 Walkthrough

Walkthrough

This PR updates the unarchive flow in DataControlsSettings to use explicit SWR cache updates with removeSessionFromList and sidebar key invalidation, replacing the prior hiddenIds filtering pattern. A comprehensive test suite validates the unarchive behavior across pagination scenarios and confirms sidebar cache revalidation.

Changes

Unarchive Flow Implementation and Testing

Layer / File(s) Summary
Data shape and handler setup
packages/web/src/components/settings/data-controls-settings.tsx
Imports SessionListResponse and removeSessionFromList; removes hiddenIds state; rebuilds sessions as concatenation of first SWR page and extraSessions without filtering.
Unarchive handler with cache updates
packages/web/src/components/settings/data-controls-settings.tsx
Replaces prior optimistic hiding with toast-driven unarchive: POST to endpoint, return early on failure, update archived-sessions SWR cache via removeSessionFromList (populateCache: true, revalidate: false), remove from local extraSessions, decrement offset, and invalidate SIDEBAR_SESSIONS_KEY.
Test infrastructure and unarchive flow cases
packages/web/src/components/settings/data-controls-settings.test.tsx
Mocks sonner and next/link; provides fetch routing for archived/sidebar endpoints with pagination and offset logic; SWR-backed renderComponent and cache cleanup. Tests verify row removal, page-1 pagination preservation while keeping page-2 visible, page-2 row removal with other rows intact, avoiding skipping after pre-load unarchive, and 500-error handling with correct toast calls and row visibility.
Sidebar cache invalidation verification
packages/web/src/components/settings/data-controls-settings.test.tsx
Adds SidebarProbe subscribing to SIDEBAR_SESSIONS_KEY via SWR. Test confirms sidebar fetch handler is called a second time after a successful unarchive (initial render + post-unarchive).

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly Related PRs

  • ColeMurray/background-agents#578: Adjusts SWR sidebar cache handling by explicitly mutating SIDEBAR_SESSIONS_KEY using removeSessionFromList in post-archive/post-unarchive flows.
  • ColeMurray/background-agents#570: Introduces removeSessionFromList and related shared archive helper logic that this PR consumes for cache synchronization.

Suggested Reviewers

  • open-inspect

Poem

🐰 With archives unbound and caches in sync,
I nudge a session free with a tiny wink—
No hidden rows vanish, the sidebar takes note,
Tests hum like carrots and toasts give a vote. 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing pagination behavior on unarchive in the archived sessions list, which is directly supported by both the file changes and PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-unarchive-rollback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

Comment thread packages/web/src/components/settings/data-controls-settings.tsx
open-inspect[bot]
open-inspect Bot previously requested changes May 13, 2026

@open-inspect open-inspect Bot left a comment

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.

Summary

PR #635, fix(web): keep archived list pagination on unarchive, by @ColeMurray. Reviewed 2 changed files (+326/-14); the request-first cache update is a good direction and the new focused tests pass, but there is one remaining pagination correctness issue before merge.

Critical Issues

  • [Functionality] packages/web/src/components/settings/data-controls-settings.tsx:71 - After a successful unarchive, the fetched row is removed from local UI state but offset is not decremented. A later Load more request can skip the next archived session because the server-side result set has shifted.

Suggestions

  • [Testing] Add coverage for unarchive followed by another Load more request with enough archived sessions to keep hasMore true. This would catch the stale-offset regression.

Nitpicks

  • None.

Positive Feedback

  • The request-first behavior fixes the failed-unarchive rollback problem cleanly by leaving visible state untouched on errors.
  • Using a scoped SWR cache update with revalidate: false avoids collapsing already-loaded extra rows on successful unarchive.
  • The tests cover success, failure, page-2 removal, and sidebar invalidation paths.

Questions

  • None.

Verdict

Request Changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/web/src/components/settings/data-controls-settings.test.tsx (1)

268-285: ⚡ Quick win

Consider extracting the SWRConfig setup to reduce duplication.

The SWRConfig settings here are identical to those in renderComponent() (lines 108-119). You could refactor renderComponent to accept optional children, eliminating the duplication.

♻️ Proposed refactor
-function renderComponent() {
+function renderComponent(additionalChildren?: React.ReactNode) {
   return render(
     <SWRConfig
       value={{
         dedupingInterval: 0,
         revalidateOnFocus: false,
         revalidateIfStale: false,
         revalidateOnReconnect: false,
         fetcher: async (url: string) => {
           const response = await fetch(url);
           if (!response.ok) throw new Error(`HTTP ${response.status}`);
           return response.json();
         },
       }}
     >
       <DataControlsSettings />
+      {additionalChildren}
     </SWRConfig>
   );
 }

Then update the sidebar test:

-    render(
-      <SWRConfig
-        value={{
-          dedupingInterval: 0,
-          revalidateOnFocus: false,
-          revalidateIfStale: false,
-          revalidateOnReconnect: false,
-          fetcher: async (url: string) => {
-            const response = await fetch(url);
-            if (!response.ok) throw new Error(`HTTP ${response.status}`);
-            return response.json();
-          },
-        }}
-      >
-        <DataControlsSettings />
-        <SidebarProbe />
-      </SWRConfig>
-    );
+    renderComponent(<SidebarProbe />);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/web/src/components/settings/data-controls-settings.test.tsx` around
lines 268 - 285, The SWRConfig block is duplicated; refactor the helper
renderComponent to accept optional children and the SWR config so tests reuse
it: update renderComponent(...) (the test helper) to wrap children with the
SWRConfig settings (dedupingInterval: 0, revalidateOnFocus: false,
revalidateIfStale: false, revalidateOnReconnect: false, and the fetcher) and
then replace the inline SWRConfig in this test by calling
renderComponent(<DataControlsSettings />, <SidebarProbe />) or
renderComponent(<><DataControlsSettings /><SidebarProbe /></>) so
DataControlsSettings and SidebarProbe are rendered without duplicating the
SWRConfig setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/web/src/components/settings/data-controls-settings.test.tsx`:
- Around line 268-285: The SWRConfig block is duplicated; refactor the helper
renderComponent to accept optional children and the SWR config so tests reuse
it: update renderComponent(...) (the test helper) to wrap children with the
SWRConfig settings (dedupingInterval: 0, revalidateOnFocus: false,
revalidateIfStale: false, revalidateOnReconnect: false, and the fetcher) and
then replace the inline SWRConfig in this test by calling
renderComponent(<DataControlsSettings />, <SidebarProbe />) or
renderComponent(<><DataControlsSettings /><SidebarProbe /></>) so
DataControlsSettings and SidebarProbe are rendered without duplicating the
SWRConfig setup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 75b5a0fe-dde8-41d8-85c8-2e033ecd2b78

📥 Commits

Reviewing files that changed from the base of the PR and between f66754a and f5a22f3.

📒 Files selected for processing (2)
  • packages/web/src/components/settings/data-controls-settings.test.tsx
  • packages/web/src/components/settings/data-controls-settings.tsx

Successful unarchive removed the row from the visible list but left the
local Load-more `offset` pointing at the pre-unarchive count. The
server-side archived list (ORDER BY updated_at DESC + OFFSET) shifts
down by one when a row leaves the result set, so the next Load more
request started one offset too high and skipped the session that took
the freed slot.

Decrements `offset` on success and adds a regression test that loads
page 1 (20 of 23 sessions), unarchives session 1, clicks Load more, and
asserts session 21 — which now sits at offset 19 — appears.

Also refactors the test helper to a flat-list `archivedSessions` mock
so pagination shifts naturally, and threads `additionalChildren`
through `renderComponent` so the sidebar-invalidation test reuses the
SWRConfig wrapper.
@github-actions

Copy link
Copy Markdown

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@ColeMurray

Copy link
Copy Markdown
Owner Author

CodeRabbit nit (SWRConfig duplication in the test file) addressed in 639ae7f — threaded an optional additionalChildren arg through renderComponent so the sidebar-invalidation test reuses the wrapper. Also refactored the fetch mock to a flat archivedSessions list, which models the server-side offset shift naturally.

@ColeMurray ColeMurray merged commit 4ca7fbb into main May 14, 2026
18 checks passed
@ColeMurray ColeMurray deleted the worktree-fix-unarchive-rollback branch May 14, 2026 04:09
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