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.
| Layer | Key Files |
|---|---|
| Presentation | CaptureInput.tsx, ProcessingView.tsx, tab components |
| State | InboxContext.tsx — React useReducer, action types (ADD, DELETE, CATEGORIZE, SNOOZE, RESTORE) |
| Services | IntegrationService.ts — pluggable Linear/Plane/Peppermint/InvoiceNinja integrations |
| Persistence | storage.ts — localStorage with Zod schema validation |
| Models | types/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
| Technology | Version | Purpose |
|---|---|---|
| React | 19.2.0 | Web UI framework |
| TypeScript | 5.9.3 | Type safety (strict + verbatimModuleSyntax) |
| Vite | 7.2.4 | Build and dev server |
| Vitest | 4.0.17 | Test runner (239 tests, 13 files) |
| Zod | ^4.3.6 | Runtime validation of localStorage data |
| react-swipeable | 7.0.2 | Swipe 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 + useReducerfor predictable, testable state transitionsuseInboxcustom 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 unsafeascasts @pathalias (@/ -> src/) via Vite + TypeScript- Tab persistence via localStorage (restored on mount)
- Single
ErrorBoundaryinApp.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 causedvoidvsCreateIssueResultunsafe casts) verbatimModuleSyntaxrequires explicitimport type— TypeScripttsc -bis stricter thannoEmitI/I'm/I'vecapitalization in Eliza reflections needs special-casing — capital I should not transfer to “you”- Double
ErrorBoundaryis 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
Links
- index — Projects index
- context-usereducer-action-types — Context + useReducer pattern used throughout
- GitHub:
irishrocker1125/GTD(repo name:cuddly-octo-happiness)