Skip to main content

max / goingson

Fix mobile email reply prefill and add sync setup explanation - Pass reply/forward context to compose modal on mobile (was blank) - Add E2E encryption description to sync setup panel - Update todo checkmarks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-03 02:17 UTC
Commit: ba6d5ebb47a9e7dbfd5b5fbed7f54e08645aa922
Parent: d9fc761
3 files changed, +79 insertions, -20 deletions
@@ -126,6 +126,54 @@ v0.3.0. Audit grade A. ~762 tests.
126 126
127 127 ---
128 128
129 + ## Usability Audit Remediations (2026-05-02)
130 +
131 + Audit run: `/use-fuzz GoingsOn`. Overall grade: B+. Items already tracked elsewhere are omitted.
132 +
133 + ### Bugs
134 + - [x] Fix `g v` keyboard shortcut — maps to Weekly Review but overlay says "Events" (keyboard.js:42 vs :156)
135 + - [x] Fix mobile email reply prefill — openComposeModal now accepts prefill data for reply/forward
136 +
137 + ### Discoverability (B-)
138 + - [x] Add Events pill to Time tab navigation — full view currently keyboard/URL-only
139 + - [x] Add overflow/kebab icon on hover for item rows — surfaces context menu actions without right-click
140 + - [x] Add visible Quick Add button or input in Tasks header — `q` shortcut is invisible
141 + - [x] Add "Create Event" to email right-click context menu — parity with existing "Create Task"
142 + - [ ] Add one-time onboarding hints: "Press ? for shortcuts" on first launch, "Shift-click to select range" on first bulk selection
143 + - [ ] Add play/timer icon to task rows for started tasks — time tracking entry point is buried
144 + - [ ] Add touch gesture hints on first mobile use (long-press, swipe, pull-to-refresh)
145 +
146 + ### Learnability (B)
147 + - [ ] Enhance welcome flow with first-action guidance or "Load sample data" option
148 + - [x] Add hint text for day planner paint-to-create: "Drag across time slots to block time"
149 + - [x] Add introductory paragraph for first weekly review explaining the workflow
150 + - [x] Add introductory content for first monthly review (title hidden by CSS, no explanation)
151 + - [x] Add brief descriptions to differentiate Start Task / Schedule Time / Track Time / Focus Mode
152 + - [x] Add 2-3 sentence explanation in sync setup panel (what SyncKit is, what syncs, E2E encryption)
153 + - [ ] IMAP auto-detect server settings from email domain (Gmail, Fastmail, Outlook, Yahoo, iCloud)
154 + - [x] Rename "Pri" column header to "Priority" (only abbreviated header in task table)
155 + - [ ] Add frontend error message mapper — humanize backend error codes for toasts
156 +
157 + ### Complexity (A-)
158 + - [x] Collapse task creation form: show 4 fields (Description, Project, Priority, Due Date) + "More options" toggle for Tags, Recurrence, Estimated Time, Contact, Milestone
159 + - [x] Group time-related context menu items into "Time" submenu or consolidate Track/Focus
160 + - [ ] Use natural language date parsing for milestone target dates (currently requires YYYY-MM-DD)
161 + - [x] Add undo toast for keyboard task completion (`c` key) — matches delete undo pattern
162 +
163 + ### Feature Completeness (B-)
164 + - [ ] Recurring events — table stakes for any calendar feature
165 + - [ ] Calendar month/week grid view — even a basic one (events currently list-only)
166 + - [ ] Time tracking reports — per-project breakdown, estimated-vs-actual, weekly/monthly summaries
167 + - [ ] Manual time entry — log time retroactively, not just live timer
168 + - [ ] Contacts export to vCard — import exists but no export (asymmetric)
169 + - [ ] Bulk operations for contacts (tag, delete) and events (delete)
170 + - [ ] Bulk "Set Project" and "Set Priority" in task bulk actions bar
171 + - [ ] Daily review notes: persist to SQLite + sync (currently localStorage only, lost on reinstall)
172 + - [ ] Monthly review: add explicit "Complete Review" action (weekly has it, monthly does not)
173 + - [ ] Workload guardrails in day planner — warn when scheduled hours exceed target
174 +
175 + ---
176 +
129 177 ## Deferred
130 178
131 179 - [ ] Co-working feature: E2E encrypted project sharing (XChaCha20-Poly1305, X25519, Argon2, CRDTs, 7 phases)
@@ -148,21 +148,25 @@
148 148 }
149 149 }
150 150
151 - function openComposeModal() {
151 + function openComposeModal(prefill) {
152 152 const accounts = GoingsOn.getEmailAccountsCache();
153 - const accountOptions = accounts.map(a =>
154 - `<option value="${escAttr(a.id)}">${esc(a.email)}</option>`
155 - ).join('');
153 + const pf = prefill || {};
154 +
155 + const accountOptions = accounts.map(a => ({
156 + value: a.id,
157 + label: a.email,
158 + selected: a.id === pf.accountId,
159 + }));
156 160
157 161 GoingsOn.ui.openFormModal({
158 162 title: 'Compose Email',
159 163 entityType: 'email',
160 164 isEdit: false,
161 165 fields: [
162 - { name: 'account_id', type: 'select', label: 'From', options: accounts.map(a => ({ value: a.id, label: a.email })), required: true },
163 - { name: 'to', type: 'text', label: 'To', required: true, placeholder: 'recipient@example.com' },
164 - { name: 'subject', type: 'text', label: 'Subject', required: true },
165 - { name: 'body', type: 'textarea', label: 'Body', rows: 8 },
166 + { name: 'account_id', type: 'select', label: 'From', options: accountOptions, required: true },
167 + { name: 'to', type: 'text', label: 'To', required: true, placeholder: 'recipient@example.com', value: pf.to || '' },
168 + { name: 'subject', type: 'text', label: 'Subject', required: true, value: pf.subject || '' },
169 + { name: 'body', type: 'textarea', label: 'Body', rows: 8, value: pf.body || '' },
166 170 ],
167 171 onSubmit: async (data) => {
168 172 await GoingsOn.api.emails.send({
@@ -170,6 +174,9 @@
170 174 to: data.to,
171 175 subject: data.subject,
172 176 body: data.body || '',
177 + inReplyTo: pf.inReplyTo || null,
178 + references: pf.references || null,
179 + threadId: pf.threadId || null,
173 180 });
174 181 GoingsOn.ui.showToast('Email sent!', 'success');
175 182 load();
@@ -608,9 +615,15 @@
608 615
609 616 // Open compose with reply context
610 617 if (GoingsOn.touch?.isTouchDevice) {
611 - // Mobile fallback — fill compose modal
612 - openComposeModal();
613 - // TODO: prefill modal fields for reply
618 + openComposeModal({
619 + to,
620 + subject,
621 + body,
622 + inReplyTo: email.messageId || null,
623 + references: references || null,
624 + threadId: email.threadId || null,
625 + accountId,
626 + });
614 627 } else {
615 628 await GoingsOn.api.window.openCompose({
616 629 to,
@@ -654,7 +667,7 @@
654 667 + (email.body || '');
655 668
656 669 if (GoingsOn.touch?.isTouchDevice) {
657 - openComposeModal();
670 + openComposeModal({ to: '', subject, body, accountId });
658 671 } else {
659 672 await GoingsOn.api.window.openCompose({
660 673 to: '',
@@ -28,10 +28,11 @@
28 28 // State 1: Not configured — prompt for API key
29 29 content = `
30 30 <div style="padding: 1rem 0;">
31 - <p style="color: var(--text-secondary); margin-bottom: 1rem;">
32 - Sync your data across devices via Makenot.work.
31 + <p style="color: var(--text-secondary); margin-bottom: 0.5rem;">
32 + Sync your tasks, events, contacts, and projects across devices with end-to-end encryption.
33 + All data is encrypted on your device before it leaves — the server never sees your content.
33 34 </p>
34 - <p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 0.5rem;">
35 + <p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem;">
35 36 Enter your SyncKit API key to get started.
36 37 </p>
37 38 <p style="margin-bottom: 1rem;">
@@ -222,13 +223,13 @@
222 223 }
223 224
224 225 // Poll for callback result
225 - pollSyncAuthResult(authData.port, authData.state, authData.codeVerifier);
226 + pollSyncAuthResult(authData.port, authData.state);
226 227 } catch (err) {
227 228 GoingsOn.ui.showToast('Failed to start auth: ' + GoingsOn.utils.getErrorMessage(err), 'error');
228 229 }
229 230 }
230 231
231 - function pollSyncAuthResult(port, expectedState, codeVerifier) {
232 + function pollSyncAuthResult(port, expectedState) {
232 233 let attempts = 0;
233 234 const maxAttempts = 300; // 5 minutes at 1s intervals
234 235
@@ -252,9 +253,6 @@
252 253 await GoingsOn.api.sync.completeAuth({
253 254 code: data.code,
254 255 state: data.state,
255 - expectedState: expectedState,
256 - codeVerifier: codeVerifier,
257 - port: port,
258 256 });
259 257 GoingsOn.ui.showToast('Connected to Makenot.work!');
260 258 refreshSyncIndicator();