Parent/Child Task Hierarchy
📝 Executive Summary
We investigated adding parent/child task hierarchy to the Notion PWA Task Manager. After researching industry best practices (Todoist, Linear, Asana, TickTick, Things 3), analyzing the Notion API's self-referencing relation capabilities, and designing mobile-first UI patterns, we recommend implementing a 1-level hierarchy (parent → child) with bidirectional auto-completion (like Linear).
This approach balances user needs with implementation simplicity, leverages our existing Notion database structure (which already has a "Parent Task" relation property), and preserves our mobile-first UX.
🔍 UX Research Findings
Key Findings from Industry Leaders
- Optimal Nesting Depth: Industry converges on 1-2 levels maximum for mobile UX. Linear and Todoist intentionally limit to 1-2 levels; TickTick/Asana allow 5-6 but users cite this as overwhelming.
- Visual Hierarchy: Successful apps combine indentation (16-24px) + chevron icons (▶/▼) + subtle color/background.
- Completion Cascading: Linear's bidirectional auto-completion is emerging as best practice.
- Filter/Search: Show orphaned children in results with parent breadcrumbs.
- Mobile: Touch targets ≥44×44pt, instant expand/collapse, limit visible hierarchy to 5-7 items.
Common Pitfalls to Avoid
- ❌ Deep nesting (>2 levels) — poor mobile UX
- ❌ No completion cascading — manual tracking overhead
- ❌ Hiding children in search — breaks discoverability
- ❌ Lag during expand/collapse — frustrates users
Recommended Patterns
- Start with 1 level, expand to 2 if demand justifies
- Implement bidirectional auto-completion with clear UI feedback
- Use standard patterns (chevrons, indentation) users recognize
- Preserve scroll position during interactions
⚙️ API Design
Notion API Relation Properties
Our database already has a "Parent Task" self-referencing relation property. The API returns relations as arrays of page IDs:
Data Model Changes
API Endpoint Changes
| Endpoint | Change | Details |
|---|---|---|
| GET /api/tasks | Modified | New params: parentId, rootOnly |
| GET /api/tasks/:id | Modified | Returns hierarchy fields |
| GET /api/tasks/:id/children | New | Fetch direct children with filters |
| POST /api/tasks | Modified | Accept parentId, validate parent |
| PATCH /api/tasks/:id | Modified | Support reparenting + circular ref validation |
| DELETE /api/tasks/:id | Modified | Auto-unparent children before delete |
Performance Considerations
- Batch parent lookups: Collect unique parentIds, fetch in parallel with Promise.all
- Cache aggressively: Parent titles (TTL 300s), child counts (TTL 60s), depth (TTL 300s)
- Rate limit impact: ~2-3x increase mitigated by caching → ~1.2x after warm-up
🎨 UI Design
Component Changes
TaskCard
- Parent tasks: Show child count badge 👥 3
- Child tasks (All view): Indent 16px with subtle border-left connector
- Child tasks (Today view): Flat with parent breadcrumb ↩ Parent Task
TaskList
- All view (Tree mode): Parent tasks at root, expand/collapse chevrons (▶/▼)
- Today view (Flat mode): All tasks top-level with parent breadcrumbs
TaskDetail
- Parent section with tappable link + remove button
- Children section with list + "Add child" button
- Clear separation between Subtasks (checklist) and Child Tasks (full tasks)
Subtask vs Child Task
| Feature | Subtasks (Checklist) | Child Tasks (Hierarchy) |
|---|---|---|
| Purpose | Quick checklist items | Full tasks with lifecycle |
| Status | ✓ checked/unchecked | Full status (Inbox, Next, etc.) |
| Due Date | No | Yes |
| Priority/Categories | No | Yes |
| Data Model | to_do blocks | Separate pages with relation |
✅ Recommended Approach
1-Level Hierarchy (parent → child)
- Simplest implementation with Notion's database structure
- Follows Linear's proven model (engineering-focused, mobile-first)
- Sufficient for 80% of task management use cases
- Avoids mobile UX complexity of deep nesting
- Can expand to 2 levels later if demand justifies
Bidirectional Completion Cascading (Linear-style)
- Parent done → Children done: Confirmation dialog, then mark all as Done. Allow undo.
- Children done → Parent done: Auto-complete parent with subtle toast. No confirmation needed.
- v1 scope: Parent → children only (with confirmation). Children → parent is Phase 2.
📋 User Stories
🚀 Implementation Phases
Phase 1: Backend Foundation (Sprint N)
Update Task type, add hierarchy fields, modify endpoints, add validation (circular refs, max depth), implement batch lookups and caching. Write unit + integration tests.
Phase 2: Frontend Display (Sprint N+1)
Show hierarchy in task list and detail views (read-only). Tree rendering with expand/collapse in All view, flat with breadcrumbs in Today view. Animations and visual regression tests.
Phase 3: Full CRUD (Sprint N+2)
Create ParentPicker modal. Enable creating, linking, unlinking parent/child tasks from TaskDetail and QuickAdd. Optimistic updates, validation error handling, E2E tests.
Phase 4: Completion Cascading (Sprint N+3) — Optional
Auto-complete children when parent done (with confirmation). Auto-complete parent when all children done (with toast). Undo affordance, preference toggle.
⚠️ Risks & Mitigation
| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| Circular relations | High | Low | Validate on create/update: walk parent chain, detect cycles |
| Orphaned children | Medium | Medium | Auto-unparent children before deleting parent |
| Deep nesting | Medium | Medium | Enforce max depth = 1 in backend validation |
| N+1 query perf | High | High | Batch parent lookups + aggressive caching |
| Offline queue ordering | Medium | Low | v1: Disallow offline child creation if parent doesn't exist |
| Cache invalidation | Medium | Medium | Start with invalidateAll(), optimize later |
| Subtask vs child confusion | High | High | Clear visual distinction + help tooltip |
| Mobile screen clutter | Medium | Medium | Limit indent to 16px, provide flat mode alternative |
| Rate limit impact | Medium | Medium | Cache aggressively, batch requests |
🧩 Edge Cases
| # | Scenario | Handling |
|---|---|---|
| 1 | Circular relations (A→B→A) | Backend validates by walking parent chain. Return 400 CIRCULAR_HIERARCHY |
| 2 | Orphaned children | Auto-unparent before archive. Show "[Deleted]" for manual Notion deletions |
| 3 | Deep nesting (3+ levels) | Backend enforces max depth. Return 400 MAX_DEPTH_EXCEEDED |
| 4 | Complete parent with children | v1: Warning toast. Phase 2: Confirmation dialog + auto-complete |
| 5 | Offline queue ordering | v1: Disallow offline child creation. v2: Dependency tracking |
| 6 | Filter shows child but not parent | Show child with breadcrumb. In tree mode, show dimmed parent |
| 7 | Very long parent titles | Truncate to ~20 chars with ellipsis |
| 8 | Complete parent, children incomplete | Allow with warning toast. No auto-complete in v1 |
| 9 | Recurrence + hierarchy | New recurring parent without children. Child recurrence under same parent |
| 10 | Search with hierarchy | Show both parent and child. Auto-expand parent in tree mode |
🚫 Out of Scope (v1)
- Drag & drop reordering
- 3+ nesting levels
- Multiple parents per task
- Granular permissions
- Template support (pre-defined children)
- Bulk reparenting
- Hierarchy analytics
- Visual connector lines
- Collapse all / Expand all buttons
- Keyboard shortcuts for hierarchy navigation
- LocalStorage persistence of expand/collapse state
- Advanced completion cascading
❓ Open Questions
- Max nesting depth: 1 or 2? — Recommend start with 1, expand later
- Denormalize parentTitle via Rollup? — API may not support creating Rollups; cache instead
- Multiple parents per task? — Enforce single parent (simplifies UI)
- Persist expand/collapse state? — Start session-only, add LocalStorage if requested
- Visual connector lines on mobile? — Likely too cluttered; test both
- Delete parent behavior? — Auto-unparent children (Option C) with confirmation
- Auto-complete children on parent done? — Phase 1: warning only. Phase 2: confirmation dialog
- Show parent when children match filter? — Yes, dimmed/collapsed with children highlighted
- Concurrent reparenting conflicts? — Last write wins; future conflict resolution UI
- Default task list: all or roots only? — Keep all for compatibility, add rootOnly=true
📚 Sources
UX Research
- Todoist — Introduction to sub-tasks
- Things 3 — Beauty and Delight in a Task Manager (MacStories)
- TickTick — How Task Nesting Helps with GTD
- Asana — Hierarchy: Understanding and Implementing Task Organization
- Linear — Parent and sub-issues
- Nielsen Norman Group — Visual Indicators to Differentiate Items in a List
API Research
- Notion API — Query a database, Page properties
- Notion API — Version 2022-06-28 changelog
- Notion Self-Referential Filters & Templates Guide
UI Design
- Mobile Navigation UX Best Practices
- List UI design: principles and examples (Justinmind)
- Filter UX Design Patterns & Best Practices