This document describes the high-level architecture of TaskFlow, a terminal-based task management application built with Rust.
TaskFlow follows The Elm Architecture (TEA), a unidirectional data flow pattern that ensures predictable state management and makes the application easy to test and reason about.
┌─────────────────────────────────────────────────────────────────┐
│ Event Loop │
│ ┌─────────┐ ┌──────────┐ ┌────────┐ ┌─────────┐ │
│ │ View │────▶│ Input │────▶│ Update │────▶│ Model │ │
│ │ (UI) │ │ (Events) │ │ (Msg) │ │ (State) │ │
│ └─────────┘ └──────────┘ └────────┘ └─────────┘ │
│ ▲ │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
src/
├── main.rs # Entry point, event loop, terminal setup
├── lib.rs # Public API, crate documentation
├── app/ # Application logic (TEA)
│ ├── model/ # Application state
│ ├── update/ # Message handlers
│ ├── message.rs # Message type definitions
│ ├── undo.rs # Undo/redo stack
│ ├── macros.rs # Keyboard macro recording
│ ├── templates.rs # Task templates
│ └── quick_add.rs # Natural language task parsing
├── domain/ # Core business entities (see Domain Modules below)
│ ├── task.rs # Task entity with status, priority, recurrence
│ ├── project.rs # Project entity for task grouping
│ ├── time_entry.rs # Time tracking entries
│ ├── work_log.rs # Journal-style work log entries
│ ├── filter.rs # Filter criteria types
│ ├── filter_dsl/ # Advanced filter query language
│ ├── pomodoro.rs # Pomodoro timer with phases
│ ├── goal.rs # OKR goals with quarterly tracking
│ ├── key_result.rs # Key results linked to goals
│ ├── habit.rs # Habit tracking with streaks
│ ├── calendar_event.rs # ICS calendar events
│ ├── analytics.rs # Productivity analytics
│ └── tag.rs # Tags with context support
├── ui/ # Terminal UI (Ratatui)
│ ├── view.rs # Main render function
│ └── components/ # Reusable widgets
├── storage/ # Persistence layer
│ ├── backends/ # Storage implementations
│ ├── import.rs # CSV/ICS import
│ └── export.rs # CSV/ICS/HTML export
└── config/ # Configuration
├── settings.rs # General settings
├── keybindings.rs # Key mappings
└── theme.rs # Color themes
The Model holds all application state in a single struct. Key fields include:
| Field | Description |
|---|---|
tasks |
HashMap of all tasks by ID |
projects |
HashMap of all projects by ID |
visible_tasks |
Filtered/sorted task IDs for display |
selected_index |
Currently selected task |
current_view |
Active view (List, Today, Calendar, etc.) |
input_mode |
Whether in editing mode |
undo_stack |
History for undo/redo |
Submodules:
filtering.rs- Task filtering, sorting, searchhierarchy.rs- Subtask/parent relationshipstime_tracking.rs- Time entry managementstorage.rs- Backend sync helpers
Messages are immutable events that describe what happened. They are categorized:
| Category | Examples |
|---|---|
NavigationMessage |
Up, Down, GoToView(ViewId) |
TaskMessage |
Create(String), Delete, ToggleComplete |
UiMessage |
StartEditing, Char(char), Submit |
SystemMessage |
Save, Quit, Undo, Redo |
TimeMessage |
StartTracking, StopTracking |
PomodoroMessage |
Start, Pause, Skip |
The Update function is pure: given current state and a message, it produces new state.
pub fn update(model: &mut Model, message: Message) {
match message {
Message::Navigation(msg) => navigation::handle_navigation(model, msg),
Message::Task(msg) => task::handle_task(model, msg),
Message::Ui(msg) => ui::handle_ui(model, msg),
Message::System(msg) => system::handle_system(model, msg),
Message::Time(msg) => time::handle_time(model, msg),
Message::Pomodoro(msg) => time::handle_pomodoro(model, msg),
Message::None => {}
}
}Submodules:
navigation.rs- Movement, view switchingtask.rs- Task CRUD, completion, prioritytime.rs- Time tracking, Pomodoroui/- Input handling, dialogs, multi-selectsystem.rs- Save, quit, import/export
The View renders the UI based on current state. It never mutates state directly.
pub fn view(model: &Model, frame: &mut Frame<'_>, theme: &Theme) {
render_header(frame, chunks[0], theme);
render_content(model, frame, chunks[1], theme);
render_footer(model, frame, chunks[2], theme);
// Render popups if active...
}UI Components (ui/components/):
task_list.rs- Main task list viewsidebar.rs- Project/view navigationcalendar.rs- Month calendar viewdashboard.rs- Overview with chartskanban.rs- Kanban board viewhelp.rs- Keybindings popup- Various editors and pickers
- Input: User presses a key (e.g.,
j) - Keybinding lookup: Key maps to action (e.g.,
MoveDown) - Message creation: Action becomes
Message::Navigation(Down) - Update:
handle_navigation()incrementsselected_index - Render:
view()re-renders with new selection highlighted
User types 'a' (add task)
↓
Message::Ui(StartEditing(InputTarget::Task))
↓
Model.input_mode = Editing, input_buffer = ""
↓
View renders input dialog
↓
User types "Buy groceries due:tomorrow #shopping"
↓
Message::Ui(Submit)
↓
Quick-add parser extracts: title, due date, tags
↓
Task created, added to model.tasks
↓
Undo action recorded
↓
Storage backend saves changes
↓
View re-renders task list
The storage layer uses a trait-based design for pluggable backends:
pub trait StorageBackend: Send + Sync {
fn create_task(&mut self, task: &Task) -> StorageResult<()>;
fn update_task(&mut self, task: &Task) -> StorageResult<()>;
fn delete_task(&mut self, id: &TaskId) -> StorageResult<()>;
fn list_tasks(&self) -> StorageResult<Vec<Task>>;
// ... projects, time entries, etc.
}Available Backends:
| Backend | File Format | Use Case |
|---|---|---|
| JSON | .json |
Default, fast, compact |
| YAML | .yaml |
Human-readable |
| SQLite | .db |
Large datasets |
| Markdown | .md files |
Git-friendly |
The domain layer (src/domain/) contains core business entities. Each module includes comprehensive documentation with examples.
| Module | Purpose | Key Types |
|---|---|---|
task.rs |
Task entity with status, priority, recurrence | Task, TaskId, TaskStatus, Priority |
project.rs |
Project grouping for tasks | Project, ProjectId |
tag.rs |
Tagging with GTD context support (@home, @work) |
Tag, is_context_tag() |
time_entry.rs |
Time tracking entries | TimeEntry, TimeEntryId |
work_log.rs |
Journal-style work notes | WorkLogEntry, WorkLogEntryId |
pomodoro.rs |
Pomodoro timer with work/break phases | PomodoroSession, PomodoroConfig |
goal.rs |
OKR objectives with quarterly tracking | Goal, GoalId, Quarter |
key_result.rs |
Measurable key results for goals | KeyResult, KeyResultId |
habit.rs |
Habit tracking with streaks | Habit, HabitFrequency |
calendar_event.rs |
ICS calendar event imports | CalendarEvent, CalendarEventStatus |
analytics.rs |
Productivity analytics and reports | CompletionTrend, VelocityMetrics |
filter_dsl/ |
Advanced filter query language | parse(), evaluate(), FilterExpr |
Documentation Features:
- Module-level doc comments with usage examples
- Workflow documentation (e.g., OKR workflow in
goal.rs) - Cross-references between related types
- Runnable doc tests for all examples
The undo system captures state changes as reversible actions:
pub enum UndoAction {
CreateTask(TaskId),
DeleteTask(Task),
UpdateTask { before: Task, after: Task },
// ... more actions
}Each undoable operation:
- Captures the "before" state
- Performs the action
- Pushes an
UndoActionto the stack - Clears the redo stack
- Predictability: All state changes go through
update() - Testability: Pure functions are easy to test
- Debugging: Can inspect any state transition
- Time-travel: Undo/redo is straightforward
- Simplicity: One place for all state
- Consistency: No scattered mutable state
- Serialization: Easy to save/restore
- Flexibility: Swap backends without changing app logic
- Testing: Use in-memory backend for tests
- Extensibility: Add new backends easily
- Unit tests: Domain logic, parsing, filtering
- Integration tests: Message → state transitions
- UI tests: Widget rendering (buffer snapshots)
- Storage tests: Each backend implementation
Tests are colocated with modules or in tests/ submodules.
Configuration files live in ~/.config/taskflow/:
~/.config/taskflow/
├── config.toml # Backend, data dir, preferences
├── keybindings.toml # Custom key mappings
└── themes/
└── custom.toml # Color themes
All configuration has sensible defaults.
- Lazy loading: Only load visible tasks
- Incremental updates: Refresh only what changed
- Efficient filtering: Pre-computed visible task list
- Background saves: Non-blocking persistence
- Structured logging: Replace println with tracing
- Async storage: Non-blocking backend operations
- Plugin system: Custom views and commands
- Sync: Cloud/network synchronization