max / goingson
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
57 files changed,
+88 insertions,
-148 deletions
| @@ -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 |
| @@ -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 |
| @@ -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. |