Child Task Hierarchy

UX Spike Report — Replace subtasks with full child tasks
Spike Complete Ready for Sprint Planning M-L Effort (1.5–2 Sprints) 2026-02-27
5
Deliverables
12
Key Decisions
6
Build Phases
2
Max Depth

Executive Summary

The app currently has two hierarchy concepts: subtasks (Notion to_do blocks — lightweight checkboxes with no status, due date, or priority) and child tasks (full Task pages linked via Notion “Parent Task” self-referencing relation, with their own status, due date, priority, and categories).

Decision: Subtasks are being removed. Child tasks are the only hierarchy going forward.

This spike investigated how child tasks should work across the entire app. After competitive analysis of 6 industry leaders, wireframing 5 interactive screens, designing detailed interaction specs, and assessing technical feasibility against the existing codebase, we recommend:

  • A flat list with expand/collapse — children render inline under the parent when expanded
  • Inline quick-add for child creation in TaskDetail (no navigation required)
  • Max 2 levels of nesting (parent → child)
  • Lazy-loaded children with local cache for instant re-expand
  • Bidirectional completion cascade: auto-complete parent when all children done (with undo); confirmation sheet for completing parent with active children

The backend infrastructure is ~80% built. Estimated total effort: M-L (1.5–2 sprints), reduced from L after agreeing to disable drag on nested tasks for v1.

Spike Deliverables

#DeliverableOwnerStatusFile
1 UX Research Document UX Researcher Done child-tasks-ux-research.html
2 Interactive Wireframes UI Designer Done child-tasks-wireframes.html
3 Interaction Design Spec UX Researcher Done child-tasks-interactions.md
4 Technical Feasibility Notes Frontend Dev Done child-tasks-technical-feasibility.md
5 Acceptance Criteria Product Owner Done child-tasks-acceptance-criteria.md
6 Recommendation + Migration Plan Business Analyst Done child-tasks-recommendation.md

Key Decisions

These decisions were agreed across the team through PO review, UI designer review, and cross-deliverable synthesis.

#DecisionChoiceSource
1Max nesting depth2 levels (parent → child)Agreed Tech feasibility + Interaction spec
2Children in main listHidden by default; shown inline when parent expandedAgreed Tech feasibility
3Section groupingChildren bypass grouping — render under parent’s sectionAgreed Tech feasibility
4Auto-complete parentON by default when all children Done (5s undo toast)PO decision
5Cascade complete childrenOFF by default — confirmation sheet with explicit “Complete all”PO decision
6Child creation flowInline quick-add in TaskDetail (primary); QuickAdd with parent field (secondary)Wireframes + Interaction spec
7Drag on nested tasksDisabled for v1 — re-parenting via TaskDetailAgreed Tech feasibility
8Child loading strategyLazy with cache + loading skeleton on first expandAgreed Tech feasibility
9Default expand stateCollapsed; persisted in localStorage per viewInteraction spec
10Swipe gestures on childrenIdentical to regular tasksInteraction spec
11Detail sheet navigationStack: Parent → Child → swipe back → ParentInteraction spec
12Progress indicatorText badge only for v1 (progress ring deferred to v2)Interaction spec

UX Research — Competitive Analysis

Studied 6 task management apps across 5 dimensions: creation flow, visual nesting, completion behaviour, navigation, and mobile patterns.

Apps Analysed

AppMax DepthAuto-Complete ParentMobile UXKey Insight
Todoist4 levelsNoExcellent — dense flat rowsInline creation, indent/outdent gestures
Things 31 level (checklists)OptionalBest-in-class mobileHeadings for grouping, not deep nesting
TickTick5 levelsConfigurableGoodFlexible but overwhelming at depth
Linear1 level (sub-issues)Yes (bidirectional)Desktop-focusedBest auto-completion model — bidirectional cascade
Asana5+ levelsNoCluttered on mobilePowerful but orphaned subtask problem
NotionUnlimitedNoSlow on mobileFlexible but no task-specific UX

Key Findings

  • Optimal depth for mobile: 1–2 levels. Apps allowing 4+ levels see usability complaints on small screens. Industry is converging on shallow hierarchies.
  • Linear’s bidirectional auto-close is best-in-class. All children done → parent auto-completes. Parent completed → explicit choice for children. We adopted a hybrid of Linear’s model.
  • Inline quick-add is universal. Every app studied offers inline creation without navigating away from the parent context.
  • Todoist’s visual pattern is our baseline. Dense flat rows, 16px indentation, no connector lines — matches our existing aesthetic.
  • Full-featured children > lightweight checklists. Apps with status/date/priority on sub-items (Linear, Todoist) handle complex projects better than checkbox-only approaches (Things 3 checklists).

