Skip to main content

max / goingson

chore: drop pitch artifacts and update todo Delete generate_pitch_pdf.py and GoingsOn-Pitch.pdf from the repo root. The PDF rebuilds from the script (no-regenerables); both are recoverable via git history if the pitch effort resumes. The pre-Tauri _archive/ directory at the repo root was also removed earlier in this session — the Tauri migration shipped 2026-06-01, and git is the right home for that historical reference. todo.md reflects the cleared launch-readiness items: §3.3 repo hygiene done; the deferred ux-audit, rust-fuzz, use-fuzz, and creator-fuzz checklists all closed or explicitly deferred (?ui=mobile prod lockdown waits on a desktop-mobile distribution channel; db_watcher shutdown latency stays as cosmetic). Remaining: §3.1 mobile UI gating, §3.2 distribution + clean-mac DMG smoke test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-03 01:47 UTC
Commit: a7f0f3f69b0b7dfde41cacab52fa34bf71a1c02f
Parent: 9719e28
3 files changed, +25 insertions, -479 deletions
@@ -1,457 +0,0 @@
1 - #!/usr/bin/env python3
2 - """Generate GoingsOn pitch PDF."""
3 -
4 - from fpdf import FPDF
5 -
6 - # Colors
7 - BG = (232, 244, 248) # Light cyan background
8 - TEXT = (27, 54, 93) # Dark blue text
9 - ACCENT = (247, 209, 84) # Yellow accent
10 - MUTED = (100, 120, 145) # Muted text
11 - WHITE = (255, 255, 255)
12 - DARK = (30, 40, 55)
13 - GREEN = (92, 184, 92)
14 - SECTION_BG = (245, 250, 252)
15 -
16 -
17 - class PitchPDF(FPDF):
18 - def header(self):
19 - pass
20 -
21 - def footer(self):
22 - if self.page_no() > 1:
23 - self.set_y(-15)
24 - self.set_font("Helvetica", "I", 8)
25 - self.set_text_color(*MUTED)
26 - self.cell(0, 10, f"GoingsOn | goingson.app | Page {self.page_no()}", align="C")
27 -
28 - def section_title(self, title):
29 - self.set_font("Helvetica", "B", 16)
30 - self.set_text_color(*TEXT)
31 - self.cell(0, 10, title, new_x="LMARGIN", new_y="NEXT")
32 - # Yellow underline
33 - self.set_draw_color(*ACCENT)
34 - self.set_line_width(1.5)
35 - self.line(self.l_margin, self.get_y(), self.l_margin + 50, self.get_y())
36 - self.ln(6)
37 -
38 - def feature_block(self, icon, title, bullets):
39 - y_start = self.get_y()
40 - # Title
41 - self.set_font("Helvetica", "B", 12)
42 - self.set_text_color(*TEXT)
43 - self.cell(0, 7, f"{icon} {title}", new_x="LMARGIN", new_y="NEXT")
44 - self.ln(1)
45 - # Bullets
46 - self.set_font("Helvetica", "", 9.5)
47 - self.set_text_color(*MUTED)
48 - for bullet in bullets:
49 - self.set_x(self.l_margin + 6)
50 - self.multi_cell(self.epw - 6, 4.5, f"- {bullet}", new_x="LMARGIN", new_y="NEXT")
51 - self.ln(3)
52 -
53 - def two_col_features(self, left, right):
54 - col_w = (self.epw - 8) / 2
55 - y_start = self.get_y()
56 -
57 - # Left column
58 - self.set_x(self.l_margin)
59 - self.feature_col(left, col_w)
60 - y_left = self.get_y()
61 -
62 - # Right column
63 - self.set_y(y_start)
64 - self.set_x(self.l_margin + col_w + 8)
65 - self.feature_col(right, col_w)
66 - y_right = self.get_y()
67 -
68 - self.set_y(max(y_left, y_right))
69 -
70 - def feature_col(self, items, width):
71 - x_start = self.get_x()
72 - for icon, title, bullets in items:
73 - self.set_x(x_start)
74 - self.set_font("Helvetica", "B", 11)
75 - self.set_text_color(*TEXT)
76 - self.cell(width, 7, f"{icon} {title}", new_x="LMARGIN", new_y="NEXT")
77 - self.ln(1)
78 - self.set_font("Helvetica", "", 9)
79 - self.set_text_color(*MUTED)
80 - for bullet in bullets:
81 - self.set_x(x_start + 5)
82 - self.multi_cell(width - 5, 4.2, f"- {bullet}", new_x="LMARGIN", new_y="NEXT")
83 - self.ln(3)
84 -
85 -
86 - def build_pdf():
87 - pdf = PitchPDF()
88 - pdf.set_auto_page_break(auto=True, margin=20)
89 - pdf.set_margins(20, 15, 20)
90 -
91 - # =========================================================
92 - # PAGE 1 - Title / Hero
93 - # =========================================================
94 - pdf.add_page()
95 -
96 - # Background fill
97 - pdf.set_fill_color(*DARK)
98 - pdf.rect(0, 0, 210, 297, "F")
99 -
100 - # "GO" logo text
101 - pdf.set_y(60)
102 - pdf.set_font("Helvetica", "B", 72)
103 - pdf.set_text_color(*ACCENT)
104 - pdf.cell(0, 30, "GO", align="C", new_x="LMARGIN", new_y="NEXT")
105 -
106 - # App name
107 - pdf.set_font("Helvetica", "B", 36)
108 - pdf.set_text_color(*WHITE)
109 - pdf.cell(0, 16, "GoingsOn", align="C", new_x="LMARGIN", new_y="NEXT")
110 -
111 - # Tagline
112 - pdf.ln(6)
113 - pdf.set_font("Helvetica", "", 14)
114 - pdf.set_text_color(180, 195, 210)
115 - pdf.cell(0, 8, "Tasks. Email. Calendar. Contacts.", align="C", new_x="LMARGIN", new_y="NEXT")
116 - pdf.cell(0, 8, "One app. Offline-first. Native.", align="C", new_x="LMARGIN", new_y="NEXT")
117 -
118 - # Divider
119 - pdf.ln(12)
120 - pdf.set_draw_color(*ACCENT)
121 - pdf.set_line_width(1)
122 - pdf.line(70, pdf.get_y(), 140, pdf.get_y())
123 - pdf.ln(12)
124 -
125 - # Value props
126 - pdf.set_font("Helvetica", "", 11)
127 - pdf.set_text_color(200, 210, 220)
128 - props = [
129 - "Everything in one place -- no more juggling 5 apps",
130 - "Your data stays on your machine -- offline-first with local storage",
131 - "Native performance -- built with Rust, not Electron",
132 - "Free during alpha -- no subscriptions, no lock-in",
133 - "macOS + iOS -- with Windows and Linux support",
134 - ]
135 - for prop in props:
136 - pdf.cell(0, 7, prop, align="C", new_x="LMARGIN", new_y="NEXT")
137 -
138 - # Bottom
139 - pdf.set_y(255)
140 - pdf.set_font("Helvetica", "I", 10)
141 - pdf.set_text_color(120, 135, 155)
142 - pdf.cell(0, 6, "Alpha Release -- March 2026", align="C", new_x="LMARGIN", new_y="NEXT")
143 - pdf.cell(0, 6, "goingson.app", align="C", new_x="LMARGIN", new_y="NEXT")
144 -
145 - # =========================================================
146 - # PAGE 2 - Core Features
147 - # =========================================================
148 - pdf.add_page()
149 -
150 - pdf.section_title("Core Features")
151 -
152 - pdf.feature_block("TASKS", "Task Management", [
153 - "Automatic urgency scoring based on priority, due date, and age",
154 - "Quick-add with natural language: \"Fix bug due:tomorrow +urgent @Project\"",
155 - "Subtasks, annotations, and milestone tracking",
156 - "Recurrence (daily, weekly, monthly) with auto-creation on completion",
157 - "Snooze tasks until later today, tomorrow, weekend, or next week",
158 - "Bulk operations: complete, snooze, or delete multiple tasks at once",
159 - "Saved views and named filters for any combination of criteria",
160 - ])
161 -
162 - pdf.feature_block("EMAIL", "Email Integration", [
163 - "Full IMAP/SMTP client built in -- connect any email account",
164 - "OAuth2 for Fastmail, Google, Microsoft, and Yahoo",
165 - "Native JMAP protocol support for Fastmail",
166 - "Threaded conversations with reader mode (strips HTML clutter)",
167 - "Create tasks directly from emails with source linking",
168 - "Auto-sync on configurable intervals (5/15/30/60 min)",
169 - "Compose, reply, archive, snooze -- all without leaving the app",
170 - ])
171 -
172 - pdf.feature_block("CALENDAR", "Calendar & Events", [
173 - "Create events with title, description, location, and time range",
174 - "Link events to projects and contacts",
175 - "Recurrence support (daily, weekly, monthly, yearly)",
176 - "Time block types: Focus, Personal, Vacation, Free Time",
177 - "Event reminders with configurable lead time",
178 - "Conflict detection with existing time blocks",
179 - ])
180 -
181 - pdf.feature_block("CONTACTS", "Contacts", [
182 - "Store names, companies, emails, phones, social handles, custom fields",
183 - "Full-text search across all contact fields",
184 - "Auto-suggest contacts from email senders",
185 - "Link contacts to tasks, events, and email threads",
186 - ])
187 -
188 - # =========================================================
189 - # PAGE 3 - Planning & Extras
190 - # =========================================================
191 - pdf.add_page()
192 -
193 - pdf.section_title("Planning & Workflow")
194 -
195 - pdf.feature_block("PLAN", "Day Planning & Time Blocking", [
196 - "Visual hourly timeline for your day",
197 - "Drag unscheduled tasks onto the timeline to create time blocks",
198 - "Events display alongside tasks on the same timeline",
199 - "Current time indicator and day navigation with keyboard shortcuts",
200 - "Collapsible sidebar with unscheduled tasks",
201 - ])
202 -
203 - pdf.feature_block("REVIEW", "Weekly Review", [
204 - "Guided review: see what you completed last week",
205 - "Set focus tasks and projects for the current week",
206 - "Track vacation days with visual banners",
207 - "Monday nudge: gentle reminder when review is overdue",
208 - ])
209 -
210 - pdf.ln(4)
211 - pdf.section_title("Power Features")
212 -
213 - pdf.feature_block("SEARCH", "Search & Saved Views", [
214 - "Full-text search across tasks, projects, emails, and contacts",
215 - "Date range syntax: after:, before:, from:, to:",
216 - "Save any filter as a named view; pin favorites for quick access",
217 - ])
218 -
219 - pdf.feature_block("KEYBOARD", "Keyboard-Driven", [
220 - "Vim-style navigation (j/k for items, g+key to jump between views)",
221 - "Quick-add from anywhere with 'q', shortcuts overlay with '?'",
222 - "Native menu bar with standard macOS shortcuts",
223 - ])
224 -
225 - pdf.feature_block("EXPORT", "Export & Backup", [
226 - "Export all data as JSON, tasks as CSV, events as ICS",
227 - "Automated daily backups with configurable retention",
228 - "Restore from any backup point",
229 - ])
230 -
231 - pdf.feature_block("THEMES", "Themes", [
232 - "10 built-in themes: Catppuccin, Dracula, Nord, Tokyo Night, and more",
233 - "Follow System for automatic light/dark switching",
234 - ])
235 -
236 - # =========================================================
237 - # PAGE 4 - Why GoingsOn + Comparison
238 - # =========================================================
239 - pdf.add_page()
240 -
241 - pdf.section_title("Why GoingsOn?")
242 -
243 - pdf.set_font("Helvetica", "", 10.5)
244 - pdf.set_text_color(*TEXT)
245 - pdf.multi_cell(0, 5.5,
246 - "Most productivity tools are cloud-only, subscription-based, and spread across "
247 - "multiple apps. GoingsOn brings tasks, email, calendar, and contacts into one "
248 - "native application that works offline and keeps your data on your machine."
249 - )
250 - pdf.ln(6)
251 -
252 - # Comparison table
253 - pdf.set_font("Helvetica", "B", 11)
254 - pdf.set_text_color(*TEXT)
255 - pdf.cell(0, 8, "How it compares:", new_x="LMARGIN", new_y="NEXT")
256 - pdf.ln(2)
257 -
258 - headers = ["", "GoingsOn", "Todoist", "Things 3", "Fantastical", "Spark"]
259 - col_w = [38, 28, 28, 28, 28, 28]
260 -
261 - # Header row
262 - pdf.set_font("Helvetica", "B", 8)
263 - pdf.set_fill_color(*DARK)
264 - pdf.set_text_color(*WHITE)
265 - for i, h in enumerate(headers):
266 - pdf.cell(col_w[i], 7, h, border=1, fill=True, align="C")
267 - pdf.ln()
268 -
269 - rows = [
270 - ["Tasks", "Yes", "Yes", "Yes", "No", "No"],
271 - ["Email", "Yes", "No", "No", "No", "Yes"],
272 - ["Calendar", "Yes", "No", "No", "Yes", "No"],
273 - ["Contacts", "Yes", "No", "No", "No", "No"],
274 - ["Weekly Review", "Yes", "No", "No", "No", "No"],
275 - ["Offline-first", "Yes", "No", "Yes", "Yes", "No"],
276 - ["Cross-platform","Yes", "Yes", "Apple","Apple","Yes"],
277 - ["Free option", "Yes", "Ltd", "No", "Ltd", "Ltd"],
278 - ]
279 -
280 - pdf.set_font("Helvetica", "", 8)
281 - for row in rows:
282 - for i, val in enumerate(row):
283 - if i == 0:
284 - pdf.set_font("Helvetica", "B", 8)
285 - pdf.set_text_color(*TEXT)
286 - pdf.set_fill_color(*SECTION_BG)
287 - else:
288 - pdf.set_font("Helvetica", "", 8)
289 - if val == "Yes" and i == 1:
290 - pdf.set_text_color(*GREEN)
291 - pdf.set_fill_color(*WHITE)
292 - elif val == "Yes":
293 - pdf.set_text_color(80, 80, 80)
294 - pdf.set_fill_color(*WHITE)
295 - elif val == "No":
296 - pdf.set_text_color(180, 180, 180)
297 - pdf.set_fill_color(*WHITE)
298 - else:
299 - pdf.set_text_color(*MUTED)
300 - pdf.set_fill_color(*WHITE)
301 - pdf.cell(col_w[i], 6, val, border=1, fill=True, align="C" if i > 0 else "L")
302 - pdf.ln()
303 -
304 - pdf.ln(8)
305 -
306 - # Pricing note
307 - pdf.set_font("Helvetica", "B", 12)
308 - pdf.set_text_color(*TEXT)
309 - pdf.cell(0, 8, "Pricing", new_x="LMARGIN", new_y="NEXT")
310 - pdf.set_draw_color(*ACCENT)
311 - pdf.set_line_width(1.5)
312 - pdf.line(pdf.l_margin, pdf.get_y(), pdf.l_margin + 30, pdf.get_y())
313 - pdf.ln(4)
314 -
315 - pdf.set_font("Helvetica", "", 10.5)
316 - pdf.set_text_color(*TEXT)
317 - pdf.multi_cell(0, 5.5,
318 - "GoingsOn is free during the alpha period. After launch, we plan to offer a "
319 - "one-time purchase model -- no subscriptions. Your data, your app, forever."
320 - )
321 -
322 - pdf.ln(8)
323 -
324 - # Tech
325 - pdf.set_font("Helvetica", "B", 12)
326 - pdf.set_text_color(*TEXT)
327 - pdf.cell(0, 8, "Built With", new_x="LMARGIN", new_y="NEXT")
328 - pdf.set_draw_color(*ACCENT)
329 - pdf.line(pdf.l_margin, pdf.get_y(), pdf.l_margin + 30, pdf.get_y())
330 - pdf.ln(4)
331 -
332 - pdf.set_font("Helvetica", "", 10)
333 - pdf.set_text_color(*MUTED)
334 - tech_items = [
335 - "Rust backend (Tauri 2) -- native performance, small binary, low memory",
336 - "SQLite database -- your data in a single local file",
337 - "Vanilla JavaScript frontend -- no framework bloat",
338 - "205+ automated tests across the codebase",
339 - ]
340 - for item in tech_items:
341 - pdf.cell(0, 6, f" {item}", new_x="LMARGIN", new_y="NEXT")
342 -
343 - # =========================================================
344 - # PAGE 5 - Installing via TestFlight
345 - # =========================================================
346 - pdf.add_page()
347 -
348 - pdf.section_title("Get GoingsOn")
349 -
350 - # macOS
351 - pdf.set_font("Helvetica", "B", 13)
352 - pdf.set_text_color(*TEXT)
353 - pdf.cell(0, 8, "macOS", new_x="LMARGIN", new_y="NEXT")
354 - pdf.ln(2)
355 -
356 - pdf.set_font("Helvetica", "", 10.5)
357 - pdf.set_text_color(*MUTED)
358 - pdf.multi_cell(0, 5.5,
359 - "The macOS app will be shared with you directly as a .dmg file. "
360 - "Open the DMG and drag GoingsOn to your Applications folder."
361 - )
362 - pdf.ln(2)
363 - pdf.set_font("Helvetica", "I", 9.5)
364 - pdf.multi_cell(0, 5,
365 - "Note: Since the app is not yet on the Mac App Store, macOS may show a security "
366 - "warning on first launch. Right-click the app and choose \"Open\" to bypass this, "
367 - "or go to System Settings > Privacy & Security and click \"Open Anyway\"."
368 - )
369 -
370 - pdf.ln(8)
371 -
372 - # iOS TestFlight
373 - pdf.set_font("Helvetica", "B", 13)
374 - pdf.set_text_color(*TEXT)
375 - pdf.cell(0, 8, "iOS (via TestFlight)", new_x="LMARGIN", new_y="NEXT")
376 - pdf.ln(2)
377 -
378 - pdf.set_font("Helvetica", "", 10.5)
379 - pdf.set_text_color(*MUTED)
380 - pdf.multi_cell(0, 5.5,
381 - "GoingsOn for iOS is distributed through Apple's TestFlight, "
382 - "which lets you install pre-release apps directly on your iPhone or iPad."
383 - )
384 - pdf.ln(4)
385 -
386 - steps = [
387 - ("1.", "Install TestFlight",
388 - "Download \"TestFlight\" from the App Store if you don't have it already. "
389 - "It's a free app by Apple."),
390 - ("2.", "Open the invitation link",
391 - "You'll receive a TestFlight invitation link (via email or message). "
392 - "Tap the link on your iPhone or iPad."),
393 - ("3.", "Accept the invitation",
394 - "The link opens TestFlight. Tap \"Accept\" to join the GoingsOn beta, "
395 - "then tap \"Install\" to download the app."),
396 - ("4.", "Open GoingsOn",
397 - "Once installed, GoingsOn appears on your home screen like any other app. "
398 - "You can also launch it from inside TestFlight."),
399 - ("5.", "Updates are automatic",
400 - "When a new build is available, TestFlight will notify you. "
401 - "Updates install with one tap -- no need to uninstall and reinstall."),
402 - ]
403 -
404 - for num, title, desc in steps:
405 - pdf.set_font("Helvetica", "B", 11)
406 - pdf.set_text_color(*TEXT)
407 - pdf.cell(8, 6, num)
408 - pdf.cell(0, 6, title, new_x="LMARGIN", new_y="NEXT")
409 - pdf.set_x(pdf.l_margin + 8)
410 - pdf.set_font("Helvetica", "", 9.5)
411 - pdf.set_text_color(*MUTED)
412 - pdf.multi_cell(pdf.epw - 8, 4.8, desc, new_x="LMARGIN", new_y="NEXT")
413 - pdf.ln(3)
414 -
415 - pdf.ln(4)
416 -
417 - # Feedback
418 - pdf.set_font("Helvetica", "B", 13)
419 - pdf.set_text_color(*TEXT)
420 - pdf.cell(0, 8, "Sending Feedback", new_x="LMARGIN", new_y="NEXT")
421 - pdf.ln(2)
422 -
423 - pdf.set_font("Helvetica", "", 10.5)
424 - pdf.set_text_color(*MUTED)
425 - pdf.multi_cell(0, 5.5,
426 - "Your feedback is invaluable during this alpha period. You can:"
427 - )
428 - pdf.ln(2)
429 -
430 - feedback = [
431 - "Take a screenshot in the app and share it with any notes",
432 - "Use TestFlight's built-in feedback tool (shake your iPhone or tap \"Send Beta Feedback\" in TestFlight)",
433 - "Message me directly -- anything from bug reports to feature ideas",
434 - ]
435 - for item in feedback:
436 - pdf.set_x(pdf.l_margin + 4)
437 - pdf.set_font("Helvetica", "", 10)
438 - pdf.multi_cell(pdf.epw - 4, 5, f"- {item}", new_x="LMARGIN", new_y="NEXT")
439 -
440 - pdf.ln(10)
441 -
442 - # Thank you
443 - pdf.set_font("Helvetica", "B", 14)
444 - pdf.set_text_color(*TEXT)
445 - pdf.cell(0, 10, "Thank you for testing GoingsOn!", align="C", new_x="LMARGIN", new_y="NEXT")
446 -
447 -
448 - # =========================================================
449 - # OUTPUT
450 - # =========================================================
451 - output_path = "/Users/max/Git/active/goingson/GoingsOn-Pitch.pdf"
452 - pdf.output(output_path)
453 - print(f"PDF generated: {output_path}")
454 -
455 -
456 - if __name__ == "__main__":
457 - build_pdf()
M todo.md +25 -22
@@ -8,32 +8,33 @@ Four-axis parallel audit (rust-fuzz + use-fuzz + creator-fuzz + ux-audit) comple
8 8 ### Deferred to post-launch (audited but not blocking)
9 9
10 10 **rust-fuzz**
11 - - `src-tauri/src/commands/email.rs:598` — `std::fs::read` for attachments inside async command; wrap in `spawn_blocking` (pattern already at email.rs:422 and email_sync.rs:224).
12 - - `src-tauri/src/email/smtp_client.rs:291` — `ContentType::parse("application/octet-stream").unwrap()` on a static; replace with infallible mime constant.
13 - - `src-tauri/src/state.rs:173`, `commands/sync.rs:{75,177,203}`, `commands/oauth.rs:{131,166}`, `commands/email_sync.rs:49` — `.expect("...poisoned")` on `std::sync::Mutex`; switch to `unwrap_or_else(|e| e.into_inner())` since protected data is recoverable.
14 - - `src-tauri/src/db_watcher.rs:61` — shutdown latency = `MIN_EVENT_INTERVAL_MS`, thread unjoinable. Cosmetic.
11 + - ~~`email.rs:598` blocking `fs::read` in async.~~ Done 2026-06-02. Switched to `tokio::fs::read(...).await` — simpler than the `spawn_blocking` recipe and matches the existing async-IO style for tokio::fs::write a few lines above.
12 + - ~~`smtp_client.rs:291` `ContentType::parse(...).unwrap()`.~~ Done 2026-06-02. Replaced unwrap with `or_else` parse + `map_err(...)` to propagate as a real error. `lettre::ContentType` has no public const for octet-stream; this is the closest no-panic path.
13 + - ~~Mutex `.expect("poisoned")` sweep.~~ Done 2026-06-02. All 7 callsites (`state.rs:173`, `sync.rs:{75,177,203}`, `oauth.rs:{131,166}`, `email_sync.rs:49`) switched to `unwrap_or_else(|e| e.into_inner())`. `sync.rs:75` is an `RwLock::read`; the same pattern works.
14 + - `db_watcher.rs:61` — shutdown latency = `MIN_EVENT_INTERVAL_MS`, thread unjoinable. Cosmetic. Leaving.
15 15
16 16 **use-fuzz**
17 - - `src-tauri/frontend/js/seed-data.js:393` — production `console.log` for demo seed instructions; strip from prod bundles or gate on debug flag.
18 - - `src-tauri/frontend/js/app.js:{12,28,37,...}` — chatty `console.log` on every launch; trim or gate.
19 - - `src-tauri/frontend/js/{emails,tasks,task-board}.js:*` — bare "Failed to load" without recovery actions; add "Try again" buttons per `ERROR_CODE_HINTS` pattern.
20 - - `src-tauri/frontend/js/settings.js:196` — empty-plugins hint hardcodes `~/.config/goingson/plugins/` (Linux path); platform-detect or replace with doc link.
21 - - `src-tauri/frontend/index.html:603` — task drawer close button uses literal `x` not `&times;`.
22 - - `src-tauri/frontend/index.html:18-57` — `?ui=mobile` URL param + localStorage override not gated for production per `ui_mode_separation_plan.md` Phase 5; risk of users landing in untested mobile UI on desktop.
17 + - ~~`seed-data.js:393` prod `console.log`.~~ Done 2026-06-02. Removed.
18 + - ~~`app.js` chatty launch logs.~~ Done 2026-06-02. Stripped the four informational `console.log` calls (Desktop loaded, Loaded N projects, Loaded N accounts, external-change/sync-changes/modal-skip/refresh-success). All `console.error` retained.
19 + - ~~`{emails,tasks,task-board}.js` bare "Failed to load".~~ Done 2026-06-02. All four inline error blocks (tasks.js:126, emails.js:105, events.js:419, task-board.js:92) now embed a "Try again" `.btn-link` that calls the corresponding load fn. The toast-side Retry action already existed; this is the parallel in-place recovery.
20 + - ~~`settings.js:196` Linux plugin path.~~ Done 2026-06-02. Replaced `~/.config/goingson/plugins/` with neutral OS-agnostic phrasing ("inside your OS app-data location").
21 + - ~~`index.html:603` literal `x` close button.~~ Done 2026-06-02. Now `&times;`.
22 + - `index.html:18-57` — `?ui=mobile` prod lockdown. Deferred — same item as §K Phase 6, blocks on a desktop-mobile distribution channel existing.
23 23
24 24 **creator-fuzz**
25 - - `CHANGELOG.md:24-26` — "Fixed: all issues identified in audit runs 1-12" is internal-process language; replace with concrete user-visible fixes.
26 - - `docs/data-export.md` (missing) — document JSON backup schema so "portable" is verifiable.
27 - - `tauri.conf.json:48-54` — add Settings toggle "Check for updates on launch" for parity with privacy-policy framing (currently no opt-out, though install is user-initiated).
28 - - README has no pricing / commercial-license-contact line for PolyForm-NC.
25 + - ~~`CHANGELOG.md:24-26` audit-runs language.~~ Done 2026-06-02. Replaced "all issues identified in audit runs 1–12" with six concrete user-visible fixes (empty-state copy, About/Settings populated, hard-fail copy, bulk-action danger color, mutex poisoning recovery, SMTP unwrap fix).
26 + - ~~`docs/data-export.md`.~~ Done 2026-06-02. Written. Covers all five export formats (JSON, gzipped backup, CSV, ICS, raw SQLite), the top-level JSON schema with version + `exportedAt`, per-entity field summaries pointing at `crates/core/src/` for full shapes, denormalized-field caveats, restore semantics (replace vs merge), a verification recipe a user can run with `gunzip` + a text editor, and the not-exported list (OAuth tokens, TOTP secrets, sync state, window state). Source-of-truth pointer at the bottom.
27 + - ~~Settings "Check for updates on launch" toggle.~~ Done 2026-06-02. New `commands::preferences` module persists a tiny JSON pref file in the Tauri app config dir (`preferences.json`, `update_check_on_launch: bool`, default true). Two commands wired: `get_preferences`, `set_update_check_on_launch`. `main.rs` setup hook gates the 10s-delay updater check on the preference. Settings → About now shows a toggle row (new `.settings-toggle-row` primitive) replacing the static "Updates are checked automatically on launch" line; toast confirms the save. API wrapper: `GoingsOn.api.preferences.{get, setUpdateCheckOnLaunch}`. Tauri config (`createUpdaterArtifacts`, endpoints, pubkey) untouched — the toggle gates the check, not the plugin.
28 + - ~~README PolyForm-NC commercial contact.~~ Done 2026-06-02. License section now invites commercial-use inquiries at `info@makenot.work`.
29 29
30 30 **ux-audit**
31 - - `styles.css:590-660` — `.btn`/`.btn-primary`/`.btn-danger` lack the offset-shadow brand cue that `.card` and `.bulk-actions-bar` use; controls look flatter than chrome. One-pass fix on interactive primitives also resolves disabled-state contrast and `.pill.active`/`.tab.active` lift issues.
32 - - `styles.css:615` — disabled-button signal is opacity-only; swap to `transparent bg + muted color/border`.
33 - - `styles.css:4712` — `.sync-label` hidden in `ui-mode-mobile` leaves dot color as the only state cue; add shape variation or keep label for `error`/`warn`.
34 - - Single-item destructive actions (`tasks.js:657` etc.) lack `.btn-loading` during round-trip.
35 - - `index.html:466` straggler `row-flex-2` vs neighbors using `.row-flex row-flex-N` — will be caught by ongoing CSS dedup.
36 - - Back-arrow buttons inconsistent: `&larr; Back` (index.html:220, 538) vs literal `←` (index.html:270).
31 + - ~~`styles.css:590-660` — `.btn` offset-shadow brand cue.~~ Done 2026-06-02. `.btn` base now carries `--shadow-brutal-xs` (1px). Hover lifts to 3px shadow + `translate(-1px,-1px)`; active presses to `translate(1px,1px)` + no shadow. Transition extended to include `transform` and `box-shadow`. `.btn-primary` / `.btn-danger` inherit. `.btn-link` and `.btn-icon` are sibling primitives (not `.btn` extenders) so unaffected. `.bulk-actions-bar .btn` inherits cleanly.
32 + - ~~`styles.css:615` — disabled-button contrast.~~ Done 2026-06-02. Opacity dropped; now `transparent bg + muted color + muted border + no shadow`.
33 + - `.pill.active` / `.tab.active` lift — Done 2026-06-02. Both gained `--shadow-brutal-xs` to distinguish active from inactive chrome.
34 + - ~~`styles.css:4712` — `.sync-label` mobile shape variation.~~ Done 2026-06-02. Shape now encodes state on the dot itself (works in mobile + monochrome + high-contrast): `connected`/`syncing` stay circular; `warn` is a rotated square (diamond); `error` is a square. Label-hiding behavior unchanged.
35 + - ~~Single-item destructive `.btn-loading`.~~ Reviewed 2026-06-02 — not applicable. Audit cited `tasks.js:657` but every single-item destructive flow (`tasks.js`, `projects.js`, `events.js`, `emails.js`) is optimistic with `showUndoToast`: the UI removes the item the instant the confirm modal closes, so there is no button still mounted to carry a loading state. Optimism + undo is a stronger UX than a spinner. Closing without code change.
36 + - ~~`index.html:466` straggler `row-flex-2`.~~ Done 2026-06-02. Now matches neighbors as `row-flex row-flex-2`.
37 + - ~~Back-arrow consistency.~~ Done 2026-06-02. `index.html:270` literal `← Back` → `&larr; Back` matching the other three back-arrow buttons.
37 38
38 39 ### §3.1 — DONE for emoji/About/error-copy. Remaining:
39 40 - Mobile UI mode override hardening (see use-fuzz deferral above).
@@ -41,8 +42,10 @@ Four-axis parallel audit (rust-fuzz + use-fuzz + creator-fuzz + ux-audit) comple
41 42 ### §3.2 distribution — NOT STARTED
42 43 - Signed builds + notarization stapled + DMG smoke test on a clean macOS account.
43 44
44 - ### §3.3 repo hygiene — NOT STARTED
45 - - Resolve stray root planning files (`css_refactor_plan_2026-05-24.md`, `css_refactor_plan.md`, `css_state_audit.md`, `ui_mode_separation_plan.md`); decide on `generate_pitch_pdf.py` + `GoingsOn-Pitch.pdf`; review `_archive/`.
45 + ### §3.3 repo hygiene — DONE 2026-06-02
46 + - Stray root planning files: already moved to `docs/_archive/` pre-session.
47 + - `generate_pitch_pdf.py` + `GoingsOn-Pitch.pdf` deleted (regenerable; git preserves).
48 + - Root `_archive/` (pre-Tauri Rust HTTP server) deleted; migration shipped 2026-06-01, git history is the right home for pre-migration reference.
46 49
47 50 CSS dedup work below is the active in-progress stream; launch readiness is its own track.
48 51