Parent/Child Task Hierarchy

Spike Report — Investigation into hierarchical task organization
Complete Ready for Sprint Planning Notion API 2022-06-28 2026-02-17
10
User Stories
4
Phases
10
Edge Cases
1
Nesting Level

📝 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:

JSON — Notion Relation Response
"Parent Task": { "type": "relation", "relation": [{ "id": "parent-task-id" }] }

Data Model Changes

TypeScript — Task Interface Additions
export interface Task { // Existing fields (unchanged) id: string; title: string; status: TaskStatus; // ... // NEW: Hierarchy fields parentId: string | null; // Notion relation (null for root) parentTitle: string | null; // Denormalized for display childCount: number; // Number of direct children depth: number; // 0 = root, 1 = child }

API Endpoint Changes

EndpointChangeDetails
GET /api/tasksModifiedNew params: parentId, rootOnly
GET /api/tasks/:idModifiedReturns hierarchy fields
GET /api/tasks/:id/childrenNewFetch direct children with filters
POST /api/tasksModifiedAccept parentId, validate parent
PATCH /api/tasks/:idModifiedSupport reparenting + circular ref validation
DELETE /api/tasks/:idModifiedAuto-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

FeatureSubtasks (Checklist)Child Tasks (Hierarchy)
PurposeQuick checklist itemsFull tasks with lifecycle
Status✓ checked/uncheckedFull status (Inbox, Next, etc.)
Due DateNoYes
Priority/CategoriesNoYes
Data Modelto_do blocksSeparate 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

Story 1:  Backend — Task Type Includes Parent/Child Data
Story 2:  Backend — Query Tasks with Hierarchy Filters
Story 3:  Backend — Fetch Task Children Endpoint
Story 4:  Backend — Create/Update Task with Parent Validation
Story 5:  Backend — Delete Task with Orphan Handling
Story 6:  Frontend — TaskCard Shows Hierarchy Indicators
Story 7:  Frontend — TaskList Renders Tree or Flat Mode
Story 8:  Frontend — TaskDetail Shows Parent and Children
Story 9:  Frontend — Create Child Task Flow
Story 10:  Integration — End-to-End Hierarchy Flow

🚀 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

RiskImpactLikelihoodMitigation
Circular relationsHighLowValidate on create/update: walk parent chain, detect cycles
Orphaned childrenMediumMediumAuto-unparent children before deleting parent
Deep nestingMediumMediumEnforce max depth = 1 in backend validation
N+1 query perfHighHighBatch parent lookups + aggressive caching
Offline queue orderingMediumLowv1: Disallow offline child creation if parent doesn't exist
Cache invalidationMediumMediumStart with invalidateAll(), optimize later
Subtask vs child confusionHighHighClear visual distinction + help tooltip
Mobile screen clutterMediumMediumLimit indent to 16px, provide flat mode alternative
Rate limit impactMediumMediumCache aggressively, batch requests

🧩 Edge Cases

#ScenarioHandling
1Circular relations (A→B→A)Backend validates by walking parent chain. Return 400 CIRCULAR_HIERARCHY
2Orphaned childrenAuto-unparent before archive. Show "[Deleted]" for manual Notion deletions
3Deep nesting (3+ levels)Backend enforces max depth. Return 400 MAX_DEPTH_EXCEEDED
4Complete parent with childrenv1: Warning toast. Phase 2: Confirmation dialog + auto-complete
5Offline queue orderingv1: Disallow offline child creation. v2: Dependency tracking
6Filter shows child but not parentShow child with breadcrumb. In tree mode, show dimmed parent
7Very long parent titlesTruncate to ~20 chars with ellipsis
8Complete parent, children incompleteAllow with warning toast. No auto-complete in v1
9Recurrence + hierarchyNew recurring parent without children. Child recurrence under same parent
10Search with hierarchyShow 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

  1. Max nesting depth: 1 or 2? — Recommend start with 1, expand later
  2. Denormalize parentTitle via Rollup? — API may not support creating Rollups; cache instead
  3. Multiple parents per task? — Enforce single parent (simplifies UI)
  4. Persist expand/collapse state? — Start session-only, add LocalStorage if requested
  5. Visual connector lines on mobile? — Likely too cluttered; test both
  6. Delete parent behavior? — Auto-unparent children (Option C) with confirmation
  7. Auto-complete children on parent done? — Phase 1: warning only. Phase 2: confirmation dialog
  8. Show parent when children match filter? — Yes, dimmed/collapsed with children highlighted
  9. Concurrent reparenting conflicts? — Last write wins; future conflict resolution UI
  10. 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