Wireframes

5 interactive screens designed at 375px mobile viewport, matching the Todoist-dense dark theme aesthetic. View wireframes →

Screen A — Task List (Expand/Collapse)

▶ ○ Buy groceries 2/4 Wed ← Collapsed parent ▼ ○ Plan vacation 0/2 Fri ← Expanded parent ○ Book flights Sat ← Child (16px indent) ○ Reserve hotel Sun ← Child (16px indent) ○ Call dentist Mon ← Regular task (no chevron)
  • Chevron (▶/▼) right of StatusCircle, 32×44px tap target (preserves consistent left edge)
  • Child count badge (done/total) right-aligned before due date
  • 16px indentation per level, no connector lines

Screen B — Parent Detail View

  • Child tasks section replaces deprecated SubtaskList
  • Each child shows: StatusCircle + title + due date, tappable to navigate
  • Section header: “Child tasks (2/4)” with [+ Add] button
  • [+ Add child task] inline form at bottom of child list

Screen C — Child Detail View

  • “↳ Parent Title” link at top, navigates to parent detail
  • “← Back to [Parent]” button below drag pill indicator
  • All standard task fields: status, priority, due date, categories, notes

Screen D — Create Child Task Flow

  • Option A (Recommended): Inline quick-add in TaskDetail — text input + date chip + submit
  • Option B: Full create form with pre-filled parent
  • NLP date parsing via chrono-node (same as QuickAdd)
  • Defaults: Status → Inbox, Category → inherited from parent, Priority → None

Screen E — Card Variants

  • Parent card: Chevron toggle + child count badge + progress text
  • Child card: 16px indent + “↳ Parent Name” breadcrumb
  • Standalone card: No hierarchy indicators (existing design)

Interaction Design

Completion Behaviour

ScenarioBehaviour
Complete a childChild → Done. If all siblings now Done → parent auto-completes + undo toast (5s)
Complete parent (incomplete children)Confirmation sheet: “Complete all” / “Complete only parent” / “Cancel”
Complete parent (all children Done)Immediate complete, no confirmation needed
Un-complete a childChild → Inbox. If parent was auto-completed → parent reverts to previous status
Un-complete a parentParent → Inbox. Children unchanged

Expand / Collapse

  • Tap chevron: toggle expand/collapse with 150ms stagger animation (50ms between children)
  • Tap task row: opens TaskDetail (does NOT toggle expand)
  • Pull to refresh: preserves expand/collapse state
  • Persist state in localStorage keyed by expand-state:{viewKey}

Swipe Gestures

  • Right swipe (>100px): toggle Done/Inbox — triggers auto-complete check if last incomplete child
  • Left swipe (<-100px): reschedule popup — identical to regular tasks
  • Edge case: completing last child via swipe → double haptic (child + parent)

Reordering

  • Within same parent: long-press drag, stored in localStorage (Notion relations have no sort order)
  • Between parents: NOT supported in v1 — use TaskDetail “Change parent” flow
  • Drag disabled on child rows and expanded parents (AGREED)

Navigation

  • TaskDetail maintains navigation stack: Parent → Child → swipe back → Parent
  • Swipe-down from child: returns to parent detail (not dismiss)
  • Swipe-down from parent/top-level: dismisses sheet
  • Stack depth matches 2-level nesting cap

Edge Cases

CaseHandling
Depth limit exceededAPI returns 400; “Add child task” hidden on depth-2 tasks
Circular referenceAPI validates; task picker excludes self + descendants
Orphaned children (parent deleted)Children promoted to top-level; toast notification
Offline operationsExpand/collapse local; mutations queued via mutate(); cascade evaluated client-side
>10 children in list viewShow first 5 + “Show N more” link; TaskDetail shows all (scrollable)

Technical Feasibility

What Already Exists (~80% of backend)

