Skip to content

fix(tasks): count the rows a project lists, and let dialogs shrink - #1880

Merged
h4yfans merged 2 commits into
mainfrom
task-count-rows-and-dialog-overflow
Aug 27, 2026
Merged

fix(tasks): count the rows a project lists, and let dialogs shrink#1880
h4yfans merged 2 commits into
mainfrom
task-count-rows-and-dialog-overflow

Conversation

@h4yfans

@h4yfans h4yfans commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Closes #1878

Summary

Two defects from one user report.

1 — a project showed three different task numbers at once

The Inbox project's tab bar read Tasks 130, the sidebar read 93, and the sections under the 130 read TO DO (0), IN PROGRESS (0), DONE (37).

None of the three was the number of rows on screen, because each counted a different set:

counted
Tasks tab (useProjectHub) every non-archived task with the project id
Sidebar (getTaskWorkspaceCounts) the same, minus the done ones
The tab itself top-level tasks bucketed into the project's own statuses

So both badges counted subtasks, which render nested under a parent rather than as rows of their own, and both counted tasks whose statusId is no longer one of the project's statuses.

That second group is the worse half. groupTasksByStatus matched them to no bucket and dropped them, so they were invisible and uneditable while every badge kept counting them. 130 − 37 = 93 is not a coincidence: the two badges were reporting on tasks the tab could not show.

Both badges now count top-level tasks — the tab all of them, the sidebar the open ones — so the tab badge equals its own sections added together, and the sidebar badge is that minus the done ones. The first status adopts tasks whose status is gone, so nothing on file is unreachable and nothing counted is unseeable. The project picker in the Tasks toolbar was counting archived tasks; it now excludes them, as the sidebar already did.

2 — a long task title carried the bulk-delete confirm button off the window

DialogContent is a CSS grid. A grid track sizes to its content's minimum, so one long unbreakable title widened the single column past max-w-lg; every row stretched to that track, and the footer's end-aligned buttons rode along with it. truncate on the title could not help, because a flex item will not shrink below its content without min-w-0.

Clamping the track with grid-cols-[minmax(0,1fr)] fixes it for every dialog in the app, not only this one. The bulk-delete row gets the min-w-0 its truncate was always missing.

Release note

Project task counts now match the list they label: subtasks count under their parent instead of separately, and a task whose status was deleted shows up in the project's first status instead of disappearing. Deleting several tasks at once no longer pushes the confirm button off the window when one of them has a long title.

Test plan

Bug 2, measured in a real browser. jsdom does no layout, so the claim was checked in headless Chromium at 1280px against the exact pre- and post-fix markup, with the title from the report repeated three times:

broken { dialogWidth: 512, confirmRight: 2704, insideViewport: false }
fixed  { dialogWidth: 512, confirmRight:  871, insideViewport: true  }

The dialog itself never exceeds 512px in either case — it is the track and the footer that escape it, which is why the button leaves the window while the dialog looks fine.

Bug 1, pinned to the render path. The new useProjectHub test builds a project holding an open parent, its subtask, a done task, a task with a deleted status, and an archived task, then asserts the badge equals the rows flattenTasksByStatus actually emits — so the badge cannot drift from the list again without going red.

Both halves of that test were mutation-checked: restoring the old count gives expected 4 to be 3, and removing the orphan-status adoption gives expected [ …(2) ] to have a length of 3 but got 2.

The badge-vs-filter equivalence matrix in task-workspace-counts.test.ts is unchanged in shape; its expectation now states the top-level rule explicitly and every count in it moved accordingly.

pnpm exec vitest --project renderer
  Test Files  692 passed (692)
       Tests  8410 passed | 7 skipped (8417)

typecheck:web, typecheck:test, eslint on the diff, git diff --check, docs:impact --base origin/main --strict, docs:build all pass. pnpm typecheck fails on origin/main before this branch exists, in the architecture boundary check (apps/mobile/vitest.config.ts -> node:url, arrived with #1863); it needs its own fix.

One note for anyone who sees a red local run: 35 renderer suites fail on Node 26 with Cannot read properties of undefined (reading 'clear'), because jsdom leaves localStorage undefined there. The repo asks for Node 24 (engines), and on Node 24 every one of them passes. Nothing to do with this branch, but it looks alarming.

Caveats

A subtask whose parent no longer exists in the task list still renders nowhere, and is now no longer counted either. It was unreachable before this change and remains so; making orphaned subtasks recoverable is a separate piece of work.

A project showed three different task numbers at once: 130 on the Tasks
tab, 93 in the sidebar, and TO DO (0) / IN PROGRESS (0) / DONE (37) in
the sections under the badge that was claiming 130.

None of them was the number of rows on screen. The hub badge counted
every non-archived task carrying the project id; the sidebar badge
counted the non-done ones. Both included subtasks, which render nested
under a parent rather than as rows, and both included tasks whose status
the project no longer has — which `groupTasksByStatus` matched to no
bucket and dropped, leaving them invisible and uneditable while every
badge kept counting them.

Both badges now count top-level tasks, and the first status adopts
tasks whose status is gone, so nothing is counted that cannot be seen
and nothing on file is unreachable. The project picker drops archived
tasks for the same reason the sidebar already did.

Separately: multi-select delete with a long task title carried the
confirm button off the dialog, and off the window entirely once the
title was long enough. `DialogContent` is a grid, and a grid track sizes
to its content's minimum, so one unbreakable string widened the single
column past `max-w-lg` and the footer's end-aligned buttons rode along.
Clamping the track with `minmax(0,1fr)` lets `truncate` do its job, in
every dialog rather than only this one.

Closes #1878
@github-actions

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit c9df123.

@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation test labels Aug 27, 2026
@h4yfans
h4yfans marked this pull request as ready for review August 27, 2026 23:33
@h4yfans
h4yfans merged commit c37c8e4 into main Aug 27, 2026
16 checks passed
@h4yfans
h4yfans deleted the task-count-rows-and-dialog-overflow branch August 27, 2026 23:33
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project task badge counts more than the tab lists; bulk-delete dialog pushes its confirm button off screen

1 participant