Skip to main content

max / goingson

8.0 KB · 240 lines History Blame Raw
1 # GoingsOn Frontend Architecture
2
3 Vanilla JavaScript frontend for the Tauri 2 desktop app. No framework, no build step, no bundler. ~45 files organized under the `GoingsOn` global namespace.
4
5 ## Namespace
6
7 All code lives under `window.GoingsOn`. No other globals. Cross-module calls use `GoingsOn.moduleName.functionName()`.
8
9 ```
10 GoingsOn
11 .api Tauri IPC abstraction (api.js)
12 .state Centralized state with pub/sub (state.js)
13 .ui Modal, toast, form utilities (components.js, components-modal.js)
14 .utils HTML escaping, validation, debounce (utils.js)
15 .projects Project list, detail, CRUD (projects.js, projects-render.js)
16 .tasks Task list, CRUD (tasks.js, tasks-render.js, tasks-kanban.js)
17 .events Event list, CRUD (events.js)
18 .emails Email list, threading (emails.js)
19 .contacts Contact CRUD (contacts.js, contacts-render.js)
20 .dayPlan Time-blocking day planner (day-planning.js, day-planning-render.js)
21 .snooze Snooze modal + actions (snooze.js)
22 .navigation View switching, sidebar (navigation.js)
23 .search Full-text search (search.js)
24 .settings Settings, export (settings.js, settings-sync.js)
25 .VirtualScroller Virtual scrolling for large lists
26 .SelectionManager Multi-select with shift/ctrl
27 .PaginationManager Page navigation
28 .handle(path, ...args) Universal onclick dispatcher
29 ```
30
31 ## Module Pattern
32
33 Every domain module is an IIFE that registers its public API on the namespace:
34
35 ```javascript
36 (function() {
37 'use strict';
38
39 // Private constants and helpers
40 const ITEMS_PER_PAGE = 50;
41 function privateHelper() { ... }
42
43 // Public functions
44 async function load() { ... }
45 function openNew() { ... }
46 function openEdit(id) { ... }
47
48 // Register on namespace
49 GoingsOn.myModule = { load, openNew, openEdit };
50 })();
51 ```
52
53 Rules:
54 - All modules use `'use strict'`
55 - Private state stays inside the IIFE closure
56 - Public API is the object assigned to `GoingsOn.moduleName`
57 - No `window.X` exports for new code
58 - Prefer `async/await` over `.then()` chains
59
60 ## State Management
61
62 `GoingsOn.state` is a single `AppStateManager` instance with reactive pub/sub.
63
64 ### Reading state
65
66 ```javascript
67 const tasks = GoingsOn.state.tasks;
68 const currentProject = GoingsOn.state.currentProjectId;
69 ```
70
71 ### Writing state
72
73 ```javascript
74 GoingsOn.state.set('tasks', updatedTasks); // Triggers subscribers
75 GoingsOn.state.update({ tasks: t, projects: p }); // Batch update
76 ```
77
78 ### Subscribing to changes
79
80 ```javascript
81 const unsubscribe = GoingsOn.state.subscribe('tasks', (newTasks, oldTasks) => {
82 renderTaskList(newTasks);
83 });
84 // Later: unsubscribe();
85 ```
86
87 ### State properties
88
89 | Property | Type | Domain |
90 |----------|------|--------|
91 | `projects` | Array | Data |
92 | `tasks` | Array | Data |
93 | `emails` | Array | Data |
94 | `emailAccounts` | Array | Data |
95 | `currentView` | string | UI |
96 | `currentProjectId` | string/null | UI |
97 | `dayPlanDate` | Date | UI |
98 | `dayPlanData` | object/null | UI |
99 | `selectedTaskIds` | Set | Selection |
100 | `selectedEmailIds` | Set | Selection |
101 | `taskPage` | number | Pagination |
102 | `emailPage` | number | Pagination |
103
104 Never create module-local caches of data that belongs in state. All shared data goes through `GoingsOn.state`.
105
106 ## API Layer
107
108 `GoingsOn.api` wraps every Tauri IPC command. The UI never calls `__TAURI__.core.invoke` directly.
109
110 ```javascript
111 // Methods map 1:1 to Rust #[tauri::command] functions
112 const projects = await GoingsOn.api.projects.list();
113 const task = await GoingsOn.api.tasks.create({ description: "Buy milk", priority: "medium" });
114 await GoingsOn.api.tasks.complete(taskId);
115 ```
116
117 API groups: `projects`, `tasks`, `annotations`, `subtasks`, `events`, `emails`, `emailAccounts`, `contacts`, `search`, `stats`, `dayPlan`, `savedViews`, `milestones`, `themes`, `sync`, `export`, `plugins`.
118
119 ## Form Modal System
120
121 All CRUD forms use `GoingsOn.ui.openFormModal()`. Define fields as data, not HTML:
122
123 ```javascript
124 function getFields(item = null) {
125 return [
126 { name: 'description', type: 'text', label: 'Description', required: true, value: item?.description || '' },
127 { name: 'priority', type: 'select', label: 'Priority', options: PRIORITY_OPTIONS, value: item?.priority || 'medium' },
128 { name: 'dueDate', type: 'datetime-local', label: 'Due Date', value: item?.dueDate || '' },
129 { name: 'notes', type: 'textarea', label: 'Notes', value: item?.notes || '' },
130 ];
131 }
132
133 // Open create form
134 GoingsOn.ui.openFormModal({
135 title: 'New Task',
136 entityType: 'task',
137 isEdit: false,
138 fields: getFields(),
139 onSubmit: async (data) => {
140 await GoingsOn.ui.apiCall(GoingsOn.api.tasks.create(data), {
141 successMessage: 'Task created',
142 reload: load,
143 });
144 },
145 });
146
147 // Open edit form
148 GoingsOn.ui.openFormModal({
149 title: 'Edit Task',
150 entityType: 'task',
151 isEdit: true,
152 entityId: task.id,
153 fields: getFields(task),
154 onSubmit: async (data) => {
155 await GoingsOn.ui.apiCall(GoingsOn.api.tasks.update(task.id, data), {
156 successMessage: 'Task updated',
157 reload: load,
158 });
159 },
160 });
161 ```
162
163 Supported field types: `text`, `textarea`, `select`, `datetime-local`, `checkbox`, `number`, `email`, `password`, `hidden`.
164
165 ## API Call Wrapper
166
167 `GoingsOn.ui.apiCall()` handles loading state, success/error toasts, and reload:
168
169 ```javascript
170 await GoingsOn.ui.apiCall(GoingsOn.api.tasks.delete(id), {
171 successMessage: 'Task deleted',
172 errorMessage: 'Failed to delete task',
173 reload: load,
174 });
175 ```
176
177 ## Utility Functions
178
179 ```javascript
180 GoingsOn.utils.escapeHtml(str) // Prevent XSS in innerHTML
181 GoingsOn.utils.escapeAttr(str) // For HTML attribute values
182 GoingsOn.utils.formatDue(date) // Human-readable due dates
183 GoingsOn.utils.formatEmailDate(date) // Email timestamp formatting
184 GoingsOn.utils.getErrorMessage(err) // Extract error message from any error type
185 ```
186
187 Always use `escapeHtml()` and `escapeAttr()` when interpolating user data into HTML strings.
188
189 ## Data Flow
190
191 ```
192 User action (click, keyboard)
193 -> GoingsOn.api.tasks.create(data) // IPC to Rust
194 -> Rust command validates + persists
195 -> Response with pre-computed display fields
196 -> GoingsOn.state.set('tasks', updated) // Update state
197 -> Subscribers re-render // Reactive update
198 ```
199
200 ### Pre-computed Response Fields
201
202 Rust response types include display-ready values. JS never calculates dates, formatting, or derived state:
203
204 | Response | Pre-computed fields |
205 |----------|-------------------|
206 | TaskResponse | `dueFormatted`, `urgencyClass`, `isOverdue`, `isSnoozed`, `subtaskCount`, `subtaskProgress` |
207 | EventResponse | `timeFormatted`, `dateFormatted`, `isPast`, `proximityClass`, `proximityLabel` |
208 | EmailResponse | `receivedFormatted` |
209
210 ## Load Order
211
212 Scripts load in order via `<script>` tags in `index.html`:
213
214 1. `goingson.js` -- creates `window.GoingsOn` namespace
215 2. `state.js` -- creates `GoingsOn.state`
216 3. `api.js` -- creates `GoingsOn.api`
217 4. `utils.js` -- populates `GoingsOn.utils`
218 5. `components.js`, `components-modal.js`, `form-modal.js` -- populates `GoingsOn.ui`
219 6. Infrastructure: `router.js`, `navigation.js`, `keyboard.js`, `selection-manager.js`, etc.
220 7. Domain modules: `projects.js`, `tasks.js`, `events.js`, `emails.js`, `contacts.js`, etc.
221 8. Feature modules: `day-planning.js`, `snooze.js`, `settings.js`, etc.
222 9. `app.js` -- initialization, event listeners, startup
223
224 ## CSS
225
226 Single stylesheet at `frontend/css/styles.css`. Uses CSS variables from the theme system. `styles.min.css` is auto-generated via `build-css.sh` (clean-css-cli) -- never edit it directly.
227
228 Follow the Neobrute design style (see `docs/styleguide.md`). No inline styles except for dynamic values.
229
230 ## Key Paths
231
232 | What | Where |
233 |------|-------|
234 | Namespace root | `src-tauri/frontend/js/goingson.js` |
235 | State manager | `src-tauri/frontend/js/state.js` |
236 | API layer | `src-tauri/frontend/js/api.js` |
237 | Form modal | `src-tauri/frontend/js/form-modal.js` |
238 | Styles | `src-tauri/frontend/css/styles.css` |
239 | Entry point | `src-tauri/frontend/index.html` |
240