From eaaed8e5eac6df177d8cd7a598a173a9e431e8a6 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 18 Sep 2026 15:34:11 -0700 Subject: [PATCH] Record mute broadcast result review and verification Add the architect review summary and the machine-readable client verification record (unit, property, acceptance, lint, build) pinned to review commit a7d9ca6299. By architect. --- docs/reviews/mute-broadcast-result-summary.md | 145 ++++++++++++++++++ .../mute-broadcast-result-verification.json | 46 ++++++ 2 files changed, 191 insertions(+) create mode 100644 docs/reviews/mute-broadcast-result-summary.md create mode 100644 docs/reviews/mute-broadcast-result-verification.json diff --git a/docs/reviews/mute-broadcast-result-summary.md b/docs/reviews/mute-broadcast-result-summary.md new file mode 100644 index 0000000..b77fbb6 --- /dev/null +++ b/docs/reviews/mute-broadcast-result-summary.md @@ -0,0 +1,145 @@ +# Mute Broadcast Result — Architect Review + +**By architect.** + +## Task and commits reviewed + +- Task: `mute-broadcast-result` (refactorer handoff + `merge_and_process refactorer 722c486ef6`). +- Merged `swarmforge-refactorer` (fast-forward from `a1e4a4f`). Commits: + - `c0e18d8` Add mute broadcast result specification (specifier) + - `24d7f0f` Implement mute broadcast result (coder) + - `722c486` Refactor mute broadcast result: share result handlers, add + property tests (refactorer) +- Architect review commit: `a7d9ca6299` (dead-store removal + error-getter + test hardening + tool-written mutation/acceptance manifests). +- Verification record `git_sha`: `a7d9ca6299` (client). +- Records/summary commit: this commit. + +The feature shows a broadcast result modal on the profile page after a +mute/unmute (Memor `0x6d16`/`0x6d17`): on success the success message, the mute +txid, and a link to that transaction on the block explorer (new tab); on failure +the broadcast error message. A successful mute flips the button to Unmute and a +successful unmute flips it back to Mute; a failed broadcast leaves the button in +its previous state. The modal stays open until dismissed and dismissing closes +it without navigating. Client-only, mirrors the like/tip and New Post result +modals. Touches `psf-memo-client` only. + +## Architectural findings + +- **UI/Core separation (good).** The mute result state machine lives in the pure + controller `src/services/profile-page.js` (`_broadcastMute`, + `getMuteBroadcastMessage`, `getMuteResultError`, `dismissMuteResult`) with the + `memoDb`/`memoFollow`/`memoMute`/viewer-address dependencies injected by the + React page. `src/components/app-body/profile/mute-result.js` and the extracted + `src/components/explorer-tx-link.js` are presentational plain + `React.createElement` components shared by the browser JSX build and the Node + acceptance adapter (`acceptance/lib/render-mute-result.js`). The whole feature + is testable without a DOM or network. +- **Dependency rule (good).** `profile-page.js` depends only on the pure + `block-explorer` util. `MemoMute`/`MemoDb` (IO) and the wallet are constructed + in the React adapter shell (`profile/index.js`) and injected inward. No core + module imports a component, service, or IO module; dependency direction runs + from the adapter shell inward to the controller. +- **Information hiding (good, one simplification).** `_broadcastMute` records + the outcome in `lastMuteResult` and exposes only getters; the reflected button + state is updated inside `_setState` *after* the injected handler succeeds, so + a failed broadcast cannot change the button. The reset of + `showMuteResultModal` at the start of `_broadcastMute` was a dead store — the + flag is set true unconditionally after the awaited broadcast, so no observer + can read the reset value. Removed (see Fixes). +- **DRY / acceptance structure (good).** The refactorer generalized the + like/tip and mute broadcast-result acceptance assertions behind + `BROADCAST_RESULTS` + `renderBroadcastResult`, the follow/unfollow/mute/unmute + broadcast checks behind `assertMemoBroadcastPrefix`, and extracted the shared + `ExplorerTxLink` used by both `LikeResult` and `MuteResult`. Behavior is + unchanged; the broad DRY run now reports only the pre-existing `handlers.js` + step-handler boilerplate (untouched lines), and the changed production/test set + reports no duplicate candidates. +- **Mutation surface.** `explorer-tx-link.js`, `mute-result.js`, and + `like-result.js` each scan as **0 mutation sites** (structural, like + `block-explorer.js`): they are built from `!`/ternary/template-literal markup + with no arithmetic, comparison, or boolean operators the tool targets. Their + behavior is pinned by unit and property tests instead. The only mutated module + with sites is `profile-page.js`. +- **Observation, not changed — merge included two earlier master commits.** + The fast-forward from `a1e4a4f` to `722c486` also brought in `ea67979` + (remove redundant author name from posts) and `8eab46d` (notifications CSS + + entry tweaks), both committed to `master` before the mute spec and outside the + refactorer handoff. They were not the subject of this review; the full client + suite (unit/property/acceptance/lint/build) passes with them. +- **Observation, not changed — `render-like-result`/`render-mute-result` + adapters are near-identical.** They render different components and DRY + reports no candidate at its thresholds, so they are left as separate test + helpers (test helpers stay separate from the modules under test). + +## Fixes applied + +- **Removed the dead `showMuteResultModal` reset.** `ProfilePage._broadcastMute` + set the modal hidden, then set it visible unconditionally after the awaited + broadcast. The reset was unobservable and left a surviving equivalent mutant + (`false -> true`); deleting it removes the site and the dead state write. +- **Pinned `getMuteResultError`'s empty-string contract.** Added two unit tests: + no result (constructor default `null`) and a failure result with no message. + These kill both `|| -> &&` mutants in the getter (the null case exercises the + short-circuit guard, the no-message case exercises the trailing `|| ''`). +- **No further production changes were needed.** Module structure, dependency + direction, and information hiding were accepted. + +## Verification results + +Record (pinned to the review commit `a7d9ca6299`): + +| File | Component | +|------|-----------| +| `docs/reviews/mute-broadcast-result-verification.json` | psf-memo-client | + +- **Language mutation** (`mutate4javascript`, `--max-workers 8`, + `--mutate-all`; wrapper confirmed all sites selected): + - `src/services/profile-page.js`: **31 killed, 0 survived, 0 uncovered.** + First differential run selected all 32 sites (30 killed / 2 survived: + the dead modal reset and the `getMuteResultError` guard) → after the fixes, + 31 sites, all killed. + - `src/components/explorer-tx-link.js`, `src/components/app-body/profile/mute-result.js`, + `src/components/post-feed/like-result.js`: 0 mutation sites (structural). + - Total: **31 killed, 0 survived, 0 uncovered.** + - `src/services/memo-db.js` was not mutated: the client HTTP adapter is a + standing mutation exclusion (ESM + `../config` directory import). +- **DRY** (`dry4javascript`, scoped to the changed production files, the + acceptance adapters, and the new/changed tests): **No duplicate candidates + found.** The broad run over `acceptance/lib/handlers.js` reports only + pre-existing step-handler boilerplate on lines untouched by this task. +- **Cyclomatic complexity / CRAP** (`crap4javascript`): max **CRAP 4.0** + (`ProfilePage._broadcastMute` CC 4, `getMuteBroadcastMessage` CC 4, + `getMuteResultError` CC 4, all 100% covered); `ExplorerTxLink` and + `MuteResult` CC 2, `LikeResult` CC 1, every other changed function CC ≤ 3 at + 100%. All functions are below the 8.0 threshold; no refactor was warranted. +- **Soft Gherkin acceptance mutation** (`gherkin-mutator --level soft + --workers 8`), `psf-memo-client/specs/mute-broadcast-result.feature`: + **5 total, 0 killed, 5 survived, 0 errors.** All 5 are single-character case + mutations of example values (`addr` in scenarios 1–4, `broadcast_error` in + scenario 4) used consistently on both the Given setup and the Then assertion + side, so the mutated value still matches — the documented intrinsic-equivalent + class. None is an implementation gap and none was chased. `gherkin-mutator` + wrote a manifest with `"scenarios":[]` because every scenario has an intrinsic + survivor; committed as tool-written. + +## Suite status + +`verify.sh client` **result: pass** at `git_sha a7d9ca6299`: + +- unit: **484 pass / 0 fail** +- property: **111 pass / 0 fail** +- acceptance: **all 36 generated suites passed** (including all 4 + mute-broadcast-result scenarios) +- lint: ok +- build: ok + +## Handoffs + +- End-of-chain `git_handoff` to `specifier` (priority 50) to merge + `swarmforge-architect` into `master`, using this records/summary commit. +- `git_handoff` to `coder` and `refactorer` (priority 00) with the review commit + `a7d9ca6299` for follow-up review: the only production change is the removal + of the dead `showMuteResultModal` reset in `ProfilePage._broadcastMute`; the + rest is two edge-case unit tests and tool-written manifests. diff --git a/docs/reviews/mute-broadcast-result-verification.json b/docs/reviews/mute-broadcast-result-verification.json new file mode 100644 index 0000000..0b4ef75 --- /dev/null +++ b/docs/reviews/mute-broadcast-result-verification.json @@ -0,0 +1,46 @@ +{ + "schema_version": 1, + "task": "mute-broadcast-result", + "component": "psf-memo-client", + "git_sha": "a7d9ca62991fed91df99115eeb8e9a9f6b3b8dfd", + "branch": "swarmforge-architect", + "timestamp": "2026-09-18T22:33:35.119Z", + "commands": [ + { + "name": "unit", + "command": "npm test", + "exit": 0, + "duration_ms": 10160, + "summary": "484 pass / 0 fail" + }, + { + "name": "property", + "command": "npm run test:property", + "exit": 0, + "duration_ms": 13247, + "summary": "111 pass / 0 fail" + }, + { + "name": "acceptance", + "command": "npm run test:acceptance", + "exit": 0, + "duration_ms": 18060, + "summary": "all 36 acceptance suites passed" + }, + { + "name": "lint", + "command": "npm run lint", + "exit": 0, + "duration_ms": 2330, + "summary": "ok" + }, + { + "name": "build", + "command": "npm run build", + "exit": 0, + "duration_ms": 65818, + "summary": "ok" + } + ], + "result": "pass" +}