# GoingsOn Frontend Architecture Vanilla JavaScript frontend for the Tauri 2 desktop app. No framework, no build step, no bundler. ~45 files organized under the `GoingsOn` global namespace. ## Namespace All code lives under `window.GoingsOn`. No other globals. Cross-module calls use `GoingsOn.moduleName.functionName()`. ``` GoingsOn .api Tauri IPC abstraction (api.js) .state Centralized state with pub/sub (state.js) .ui Modal, toast, form utilities (components.js, components-modal.js) .utils HTML escaping, validation, debounce (utils.js) .projects Project list, detail, CRUD (projects.js, projects-render.js) .tasks Task list, CRUD (tasks.js, tasks-render.js, tasks-kanban.js) .events Event list, CRUD (events.js) .emails Email list, threading (emails.js) .contacts Contact CRUD (contacts.js, contacts-render.js) .dayPlan Time-blocking day planner (day-planning.js, day-planning-render.js) .snooze Snooze modal + actions (snooze.js) .navigation View switching, sidebar (navigation.js) .search Full-text search (search.js) .settings Settings, export (settings.js, settings-sync.js) .VirtualScroller Virtual scrolling for large lists .SelectionManager Multi-select with shift/ctrl .PaginationManager Page navigation .handle(path, ...args) Universal onclick dispatcher ``` ## Module Pattern Every domain module is an IIFE that registers its public API on the namespace: ```javascript (function() { 'use strict'; // Private constants and helpers const ITEMS_PER_PAGE = 50; function privateHelper() { ... } // Public functions async function load() { ... } function openNew() { ... } function openEdit(id) { ... } // Register on namespace GoingsOn.myModule = { load, openNew, openEdit }; })(); ``` Rules: - All modules use `'use strict'` - Private state stays inside the IIFE closure - Public API is the object assigned to `GoingsOn.moduleName` - No `window.X` exports for new code - Prefer `async/await` over `.then()` chains ## State Management `GoingsOn.state` is a single `AppStateManager` instance with reactive pub/sub. ### Reading state ```javascript const tasks = GoingsOn.state.tasks; const currentProject = GoingsOn.state.currentProjectId; ``` ### Writing state ```javascript GoingsOn.state.set('tasks', updatedTasks); // Triggers subscribers GoingsOn.state.update({ tasks: t, projects: p }); // Batch update ``` ### Subscribing to changes ```javascript const unsubscribe = GoingsOn.state.subscribe('tasks', (newTasks, oldTasks) => { renderTaskList(newTasks); }); // Later: unsubscribe(); ``` ### State properties | Property | Type | Domain | |----------|------|--------| | `projects` | Array | Data | | `tasks` | Array | Data | | `emails` | Array | Data | | `emailAccounts` | Array | Data | | `currentView` | string | UI | | `currentProjectId` | string/null | UI | | `dayPlanDate` | Date | UI | | `dayPlanData` | object/null | UI | | `selectedTaskIds` | Set | Selection | | `selectedEmailIds` | Set | Selection | | `taskPage` | number | Pagination | | `emailPage` | number | Pagination | Never create module-local caches of data that belongs in state. All shared data goes through `GoingsOn.state`. ## API Layer `GoingsOn.api` wraps every Tauri IPC command. The UI never calls `__TAURI__.core.invoke` directly. ```javascript // Methods map 1:1 to Rust #[tauri::command] functions const projects = await GoingsOn.api.projects.list(); const task = await GoingsOn.api.tasks.create({ description: "Buy milk", priority: "medium" }); await GoingsOn.api.tasks.complete(taskId); ``` API groups: `projects`, `tasks`, `annotations`, `subtasks`, `events`, `emails`, `emailAccounts`, `contacts`, `search`, `stats`, `dayPlan`, `savedViews`, `milestones`, `themes`, `sync`, `export`, `plugins`. ## Form Modal System All CRUD forms use `GoingsOn.ui.openFormModal()`. Define fields as data, not HTML: ```javascript function getFields(item = null) { return [ { name: 'description', type: 'text', label: 'Description', required: true, value: item?.description || '' }, { name: 'priority', type: 'select', label: 'Priority', options: PRIORITY_OPTIONS, value: item?.priority || 'medium' }, { name: 'dueDate', type: 'datetime-local', label: 'Due Date', value: item?.dueDate || '' }, { name: 'notes', type: 'textarea', label: 'Notes', value: item?.notes || '' }, ]; } // Open create form GoingsOn.ui.openFormModal({ title: 'New Task', entityType: 'task', isEdit: false, fields: getFields(), onSubmit: async (data) => { await GoingsOn.ui.apiCall(GoingsOn.api.tasks.create(data), { successMessage: 'Task created', reload: load, }); }, }); // Open edit form GoingsOn.ui.openFormModal({ title: 'Edit Task', entityType: 'task', isEdit: true, entityId: task.id, fields: getFields(task), onSubmit: async (data) => { await GoingsOn.ui.apiCall(GoingsOn.api.tasks.update(task.id, data), { successMessage: 'Task updated', reload: load, }); }, }); ``` Supported field types: `text`, `textarea`, `select`, `datetime-local`, `checkbox`, `number`, `email`, `password`, `hidden`. ## API Call Wrapper `GoingsOn.ui.apiCall()` handles loading state, success/error toasts, and reload: ```javascript await GoingsOn.ui.apiCall(GoingsOn.api.tasks.delete(id), { successMessage: 'Task deleted', errorMessage: 'Failed to delete task', reload: load, }); ``` ## Utility Functions ```javascript GoingsOn.utils.escapeHtml(str) // Prevent XSS in innerHTML GoingsOn.utils.escapeAttr(str) // For HTML attribute values GoingsOn.utils.formatDue(date) // Human-readable due dates GoingsOn.utils.formatEmailDate(date) // Email timestamp formatting GoingsOn.utils.getErrorMessage(err) // Extract error message from any error type ``` Always use `escapeHtml()` and `escapeAttr()` when interpolating user data into HTML strings. ## Data Flow ``` User action (click, keyboard) -> GoingsOn.api.tasks.create(data) // IPC to Rust -> Rust command validates + persists -> Response with pre-computed display fields -> GoingsOn.state.set('tasks', updated) // Update state -> Subscribers re-render // Reactive update ``` ### Pre-computed Response Fields Rust response types include display-ready values. JS never calculates dates, formatting, or derived state: | Response | Pre-computed fields | |----------|-------------------| | TaskResponse | `dueFormatted`, `urgencyClass`, `isOverdue`, `isSnoozed`, `subtaskCount`, `subtaskProgress` | | EventResponse | `timeFormatted`, `dateFormatted`, `isPast`, `proximityClass`, `proximityLabel` | | EmailResponse | `receivedFormatted` | ## Load Order Scripts load in order via `