LayerWhat’s BuiltFile
TypesparentId, parentTitle, childCount, depth on Task interfacefrontend + backend types.ts
MappernotionToTask() reads Parent Task relationmapper.ts:23
EnrichmentqueryTasks() builds in-memory map for parentTitle, childCount, depthclient.ts:72-94
Child querygetChildTasks(parentId) via relation filterclient.ts:142-153
API endpointGET /api/tasks/:id/childrenroutes/tasks.ts:81-90
Frontend APIfetchChildTasks(taskId)api.ts:92-94
RenderingChild count badge, parent link, depth indentation, child list in detailTaskCard, TaskList, TaskDetail

Key Gaps

GapImpactFix
taskToNotionProperties() doesn’t map parentIdCannot create/update Parent Task relationAdd “Parent Task” relation mapping to mapper.ts:30-62
CreateTaskInput / UpdateTaskInput lack parentIdBackend types reject parentIdAdd parentId?: string to both
optimisticCreate hardcodes parentId: nullCannot create child tasks in UIAdd optional parentId parameter
No expand/collapse stateCan’t show/hide children in listAdd expandedTaskIds + childTasksCache to TaskContext
Section grouping splits familiesChildren sorted by date, not parentFilter parentId != null from main list; inject inline on expand

Notion API Constraints

  • Querying children: Efficient — single API call with relation filter
  • Recursive queries: Not supported — each level requires N additional calls
  • Rate limit: 3 req/s per integration — rapid expansion of 5 tasks could take ~2s
  • Page-scoped enrichment: parentTitle/childCount only accurate within 100-result query page

Recommendation

Consolidate on child tasks (full Notion pages via “Parent Task” relation). Remove subtasks (to_do blocks) entirely.

Why This Approach

  • Unified model: One hierarchy concept instead of two. Eliminates user confusion between “subtask” and “child task.”
  • Full capabilities: Child tasks have status, due date, priority, categories — unlike checkbox-only subtasks.
  • Infrastructure exists: Backend is ~80% built. The Notion “Parent Task” relation is already in use. Frontend already renders parent links, child counts, and depth indentation.
  • Industry alignment: All 6 apps studied use full-featured sub-items (or are moving toward them). Checkbox-only checklists are falling out of favour for task management.
  • Incremental delivery: 6 phases, each independently shippable. No big-bang migration required.

What NOT to Do

  • Don’t convert existing to_do subtasks to child tasks. Data model mismatch (text-only → full task), high API cost, low user value. Let them age out naturally.
  • Don’t support 3+ nesting levels. 375px viewport can’t show it usably; Notion API costs scale per level.
  • Don’t add drag-to-reparent in v1. Too error-prone on mobile. TaskDetail-based flow is safer.

Implementation Phases

Ordered by dependency. Total: ~15 working days across 2 sprints. Phases 1 & 2 can run in parallel.

Phase 1 Backend — Enable Child Task Creation S (~2 days)
TaskFile
Add parentId to CreateTaskInput and UpdateTaskInputbackend/src/types.ts
Map parentId to “Parent Task” relation in taskToNotionProperties()backend/src/notion/mapper.ts
Add depth validation (reject > 2)backend/src/routes/tasks.ts
Add circular reference validationbackend/src/routes/tasks.ts
Phase 2 Frontend — Expand/Collapse in List View M (~4 days)
TaskFile
Add expand state + child cache to TaskContextcontext/TaskContext.tsx
Persist expand state in localStoragecontext/TaskContext.tsx
Filter children from main list (!t.parentId)components/TaskList.tsx
Add chevron toggle to TaskCardcomponents/TaskCard.tsx
Render expanded children inlinecomponents/TaskList.tsx
Loading skeleton on first expandcomponents/TaskList.tsx
Animate expand/collapse (150ms stagger)components/TaskList.tsx
Phase 3 Frontend — Child Task Creation M (~3 days)
TaskFile
Add parentId to optimisticCreatecontext/TaskContext.tsx
Inline quick-add form in TaskDetailcomponents/TaskDetail.tsx
Wire NLP date parsing (reuse chrono-node)components/TaskDetail.tsx
Add “Parent” field to QuickAddcomponents/QuickAdd.tsx
Optimistic child count updatecontext/TaskContext.tsx
Phase 4 Completion Cascade Logic M (~3 days)
TaskFile
Auto-complete parent when all children Donecontext/TaskContext.tsx
Undo toast (5s timeout)new: components/UndoToast.tsx
Confirmation sheet for parent completeTaskDetail.tsx or TaskCard.tsx
Un-complete child reverts auto-completed parentcontext/TaskContext.tsx
Batch status update APIbackend/src/routes/tasks.ts
Phase 5 Deprecate SubtaskList S (~1 day)
ActionFiles to Remove / Modify
Delete SubtaskList componentDelete: SubtaskList.tsx; Remove from: TaskDetail.tsx
Remove subtask API functionsapi.ts: lines 78–115
Remove subtask backend routesroutes/tasks.ts: lines 94–150
Remove subtask client functionsclient.ts: lines 157–203
Remove Subtask typefrontend/types.ts + backend types
Phase 6 Navigation & Polish S (~2 days)
TaskFile
Navigation stack in TaskDetailcomponents/TaskDetail.tsx
“Back to [Parent]” indicatorcomponents/TaskDetail.tsx
Rename “subtask” labels to “child task”components/TaskCard.tsx
Disable drag on nested taskshooks/useDragReorder.ts
Orphan promotion on parent deletebackend/src/routes/tasks.ts

