Skip to main content

max / goingson

Strip AI tells from comments, docs and UI copy Em dashes, ellipsis characters and false-contrast phrasing removed across Rust comments, JS strings, shell scripts and the frontend lint messages. No behavior change.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-26 22:15 UTC
Signed with PGP, not checked
Commit: 734ba93aa53e9740597fad681733a054871c165e
Parent: 36aa062
57 files changed, +88 insertions, -148 deletions
M CONTRIBUTING.md +10 -10
@@ -232,7 +232,7 @@
232 232
233 233 - **Edit:** `src-tauri/frontend/css/styles.css`
234 234 - **Build:** Run `node src-tauri/frontend/build-css.js` to generate `styles.min.css` (Tauri runs this automatically via `beforeBuildCommand`)
235 - - **Never** edit `styles.min.css` directly — it's auto-generated via clean-css-cli
235 + - **Never** edit `styles.min.css` directly; it's auto-generated via clean-css-cli
236 236 - The HTML loads `styles.min.css`
237 237
238 238 ## UI Modes
@@ -245,16 +245,16 @@
245 245 Mode does **not** change at runtime. Desktop binaries stay desktop even when the window is narrowed; mobile binaries stay mobile. The mode is a property of the build, not the viewport size.
246 246
247 247 **Detection precedence** (`index.html` inline script):
248 - 1. `?ui=mobile|desktop` URL param — dev / testing / bug repros.
249 - 2. `localStorage.goingson.uiMode` — dev Settings toggle.
248 + 1. `?ui=mobile|desktop` URL param, for dev / testing / bug repros.
249 + 2. `localStorage.goingson.uiMode`, the dev Settings toggle.
250 250 3. `navigator.userAgentData.mobile` (UA Client Hints) when available.
251 251 4. UA regex (`iPhone OS|iPad|Android`) with iPad-as-Mac fallback (`navigator.maxTouchPoints > 1` on a Mac-reporting platform).
252 252
253 253 **Dev preview:** `?ui=mobile` URL param, or `GoingsOn.viewport.setOverride('mobile')` from the console.
254 254
255 - **Adding mobile rules:** prefix the selector with `.ui-mode-mobile`. Do **not** add new `@media (max-width: ...)` queries to switch UI modes — that path is gone. Intra-mode responsive queries (e.g. wide-vs-narrow desktop) are allowed, but their selectors must be `.ui-mode-desktop`-prefixed inside the query.
255 + **Adding mobile rules:** prefix the selector with `.ui-mode-mobile`. Do **not** add new `@media (max-width: ...)` queries to switch UI modes; that path is gone. Intra-mode responsive queries (e.g. wide-vs-narrow desktop) are allowed, but their selectors must be `.ui-mode-desktop`-prefixed inside the query.
256 256
257 - **Input capability is a separate axis.** Use `@media (hover: none)` only for hover suppression. Use `GoingsOn.touch.isTouchDevice` only for input-model decisions (drag vs long-press, tap targets). Never use either for visibility or layout — that's what UI mode is for.
257 + **Input capability is a separate axis.** Use `@media (hover: none)` only for hover suppression. Use `GoingsOn.touch.isTouchDevice` only for input-model decisions (drag vs long-press, tap targets). Never use either for visibility or layout; that's what UI mode is for.
258 258
259 259 ## SyncKit Integration
260 260
@@ -268,7 +268,7 @@
268 268
269 269 - **Rust unit tests:** In-file `#[cfg(test)]` modules in each crate
270 270 - **Rust integration tests:** `tests/` directories in each crate
271 - - **JS tests:** `node src-tauri/frontend/js/tests/run.js` — no npm, plain Node.
271 + - **JS tests:** `node src-tauri/frontend/js/tests/run.js`. No npm, plain Node.
272 272 `run.js` mocks the browser globals, `require`s the modules under test in
273 273 `index.html` order, then runs the suites. Adding a module to the suite means
274 274 adding its `require` plus whatever browser API it touches to the mock block.
@@ -294,15 +294,15 @@
294 294
295 295 ## When Adding Features
296 296
297 - 1. **Start with the Rust types** — define the model in `crates/core/src/models/`
297 + 1. **Start with the Rust types**: define the model in `crates/core/src/models/`
298 298 2. **Add repository trait method** in `crates/core/src/repository.rs`
299 299 3. **Implement in SQLite** in `crates/db-sqlite/src/repository/`
300 300 4. **Add Tauri command** in `src-tauri/src/commands/` (thin wrapper)
301 301 5. **Create response type** with pre-computed fields
302 - 6. **Build JS integration** — call command, render result using namespace pattern
302 + 6. **Build JS integration**: call command, render result using namespace pattern
303 303
304 304 ## When Fixing Bugs
305 305
306 - 1. **Identify the layer** — is it core logic, repository, command, or UI?
307 - 2. **Fix at the right layer** — don't patch JS for a Rust bug
306 + 1. **Identify the layer**: is it core logic, repository, command, or UI?
307 + 2. **Fix at the right layer**: don't patch JS for a Rust bug
308 308 3. **Add pre-computation** if JS is doing repeated calculations that belong in Rust
M Cargo.toml +3 -3
@@ -40,7 +40,7 @@
40 40 mailparse = "0.16"
41 41 lettre = { version = "0.11", default-features = false, features = ["tokio1", "tokio1-rustls", "rustls-platform-verifier", "ring", "smtp-transport", "builder"] }
42 42
43 - # TLS — one rustls stack across HTTP (reqwest), SMTP (lettre) and IMAP, all validating
43 + # TLS: one rustls stack across HTTP (reqwest), SMTP (lettre) and IMAP, all validating
44 44 # against the OS-native trust store via rustls-platform-verifier, with a single ring
45 45 # crypto provider installed at startup. Matches the trust source tauri-plugin-updater
46 46 # already uses, so the binary carries one TLS stack instead of native-tls + rustls.
@@ -59,8 +59,8 @@
59 59
60 60 # Secure credential storage
61 61 # Pinned to the v1 store bundle explicitly: Apple Keychain, Windows credential
62 - # manager, and Secret Service on Linux. Do not enable `linux-keyutils-keyring-store` —
63 - # that backing store is in-memory and drops every entry on reboot, which silently
62 + # manager, and Secret Service on Linux. Do not enable `linux-keyutils-keyring-store`.
63 + # That backing store is in-memory and drops every entry on reboot, which silently
64 64 # destroyed stored secrets before keyring 4 moved the default to Secret Service.
65 65 keyring = { version = "4", default-features = false, features = ["v1"] }
66 66
M synckit.toml +1 -1
@@ -1,4 +1,4 @@
1 - # SyncKit configuration — embedded in distribution builds.
1 + # SyncKit configuration, embedded in distribution builds.
2 2 # The API key is a client identifier (not a secret). It identifies this app
3 3 # to the MNW server. User authentication happens via OAuth2 PKCE.
4 4 api_key = "68d41709b33b41d0ec3d56b1dc8516188a8eaf7b9c0ea32094476c67c8fa7d9c"
@@ -25,9 +25,9 @@
25 25 # Issuer + key id are two thirds of the notary credential (the .p8 is the third),
26 26 # so they live in ~/.tauri/passwords.env, which is not in git. Source it first:
27 27 # source ~/.tauri/passwords.env && ./dist/release-ios.sh
28 - API_KEY_ID="${APPLE_API_KEY_ID:?set APPLE_API_KEY_ID -- source ~/.tauri/passwords.env}"
29 - API_ISSUER_ID="${APPLE_API_ISSUER_ID:?set APPLE_API_ISSUER_ID -- source ~/.tauri/passwords.env}"
30 - API_KEY_PATH="${APPLE_API_KEY_PATH:?set APPLE_API_KEY_PATH -- source ~/.tauri/passwords.env}"
28 + API_KEY_ID="${APPLE_API_KEY_ID:?set APPLE_API_KEY_ID; source ~/.tauri/passwords.env}"
29 + API_ISSUER_ID="${APPLE_API_ISSUER_ID:?set APPLE_API_ISSUER_ID; source ~/.tauri/passwords.env}"
30 + API_KEY_PATH="${APPLE_API_KEY_PATH:?set APPLE_API_KEY_PATH; source ~/.tauri/passwords.env}"
31 31 ARCHIVE_PATH="src-tauri/gen/apple/build/goingson-desktop_iOS.xcarchive"
32 32 EXPORT_DIR="dist/ios"
33 33 EXPORT_PLIST="src-tauri/gen/apple/ExportOptions.plist"
@@ -119,7 +119,7 @@
119 119 | sort -rn | head -1 | cut -d' ' -f2- || true)
120 120
121 121 # If the freshest IPA is older than the archive, the archive hasn't been
122 - # exported yet — export it now.
122 + # exported yet, so export it now.
123 123 if [ -z "$IPA_PATH" ] || [ "$ARCHIVE_PATH" -nt "$IPA_PATH" ]; then
124 124 echo ""
125 125 echo "=== Exporting IPA from archive ==="
@@ -144,7 +144,7 @@
144 144 echo ""
145 145 echo "=== Uploading to TestFlight ==="
146 146 echo "IPA: $IPA_PATH"
147 - # altool exits 0 even when it logs "ERROR:" / "Failed to upload" — grep
147 + # altool exits 0 even when it logs "ERROR:" / "Failed to upload", so grep
148 148 # the output to confirm the success banner before declaring victory.
149 149 set +e
150 150 UPLOAD_OUTPUT=$(xcrun altool --upload-app \
@@ -6,11 +6,11 @@
6 6 # App Store Connect API key, staples the ticket, and verifies the result.
7 7 #
8 8 # Run on the Mac (mbp). Two signing modes:
9 - # default -- GUI session, signs from the unlocked login.keychain.
10 - # --keychain -- HEADLESS/REMOTE: builds an ephemeral Developer ID build
9 + # default : GUI session, signs from the unlocked login.keychain.
10 + # --keychain : HEADLESS/REMOTE: builds an ephemeral Developer ID build
11 11 # keychain (dist/build-keychain.sh) so this works over SSH from
12 12 # fw13 without a GUI unlock. The signing identity is derived
13 - # automatically from the imported .p12 -- no need to fill in
13 + # automatically from the imported .p12, so there is no need to fill in
14 14 # SIGNING_IDENTITY.
15 15 #
16 16 # Org-transfer note: --keychain signs with whatever team the .p12 holds, so it
@@ -23,7 +23,7 @@
23 23 # - A Developer ID Application identity, either:
24 24 # * installed in login.keychain (default GUI mode), or
25 25 # * exported to a .p12 referenced by ~/.tauri/passwords.env (--keychain mode)
26 - # - App Store Connect API key (.p8) at API_KEY_PATH -- only for notarization
26 + # - App Store Connect API key (.p8) at API_KEY_PATH, only for notarization
27 27 # - Tauri updater signing key at ~/.tauri/goingson.key (for OTA artifacts)
28 28 #
29 29 # Usage:
@@ -124,13 +124,13 @@
124 124 exit 1
125 125 fi
126 126 if [ -z "${GOINGSON_TAURI_PASSWORD:-}" ]; then
127 - echo "Note: GOINGSON_TAURI_PASSWORD unset -- source ~/.tauri/passwords.env first if you want OTA updater artifacts signed."
127 + echo "Note: GOINGSON_TAURI_PASSWORD unset; source ~/.tauri/passwords.env first if you want OTA updater artifacts signed."
128 128 fi
129 129
130 130 # --- Build + sign (+ notarize + staple) ---
131 131 # Setting APPLE_API_ISSUER + APPLE_API_KEY + APPLE_API_KEY_PATH is what flips
132 132 # Tauri from "sign only" to "sign + notarize + staple" in one pass. With
133 - # --sign-only those are omitted, so Tauri signs but does not notarize -- the
133 + # --sign-only those are omitted, so Tauri signs but does not notarize. The
134 134 # org-transfer-deferral path (no team match needed to sign).
135 135 echo ""
136 136 if [ "$NOTARIZE" = true ]; then
@@ -151,7 +151,7 @@
151 151 )
152 152 fi
153 153 # `app` MUST be in the bundle list for the updater artifact (.app.tar.gz + .sig)
154 - # to be produced — `updater` alone with only `dmg` builds nothing updater-enabled
154 + # to be produced. `updater` alone with only `dmg` builds nothing updater-enabled
155 155 # ("no updater-enabled targets were built"). The .app is also what OTA ships.
156 156 env "${build_env[@]}" cargo tauri build --bundles app dmg updater
157 157
@@ -169,7 +169,7 @@
169 169 echo ""
170 170 echo "=== Verifying signature ==="
171 171 # Tauri removes the intermediate .app after bundling the DMG, so it may be
172 - # gone by now — the DMG (stapler + spctl below) is the real gate.
172 + # gone by now; the DMG (stapler + spctl below) is the real gate.
173 173 if [ -d "$APP_GLOB" ]; then
174 174 echo "--- codesign (app) ---"
175 175 codesign --verify --deep --strict --verbose=2 "$APP_GLOB"
@@ -193,11 +193,11 @@
193 193 fi
194 194 else
195 195 # Sign-only: confirm the signature exists and report the signing team, but
196 - # do NOT assert Gatekeeper acceptance -- an un-notarized DMG is rejected on
196 + # do NOT assert Gatekeeper acceptance; an un-notarized DMG is rejected on
197 197 # other Macs by design (right-click-open works for hand-picked beta testers).
198 198 echo "--- codesign (signing authority / team) ---"
199 199 codesign -dvvv "$APP_GLOB" 2>&1 | grep -E 'Authority|TeamIdentifier' || true
200 - echo "Note: --sign-only -- DMG is signed but NOT notarized; Gatekeeper will"
200 + echo "Note: --sign-only, so the DMG is signed but NOT notarized; Gatekeeper will"
201 201 echo " flag it on other Macs until you re-run without --sign-only post-transfer."
202 202 fi
203 203 fi
@@ -24,47 +24,47 @@
24 24 }
25 25
26 26 # 1. No raw hex literals in JS or source HTML (HTML entities &#NNNN; are OK;
27 - # <meta name="theme-color"> is a documented exception — value is set
27 + # <meta name="theme-color"> is a documented exception; value is set
28 28 # programmatically by js/themes.js to track the active theme).
29 29 hits=$(grep -rnE '#[0-9a-fA-F]{3,8}\b' "$SRC_JS" $SRC_HTML 2>/dev/null \
30 30 | grep -vE '&#[0-9]+;' \
31 31 | grep -vE 'meta name="theme-color"' \
32 32 || true)
33 - report "no-raw-hex" "Raw hex literal in JS/HTML — use a CSS class or token instead." "$hits"
33 + report "no-raw-hex" "Raw hex literal in JS/HTML. Use a CSS class or token instead." "$hits"
34 34
35 35 # 2. No style.cssText anywhere in JS.
36 36 hits=$(grep -rn 'cssText' "$SRC_JS" 2>/dev/null || true)
37 - report "no-csstext" "style.cssText injection — move styles into a CSS class." "$hits"
37 + report "no-csstext" "style.cssText injection. Move styles into a CSS class." "$hits"
38 38
39 39 # 3. No var(--token, #fallback). Fallback hex defeats theme switching.
40 40 hits=$(grep -rnE 'var\(--[a-z-]+,\s*#' "$FRONTEND" --include='*.js' --include='*.html' --include='styles.css' 2>/dev/null | grep -v styles.min.css || true)
41 - report "no-var-fallback-hex" "var(--token, #fallback) — drop the fallback; it bypasses themes." "$hits"
41 + report "no-var-fallback-hex" "var(--token, #fallback). Drop the fallback; it bypasses themes." "$hits"
42 42
43 - # 4. No window.confirm / bare confirm() — route through GoingsOn.ui.showConfirmDialog.
43 + # 4. No window.confirm / bare confirm(). Route through GoingsOn.ui.showConfirmDialog.
44 44 # Skip js/tests/: test fixtures carry attack payloads and mocks that
45 45 # legitimately contain `confirm(`/`alert(` as data, not as app calls.
46 46 hits=$(grep -rnE '\b(window\.)?confirm\(' "$SRC_JS" 2>/dev/null | grep -v '/tests/' | grep -vE 'showConfirmDialog|confirmDelete|//\s*\*|\*\s' || true)
47 - report "no-window-confirm" "window.confirm() — use GoingsOn.ui.showConfirmDialog instead." "$hits"
47 + report "no-window-confirm" "window.confirm(). Use GoingsOn.ui.showConfirmDialog instead." "$hits"
48 48
49 49 # 5. No inline style= that touches color / background / border / shadow / font / padding values.
50 50 hits=$(grep -rnE 'style="[^"]*(color|background|border|shadow|font-size|font-family|padding)' "$SRC_JS" $SRC_HTML 2>/dev/null || true)
51 - report "no-styled-attrs" "Inline style= with color/background/border/shadow/font/padding — use a class." "$hits"
51 + report "no-styled-attrs" "Inline style= with color/background/border/shadow/font/padding. Use a class." "$hits"
52 52
53 53 # 6. Deprecated empty-state classes have been removed.
54 54 hits=$(grep -rnE 'empty-dashboard-list|kanban-empty|virtual-scroller-empty' "$FRONTEND" --include='*.js' --include='*.html' --include='styles.css' 2>/dev/null | grep -v styles.min.css || true)
55 - report "no-deprecated-empty-states" "Deprecated class — use .empty-state with --compact / --dashboard / --error." "$hits"
55 + report "no-deprecated-empty-states" "Deprecated class. Use .empty-state with --compact / --dashboard / --error." "$hits"
56 56
57 57 # 7. No native browser dialogs. Charter rule from Phase 7 roll-up.
58 58 # Skip js/tests/ (attack payloads / mocks reference these as data, not calls).
59 59 hits=$(grep -rnE '\b(window\.)?(confirm|prompt|alert)\(' "$SRC_JS" 2>/dev/null \
60 60 | grep -v '/tests/' \
61 61 | grep -vE 'showConfirmDialog|showPromptDialog|confirmDelete|//\s|\*\s' || true)
62 - report "no-native-dialogs" "window.confirm/prompt/alert are banned — use GoingsOn.ui.show{Confirm,Prompt}Dialog or showToast." "$hits"
62 + report "no-native-dialogs" "window.confirm/prompt/alert are banned. Use GoingsOn.ui.show{Confirm,Prompt}Dialog or showToast." "$hits"
63 63
64 64 # 8. Frontend JS test suite, including the CHRONIC-XSS escaping-enforcement gate
65 65 # (js/tests/run.js). GO has no CI and running node inside `cargo build` is an
66 66 # anti-pattern, so the gate lives here in the lint script that's run before a
67 - # commit — wiring it in makes the "build-failing gate" literal rather than a
67 + # commit; wiring it in makes the "build-failing gate" literal rather than a
68 68 # test you have to remember to invoke. run.js exits non-zero on any failure.
69 69 if command -v node >/dev/null 2>&1; then
70 70 if ! js_out=$(node "$SRC_JS/tests/run.js" 2>&1); then
@@ -74,7 +74,7 @@
74 74 violations=$((violations + 1))
75 75 fi
76 76 else
77 - echo "[frontend-js-tests] WARNING: node not found — skipping JS tests and the XSS gate"
77 + echo "[frontend-js-tests] WARNING: node not found, skipping JS tests and the XSS gate"
78 78 fi
79 79
80 80 # 9. Inline event-handler ratchet (CSP `unsafe-inline` drawdown).
@@ -18,7 +18,7 @@
18 18 ```
19 19
20 20 - `--db` defaults to the desktop app's database (`com.goingson.app`'s
21 - `app_data_dir`). The database must already exist — run GoingsOn once to create
21 + `app_data_dir`). The database must already exist; run GoingsOn once to create
22 22 the schema and the single desktop user.
23 23 - Reads (`list_projects`, `list_tasks`, `get_task`) are always callable.
24 24 - Writes are refused unless their capability is granted. Grant them with
@@ -32,13 +32,13 @@
32 32
33 33 Read:
34 34
35 - - `list_projects` — id, name, type, status.
36 - - `list_tasks(project?, status?, tag?, limit?, offset?)` — compact task rows,
35 + - `list_projects`: id, name, type, status.
36 + - `list_tasks(project?, status?, tag?, limit?, offset?)`: compact task rows,
37 37 paged (default 50, max 200). Descriptions over 240 chars are clipped and the
38 38 row marked `truncated`; `get_task` has the full text. The reply carries
39 - `total` and, while pages remain, `next_offset` — walk until it is absent.
40 - - `get_task(id)` — one task with subtasks and annotations.
41 - - `list_problems(source?, status?, project_id?)` — problems ranked by
39 + `total` and, while pages remain, `next_offset`. Walk until it is absent.
40 + - `get_task(id)`: one task with subtasks and annotations.
41 + - `list_problems(source?, status?, project_id?)`: problems ranked by
42 42 `painhours`, most urgent first. Defaults to `Open`; pass `status: "all"` for
43 43 settled ones too.
44 44
@@ -46,15 +46,15 @@
46 46
47 47 | Tool | Capability |
48 48 |------|------------|
49 - | `create_project(name, type?, description?)` — idempotent on name | `go.project.create` |
50 - | `update_project(project, {status?, type?, description?})` — resolved by name, overlays only the fields you pass | `go.project.update` |
49 + | `create_project(name, type?, description?)`, idempotent on name | `go.project.create` |
50 + | `update_project(project, {status?, type?, description?})`, resolved by name, overlays only the fields you pass | `go.project.update` |
51 51 | `create_task({description, project?, tags?, due?, priority?})` | `go.task.create` |
52 - | `bulk_import_tasks([...])` — the `/dellm` primitive | `go.task.bulk_import` |
53 - | `update_task(id, fields)` — overlays only the fields you pass | `go.task.update` |
52 + | `bulk_import_tasks([...])`, the `/dellm` primitive | `go.task.bulk_import` |
53 + | `update_task(id, fields)`, overlays only the fields you pass | `go.task.update` |
54 54 | `complete_task(id)` | `go.task.complete` |
55 - | `report_problems([...], source?)` — the `/audit` and `/fuzz` primitive | `go.problem.report` |
56 - | `promote_problem(id, description?, priority?)` — problem to task | `go.problem.promote` |
57 - | `update_problem(id, {status?, project?})` — triage state and attribution | `go.problem.update` |
55 + | `report_problems([...], source?)`, the `/audit` and `/fuzz` primitive | `go.problem.report` |
56 + | `promote_problem(id, description?, priority?)`, problem to task | `go.problem.promote` |
57 + | `update_problem(id, {status?, project?})`, triage state and attribution | `go.problem.update` |
58 58
59 59 `bulk_import_tasks` dedupes on a `source:` provenance tag (e.g.
60 60 `source:todo.md:42`), so re-running a migration wave does not double-insert.
@@ -63,13 +63,13 @@
63 63 ## Problems
64 64
65 65 GoingsOn is the list of solutions. A *problem* is a candidate for work that came
66 - from somewhere else — a wam ticket, an `/audit` or `/fuzz` finding — and it is
66 + from somewhere else (a wam ticket, an `/audit` or `/fuzz` finding), and it is
67 67 not a task until someone promotes it. That split is the point: a lens can report
68 68 everything it finds without filling the task list with work nobody chose.
69 69
70 70 Urgency is not stored. Each problem carries `pain` (how much it hurts a hit
71 71 user) and `scale` (how broadly it hits), both 1-5, which combine with its age
72 - into a 0-100 `painhours` score from the shared `painhours` crate — the same
72 + into a 0-100 `painhours` score from the shared `painhours` crate, the same
73 73 model wam ranks tickets by, so one list can span both. An untriaged problem
74 74 climbs on its own; promoting or dismissing it freezes the score.
75 75
@@ -82,7 +82,7 @@
82 82 comes straight back on the next report, while a dismissed one stays down.
83 83
84 84 `update_project` is how a session retires a project: set `status` to `Archived`.
85 - There is no `delete_project` — the underlying delete is a hard DELETE with no
85 + There is no `delete_project`; the underlying delete is a hard DELETE with no
86 86 soft-delete behind it, which is the wrong default to hand a session. Renaming is
87 87 not offered either, since the name is how `create_task` and `bulk_import_tasks`
88 88 resolve a project.