Skip to main content

max / goingson

exorcise: remaining public docs (em-dashes) Replaced prose em-dashes with colons/commas/periods across architecture, data-export, design-system, troubleshooting. Punctuation only (verified: no words changed beyond post-period capitalization); code fences, CSS token names, inline code untouched. ASCII '--' prose dashes in README/contributing/frontend_architecture left as-is pending a separate style decision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-12 16:28 UTC
Commit: b8ea7ee039d66d2c43d9639c30cf968c558ba528
Parent: df7eb7d
4 files changed, +48 insertions, -48 deletions
@@ -338,7 +338,7 @@ Frontend (JS)
338 338 - Easy to swap databases or add new ones
339 339
340 340 ### Vanilla Frontend
341 - - No JavaScript framework — vanilla JS with IIFE modules
341 + - No JavaScript framework: vanilla JS with IIFE modules
342 342 - All code under `GoingsOn` global namespace (no `window.*` exports)
343 343 - Centralized state via `GoingsOn.state` with pub/sub reactivity
344 344 - IPC via Tauri invoke
@@ -54,19 +54,19 @@ The full field list for each entity is the serde-serialized form of the Rust typ
54 54 }
55 55 ```
56 56
57 - **Task** (`crates/core/src/models/task.rs`) — the densest entity. Required: `id`, `description`, `status`, `priority`, `tags`, `urgency`, `createdAt`. Optional fields cover scheduling (`due`, `scheduledStart`, `scheduledDuration`), recurrence (`recurrence`, `recurrenceRule`, `recurrenceParentId`), workflow (`snoozedUntil`, `waitingForResponse`), linkage (`projectId`, `milestoneId`, `contactId`, `sourceEmailId`), nested data (`annotations`, `subtasks`), and time tracking (`estimatedMinutes`, `actualMinutes`).
57 + **Task** (`crates/core/src/models/task.rs`): the densest entity. Required: `id`, `description`, `status`, `priority`, `tags`, `urgency`, `createdAt`. Optional fields cover scheduling (`due`, `scheduledStart`, `scheduledDuration`), recurrence (`recurrence`, `recurrenceRule`, `recurrenceParentId`), workflow (`snoozedUntil`, `waitingForResponse`), linkage (`projectId`, `milestoneId`, `contactId`, `sourceEmailId`), nested data (`annotations`, `subtasks`), and time tracking (`estimatedMinutes`, `actualMinutes`).
58 58
59 - **Event** (`crates/core/src/models/event.rs`) — calendar event with start/end times, optional project/contact links, recurrence rule.
59 + **Event** (`crates/core/src/models/event.rs`): calendar event with start/end times, optional project/contact links, recurrence rule.
60 60
61 - **Email** (`crates/core/src/models/email.rs`) — message metadata, addresses, body (text and html), attachments are stored by reference (path, mime, size) — attachment **contents** are not embedded in the JSON to keep file sizes sane. To export attachment payloads, copy the `attachments/` directory next to the SQLite file.
61 + **Email** (`crates/core/src/models/email.rs`): message metadata, addresses, body (text and html), attachments are stored by reference (path, mime, size). Attachment **contents** are not embedded in the JSON to keep file sizes sane. To export attachment payloads, copy the `attachments/` directory next to the SQLite file.
62 62
63 - **Contact** (`crates/core/src/contact.rs`) — name, multi-field emails/phones/socials, tags, notes.
63 + **Contact** (`crates/core/src/contact.rs`): name, multi-field emails/phones/socials, tags, notes.
64 64
65 65 ### Denormalized display fields
66 66
67 67 A few fields are duplicated for display convenience and are not authoritative:
68 68
69 - - `Task.projectName`, `Task.contactName` — copies of the linked project/contact name at export time. On restore, GoingsOn re-derives these from the linked IDs; if a linked project was deleted before export, the display name still appears but the link won't resolve.
69 + - `Task.projectName`, `Task.contactName`: copies of the linked project/contact name at export time. On restore, GoingsOn re-derives these from the linked IDs; if a linked project was deleted before export, the display name still appears but the link won't resolve.
70 70
71 71 If you're processing exports programmatically, treat IDs as the source of truth and ignore the `*Name` fields.
72 72
@@ -74,8 +74,8 @@ If you're processing exports programmatically, treat IDs as the source of truth
74 74
75 75 Restoring a `.json.gz` backup goes through one of two modes:
76 76
77 - - **Replace all** — clears the existing database, then loads the backup. Use this when restoring onto a fresh install.
78 - - **Merge** — adds the backup's entities to whatever is already there. Useful for combining devices, but will create duplicates if the same entities exist on both sides (entities are matched by ID; a duplicate task on two devices created independently has two different IDs).
77 + - **Replace all**: clears the existing database, then loads the backup. Use this when restoring onto a fresh install.
78 + - **Merge**: adds the backup's entities to whatever is already there. Useful for combining devices, but will create duplicates if the same entities exist on both sides (entities are matched by ID; a duplicate task on two devices created independently has two different IDs).
79 79
80 80 Plain `.json` exports are not restorable through the UI. They are intended for inspection and migration to other tools. If you want a round-trip backup, use the `.json.gz` format.
81 81
@@ -88,13 +88,13 @@ Open the `.json` file in a text editor. You should see:
88 88 1. The five top-level arrays match the counts shown in Settings → Data → Export summary.
89 89 2. Every `id` field is a UUID.
90 90 3. Every timestamp ends with `Z` (UTC).
91 - 4. Search for an entity you remember (a task title, a contact name) — it should appear verbatim.
91 + 4. Search for an entity you remember (a task title, a contact name). It should appear verbatim.
92 92
93 93 For a `.json.gz` backup, decompress first: `gunzip -k backup.json.gz` produces `backup.json` which you can inspect the same way.
94 94
95 95 ## What's not exported
96 96
97 - - **OAuth tokens and email passwords.** Re-authenticate after restoring. This is intentional — credentials should not travel in a portable file.
97 + - **OAuth tokens and email passwords.** Re-authenticate after restoring. This is intentional: credentials should not travel in a portable file.
98 98 - **TOTP secrets.** Same reason.
99 99 - **Cloud sync state.** A restored database starts as if sync had never been configured; re-enable it in Settings → Cloud sync.
100 100 - **Plugin runtime state.** Plugins are loaded fresh from the plugin directory on next launch.
@@ -103,4 +103,4 @@ For a `.json.gz` backup, decompress first: `gunzip -k backup.json.gz` produces `
103 103
104 104 ## Source of truth
105 105
106 - The export schema is defined in `src-tauri/src/export/backup.rs::FullExport`. Entity field lists are the serde forms of the structs in `crates/core/src/`. Any drift between this document and the code is a bug in this document — open an issue or PR.
106 + The export schema is defined in `src-tauri/src/export/backup.rs::FullExport`. Entity field lists are the serde forms of the structs in `crates/core/src/`. Any drift between this document and the code is a bug in this document: open an issue or PR.
@@ -1,6 +1,6 @@
1 - # GoingsOn Design System — Charter
1 + # GoingsOn Design System: Charter
2 2
3 - This is the **canonical primitive list** for the GoingsOn frontend. Every JS module that renders markup MUST use the primitive named here. If a render need does not match a primitive, the fix is to extend the primitive — not to fork it locally.
3 + This is the **canonical primitive list** for the GoingsOn frontend. Every JS module that renders markup MUST use the primitive named here. If a render need does not match a primitive, the fix is to extend the primitive, not to fork it locally.
4 4
5 5 For visual specs (colors, sizes, shadows, hover behavior) see `styleguide.md`. This file is the inventory and the rules.
6 6
@@ -8,7 +8,7 @@ For visual specs (colors, sizes, shadows, hover behavior) see `styleguide.md`. T
8 8
9 9 ---
10 10
11 - ## Token layer — `styles.css :root`
11 + ## Token layer: `styles.css :root`
12 12
13 13 The only place hex literals are allowed (besides `themes/`). Every JS render path consumes these via CSS classes; never via `var(--…)` in a JS string and never via fallback hex.
14 14
@@ -21,8 +21,8 @@ The only place hex literals are allowed (besides `themes/`). Every JS render pat
21 21 | Border | `--border-width` (2px), `--border-width-sm`, `--border-color`, `--border-light` | `--border-color` themeable; widths invariant |
22 22 | Shadow | `--shadow-offset-xs/sm/md/lg/xl`, `--shadow-brutal-xs/md/lg/xl` | Theme-invariant (Neobrute signature) |
23 23 | Radius | `--radius-xs/sm/md/lg/xl/full` | Invariant |
24 - | Spacing | `--space-1` … `--space-6` (0.25 → 1.5rem) | Invariant |
25 - | Type size | `--font-size-xxs` … `--font-size-4xl` | Invariant |
24 + | Spacing | `--space-1` ... `--space-6` (0.25 → 1.5rem) | Invariant |
25 + | Type size | `--font-size-xxs` ... `--font-size-4xl` | Invariant |
26 26 | Line height | `--line-height-tight/normal/relaxed` | Invariant |
27 27 | Font family | `--font-sans`, `--font-serif`, `--font-mono`, `--font-display` | Invariant |
28 28 | Layout width | `--width-container`, `--width-modal`, `--width-sidebar` | Invariant |
@@ -33,42 +33,42 @@ The only place hex literals are allowed (besides `themes/`). Every JS render pat
33 33
34 34 ---
35 35
36 - ## Component primitives — canonical class is the contract
36 + ## Component primitives: canonical class is the contract
37 37
38 38 Each primitive below lists its **canonical class** (use this, only this). Modifier classes follow `--modifier` or `state-*` patterns. If you find yourself wanting a new modifier, add it here first.
39 39
40 - ### Button — `.btn`
40 + ### Button: `.btn`
41 41 Variants: `.btn-primary`, `.btn-secondary`, `.btn-danger`, `.btn-icon`, `.btn-text`, `.btn-link`.
42 42 Sizes: `.btn-sm` (default size is medium).
43 43 State: `.btn-loading`.
44 44 **Never** style a `<button>` without `.btn`. **Never** inline a hex color on a button.
45 45
46 - ### Card — `.card`
46 + ### Card: `.card`
47 47 Sub-parts: `.card-header`, `.card-title`, `.card-description`, `.card-meta`, `.card-badge`.
48 48 Container: `.cards-grid`.
49 49 Used by: projects-render, contacts-render, dashboard tiles.
50 50
51 - ### Form field — `.form-group`
51 + ### Form field: `.form-group`
52 52 Sub-parts: `.form-label`, `.form-input | .form-select | .form-textarea`, `.form-actions`, `.form-row`.
53 53 Canonical render helper (**to be added**): `GoingsOn.ui.renderFormField({ kind, label, value, error, help })`. Until it exists, hand-rolled `.form-group` blocks are tolerated; once it lands they are not.
54 54
55 - ### Badge / Tag — `.badge`, `.tag`
55 + ### Badge / Tag: `.badge`, `.tag`
56 56 Color variant: `[data-color="green|yellow|red|cyan|purple|muted"]`.
57 57 Status variant on `.tag`: `.status-active | .status-onhold | .status-archived | .status-inactive | .status-completed`.
58 58
59 - ### Modal — `.modal-overlay` (single global)
59 + ### Modal: `.modal-overlay` (single global)
60 60 Open via `GoingsOn.ui.openModal(title, html, opts)`. Sub-parts: `.modal-container` (+ `.modal-large`), `.modal-header`, `.modal-title`, `.modal-content`, `.modal-close`. Visibility via `.hidden` / `.closing`. **There is only one modal overlay in the DOM** (`#modal-overlay` in `index.html`); never create another.
61 61
62 - ### Toast — `.toast`
62 + ### Toast: `.toast`
63 63 Variants: `.toast-info`, `.toast-success`, `.toast-error`, `.toast-undo`.
64 64 Undo sub-parts: `.undo-message`, `.undo-btn`, `.undo-countdown`.
65 65 Show via `GoingsOn.ui.showToast(msg, type, opts)` or `GoingsOn.ui.showUndoToast(...)`.
66 - **Rule (to be enforced):** positioning, shadow, and color belong on these classes in CSS — `showToast` MUST NOT inject `style.cssText`. Today's helper violates this; fix in remediation.
66 + **Rule (to be enforced):** positioning, shadow, and color belong on these classes in CSS. `showToast` MUST NOT inject `style.cssText`. Today's helper violates this; fix in remediation.
67 67
68 68 ### Confirm dialog
69 - Render via `GoingsOn.ui.showConfirmDialog(title, message, opts)` or `GoingsOn.ui.confirmDelete(name, action)`. Uses the global modal. **Never** call `window.confirm()` (one offender remains in `contacts.js` — fix in remediation).
69 + Render via `GoingsOn.ui.showConfirmDialog(title, message, opts)` or `GoingsOn.ui.confirmDelete(name, action)`. Uses the global modal. **Never** call `window.confirm()` (one offender remains in `contacts.js`, fix in remediation).
70 70
71 - ### Empty state — `.empty-state`
71 + ### Empty state: `.empty-state`
72 72 Canonical: `<div class="empty-state"><div class="empty-state-icon">…</div><p class="empty-state-text">…</p><button class="btn btn-primary">…</button></div>`.
73 73 Render via `GoingsOn.ui.renderEmptyState(message, buttonLabel?, onClick?)`.
74 74 The non-canonical classes `.empty-dashboard-list`, `.kanban-empty`, `.virtual-scroller-empty` are **deprecated**; consolidate to `.empty-state` with size modifiers in remediation.
@@ -77,24 +77,24 @@ The non-canonical classes `.empty-dashboard-list`, `.kanban-empty`, `.virtual-sc
77 77 Classes: `.skeleton-shimmer`, `.skeleton-row`, `.skeleton-lines`, `.skeleton-line.long | .medium | .short`, `.spinner`, `.loading`.
78 78 Canonical helper (**to be added**): `GoingsOn.ui.renderSkeleton(kind, rows)`. No view uses skeletons today; once the helper exists, list views should switch on by default for the first paint after `invoke()`.
79 79
80 - ### Context menu — `.context-menu`
80 + ### Context menu: `.context-menu`
81 81 State: `.visible`. Items: `.context-menu-item` (+ `--danger`), `.context-menu-separator`, `.context-menu-header`.
82 82 Open via `GoingsOn.ui.showContextMenu(x, y, items)`.
83 83
84 - ### Tab / pill nav — `.tab-navigation` / `.pill-nav`
85 - Active state: `.tab.active` / `.pill.active`. Used in shell (`index.html`) only — feature modules should not introduce new tab styles.
84 + ### Tab / pill nav: `.tab-navigation` / `.pill-nav`
85 + Active state: `.tab.active` / `.pill.active`. Used in shell (`index.html`) only. Feature modules should not introduce new tab styles.
86 86
87 - ### Filter bar — `.filter-bar`
87 + ### Filter bar: `.filter-bar`
88 88 Children: `.filter-select`, `.filter-checkbox`. Used in tasks and emails filter rows.
89 89
90 - ### Progress bar — `.progress-bar-container` + `.progress-bar`
90 + ### Progress bar: `.progress-bar-container` + `.progress-bar`
91 91 Used in tasks (subtask completion) and reviews.
92 92
93 93 ### Row primitives
94 94
95 95 | Kind | Canonical class | Render helper (today) |
96 96 |---|---|---|
97 - | Task row | `.task-row` (in `.task-table`) | `renderTaskRow(t, index)` — `tasks-render.js` |
97 + | Task row | `.task-row` (in `.task-table`) | `renderTaskRow(t, index)`, `tasks-render.js` |
98 98 | Event row | `.event-row-virtual` (in `.event-table-virtual`) | inline in `events.js` |
99 99 | Project card | `.card` (in `.cards-grid`) | inline in `projects-render.js` |
100 100 | Contact card | `.card.contact-card` | inline in `contacts-render.js` |
@@ -108,31 +108,31 @@ Used in tasks (subtask completion) and reviews.
108 108 ### Bulk selection
109 109 Bar: `.bulk-actions-bar`. Controls: `.bulk-checkbox`, `.bulk-select-all`, `.bulk-count`. Row state: `.selected`, `.keyboard-selected`.
110 110
111 - ### Kanban — `.kanban-board`
111 + ### Kanban: `.kanban-board`
112 112 Children: `.kanban-column`, `.kanban-card`, `.kanban-card-empty`. Used only by tasks-kanban view.
113 113
114 - ### Day-plan timeline — `.timeline-slot`
114 + ### Day-plan timeline: `.timeline-slot`
115 115 Blocks: `.time-block`, `.block-focus`, `.block-personal`. Slot height read from `--timeline-slot-h`.
116 116
117 - ### Weekly review grid — `.weekly-grid`
117 + ### Weekly review grid: `.weekly-grid`
118 118 Cells: `.weekly-cell`, `.weekly-day-header`.
119 119
120 - ### Subtasks — `.subtask-item`
120 + ### Subtasks: `.subtask-item`
121 121 Variant: `.subtask-item-linked` (left-border indicator for linked task). Children: `.subtask-checkbox`, `.subtask-text-done`.
122 122
123 - ### Shell — `.app-header` / `.app-body` / `.main-content` / `.page-header` / `.page-title`
123 + ### Shell: `.app-header` / `.app-body` / `.main-content` / `.page-header` / `.page-title`
124 124 Feature modules do not redefine shell classes.
125 125
126 126 ---
127 127
128 - ## Theme contract — `js/themes.js`
128 + ## Theme contract: `js/themes.js`
129 129
130 130 Every theme is a TOML file under `themes/helix/` with a `[palette]` block (Helix-style names) and UI-key references. At runtime, `js/themes.js` maps 13 dotted TOML paths to CSS custom properties; selection persists to `localStorage` (`goingson-theme`).
131 131
132 132 **Rules:**
133 133 1. A theme overrides **color tokens only**. Spacing, radius, shadow offsets, type are theme-invariant.
134 134 2. Every color token in `:root` either has a mapping in `js/themes.js` or is annotated `/* theme-invariant */`. Adding a new color token requires updating the mapping in the same PR.
135 - 3. JS rendering paths never read theme values directly — they use CSS classes that consume `var(--…)`. No JS string should contain `var(--accent-…, #fallback)` because the fallback bypasses the theme.
135 + 3. JS rendering paths never read theme values directly. They use CSS classes that consume `var(--…)`. No JS string should contain `var(--accent-…, #fallback)` because the fallback bypasses the theme.
136 136
137 137 ---
138 138
@@ -141,7 +141,7 @@ Every theme is a TOML file under `themes/helix/` with a `[palette]` block (Helix
141 141 1. `style="display:none"` in HTML is allowed only on the modal overlay and similar shell-level slots; feature views use `.hidden`.
142 142 2. No `style.cssText` in JS that contains a color, shadow, or border value. Layout-only inline styles (`flex`, `gap`, `min-width`) are tolerated during remediation; the goal is zero.
143 143 3. No hex literal in any file outside `styles.css` and `themes/*.toml`.
144 - 4. No `var(--token, #fallback)` — the fallback defeats theming.
144 + 4. No `var(--token, #fallback)`. The fallback defeats theming.
145 145
146 146 ---
147 147
@@ -150,19 +150,19 @@ Every theme is a TOML file under `themes/helix/` with a `[palette]` block (Helix
150 150 These apply across every surface. Violations caught by reviewer checklist or `scripts/lint-frontend.sh`. Derived from the internal Phase 7 UX audit roll-up.
151 151
152 152 ### State communication
153 - Every visual state — active, selected, running, error, success — must pair color with a second non-color signal: shape, position, weight, icon, or text. Color alone is not sufficient. (Phase 7 Pattern 1 — 6 surfaces affected.)
153 + Every visual state (active, selected, running, error, success) must pair color with a second non-color signal: shape, position, weight, icon, or text. Color alone is not sufficient. (Phase 7 Pattern 1, 6 surfaces affected.)
154 154
155 155 ### Filter & view state in the URL
156 - Every filter, sort, and view-mode setting that changes what the user sees must be mirrored to `location.search` on change and restored on init. Filter state must not live only in the DOM or in module-level JS. A shared `js/query-state.js` helper covers all surfaces. (Pattern 2 — 5 surfaces affected.)
156 + Every filter, sort, and view-mode setting that changes what the user sees must be mirrored to `location.search` on change and restored on init. Filter state must not live only in the DOM or in module-level JS. A shared `js/query-state.js` helper covers all surfaces. (Pattern 2, 5 surfaces affected.)
157 157
158 158 ### Bulk operations always undoable
159 - Every bulk operation (any action touching more than one record at once) must wrap its API call in `GoingsOn.ui.showUndoToast` with a captured pre-state and an inverse operation. Use the shared `bulkActionWithUndo(action, inverse, ids, prevState)` helper. (Pattern 3 — 3 surfaces affected.)
159 + Every bulk operation (any action touching more than one record at once) must wrap its API call in `GoingsOn.ui.showUndoToast` with a captured pre-state and an inverse operation. Use the shared `bulkActionWithUndo(action, inverse, ids, prevState)` helper. (Pattern 3, 3 surfaces affected.)
160 160
161 161 ### Native dialogs forbidden
162 162 `window.confirm`, `window.prompt`, and `window.alert` are banned. Use `GoingsOn.ui.showConfirmDialog`, `GoingsOn.ui.showPromptDialog`, and `GoingsOn.ui.showToast`. Native dialogs are disabled on iOS WKWebView and unstyled on all platforms. Lint rule `no-native-dialogs` enforces this.
163 163
164 164 ### Mobile is responsive CSS by default
165 - JS branches on `GoingsOn.touch.isTouchDevice` (or media-query equivalents) require explicit justification documented here. Default is shared component + CSS layout reflow. (Pattern 5 — Phase 6 architectural finding.)
165 + JS branches on `GoingsOn.touch.isTouchDevice` (or media-query equivalents) require explicit justification documented here. Default is shared component + CSS layout reflow. (Pattern 5, Phase 6 architectural finding.)
166 166
167 167 ### Multi-step flows show progress
168 168 Any flow with more than two sequential modal steps shows a "Step N of M" indicator in the modal header. Applies to OAuth, encryption setup, plugin import wizards.
@@ -1,4 +1,4 @@
1 - # Troubleshooting — GoingsOn
1 + # Troubleshooting: GoingsOn
2 2
3 3 ## IMAP Connection Failures
4 4
@@ -76,9 +76,9 @@ GoingsOn stores OAuth tokens, email passwords, and sync credentials in the OS ke
76 76 | Linux | Secret Service (D-Bus) | Ensure service running: `systemctl --user start secrets.service` |
77 77
78 78 **Keychain keys:**
79 - - `goingson:oauth:{account_id}` — OAuth refresh tokens
80 - - `goingson:password:{account_id}` — Email passwords
81 - - `goingson:sync:token` — SyncKit auth token
79 + - `goingson:oauth:{account_id}`: OAuth refresh tokens
80 + - `goingson:password:{account_id}`: Email passwords
81 + - `goingson:sync:token`: SyncKit auth token
82 82
83 83 ## Error Codes
84 84