Dependency Graph

Phase 1 (Backend) ──┐ ├──▸ Phase 3 (Creation) ──▸ Phase 4 (Cascade) ──▸ Phase 5 (Deprecate) Phase 2 (Expand) ───┘ │ Phase 6 (Polish) ◂──┘

Critical path: Phase 1 → Phase 3 → Phase 4 → Phase 5. Phases 1 & 2 run in parallel.

Migration Plan

Strategy: Cut Over, Don’t Convert

Do NOT convert existing to_do block subtasks to child tasks.

  • Data model mismatch: Subtasks are text-only checkboxes. Child tasks are full pages with status, due, priority. No meaningful 1:1 mapping.
  • API cost: Converting requires N×M API calls (create page + delete block per subtask). Risky on a live Notion database.
  • Low user impact: Most subtasks are ephemeral checklists (“Pack X, pack Y”). Users can manually recreate important ones as child tasks.

Migration Phases

PhaseWhat HappensWhen
A. Soft Deprecation Relabel “Subtasks” to “Checklist (legacy)”. Both systems visible in TaskDetail. Child task UI added. Sprint 1, Week 1
B. Hard Deprecation Remove “Add subtask” input (read-only display remains). Child tasks feature-complete. Legacy section collapsed by default. Sprint 1, Week 2
C. Removal Delete SubtaskList component, all subtask API functions, backend routes, and client functions. Sprint 2, Week 1
D. Cleanup Remove Subtask type, verify tsc --noEmit, manual regression test. Sprint 2, Week 1

What Happens to Existing Subtasks

Existing Notion to_do blocks are not deleted from the Notion database. They remain as block children of their parent page. The PWA simply stops rendering them after Phase C. Users who need those items as child tasks can recreate them manually.

Risk Assessment

RiskLikelihoodImpactMitigation
Page-scoped enrichment inaccuracy Medium High Children hidden from main list (mitigates orphan display). getChildTasks() returns full data. Accept edge-case inaccuracy for users with >100 active tasks.
Rate limiting under rapid expansion Medium Medium Queue expand requests. Cache eliminates repeat fetches. Loading skeleton covers latency.
Completion cascade edge cases Low Medium Track autoCompletedParents map. Only auto-revert if parent was auto-completed (not manually).
TaskContext complexity (41+ fields) Medium Medium Consider extracting to useHierarchy hook in v2 refactor. Acceptable for v1.
Offline child creation with temp parent Low Low Defer solving. Block child creation for offline-created parents in v1.
Circular references (via direct Notion edit) Very Low Low API validates on create/update. Render as top-level with warning badge if detected.

Deferred Items

FeatureReasonTarget
Drag-to-reparentComplex mobile UX, high accidental-reparent riskv2
Grandchildren (depth 3+)375px viewport too narrow; API cost scales per levelv2+
Progress ring on StatusCircleText badge sufficient; SVG arc adds complexityv2
NLP parent assignmentAmbiguity in parent name matchingv2
Folded screen optimizationPO deferred; 375px design works on both screensFuture
Accessibility tree semanticsPO deferred for v1; basic touch targets includedFuture
Offline child creation (temp parent)Edge case; needs post-sync ID resolutionv2
Bulk child operationsSingle-task operations sufficient initiallyv2
Child task reordering across parentsRelation change + complex drag UXv2