| 1 |
# GoingsOn Architecture |
| 2 |
|
| 3 |
Email, calendar, tasks in one place. Project management for individuals and small teams. |
| 4 |
|
| 5 |
A Rust-based productivity application built with Tauri 2 (Rust backend + Vanilla JS frontend), SQLite with sqlx 0.8, and a "Neobrute" design aesthetic. 3 crates: `core` (domain models), `db-sqlite` (repository), `plugin-runtime` (Rhai plugins). |
| 6 |
|
| 7 |
## High-Level Overview |
| 8 |
|
| 9 |
``` |
| 10 |
┌─────────────────────────────────────────────────────────┐ |
| 11 |
│ User Interface │ |
| 12 |
│ ┌────────────────────────────────────────────────────┐ │ |
| 13 |
│ │ Tauri Desktop (Vanilla JS) │ │ |
| 14 |
│ └─────────────────────┬──────────────────────────────┘ │ |
| 15 |
│ │ │ |
| 16 |
│ ┌─────────────────────▼──────────────────────────────┐ │ |
| 17 |
│ │ Tauri Commands (src-tauri/) │ │ |
| 18 |
│ └─────────────────────┬──────────────────────────────┘ │ |
| 19 |
└────────────────────────┼─────────────────────────────────┘ |
| 20 |
│ |
| 21 |
┌────────────────────────▼─────────────────────────────────┐ |
| 22 |
│ goingson-core │ |
| 23 |
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌────────────┐ │ |
| 24 |
│ │ Models │ │Repository│ │ Urgency │ │ Parser │ │ |
| 25 |
│ │ │ │ Traits │ │ Calculator │ │ (Quick-Add)│ │ |
| 26 |
│ └──────────┘ └────┬─────┘ └────────────┘ └────────────┘ │ |
| 27 |
└────────────────────┼─────────────────────────────────────┘ |
| 28 |
│ |
| 29 |
┌──────▼───────────┐ |
| 30 |
│ goingson-db- │ |
| 31 |
│ sqlite │ |
| 32 |
│ (Desktop) │ |
| 33 |
└──────────────────┘ |
| 34 |
|
| 35 |
Additional crates: |
| 36 |
plugin-runtime Rhai plugin system (import plugins) |
| 37 |
``` |
| 38 |
|
| 39 |
## Workspace Structure |
| 40 |
|
| 41 |
``` |
| 42 |
goingson/ |
| 43 |
├── crates/ |
| 44 |
│ ├── core/ # Domain models, traits, business logic |
| 45 |
│ ├── db-sqlite/ # SQLite repository implementations |
| 46 |
│ └── plugin-runtime/ # Rhai plugin system |
| 47 |
├── src-tauri/ # Tauri desktop app (single-user) |
| 48 |
└── migrations/ |
| 49 |
└── sqlite/ # SQLite schema migrations (33 files) |
| 50 |
``` |
| 51 |
|
| 52 |
## Crate Dependencies |
| 53 |
|
| 54 |
``` |
| 55 |
goingson-desktop (src-tauri) |
| 56 |
├── goingson-db-sqlite |
| 57 |
│ └── goingson-core |
| 58 |
└── plugin-runtime |
| 59 |
└── goingson-core (for shared types) |
| 60 |
``` |
| 61 |
|
| 62 |
## Core Crate (`crates/core/`) |
| 63 |
|
| 64 |
The core crate defines domain models and repository traits, independent of persistence. |
| 65 |
|
| 66 |
### Modules |
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
| `models/` | Domain types (17 model files) | |
| 71 |
| `repository.rs` | Repository traits (data access contracts) | |
| 72 |
| `urgency.rs` | TaskWarrior-inspired urgency calculation algorithm | |
| 73 |
| `parser.rs` | Quick-add natural language parser | |
| 74 |
| `recurrence.rs` | Task/event recurrence logic | |
| 75 |
| `validation.rs` | Input validation trait | |
| 76 |
| `constants.rs` | Named constants for thresholds, formats | |
| 77 |
| `error.rs` | Unified CoreError type | |
| 78 |
|
| 79 |
### Key Types |
| 80 |
|
| 81 |
```rust |
| 82 |
// Domain entities |
| 83 |
Project, Task, Event, Email, EmailAccount, User |
| 84 |
Contact, ContactEmail, ContactPhone, SocialHandle, ContactCustomField |
| 85 |
SavedView, Annotation, Subtask, Milestone |
| 86 |
WeeklyReview, BackupSettings |
| 87 |
|
| 88 |
// Enums with display/parse support |
| 89 |
ProjectType, ProjectStatus, TaskStatus, Priority, Recurrence |
| 90 |
SortDirection, SortField, TaskSortColumn |
| 91 |
ViewType, ViewFilters, BlockType |
| 92 |
EmailAuthType, MilestoneStatus |
| 93 |
|
| 94 |
// DTOs for creation/updates |
| 95 |
NewProject, NewTask, NewEvent, NewEmail |
| 96 |
UpdateProject, UpdateTask, UpdateEvent |
| 97 |
|
| 98 |
// Newtype IDs |
| 99 |
ProjectId, TaskId, EventId, EmailId, ContactId |
| 100 |
EmailAccountId, AnnotationId, SubtaskId, SavedViewId |
| 101 |
MilestoneId, CustomFieldId |
| 102 |
``` |
| 103 |
|
| 104 |
### Repository Traits |
| 105 |
|
| 106 |
```rust |
| 107 |
ProjectRepository, TaskRepository, EventRepository |
| 108 |
EmailRepository, EmailAccountRepository, ContactRepository |
| 109 |
SearchRepository, StatsRepository, SavedViewRepository |
| 110 |
AnnotationRepository, SubtaskRepository, MilestoneRepository |
| 111 |
WeeklyReviewRepository, BackupSettingsRepository |
| 112 |
UserRepository |
| 113 |
``` |
| 114 |
|
| 115 |
## Database Layer (`crates/db-sqlite/`) |
| 116 |
|
| 117 |
SQLite persistence for the desktop app. Single-user, local storage. 50 migrations in `migrations/sqlite/`. |
| 118 |
|
| 119 |
``` |
| 120 |
src/ |
| 121 |
├── lib.rs # SQLite pool initialization |
| 122 |
├── utils.rs # format_datetime, parse_uuid, email validation |
| 123 |
└── repository/ |
| 124 |
├── mod.rs # Re-exports all repositories |
| 125 |
├── project_repo.rs |
| 126 |
├── task_repo.rs |
| 127 |
├── event_repo.rs |
| 128 |
├── email_repo.rs |
| 129 |
├── email_account_repo.rs |
| 130 |
├── contact_repo.rs |
| 131 |
├── user_repo.rs |
| 132 |
├── search_repo.rs # FTS5 full-text search |
| 133 |
├── stats_repo.rs # Dashboard aggregations |
| 134 |
├── saved_view_repo.rs |
| 135 |
├── annotation_repo.rs |
| 136 |
├── subtask_repo.rs |
| 137 |
├── milestone_repo.rs |
| 138 |
├── weekly_review_repo.rs |
| 139 |
└── backup_settings_repo.rs |
| 140 |
``` |
| 141 |
|
| 142 |
## Tauri Desktop App (`src-tauri/`) |
| 143 |
|
| 144 |
Single-user desktop application. |
| 145 |
|
| 146 |
``` |
| 147 |
src/ |
| 148 |
├── main.rs # Tauri app setup, command registration |
| 149 |
├── state.rs # AppState with repository instances |
| 150 |
├── notifications.rs # Snooze watcher, native notifications |
| 151 |
├── email/ # IMAP/SMTP client |
| 152 |
└── commands/ |
| 153 |
├── mod.rs # Re-exports all commands |
| 154 |
├── error.rs # Error type definitions |
| 155 |
├── task.rs # Task CRUD, annotations, subtasks, snoozing, waiting |
| 156 |
├── project.rs # Project management |
| 157 |
├── event.rs # Calendar events |
| 158 |
├── email.rs # Email CRUD, threading, archive |
| 159 |
├── email_account.rs # Email account setup, sync interval |
| 160 |
├── email_sync.rs # IMAP/SMTP sync orchestration |
| 161 |
├── contact.rs # Contact CRUD, emails, phones, social handles |
| 162 |
├── search.rs # Full-text search across all entities |
| 163 |
├── stats.rs # Dashboard statistics |
| 164 |
├── day_planning.rs # Time blocking |
| 165 |
├── weekly_review.rs # Weekly review workflow |
| 166 |
├── saved_views.rs # Custom filter views |
| 167 |
├── milestone.rs # Milestone CRUD and reordering |
| 168 |
├── plugin.rs # Plugin registry, hot-reload, import |
| 169 |
├── oauth.rs # OAuth2 flows (Fastmail, Google, Microsoft) |
| 170 |
├── export.rs # JSON, CSV, ICS export; backup/restore |
| 171 |
├── sync.rs # Cloud sync via SyncKit |
| 172 |
├── themes.rs # Theme list and color queries |
| 173 |
└── window.rs # Window management, compose window |
| 174 |
``` |
| 175 |
|
| 176 |
### Command Pattern |
| 177 |
|
| 178 |
Tauri commands are async functions that: |
| 179 |
1. Accept `State<Arc<AppState>>` for repository access |
| 180 |
2. Deserialize input from frontend via `#[serde(rename_all = "camelCase")]` |
| 181 |
3. Call repository methods |
| 182 |
4. Serialize response types back to frontend |
| 183 |
|
| 184 |
```rust |
| 185 |
#[tauri::command] |
| 186 |
pub async fn create_task( |
| 187 |
state: State<'_, Arc<AppState>>, |
| 188 |
input: TaskInput, |
| 189 |
) -> Result<TaskResponse, String> { |
| 190 |
state.tasks |
| 191 |
.create(DESKTOP_USER_ID, new_task) |
| 192 |
.await |
| 193 |
.map(TaskResponse::from) |
| 194 |
.map_err(|e| e.to_string()) |
| 195 |
} |
| 196 |
``` |
| 197 |
|
| 198 |
## Frontend Architecture (Tauri Desktop) |
| 199 |
|
| 200 |
The desktop frontend uses vanilla JavaScript organized under the `GoingsOn` global namespace. 66 source files. |
| 201 |
|
| 202 |
### Namespace Organization |
| 203 |
|
| 204 |
``` |
| 205 |
window.GoingsOn = { |
| 206 |
api: { ... }, // Tauri IPC abstraction layer |
| 207 |
state: { ... }, // Centralized state with pub/sub |
| 208 |
ui: { ... }, // Modal, toast, form utilities |
| 209 |
utils: { ... }, // HTML escaping, validation |
| 210 |
|
| 211 |
// Domain modules (IIFE-wrapped) |
| 212 |
projects: { ... }, |
| 213 |
tasks: { ... }, |
| 214 |
events: { ... }, |
| 215 |
emails: { ... }, |
| 216 |
contacts: { ... }, |
| 217 |
|
| 218 |
// Feature modules |
| 219 |
savedViews: { ... }, |
| 220 |
snooze: { ... }, |
| 221 |
navigation: { ... }, |
| 222 |
settings: { ... }, |
| 223 |
app: { ... }, |
| 224 |
|
| 225 |
// Infrastructure |
| 226 |
VirtualScroller, // Virtual scrolling for large lists |
| 227 |
SelectionManager, // Multi-select with shift/ctrl |
| 228 |
PaginationManager, // Page navigation |
| 229 |
}; |
| 230 |
``` |
| 231 |
|
| 232 |
### Module Pattern |
| 233 |
|
| 234 |
Each domain module is wrapped in an IIFE and exposes its public API through the namespace: |
| 235 |
|
| 236 |
```javascript |
| 237 |
(function() { |
| 238 |
'use strict'; |
| 239 |
// Private state and helpers |
| 240 |
async function load() { ... } |
| 241 |
function openNew() { ... } |
| 242 |
|
| 243 |
// Public API |
| 244 |
GoingsOn.myModule = { load, openNew }; |
| 245 |
})(); |
| 246 |
``` |
| 247 |
|
| 248 |
### Pre-computed Response Fields |
| 249 |
|
| 250 |
Rust response types include pre-computed display values so JS never calculates dates, formatting, or derived state: |
| 251 |
|
| 252 |
|
| 253 |
|
| 254 |
| TaskResponse | `dueFormatted`, `urgencyClass`, `isOverdue`, `isSnoozed`, `subtaskCount`, `subtaskCompleted`, `subtaskProgress` | |
| 255 |
| EventResponse | `timeFormatted`, `dateFormatted`, `isPast`, `proximityClass`, `proximityLabel` | |
| 256 |
| EmailResponse | `receivedFormatted` | |
| 257 |
| EmailAccountResponse | `lastSyncFormatted` | |
| 258 |
|
| 259 |
### Centralized State |
| 260 |
|
| 261 |
All shared data lives in `GoingsOn.state` with reactive pub/sub: |
| 262 |
|
| 263 |
```javascript |
| 264 |
GoingsOn.state.set('tasks', updatedTasks); // Triggers subscribers |
| 265 |
GoingsOn.state.subscribe('tasks', (newVal, oldVal) => { ... }); |
| 266 |
``` |
| 267 |
|
| 268 |
### File Organization |
| 269 |
|
| 270 |
``` |
| 271 |
src-tauri/frontend/ |
| 272 |
├── css/ |
| 273 |
│ └── styles.css # Design system + all components |
| 274 |
├── fonts/ |
| 275 |
│ └── Reglo-Bold.woff2 # Display font |
| 276 |
├── js/ |
| 277 |
│ ├── goingson.js # Namespace root (window.GoingsOn) |
| 278 |
│ ├── api.js # Tauri IPC abstraction |
| 279 |
│ ├── state.js # Centralized state + pub/sub |
| 280 |
│ ├── utils.js # Escaping, validation, debounce |
| 281 |
│ ├── router.js # View routing |
| 282 |
│ ├── app.js # App initialization, menu listeners |
| 283 |
│ │ |
| 284 |
│ ├── components.js # Toast, confirm dialog |
| 285 |
│ ├── components-modal.js # Modal system |
| 286 |
│ ├── form-modal.js # Form modal (openFormModal) |
| 287 |
│ ├── navigation.js # View switching, sidebar |
| 288 |
│ ├── keyboard.js # Keyboard shortcuts |
| 289 |
│ ├── selection-manager.js # Multi-select with shift/ctrl |
| 290 |
│ ├── pagination-manager.js # Page navigation |
| 291 |
│ ├── virtual-scroller.js # Virtual scrolling for large lists |
| 292 |
│ ├── context-menus.js # Right-click context menus |
| 293 |
│ ├── bulk-actions.js # Multi-select bulk operations |
| 294 |
│ ├── touch.js # Touch event handling |
| 295 |
│ ├── mobile.js # Mobile-specific behavior |
| 296 |
│ │ |
| 297 |
│ ├── tasks.js # Task list, CRUD |
| 298 |
│ ├── tasks-render.js # Task rendering |
| 299 |
│ ├── tasks-kanban.js # Kanban board view |
| 300 |
│ ├── projects.js # Project list, detail, CRUD |
| 301 |
│ ├── projects-render.js # Project rendering |
| 302 |
│ ├── events.js # Event list, CRUD |
| 303 |
│ ├── emails.js # Email list, threading, CRUD |
| 304 |
│ ├── email-accounts.js # Email account management |
| 305 |
│ ├── contacts.js # Contact CRUD |
| 306 |
│ ├── contacts-render.js # Contact rendering |
| 307 |
│ │ |
| 308 |
│ ├── day-planning.js # Time-blocking day planner |
| 309 |
│ ├── day-planning-render.js # Day plan rendering |
| 310 |
│ ├── weekly-review.js # Weekly review workflow |
| 311 |
│ ├── weekly-review-render.js # Weekly review rendering |
| 312 |
│ ├── snooze.js # Snooze modal + actions |
| 313 |
│ ├── settings.js # Settings, export |
| 314 |
│ ├── settings-sync.js # Cloud sync settings |
| 315 |
│ ├── themes.js # Theme switching |
| 316 |
│ ├── export.js # Data export |
| 317 |
│ ├── import.js # Data import from JSON |
| 318 |
│ ├── seed-data.js # Demo data seeding |
| 319 |
│ │ |
| 320 |
│ └── tests/ |
| 321 |
│ ├── test-runner.js # Test framework |
| 322 |
│ └── run.js # Test execution |
| 323 |
└── index.html # Entry point |
| 324 |
``` |
| 325 |
|
| 326 |
## Data Flow |
| 327 |
|
| 328 |
``` |
| 329 |
Frontend (JS) |
| 330 |
→ invoke("command_name", { args }) |
| 331 |
→ Tauri IPC |
| 332 |
→ commands/module.rs |
| 333 |
→ Repository trait method |
| 334 |
→ SQLite query |
| 335 |
→ Response (with pre-computed display fields) → Frontend |
| 336 |
→ JS renders pre-computed values directly to DOM |
| 337 |
``` |
| 338 |
|
| 339 |
## Key Design Decisions |
| 340 |
|
| 341 |
### Clean Architecture |
| 342 |
- Core domain models have no dependencies on persistence |
| 343 |
- Repository traits define contracts, implementations are separate crates |
| 344 |
- Easy to swap databases or add new ones |
| 345 |
|
| 346 |
### Vanilla Frontend |
| 347 |
- No JavaScript framework — vanilla JS with IIFE modules |
| 348 |
- All code under `GoingsOn` global namespace (no `window.*` exports) |
| 349 |
- Centralized state via `GoingsOn.state` with pub/sub reactivity |
| 350 |
- IPC via Tauri invoke |
| 351 |
- Virtual scrolling for large lists (`GoingsOn.VirtualScroller`) |
| 352 |
- Optimized for desktop-class performance |
| 353 |
|
| 354 |
### TaskWarrior-Inspired Features |
| 355 |
- Urgency calculation algorithm |
| 356 |
- Quick-add parser with natural language |
| 357 |
- Annotations and recurring tasks |
| 358 |
|
| 359 |
## Testing Strategy |
| 360 |
|
| 361 |
- 658 Rust tests + 48 JS tests |
| 362 |
- Unit tests in core crate for business logic |
| 363 |
- Integration tests for repository implementations |
| 364 |
- Tauri command tests |
| 365 |
- JS tests in `frontend/js/tests/` |
| 366 |
|