Repository navigation
feat: add subtask management features to view settings and API - #625
Conversation
- Introduced subtask mode options in the view settings panel for "Table" and "Roadmap" layouts, allowing users to choose between collapsed, expanded, or hidden views for subtasks. - Updated internationalization files to include new translations for subtask-related labels and messages in English, Spanish, French, Japanese, Korean, Portuguese, Russian, Vietnamese, and Chinese. - Enhanced the Task and ViewConfig interfaces to include subtask counts and modes, facilitating better task management and display. - Implemented backend logic to support subtask counting and filtering, ensuring that subtasks can be excluded from task lists based on user preferences. - Added tests to verify the correct functionality of subtask counting and filtering in the task repository.
There was a problem hiding this comment.
Important
The new subtask_mode setting cannot be saved back to "Nested, collapsed" as a personal override while the shared view is "Nested, expanded" — the choice is silently discarded. One other lower-severity concern inline, plus a cache-invalidation note below.
Reviewed changes
Reviewed the full PR (33 files, 1 commit) end to end.
- API subtask counts and filter —
ListTasksnow attachessubtask_count/subtask_done_countper task through one batched query, and a newexclude_subtasksfilter omits tasks whose live parent is a non-Epic task; applied to the list and count endpoints. - View config
subtask_mode— newcollapsed/expanded/hiddenfield threaded through the entity, DTOs, and the per-user override merge/diff. - Web nested rows —
TaskRowgains a chevron, progress chip, and indentation; newSubtaskRowslazily fetches and recurses over children with the view's filters/sort and an inline "Add subtask"; the Timeline gets the same viaRoadmapChildRows. - View settings and i18n — a Subtasks selector for Table/Roadmap views, translations in 9 locales, and per-user/per-view expansion remembered in
localStorage. - Tests — Go repository tests for counts and
exclude_subtasks, web unit tests, and a new e2e feature + spec.
ℹ️ Nested rows and the parent chip aren't refreshed by the task-detail modal
The new nested children query (childTasksInfiniteQueryOptions, key ["projects", id, "tasks", parentId, "children", filters]) is only invalidated through the broad ["projects", id, "tasks"] prefix from SubtaskRows.handleAdd, the list's field/status edits, and realtime. The task-detail modal's subtask callbacks still invalidate only the exact subtasksInfiniteQueryOptions(...).queryKey, so editing or adding a child there leaves the nested rows and the parent's done/total chip stale until a realtime task.* event (or the 15s staleTime) refetches them. If the acting client doesn't receive its own realtime event, this is user-visible.
Technical details
# Detail-modal subtask edits don't invalidate the nested child query
## Affected sites
- `apps/web/src/components/projects/interactions/task-detail/index.tsx:464-487` — `onSubtaskUpdate` / `onSubtaskCreate` invalidate the exact key `["projects", projectId, "tasks", task.id, "subtasks"]`.
- `apps/web/src/lib/interaction-api.ts:903-916` — the nested query uses the distinct key `[..., parentId, "children", filters]`, which the exact key does not match.
- `apps/web/src/components/projects/interactions/interaction-layout.tsx` — the parent's `subtask_count` / `subtask_done_count` chip lives on the top-level list (`allTasksQueryOptions`), also not matched by the exact key.
## Required outcome
A subtask change made from the detail modal should refresh both the nested rows and the parent's progress chip without relying on a realtime round-trip.
## Suggested approach
Change the two modal invalidations to the prefix `{ queryKey: ["projects", projectId, "tasks"] }`, matching the pattern already used by `SubtaskRows.handleAdd`.deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the delta from 24b1c1c to 8943b40; the prior review's three findings (one important, two informational) are all addressed, and no new issues were introduced.
- Persisted an explicit "Nested, collapsed" personal override — a new
pickSubtaskModehelper stores the literal"collapsed"when the shared view is"expanded"/"hidden"(and only leaves it unset when the shared default is already collapsed), fixing the silent discard, with unit tests inpick-subtask-mode.test.ts. - Scoped the new parent-based reads to the project —
exclude_subtasksnow requiressp.project_id = tasks.project_id, andattachSubtaskCountsjoins the parent on a matchingproject_id; the repository tests add a cross-project child to cover both paths. - Widened the detail modal's subtask invalidation —
onSubtaskUpdate/onSubtaskCreatenow invalidate the["projects", projectId, "tasks"]prefix, so the nested rows and the parent's progress chip refresh without waiting on a realtime round-trip.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…guration normalization
There was a problem hiding this comment.
Important
The new Epic exclusion in attachSubtaskCounts zeroes subtask_count for Epic parents, which is exactly the signal the Timeline (Roadmap) view reads to render the chevron/chip and to expand an Epic into its child tasks. Because the Timeline's default view lists Epics only, its nested-child feature is now unreachable — and the PR's own e2e scenario "An Epic shows its child count and expands to its tasks" would fail.
Reviewed changes
Reviewed the delta from 8943b40 to 3327b82 (Go repository/service/DTO/handler, the web subtask store and API, docs, and a migration). One must-address regression, flagged inline.
- Sprint-scoped
exclude_subtasks— theNOT EXISTSnow requires the parent to share the child's sprint (sp.sprint_id IS NOT DISTINCT FROM tasks.sprint_id), so a child whose parent is in another sprint stays in the flat list. - Epics excluded from
attachSubtaskCounts— the batched count query now skips Epic parents, anddocs/api/http-design.mdstates Epics report nosubtask_count. - View config normalization —
normalizeViewConfigdrops unknownsubtask_modevalues on create, update, and the per-user override path. - Filter/DB plumbing —
exclude_subtasksis ignored whenparent_task_idis set, a partial index ontasks(parent_task_id)is added, and the Go tests cover the new sprint scope and the Epic skip. - Web — the inline "Add subtask" now inherits the parent's sprint; the manual expand/collapse record drops an entry once it matches the view default.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the delta from 3327b82 to d331f33; the prior review's must-address regression is fixed and no new issues were introduced.
- Restored Epic
subtask_counton the server — the Epic predicate was removed fromattachSubtaskCounts, so Epics again report their child counts and the Timeline's chevron/chip and child expansion keep working; the Go test now asserts an Epic count of 1. - Moved the double-display guard to the Table client —
ListGroup.renderTaskRowzeroes the counts for a row whose type is the system Epic, since an Epic's children are already listed flat there, so the Table shows no chevron/chip for Epics without touching the server count the Timeline depends on. - Updated the API docs —
docs/api/http-design.mdno longer claims Epics report nosubtask_count.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Summary
Shows subtasks hierarchically in the Table (product backlog, sprints) and Timeline views, as requested in #622. Parent rows get a chevron and a
done/totalprogress chip; expanding a row loads its children on demand, indented under the parent, with the view's filters and sort applied. Board cards show the same chip but stay flat.Closes #622
What changed
API (
services/api)subtask_countandsubtask_done_count(live direct children; children in adone-category status), computed in one batched query per page.exclude_subtasks=truelist filter: omits tasks whose parent is a live non-Epic task, so a subtask is not shown both nested and as its own row. Tasks that only belong to an Epic stay in the list. It also applies to the count endpoint.parent_task_idlisting (already supported) keeps ignoring the sprint filter while status/assignee/sort filters apply; the web client relies on that.subtask_mode(collapsed|expanded|hidden; empty meanscollapsed), included in the per-user override merge/diff logic.docs/api/http-design.mdupdated.Web (
apps/web)TaskRowgets the chevron, progress chip and indentation (capped at 3 levels, tighter on mobile);SubtaskRowslazily fetches children with loading, error/retry, load-more, "N of M match the current filters" and an inline "Add subtask" row.localStorage.Behaviour to be aware of
Subtasksystem type, so subtasks are ordinary tasks linked byparent_task_id; nesting works off that link.Type of Change
Testing
exclude_subtasks.subtask-rows.test.tsx,roadmap-nesting.test.tsx; full suite,tsc -band biome pass.features/projects/subtask-hierarchy.featureandtests/projects/subtask-hierarchy.spec.ts(24 tests, passing on chromium and mobile-chrome against the e2e stack); the existing interaction-views, view-settings and timeline specs still pass.Checklist
🤖 Generated with Claude Code