max / goingson
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+154 insertions,
-176 deletions
| @@ -2,7 +2,12 @@ | |||
| 2 | 2 | ||
| 3 | 3 | Email, calendar, tasks in one place. Project management for individuals and small teams. | |
| 4 | 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. 2 crates: `core` (domain models), `db-sqlite` (repository). | |
| 5 | + | A Rust productivity application on Tauri 2. The screens are described in Rust and | |
| 6 | + | rendered by `quasi-webview`; there is no hand-written frontend. SQLite through | |
| 7 | + | `rusqlite` behind an `r2d2` pool. Platinum-informed look, from the `makeover` | |
| 8 | + | family. Five workspace members: `core` (domain models), `db-sqlite` | |
| 9 | + | (repositories), `go-mcp` (an MCP server over the same file), `got` (a terminal | |
| 10 | + | reader over the same file), and `src-tauri` (the app, and the screens). | |
| 6 | 11 | ||
| 7 | 12 | ## High-Level Overview | |
| 8 | 13 | ||
| @@ -10,11 +15,14 @@ | |||
| 10 | 15 | ┌─────────────────────────────────────────────────────────┐ | |
| 11 | 16 | │ User Interface │ | |
| 12 | 17 | │ ┌────────────────────────────────────────────────────┐ │ | |
| 13 | - | │ │ Tauri Desktop (Vanilla JS) │ │ | |
| 18 | + | │ │ Described screens (src-tauri/src/quasi/) │ │ | |
| 19 | + | │ │ rendered by quasi-webview, served at quasi:// │ │ | |
| 14 | 20 | │ └─────────────────────┬──────────────────────────────┘ │ | |
| 15 | 21 | │ │ │ | |
| 16 | 22 | │ ┌─────────────────────▼──────────────────────────────┐ │ | |
| 17 | - | │ │ Tauri Commands (src-tauri/) │ │ | |
| 23 | + | │ │ Commands (src-tauri/commands/) │ │ | |
| 24 | + | │ │ called as plain functions by the screens, and │ │ | |
| 25 | + | │ │ registered for IPC for the few host jobs │ │ | |
| 18 | 26 | │ └─────────────────────┬──────────────────────────────┘ │ | |
| 19 | 27 | └────────────────────────┼─────────────────────────────────┘ | |
| 20 | 28 | │ | |
| @@ -39,18 +47,25 @@ | |||
| 39 | 47 | goingson/ | |
| 40 | 48 | ├── crates/ | |
| 41 | 49 | │ ├── core/ # Domain models, traits, business logic | |
| 42 | - | │ └── db-sqlite/ # SQLite repository implementations | |
| 43 | - | ├── src-tauri/ # Tauri desktop app (single-user) | |
| 50 | + | │ ├── db-sqlite/ # SQLite repository implementations | |
| 51 | + | │ ├── go-mcp/ # MCP server over the same goingson.db | |
| 52 | + | │ └── got/ # Terminal cockpit over the same goingson.db | |
| 53 | + | ├── src-tauri/ # Tauri desktop app, and the described screens | |
| 44 | 54 | └── migrations/ | |
| 45 | - | └── sqlite/ # SQLite schema migrations (33 files) | |
| 55 | + | └── sqlite/ # SQLite schema migrations (69 files) | |
| 46 | 56 | ``` | |
| 47 | 57 | ||
| 58 | + | `go-mcp` and `got` open the same SQLite file as peer readers and write through | |
| 59 | + | the normal repository layer, so the sync-changelog triggers fire and a running | |
| 60 | + | desktop app stays consistent. | |
| 61 | + | ||
| 48 | 62 | ## Crate Dependencies | |
| 49 | 63 | ||
| 50 | 64 | ``` | |
| 51 | - | goingson-desktop (src-tauri) | |
| 52 | - | └── goingson-db-sqlite | |
| 53 | - | └── goingson-core | |
| 65 | + | goingson-desktop (src-tauri) go-mcp got | |
| 66 | + | └──────────────┬─────────────┴────────┘ | |
| 67 | + | goingson-db-sqlite | |
| 68 | + | └── goingson-core | |
| 54 | 69 | ``` | |
| 55 | 70 | ||
| 56 | 71 | ## Core Crate (`crates/core/`) | |
| @@ -61,11 +76,15 @@ | |||
| 61 | 76 | ||
| 62 | 77 | | Module | Purpose | | |
| 63 | 78 | |--------|---------| | |
| 64 | - | | `models/` | Domain types (17 model files) | | |
| 65 | - | | `repository.rs` | Repository traits (data access contracts) | | |
| 79 | + | | `models/` | Domain types (19 model files) | | |
| 80 | + | | `repository/` | Repository traits, one module per area | | |
| 66 | 81 | | `urgency.rs` | TaskWarrior-inspired urgency calculation algorithm | | |
| 67 | - | | `parser.rs` | Quick-add natural language parser | | |
| 82 | + | | `parser.rs`, `search_parser.rs`, `date_parser.rs` | Quick-add, search and date parsing | | |
| 68 | 83 | | `recurrence.rs` | Task/event recurrence logic | | |
| 84 | + | | `weekly_review.rs`, `monthly_review.rs`, `day_planning.rs` | The review and planning workflows | | |
| 85 | + | | `email_compose.rs`, `email_convert.rs`, `email_sync.rs` | Email domain logic | | |
| 86 | + | | `backup_restore.rs`, `import/` | Backup and import | | |
| 87 | + | | `tz.rs`, `date_utils.rs`, `text_utils.rs` | Shared helpers | | |
| 69 | 88 | | `validation.rs` | Input validation trait | | |
| 70 | 89 | | `constants.rs` | Named constants for thresholds, formats | | |
| 71 | 90 | | `error.rs` | Unified CoreError type | | |
| @@ -76,8 +95,9 @@ | |||
| 76 | 95 | // Domain entities | |
| 77 | 96 | Project, Task, Event, Email, EmailAccount, User | |
| 78 | 97 | Contact, ContactEmail, ContactPhone, SocialHandle, ContactCustomField | |
| 79 | - | SavedView, Annotation, Subtask, Milestone | |
| 80 | - | WeeklyReview, BackupSettings | |
| 98 | + | SavedView, Annotation, Subtask, Milestone, Attachment | |
| 99 | + | TaskDependency, TimeSession, Context, DailyNote, Problem | |
| 100 | + | WeeklyReview, MonthlyReview, SyncAccount, BackupSettings | |
| 81 | 101 | ||
| 82 | 102 | // Enums with display/parse support | |
| 83 | 103 | ProjectType, ProjectStatus, TaskStatus, Priority, Recurrence | |
| @@ -101,36 +121,34 @@ | |||
| 101 | 121 | ProjectRepository, TaskRepository, EventRepository | |
| 102 | 122 | EmailRepository, EmailAccountRepository, ContactRepository | |
| 103 | 123 | SearchRepository, StatsRepository, SavedViewRepository | |
| 104 | - | AnnotationRepository, SubtaskRepository, MilestoneRepository | |
| 124 | + | MilestoneRepository, AttachmentRepository, ContextRepository | |
| 125 | + | DailyNoteRepository, MonthlyReviewRepository, ProblemRepository | |
| 126 | + | SyncAccountRepository, UserRepository | |
| 105 | 127 | WeeklyReviewRepository, BackupSettingsRepository | |
| 106 | - | UserRepository | |
| 107 | 128 | ``` | |
| 108 | 129 | ||
| 109 | 130 | ## Database Layer (`crates/db-sqlite/`) | |
| 110 | 131 | ||
| 111 | - | SQLite persistence for the desktop app. Single-user, local storage. 50 migrations in `migrations/sqlite/`. | |
| 132 | + | SQLite persistence, single-user and local. 69 migrations in `migrations/sqlite/`. | |
| 133 | + | ||
| 134 | + | `rusqlite` behind an `r2d2` pool. **The repositories are synchronous**: an async | |
| 135 | + | caller reaches them through `tokio::task::spawn_blocking`. The tree moved off | |
| 136 | + | sqlx-sqlite on 2026-08-07 and the repository traits went sync in the same pass, | |
| 137 | + | because the async had come from sqlx rather than from anything the app wanted. | |
| 138 | + | Wiki: `sql-library-standard`. | |
| 112 | 139 | ||
| 113 | 140 | ``` | |
| 114 | 141 | src/ | |
| 115 | - | ├── lib.rs # SQLite pool initialization | |
| 142 | + | ├── lib.rs # Pool initialization | |
| 143 | + | ├── migrate.rs # Migration runner | |
| 144 | + | ├── migrations.rs # The embedded migration set | |
| 116 | 145 | ├── utils.rs # format_datetime, parse_uuid, email validation | |
| 117 | - | └── repository/ | |
| 118 | - | ├── mod.rs # Re-exports all repositories | |
| 119 | - | ├── project_repo.rs | |
| 120 | - | ├── task_repo.rs | |
| 121 | - | ├── event_repo.rs | |
| 122 | - | ├── email_repo.rs | |
| 123 | - | ├── email_account_repo.rs | |
| 124 | - | ├── contact_repo.rs | |
| 125 | - | ├── user_repo.rs | |
| 126 | - | ├── search_repo.rs # FTS5 full-text search | |
| 127 | - | ├── stats_repo.rs # Dashboard aggregations | |
| 128 | - | ├── saved_view_repo.rs | |
| 129 | - | ├── annotation_repo.rs | |
| 130 | - | ├── subtask_repo.rs | |
| 131 | - | ├── milestone_repo.rs | |
| 132 | - | ├── weekly_review_repo.rs | |
| 133 | - | └── backup_settings_repo.rs | |
| 146 | + | └── repository/ # One module per aggregate, plus: | |
| 147 | + | ├── mod.rs # Re-exports | |
| 148 | + | ├── restore.rs # Backup restore, which writes across every table | |
| 149 | + | ├── search_repo.rs # FTS5 full-text search | |
| 150 | + | ├── stats_repo.rs # Dashboard aggregations | |
| 151 | + | └── task_repo_state.rs # The task state machine, split off task_repo | |
| 134 | 152 | ``` | |
| 135 | 153 | ||
| 136 | 154 | ## Tauri Desktop App (`src-tauri/`) | |
| @@ -139,10 +157,14 @@ | |||
| 139 | 157 | ||
| 140 | 158 | ``` | |
| 141 | 159 | src/ | |
| 142 | - | ├── main.rs # Tauri app setup, command registration | |
| 160 | + | ├── main.rs, lib.rs # Tauri app setup, command registration | |
| 143 | 161 | ├── state.rs # AppState with repository instances | |
| 162 | + | ├── quasi/ # The screens, described. This is the frontend | |
| 163 | + | ├── syncstore/ # The SyncKit manifest, blob policy and observer | |
| 144 | 164 | ├── notifications.rs # Snooze watcher, native notifications | |
| 145 | - | ├── email/ # IMAP/SMTP client | |
| 165 | + | ├── email/, jmap/ # IMAP/SMTP and JMAP clients | |
| 166 | + | ├── oauth/, external_sync/ | |
| 167 | + | ├── export/, problems/ | |
| 146 | 168 | └── commands/ | |
| 147 | 169 | ├── mod.rs # Re-exports all commands | |
| 148 | 170 | ├── error.rs # Error type definitions | |
| @@ -169,79 +191,85 @@ | |||
| 169 | 191 | ||
| 170 | 192 | ### Command Pattern | |
| 171 | 193 | ||
| 172 | - | Tauri commands are async functions that: | |
| 173 | - | 1. Accept `State<Arc<AppState>>` for repository access | |
| 174 | - | 2. Deserialize input from frontend via `#[serde(rename_all = "camelCase")]` | |
| 175 | - | 3. Call repository methods | |
| 176 | - | 4. Serialize response types back to frontend | |
| 194 | + | A command accepts `State<Arc<AppState>>`, calls repository methods, and returns a | |
| 195 | + | response type or an `ApiError`. The repositories are synchronous, so the body has | |
| 196 | + | no `.await` in it even though the command is `async`. | |
| 177 | 197 | ||
| 178 | 198 | ```rust | |
| 179 | 199 | #[tauri::command] | |
| 180 | - | pub async fn create_task( | |
| 181 | - | state: State<'_, Arc<AppState>>, | |
| 182 | - | input: TaskInput, | |
| 183 | - | ) -> Result<TaskResponse, String> { | |
| 184 | - | state.tasks | |
| 185 | - | .create(DESKTOP_USER_ID, new_task) | |
| 186 | - | .await | |
| 187 | - | .map(TaskResponse::from) | |
| 188 | - | .map_err(|e| e.to_string()) | |
| 200 | + | #[instrument(skip_all)] | |
| 201 | + | pub async fn list_tasks(state: State<'_, Arc<AppState>>) -> Result<Vec<TaskResponse>, ApiError> { | |
| 202 | + | let tasks = state.tasks.list_all(DESKTOP_USER_ID)?; | |
| 203 | + | Ok(tasks.into_iter().map(TaskResponse::from).collect()) | |
| 189 | 204 | } | |
| 190 | 205 | ``` | |
| 191 | 206 | ||
| 192 | - | ## Frontend Architecture (Tauri Desktop) | |
| 207 | + | **Commands have two callers now, and only one of them is IPC.** A described | |
| 208 | + | screen calls the command as a plain Rust function (67 sites), which is why the | |
| 209 | + | command layer survived the swap intact rather than being absorbed into the | |
| 210 | + | screens. The 261 registered commands are still reachable over IPC, which is what | |
| 211 | + | the mobile entry point and the handful of host jobs in `frontend/js/host.js` use. | |
| 193 | 212 | ||
| 194 | - | The desktop frontend uses vanilla JavaScript organized under the `GoingsOn` global namespace. 66 source files. | |
| 213 | + | ## The Frontend: described screens | |
| 195 | 214 | ||
| 196 | - | ### Namespace Organization | |
| 215 | + | There is no hand-written frontend. The window opens on `quasi://localhost/tasks` | |
| 216 | + | and there is no other document. `index.html` and the 84 scripts under | |
| 217 | + | `frontend/js/` were deleted on 2026-08-22. | |
| 218 | + | ||
| 219 | + | ### What it was for | |
| 220 | + | ||
| 221 | + | Not porting screens for its own sake. `escape.js` and its 332 `esc()` call sites | |
| 222 | + | existed because screens were built by concatenating strings in JavaScript, and | |
| 223 | + | every one of them had to remember to escape. Escaping is typed in Rust at the | |
| 224 | + | renderer now, so the escapers and the CHRONIC-XSS gate that policed them are both | |
| 225 | + | gone, in that order and not the other one. | |
| 226 | + | ||
| 227 | + | ### The shape | |
| 228 | + | ||
| 229 | + | One module per screen under `src-tauri/src/quasi/`, each contributing its own | |
| 230 | + | routes. There is no `Router::merge`, so composition is a chain of functions that | |
| 231 | + | each take the router and give it back, rather than a central table that has to be | |
| 232 | + | kept in step with the modules. | |
| 197 | 233 | ||
| 198 | 234 | ``` | |
| 199 | - | window.GoingsOn = { | |
| 200 | - | api: { ... }, // Tauri IPC abstraction layer | |
| 201 | - | state: { ... }, // Centralized state with pub/sub | |
| 202 | - | ui: { ... }, // Modal, toast, form utilities | |
| 203 | - | utils: { ... }, // HTML escaping, validation | |
| 204 | - | ||
| 205 | - | // Domain modules (IIFE-wrapped) | |
| 206 | - | projects: { ... }, | |
| 207 | - | tasks: { ... }, | |
| 208 | - | events: { ... }, | |
| 209 | - | emails: { ... }, | |
| 210 | - | contacts: { ... }, | |
| 211 | - | ||
| 212 | - | // Feature modules | |
| 213 | - | savedViews: { ... }, | |
| 214 | - | snooze: { ... }, | |
| 215 | - | navigation: { ... }, | |
| 216 | - | settings: { ... }, | |
| 217 | - | app: { ... }, | |
| 218 | - | ||
| 219 | - | // Infrastructure | |
| 220 | - | VirtualScroller, // Virtual scrolling for large lists | |
| 221 | - | SelectionManager, // Multi-select with shift/ctrl | |
| 222 | - | PaginationManager, // Page navigation | |
| 223 | - | }; | |
| 235 | + | src-tauri/src/quasi/ | |
| 236 | + | ├── mod.rs # The router chain and the document's stylesheet list | |
| 237 | + | ├── shell.rs # Not a screen: the navigation, and the running-timer band | |
| 238 | + | ├── assets.rs # Not a screen: stylesheets, scripts and fonts | |
| 239 | + | ├── theming.rs # The chosen theme, served as a stylesheet | |
| 240 | + | ├── tasks.rs, task_list.rs, board.rs | |
| 241 | + | ├── projects.rs, events.rs, contacts.rs, emails.rs, compose.rs | |
| 242 | + | ├── day_planning.rs, time_tracking.rs | |
| 243 | + | ├── weekly_review.rs, monthly_review.rs | |
| 244 | + | ├── settings.rs, data.rs, problems.rs | |
| 224 | 245 | ``` | |
| 225 | 246 | ||
| 226 | - | ### Module Pattern | |
| 247 | + | A screen module reads `AppState` repositories directly, or calls a command as a | |
| 248 | + | plain function, and returns a description. Each module also carries its own | |
| 249 | + | findings about what a real screen needed that the description layer could not | |
| 250 | + | say, because a finding that lives only in a commit message is a finding nobody | |
| 251 | + | acts on. | |
| 227 | 252 | ||
| 228 | - | Each domain module is wrapped in an IIFE and exposes its public API through the namespace: | |
| 253 | + | ### The five surviving scripts | |
| 229 | 254 | ||
| 230 | - | ```javascript | |
| 231 | - | (function() { | |
| 232 | - | 'use strict'; | |
| 233 | - | // Private state and helpers | |
| 234 | - | async function load() { ... } | |
| 235 | - | function openNew() { ... } | |
| 255 | + | Each does something a description cannot say, and there is no sixth without that | |
| 256 | + | being true of it. | |
| 236 | 257 | ||
| 237 | - | // Public API | |
| 238 | - | GoingsOn.myModule = { load, openNew }; | |
| 239 | - | })(); | |
| 240 | - | ``` | |
| 258 | + | | Script | Job | | |
| 259 | + | |---|---| | |
| 260 | + | | `host.js` | the few host jobs that need IPC, such as opening the compose window | | |
| 261 | + | | `quasi-clock.js` | a ticking clock, which is time passing rather than state changing | | |
| 262 | + | | `quasi-download.js` | handing the viewer a file | | |
| 263 | + | | `quasi-fill.js` | refilling a form the server rejected | | |
| 264 | + | | `quasi-selection.js` | multi-select across a list | | |
| 241 | 265 | ||
| 242 | - | ### Pre-computed Response Fields | |
| 266 | + | htmx is vendored under `frontend/vendor/`. | |
| 243 | 267 | ||
| 244 | - | Rust response types include pre-computed display values so JS never calculates dates, formatting, or derived state: | |
| 268 | + | ### Pre-computed response fields | |
| 269 | + | ||
| 270 | + | Response types still carry pre-computed display values, so nothing downstream | |
| 271 | + | calculates dates, formatting or derived state. The consumer changed from a JS | |
| 272 | + | render function to a description, and the reason did not. | |
| 245 | 273 | ||
| 246 | 274 | | Response Type | Pre-computed Fields | | |
| 247 | 275 | |--------------|---------------------| | |
| @@ -250,87 +278,36 @@ | |||
| 250 | 278 | | EmailResponse | `receivedFormatted` | | |
| 251 | 279 | | EmailAccountResponse | `lastSyncFormatted` | | |
| 252 | 280 | ||
| 253 | - | ### Centralized State | |
| 281 | + | ### What the swap left behind | |
| 254 | 282 | ||
| 255 | - | All shared data lives in `GoingsOn.state` with reactive pub/sub: | |
| 283 | + | Six features went out with `index.html` and come back as each is described. Max | |
| 284 | + | ruled (b) on goingson `da48cb6d`: no build ships until the flip is finished, so | |
| 285 | + | the interval where they are missing is an interval nobody is in. Compose, | |
| 286 | + | Settings > Sync and Settings > About are back. Settings > Sharing, Create Backup, | |
| 287 | + | the search box and the blocking graph are not, and the last two are refused | |
| 288 | + | rather than pending: they are things a host draws, not things a description says. | |
| 256 | 289 | ||
| 257 | - | ```javascript | |
| 258 | - | GoingsOn.state.set('tasks', updatedTasks); // Triggers subscribers | |
| 259 | - | GoingsOn.state.subscribe('tasks', (newVal, oldVal) => { ... }); | |
| 260 | - | ``` | |
| 290 | + | `check_vocabulary_use` is a real hole rather than an obsolete check. It asked | |
| 291 | + | which generated classes no markup emits, and the markup is `quasi-webview`'s | |
| 292 | + | emitter now, in another crate. goingson `43a682b0`. | |
| 261 | 293 | ||
| 262 | - | ### File Organization | |
| 263 | - | ||
| 264 | - | ``` | |
| 265 | - | src-tauri/frontend/ | |
| 266 | - | ├── css/ | |
| 267 | - | │ └── styles.css # Design system + all components | |
| 268 | - | ├── fonts/ | |
| 269 | - | │ └── Reglo-Bold.woff2 # Display font | |
| 270 | - | ├── js/ | |
| 271 | - | │ ├── goingson.js # Namespace root (window.GoingsOn) | |
| 272 | - | │ ├── api.js # Tauri IPC abstraction | |
| 273 | - | │ ├── state.js # Centralized state + pub/sub | |
| 274 | - | │ ├── utils.js # Escaping, validation, debounce | |
| 275 | - | │ ├── router.js # View routing | |
| 276 | - | │ ├── app.js # App initialization, menu listeners | |
| 277 | - | │ │ | |
| 278 | - | │ ├── components.js # Toast, confirm dialog | |
| 279 | - | │ ├── components-modal.js # Modal system | |
| 280 | - | │ ├── form-modal.js # Form modal (openFormModal) | |
| 281 | - | │ ├── navigation.js # View switching, sidebar | |
| 282 | - | │ ├── keyboard.js # Keyboard shortcuts | |
| 283 | - | │ ├── selection-manager.js # Multi-select with shift/ctrl | |
| 284 | - | │ ├── pagination-manager.js # Page navigation | |
| 285 | - | │ ├── virtual-scroller.js # Virtual scrolling for large lists | |
| 286 | - | │ ├── card-grid-scroller.js # Virtual scrolling for multi-column card grids | |
| 287 | - | │ ├── context-menus.js # Right-click context menus | |
| 288 | - | │ ├── bulk-actions.js # Multi-select bulk operations | |
| 289 | - | │ ├── touch.js # Touch event handling | |
| 290 | - | │ ├── mobile.js # Mobile-specific behavior | |
| 291 | - | │ │ | |
| 292 | - | │ ├── tasks.js # Task list, CRUD | |
| 293 | - | │ ├── tasks-render.js # Task rendering | |
| 294 | - | │ ├── tasks-kanban.js # Kanban board view | |
| 295 | - | │ ├── projects.js # Project list, detail, CRUD | |
| 296 | - | │ ├── projects-render.js # Project rendering | |
| 297 | - | │ ├── events.js # Event list, CRUD | |
| 298 | - | │ ├── emails.js # Email list, threading, CRUD | |
| 299 | - | │ ├── email-accounts.js # Email account management | |
| 300 | - | │ ├── contacts.js # Contact CRUD | |
| 301 | - | │ ├── contacts-render.js # Contact rendering | |
| 302 | - | │ │ | |
| 303 | - | │ ├── day-planning.js # Time-blocking day planner | |
| 304 | - | │ ├── day-planning-render.js # Day plan rendering | |
| 305 | - | │ ├── weekly-review.js # Weekly review workflow | |
| 306 | - | │ ├── weekly-review-render.js # Weekly review rendering | |
| 307 | - | │ ├── snooze.js # Snooze modal + actions | |
| 308 | - | │ ├── settings.js # Settings, export | |
| 309 | - | │ ├── settings-sync.js # Cloud sync settings | |
| 310 | - | │ ├── themes.js # Theme switching | |
| 311 | - | │ ├── export.js # Data export | |
| 312 | - | │ ├── import.js # Data import from JSON | |
| 313 | - | │ ├── seed-data.js # Demo data seeding | |
| 314 | - | │ │ | |
| 315 | - | │ └── tests/ | |
| 316 | - | │ ├── test-runner.js # Test framework | |
| 317 | - | │ └── run.js # Test execution | |
| 318 | - | └── index.html # Entry point | |
| 319 | - | ``` | |
| 294 | + | For the stylesheets and what may be written by hand, see `design-system.md`. | |
| 320 | 295 | ||
| 321 | 296 | ## Data Flow | |
| 322 | 297 | ||
| 323 | 298 | ``` | |
| 324 | - | Frontend (JS) | |
| 325 | - | → invoke("command_name", { args }) | |
| 326 | - | → Tauri IPC | |
| 327 | - | → commands/module.rs | |
| 328 | - | → Repository trait method | |
| 299 | + | A request arrives at quasi://localhost/<path> | |
| 300 | + | → the screen module that claimed that route | |
| 301 | + | → AppState repository, or a command called as a plain function | |
| 329 | 302 | → SQLite query | |
| 330 | - | → Response (with pre-computed display fields) → Frontend | |
| 331 | - | → JS renders pre-computed values directly to DOM | |
| 303 | + | → Response (with pre-computed display fields) | |
| 304 | + | → the module returns a description | |
| 305 | + | → quasi-webview renders it to HTML, escaping as it goes | |
| 332 | 306 | ``` | |
| 333 | 307 | ||
| 308 | + | htmx swaps the body on a navigation. The head is not swapped, which is why a | |
| 309 | + | pinned theme change takes effect at the next launch rather than immediately. | |
| 310 | + | ||
| 334 | 311 | ## Key Design Decisions | |
| 335 | 312 | ||
| 336 | 313 | ### Clean Architecture | |
| @@ -338,13 +315,12 @@ | |||
| 338 | 315 | - Repository traits define contracts, implementations are separate crates | |
| 339 | 316 | - Easy to swap databases or add new ones | |
| 340 | 317 | ||
| 341 | - | ### Vanilla Frontend | |
| 342 | - | - No JavaScript framework: vanilla JS with IIFE modules | |
| 343 | - | - All code under `GoingsOn` global namespace (no `window.*` exports) | |
| 344 | - | - Centralized state via `GoingsOn.state` with pub/sub reactivity | |
| 345 | - | - IPC via Tauri invoke | |
| 346 | - | - Virtual scrolling for large lists (`GoingsOn.VirtualScroller`) | |
| 347 | - | - Optimized for desktop-class performance | |
| 318 | + | ### Described screens | |
| 319 | + | - A screen states what it is, not how it is drawn. The renderer decides the rest | |
| 320 | + | - Escaping is typed at the renderer, so no call site can forget it | |
| 321 | + | - One description serves any host quasi has a renderer for | |
| 322 | + | - A need the vocabulary cannot state is a gap filed against quasi, never a | |
| 323 | + | local workaround | |
| 348 | 324 | ||
| 349 | 325 | ### TaskWarrior-Inspired Features | |
| 350 | 326 | - Urgency calculation algorithm | |
| @@ -353,8 +329,10 @@ | |||
| 353 | 329 | ||
| 354 | 330 | ## Testing Strategy | |
| 355 | 331 | ||
| 356 | - | - 658 Rust tests + 48 JS tests | |
| 357 | - | - Unit tests in core crate for business logic | |
| 358 | - | - Integration tests for repository implementations | |
| 359 | - | - Tauri command tests | |
| 360 | - | - JS tests in `frontend/js/tests/` | |
| 332 | + | - Unit tests in the core crate for business logic | |
| 333 | + | - Integration tests for the repository implementations | |
| 334 | + | - Command tests under `src-tauri/src/commands/tests/` | |
| 335 | + | - Screen tests beside the modules they cover | |
| 336 | + | - `build.rs` checks the stylesheet against what the renderer can emit, and the | |
| 337 | + | manifest against its storage-version ledger | |
| 338 | + | - `scripts/lint-frontend.sh` for the three stylesheet rules |