GTD

A Getting Things Done inbox application built as a monorepo with two platform implementations: a React/TypeScript web app and a native SwiftUI iOS app. Repo: cuddly-octo-happiness. Current version: v1.6 (complete — all 21 phases shipped).

Overview

Implements the GTD capture-and-process workflow. Users capture inbox items, then triage them by swiping or categorizing into four buckets: tasks, projects, reference, and ideas. An embedded Eliza-based chat interface (added in v1.6) provides a conversational assistant with 5 swappable personas for context-aware coaching during review.

Key design choices: local storage only (no backend, privacy-first, offline), separate Capture and Review tabs (different mental modes), swipe-to-categorize gestures (react-swipeable, 50px threshold), snooze with 3-strike auto-delete (prevents inbox stagnation), and one-way push integration to Linear/Plane/Peppermint (no sync complexity).

Architecture

Web (gtd-web/): 5-layer React architecture.

LayerKey Files
PresentationCaptureInput.tsx, ProcessingView.tsx, tab components
StateInboxContext.tsx — React useReducer, action types (ADD, DELETE, CATEGORIZE, SNOOZE, RESTORE)
ServicesIntegrationService.ts — pluggable Linear/Plane/Peppermint/InvoiceNinja integrations
Persistencestorage.ts — localStorage with Zod schema validation
Modelstypes/index.ts — InboxItem, Category enum, GTDExport

iOS (GTDInbox/): SwiftUI with @Observable InboxStore backed by UserDefaults. Feature parity lags the web app — no Eliza, filters, or integrations yet.

Eliza engine (gtd-web/src/eliza/): Pure function transformer (input + state → output + state). 5 personas: assistant, coach, pm, gym, events. Uses registerPersona() self-registering pattern. Rule precedence: explicit rules > persona rules > default rules. Round-robin response cycling prevents repetition.

Tech Stack

TechnologyVersionPurpose
React19.2.0Web UI framework
TypeScript5.9.3Type safety (strict + verbatimModuleSyntax)
Vite7.2.4Build and dev server
Vitest4.0.17Test runner (239 tests, 13 files)
Zod^4.3.6Runtime validation of localStorage data
react-swipeable7.0.2Swipe gesture handling
SwiftUI + Swift 5.x—iOS app

Status (as of 2026-02-15)

All 6 milestones complete. Phase 21 Chat UI is the final shipped phase. Health metrics: lint pass (0 errors), typecheck pass (0 errors), build pass, 239 tests green, 0 security vulnerabilities.

Pending polish items:

  • Chat UX smoothness (task extraction sensitivity, typing indicators, conversation starters)
  • iOS feature parity (Eliza, filters, integrations not yet ported)
  • localStorage 5MB limit — no migration to IndexedDB yet
  • PeppermintIntegration.connect() is a stub

Key Patterns

  • Context + useReducer for predictable, testable state transitions
  • useInbox custom hook wraps context, exposes memoized filtered lists
  • Undoable actions (delete + snooze return pre-action state)
  • Integration interface: connect() / getProjects() / createTask() — optional methods (e.g., getTeams?()) avoid unsafe as casts
  • @path alias (@/ -> src/) via Vite + TypeScript
  • Tab persistence via localStorage (restored on mount)
  • Single ErrorBoundary in App.tsx — inner boundary catches, outer is dead code

Lessons Captured

  • 50px swipe threshold prevents accidental triggers — always set minimum gesture thresholds
  • Interface return types must match across all implementations (createTask() mismatch caused void vs CreateIssueResult unsafe casts)
  • verbatimModuleSyntax requires explicit import type — TypeScript tsc -b is stricter than noEmit
  • I/I'm/I've capitalization in Eliza reflections needs special-casing — capital I should not transfer to “you”
  • Double ErrorBoundary is dead code, not extra safety — one boundary at the right level is sufficient
  • Test coverage audits surface API design issues (private filter functions discovered via test friction → exported)
  • Web before iOS: faster iteration, API access simpler; iOS follows for parity