Skip to content

fix(taskmanager): drain all pages for workspace/board snapshots (#272)#275

Open
ProfSynapse wants to merge 2 commits into
mainfrom
fix/issue-272-taskboard-pagination
Open

fix(taskmanager): drain all pages for workspace/board snapshots (#272)#275
ProfSynapse wants to merge 2 commits into
mainfrom
fix/issue-272-taskboard-pagination

Conversation

@ProfSynapse

Copy link
Copy Markdown
Owner

Summary

Fixes the task-board / workspace-summary undercount reported in #272. Counts and status breakdowns were computed from a single 200-row page while totals reported the true count — so workspaces with >200 tasks (and archived-project tasks consuming page slots) showed wrong numbers.

Addresses #272. (Leaving the issue open for the author to close after manual verification.)

Root cause (verified against current main)

  • BaseRepository.queryPaginated hard-caps pageSize at Math.min(pageSize ?? 25, 200) with plain LIMIT/OFFSET. Requests for pageSize: 10000 silently clamp to 200, but totalItems stays the true count.
  • TaskService.getWorkspaceSummary and TaskBoardDataController.loadBoardData both treated that one clamped 200-row page as the whole set, computing per-project counts / byStatus / next-actions / recently-completed from ≤200 rows. Archived-project filtering happened after the fetch (truncate-before-filter), so archived tasks burned page slots.

Fix

  • Both consumers now drain all pages ({page, pageSize: 200} loop until !hasNextPage) via small helpers (collectAllPages / loadAllPages).
  • Archived-project tasks are excluded at the task level: a task is visible iff it has no owning project or its project is not archived. TaskBoardDataController filters archived projects first, then drains only visible projects' tasks (archived-project tasks are never fetched).
  • BaseRepository's 200 cap is left intact (sane per-query cap); no cursor pagination introduced.

Intentional semantic notes (please read)

  1. tasks.total is now visible-only (visibleTasks.length) rather than the all-task totalItems, so it is consistent with byStatus/nextActions which are also visible-only.
  2. Asymmetry: projects.total stays the true count (incl. archived); only tasks.total is visible-only. Documented at the return site. If the project header should also be visible-only, that's a scoped follow-up.

Tests

6 regression tests + 2 added in review: >200 task-page drain, >200 project-page drain, archived-before-snapshot, all-projects-archived → orphan-only visibility, projects/tasks total asymmetry, subtask parity, board orphan-task absence, and the restored note-link .catch path on a visible task.

Verification

  • npm run build clean · npm run lint clean
  • Full jest: 3706 pass / 21 skip / 1 fail — the lone failure is the pre-existing, documented TaskBoardEditCoordinator jsdom-Modal issue (untouched files; not a regression).

Non-blocking follow-ups (from peer review)

  • Board double-fetches note links (listTasks already returns enriched TaskWithNoteLinks, then loadBoardData discards and re-fetches via getNoteLinks). Correctness fine; minor wasted work. Tracked as a follow-up, not changed here.
  • OFFSET drain → cursor pagination only if workspaces ever reach tens of thousands of tasks.

Review

Peer-reviewed (APPROVE, 0 blocking): docs/review/issue-272-fix-review-2026-06-22.md, docs/review/test-coverage-271-272-2026-06-22.md.

🤖 Generated with Claude Code

ProfSynapse and others added 2 commits June 22, 2026 10:56
Workspaces with >200 tasks undercounted in loadWorkspace summary and the
Task Board. BaseRepository.queryPaginated hard-caps pageSize at 200, so a
single large-pageSize request (pageSize:1000/10000) silently returned only
the first 200 rows while totalItems still reported the true count — making
byStatus/nextActions/recentlyCompleted/per-project counts inconsistent with
the reported totals.

Fix (keeps the 200 cap intact — the bug was consumers treating one page as
the whole set):
- TaskService.getWorkspaceSummary: drain all project + task pages via a new
  collectAllPages helper, then compute every count from visibleTasks
  (tasks whose owning project is not archived). Filtering at the TASK level
  (visible iff no owning project OR project not archived) subsumes the
  zero-project edge: archived-project tasks are never counted, genuinely
  orphan tasks stay visible.
- TaskBoardDataController.loadBoardData: drain all project pages, filter
  archived projects FIRST, then per-visible-project drain listTasks with
  includeSubtasks:true (preserves the prior subtask-inclusion default).

Intentional semantic change (user-confirmed, #272): getWorkspaceSummary
tasks.total flips from all-task totalItems -> visibleTasks.length so it is
consistent with byStatus. projects.total stays the TRUE count (includes
archived) — #272 is a task-undercount bug; flipping projects.total would be
unflagged scope expansion. Asymmetry documented at the return site.

Regression tests cover the >200 drain (both task and project pages),
archived-before-snapshot exclusion, all-projects-archived orphan visibility,
the projects/tasks total asymmetry, and subtask parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01NxKeRz1gihguL9wcidm78m
…atch coverage (#272)

Closes two non-blocking peer-review coverage gaps on the #272 fix (TEST-ONLY):

1. Board-vs-summary orphan divergence was unpinned. TaskBoardDataController
   loads tasks per-visible-project and filters on projectMap, so orphan /
   archived-parent tasks can never reach the board (unlike getWorkspaceSummary,
   which keeps orphans visible). New test seeds a task whose projectId is not a
   visible project and asserts it is ABSENT from the board result.

2. The note-link-failure .catch path (TaskBoardDataController:189) lost coverage
   when its rejection case was tied to a now-unfetched archived task. New test
   rejects getNoteLinks for a VISIBLE task and asserts the task still appears
   with noteLinks: [].

No production-code changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01NxKeRz1gihguL9wcidm78m
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