Skip to content

fix: recover project pagination after deletion - #3771

Merged
nighca merged 2 commits into
goplus:devfrom
khangb2306668:fix/projects-pagination-after-delete
Nov 15, 2026
Merged

nighca merged 2 commits into
goplus:devfrom
khangb2306668:fix/projects-pagination-after-delete

Conversation

@khangb2306668

Copy link
Copy Markdown
Contributor

Summary

When the last project on a later page was deleted, the projects list remained on the now-empty page and displayed “No projects” even though projects still existed on previous pages.

This change recalculates the last available page after a project deletion and navigates to it when necessary.

Changes

  • Return to the previous available page when the current page becomes empty.
  • Preserve the selected sort order.
  • Keep the current page when it still contains projects.
  • Show the empty state only when no projects remain.
  • Keep the URL and pagination state in sync.
  • Add regression tests for pages 2 and 4, preserving the current page, and deleting the final project.

Testing

  • 7 regression tests passed.
  • Prettier check passed.
  • ESLint passed.

Fixes #3734

@fennoai fennoai 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.

Review: recover project pagination after deletion

Nice, well-scoped fix. The clamping logic is correct across all the cases I traced, the guards (page.value <= 1, Math.max(1, …)) make it one-directional and idempotent (no infinite loop), and the four new tests cover the key scenarios (empty-page recovery, stepping back, keeping a populated page, and the empty state), asserting both the route p param and the resulting fetch pageIndex. The fix is also correctly scoped to projects.vue only — sibling pages (likes/followers/following) have no deletable items, so they don't need it.

No blocking issues and no security concerns. A few optional polish suggestions are inline below.

Summary of non-inline notes

  • (Optional) Test gap — multi-page clamp: Every recovery test drops total by exactly one, so the clamp target happens to equal page - 1. A case where, e.g., p=5 must clamp directly to p=2 would lock in the intended "jump to the new last page" behavior and guard against a future regression to a naive page--.

}
})

watch([() => queryRet.data.value, pageSize], ([data]) => {

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.

The pageSize watch source is effectively redundant and slightly misleading. pageSize already feeds listParams, so any pageSize change triggers a refetch which updates queryRet.data.value and re-runs this watch anyway. It's also misleading because the callback destructures only [data] — the new pageSize value is never read from the args; the body reads pageSize.value directly. Consider dropping pageSize from the sources (relying on the data change), or add a brief comment explaining why it's kept.

Separately, a one-line comment on this block would help a lot: the intent (clamp back to the new last page after a deletion shrinks total) is only recorded in the PR title, and it's non-obvious that this clamps to lastPage rather than page - 1 — in the tested cases those coincide, which could tempt a future reader to "simplify" it to page.value--.

watch([() => queryRet.data.value, pageSize], ([data]) => {
if (data == null || page.value <= 1) return

const lastPage = Math.max(1, Math.ceil(data.total / pageSize.value))

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.

Minor DRY: this duplicates pageTotal (line 42, Math.ceil((…total ?? 0) / pageSize.value)) with the only difference being the Math.max(1, …) floor. You could derive one from the other, e.g. const lastPage = Math.max(1, pageTotal.value), which reads more clearly and keeps the two sites from drifting if page-size/total semantics ever change.

if (data == null || page.value <= 1) return

const lastPage = Math.max(1, Math.ceil(data.total / pageSize.value))
if (page.value > lastPage) page.value = lastPage

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.

Low-priority / informational: deleting the last remaining item on a non-first page triggers two sequential list requests — @removed refetches at the stale pageIndex (returning an empty page + reduced total), then this watch sets page.value, which changes listParams and fires a second fetch for the corrected page (plus a brief empty-page flash in legacy query mode). It converges and is bounded, so it's acceptable given how rare the trigger is; if you want to avoid the wasted request you could clamp the page in the @removed handler before refetching. A short comment acknowledging the intentional refetch-then-clamp would help future readers either way.

@codecov

codecov Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.01%. Comparing base (cc80cb7) to head (ce2c54d).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #3771      +/-   ##
==========================================
+ Coverage   59.96%   60.01%   +0.04%     
==========================================
  Files         772      772              
  Lines       27728    27732       +4     
  Branches     5231     5233       +2     
==========================================
+ Hits        16627    16642      +15     
+ Misses       9586     9582       -4     
+ Partials     1515     1508       -7     
Flag Coverage Δ
gui 59.48% <100.00%> (+0.04%) ⬆️
tools-ai 79.61% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...rc/apps/xbuilder/pages/community/user/projects.vue 91.07% <100.00%> (+14.14%) ⬆️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@nighca
nighca merged commit cf627cb into goplus:dev Nov 15, 2026
8 checks passed

This branch was successfully deployed

1 active deployment
Preview – builder — ce2c54da Deployed Nov 13, 2026 by vercel[bot]
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.

Fix misleading “No projects” state after deleting all projects on a later page

2 participants