Skip to main content

max / goingson

5.1 KB · 178 lines History Blame Raw
1 # Contributing to GoingsOn
2
3 ## Development Setup
4
5 ### Prerequisites
6
7 - Rust stable toolchain (2024 edition)
8 - Tauri 2 CLI: `cargo install tauri-cli --version '^2'`
9 - Linux only: WebKitGTK system dependencies (see README.md)
10 - macOS / Windows: no extra system dependencies
11
12 ### Running
13
14 ```sh
15 cargo tauri dev # Dev mode with hot-reload frontend
16 cargo test --workspace # Run all Rust tests
17 ```
18
19 ## Architecture Rules
20
21 ### Rust Does Heavy Lifting
22
23 All computation happens in Rust. JavaScript only renders and handles interactions.
24
25 | Rust should | JavaScript should |
26 |-------------|-------------------|
27 | Filter, sort, group data | Render pre-computed data to DOM |
28 | Date/time calculations | Handle UI interactions (clicks, keyboard) |
29 | Business logic (urgency, recurrence) | Call Tauri commands via `GoingsOn.api` |
30 | Aggregations and statistics | Manage ephemeral UI state (modal open, selection) |
31
32 If you find yourself writing a `for` loop over data in JS, it probably belongs in Rust.
33
34 ### Pre-compute Display Values
35
36 When data requires computation for display, add it to the Rust response type. Don't make JS compute it:
37
38 ```rust
39 // GOOD: Rust pre-computes
40 pub struct TaskResponse {
41 pub is_snoozed: bool,
42 pub subtask_progress: f32,
43 pub urgency_class: String,
44 pub due_formatted: Option<String>,
45 }
46 ```
47
48 ```javascript
49 // JS just uses the value
50 element.classList.add(`urgency-${task.urgencyClass}`);
51 element.textContent = task.dueFormatted;
52 ```
53
54 ### Single Source of Truth
55
56 - Business logic: `crates/core/`
57 - Persistence: `crates/db-sqlite/`
58 - Tauri commands: thin wrappers in `src-tauri/src/commands/`
59 - JS: renders what Rust sends, never duplicates logic
60
61 ## Adding a New Feature End-to-End
62
63 Example: adding a "notes" field to projects.
64
65 ### 1. Core model
66
67 Add the field to the model in `crates/core/src/models/project.rs`:
68
69 ```rust
70 pub struct Project {
71 // ... existing fields
72 pub notes: Option<String>,
73 }
74 ```
75
76 Update `NewProject` and `UpdateProject` if needed.
77
78 ### 2. Migration
79
80 Add a SQLite migration in `migrations/sqlite/`:
81
82 ```sql
83 -- 033_add_project_notes.sql
84 ALTER TABLE projects ADD COLUMN notes TEXT;
85 ```
86
87 ### 3. Repository
88
89 Update the repository implementation in `crates/db-sqlite/src/repository/project_repo.rs`:
90 - Update INSERT/UPDATE queries to include the new column
91 - Update SELECT queries to read it
92
93 ### 4. Tauri command
94
95 Update the response type in `src-tauri/src/commands/project.rs` if the field needs transformation, or it flows through automatically via serde.
96
97 ### 5. Frontend JS
98
99 Update the form fields in `src-tauri/frontend/js/projects.js`:
100
101 ```javascript
102 function getFormFields(project = null) {
103 return [
104 // ... existing fields
105 { name: 'notes', type: 'textarea', label: 'Notes', value: project?.notes || '' },
106 ];
107 }
108 ```
109
110 The form modal and API call infrastructure handle the rest.
111
112 ### 6. Tests
113
114 - Unit test in core if there's new business logic
115 - Integration test in `tests/` for the full round-trip
116 - Manual test via the app for UI correctness
117
118 ## Adding a New Entity Type
119
120 Larger scope. Follow the existing pattern:
121
122 1. **Core model** (`crates/core/src/models/new_entity.rs`) -- struct + enums
123 2. **ID type** (`crates/core/src/id_types.rs`) -- `define_sqlite_id!(NewEntityId)`
124 3. **Repository trait** (`crates/core/src/repository.rs`) -- add trait
125 4. **SQLite implementation** (`crates/db-sqlite/src/repository/new_entity_repo.rs`)
126 5. **Migration** (`migrations/sqlite/`)
127 6. **Tauri commands** (`src-tauri/src/commands/new_entity.rs`) -- register in `main.rs`
128 7. **API methods** (`frontend/js/api.js`) -- add to the api object
129 8. **JS module** (`frontend/js/new-entity.js`) -- IIFE pattern, register on `GoingsOn.newEntity`
130 9. **Navigation** -- add sidebar entry, route handler
131
132 ## Code Style
133
134 ### Rust
135
136 - `?` for error propagation, `.unwrap()` only in tests
137 - `Option::and_then`/`map` over `if let Some`/`match` for simple transforms
138 - Typed errors (`CoreError`) not string errors
139 - Follow existing patterns in `commands/*.rs`
140
141 ### JavaScript
142
143 - Use `GoingsOn.ui.apiCall()` for all API calls with toasts
144 - Use `GoingsOn.ui.openFormModal()` for all CRUD forms
145 - Use `GoingsOn.utils.escapeHtml()` / `escapeAttr()` for user data in HTML
146 - No `window.X` exports -- use the `GoingsOn` namespace
147 - `async/await` over `.then()` chains
148
149 ### CSS
150
151 - CSS variables from the design system
152 - Neobrute style (see `docs/styleguide.md`)
153 - No inline styles except for dynamic values
154 - Only edit `styles.css`, never `styles.min.css`
155
156 ## Testing
157
158 - **Rust unit tests:** in-file `#[cfg(test)]` modules
159 - **Rust integration tests:** `tests/` directory
160 - **JS tests:** `frontend/js/tests/` (run via the test runner)
161 - Always verify the IPC round-trip works (Rust command -> JS render)
162
163 ## Git Workflow
164
165 Contributions via `git send-email`. No PRs, no issue tracker. Discussion via email.
166
167 ## Key Paths
168
169 | What | Where |
170 |------|-------|
171 | Domain types | `crates/core/src/models/` |
172 | Repository traits | `crates/core/src/repository.rs` |
173 | SQLite implementations | `crates/db-sqlite/src/repository/` |
174 | Tauri commands | `src-tauri/src/commands/` |
175 | Frontend JS | `src-tauri/frontend/js/` |
176 | Styles | `src-tauri/frontend/css/styles.css` |
177 | Migrations | `migrations/sqlite/` |
178