Skip to content

fix: add pagination to user, project, and subtask lists - #493

Merged
pikann merged 2 commits into
masterfrom
fix/paginated-list-caps
Sep 14, 2026
Merged

pikann merged 2 commits into
masterfrom
fix/paginated-list-caps

Conversation

@pikann

@pikann pikann commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #492 — the admin Users page fetched a paginated list but never advanced past page 1 (the page setter was declared but never used), so accounts beyond the first 20 were invisible with no page controls to reach them, even though the backend already supported real pagination and the UI displayed the true total.

An audit for the same pattern elsewhere in the app found it in three more places, all fixed here.

What was broken

  • Admin Users (/admin/users): const [page] = useState(1) — the setter was unused, so the page was permanently stuck at 1.
  • Projects list (home dashboard grid, sidebar project switcher): projectsQueryOptions(page=1, pageSize=50) was always called with defaults and never advanced — same shape of bug, capped at 50 projects, with the true total shown right next to the truncated grid.
  • Task detail Subtasks panel: listSubtasks hardcoded page: 1, page_size: 200 and discarded the response's cursor, so a task with more than 200 direct children silently hid the rest.
  • Home page "My Tasks" widget: used the same capped projects query just to resolve project names for group headers, falling back to a raw project id past the 50th project.

Also found and fixed an adjacent bug the Users page surfaced once paging actually worked: the "must change password" stat was computed by filtering the current page's users instead of a system-wide count, so it would have started fluctuating as an admin paged through users.

Fix

  • Added a reusable Pagination component (page-number style) for the admin Users table — fits a structured data table with row actions better than infinite scroll, matching typical admin UX.
  • Added projectsInfiniteQueryOptions and subtasksInfiniteQueryOptions (cursor-paginated), mirroring the app's existing picker/board conventions, and wired a "Load more" button / scroll handler into the home grid, sidebar switcher, and Subtasks panel.
  • Added CountUsersMustChangePassword through the repository → service → handler layers plus a must_change_password_count field on the paginated response, so the stat stays accurate regardless of which page is showing.
  • Audited every other paginated endpoint in the app (every backend parsePage/parsePageSize/pagingParams call site cross-referenced against every frontend infiniteQueryOptions consumer) — confirmed notifications, agent activity, conversations, the actual task board/backlog view, and the epic/task pickers all already wire fetchNextPage to real UI.
  • Added translations for the new UI strings across all 9 supported locales (a dedicated test enforces key parity across languages).

Test plan

  • tsc -b and biome check pass
  • go build ./..., go vet ./..., gofmt, and go test ./... all pass
  • Full frontend test suite passes (695 tests), including new coverage for Pagination, projectsInfiniteQueryOptions, subtasksInfiniteQueryOptions, and the must-change-password count
  • Verified live against a local dev stack: seeded 35 users / 56 projects and confirmed page 2 returns the remaining rows with total unchanged; created a throwaway task with 3 subtasks and confirmed cursor pagination returns the correct items across pages, ending in next_cursor: null; confirmed must_change_password_count reflects a true workspace-wide count (18 of 22 real users) regardless of which page is displayed

🤖 Generated with Claude Code

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

Important

The background drain in AssignedTasksList shares the ["projects","all"] infinite-query cache with the home grid, so it loads every project page and effectively disables the grid's new "Load more projects" button. The same effect also re-fires without bound when a later page fetch fails.

Reviewed changes

  • Workspace-wide must-change-password countCountUsersMustChangePassword added to the user repo/service interfaces plus the Postgres implementation, and exposed as PagedUsersResponse.MustChangePasswordCount; the admin stats bar no longer approximates this from the current page.
  • Pagination primitive — new components/ui/pagination.tsx (collapsing page range, prev/next, aria-current, i18n) wired into the admin users route, which gains real page state, keepPreviousData, and a snap-back effect for a shrinking result set.
  • Project list infinite paginationprojectsInfiniteQueryOptions (["projects","all"], 50/page) replaces the single-page project fetch across the home grid, the sidebar switcher, and the assigned-tasks widget, with a load-more button on the home grid.
  • Subtask cursor paginationlistSubtasks now returns TaskListResult and pages via next_cursor at 50/page behind subtasksInfiniteQueryOptions; the unused epicChildTasksQueryOptions was removed and the subtasks section gained a load-more affordance.
  • i18n and tests — new pagination/load-more keys across all 9 locales; pagination.test.tsx, subtasks-section.test.tsx, and API-test updates.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread apps/web/src/components/home/assigned-tasks-list.tsx Outdated

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

✅ No new issues found.

Reviewed changes — the single commit pushed since the prior pullfrog review (4c2fce38), which addresses both findings from that review.

  • Added projectsLookupInfiniteQueryOptions — the assigned-tasks widget now pages the project list under its own ["projects","all","lookup"] cache key, so its eager background drain no longer exhausts the ["projects","all"] cache shared with the home grid and sidebar switcher.
  • Gated the widget's drain effect on !isFetchNextPageError — a failed next-page fetch no longer re-fires fetchNextPage in an unbounded loop, since hasNextPage stays true after a failure.
  • Extracted shared fetchProjectsPage / getNextProjectsPageParam helpers so both project infinite-query options page identically.
  • Added regression coverage: the widget test asserts the drain makes exactly one failed attempt, and project-api.test.ts asserts the lookup key is distinct from the browsable key.

The Go must-change-password count path, the Pagination primitive, subtask cursor paging, and i18n parity were covered by the prior review and are unchanged here.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pikann
pikann merged commit 37e2e73 into master Sep 14, 2026
7 checks passed
@pikann
pikann deleted the fix/paginated-list-caps branch September 14, 2026 15: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.

[Bug] Users beyond the first 20 cannot be accessed from the user list

1 participant