mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo.git
synced 2026-09-21 16:52:01 -07:00
Review account-avatar-display: refresh mutation manifests, add review summary
By architect.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# Review summary: account-avatar-display
|
||||
|
||||
**Architect review of the refactorer handoff for task `account-avatar-display`.**
|
||||
|
||||
## Commits reviewed
|
||||
- `c849c41` (specifier): Specify account avatar image display on the account page
|
||||
(`account-avatar-display.feature`).
|
||||
- `afd4585` (coder): Implement account avatar image display — pure
|
||||
`AvatarImage` component, `AccountPage` display helpers, account view wiring,
|
||||
acceptance handlers/adapter, unit tests.
|
||||
- `8088d73` (refactorer): Add property tests for the account page avatar display
|
||||
logic (`account-page.property.test.js`).
|
||||
|
||||
Merged onto the architect worktree on `swarmforge-architect`.
|
||||
|
||||
## Architectural findings and fixes applied
|
||||
The structure is sound and consistent with the codebase's layered layout.
|
||||
UI/Core separation and the dependency rule hold:
|
||||
|
||||
- **UI/Core separation:** `AvatarImage` (`src/components/account/avatar-image.js`)
|
||||
is a pure presentational component written in plain `React.createElement` style
|
||||
(no JSX, no I/O), so the same module is usable by the browser build and by the
|
||||
Node acceptance adapter. The display logic lives in the testable
|
||||
`AccountPage` service (`getDisplayAvatarUrl` / `hasAvatarImage` /
|
||||
`getAvatarImageUrl`), and the `Account` view wires the two together.
|
||||
- **Dependency rule:** the acceptance adapter
|
||||
(`acceptance/lib/render-account-avatar.js`) depends inward on the pure
|
||||
component; the component has no outward dependencies.
|
||||
- **Information hiding:** the service exposes clear semantic methods and the
|
||||
component hides the `<img>` markup (src/alt/class). The `hasAvatarImage` and
|
||||
`getAvatarImageUrl` methods are thin wrappers over `getDisplayAvatarUrl`, but
|
||||
they give the acceptance handlers distinct, readable names and their
|
||||
consistency is pinned by the property tests — left as-is.
|
||||
|
||||
No structural changes were required. The only non-test source diffs are
|
||||
tool-generated mutation manifests (see below).
|
||||
|
||||
**Constitution note:** the refactorer also updated
|
||||
`swarmforge/constitution/articles/engineering.prompt` to make the Language
|
||||
Defaults section JavaScript-specific (mocha/chai/standard/c8/semantic-release
|
||||
as preferred dev deps), replacing the Clojure/Java defaults that do not apply to
|
||||
this project. This is a reasonable governance cleanup for a JavaScript monorepo
|
||||
and is a soft "prefer" recommendation, so it was kept. It is unrelated to the
|
||||
feature itself and is called out here for the human.
|
||||
|
||||
## Verification results
|
||||
|
||||
### Language mutation (`mutate4javascript`, full coverage, `--max-workers 8`)
|
||||
- **`account-page.js`**: 11 killed, **0 survived**, 0 uncovered. The refreshed
|
||||
embedded manifest now includes the new `getAvatarUrl`, `getDisplayAvatarUrl`,
|
||||
`hasAvatarImage`, `getAvatarImageUrl`, `hasSetAvatarUrlButton`, and
|
||||
`clickSetAvatarUrl` functions.
|
||||
- **`avatar-image.js`**: 0 mutation sites found — a tool limitation with this
|
||||
simple React component (a conditional return plus a `React.createElement` call).
|
||||
The component is well covered by 6 unit tests (undefined/null/empty/valid URL,
|
||||
alt text, class) and the acceptance suite, so no coverage gap exists.
|
||||
|
||||
### DRY (`dry4javascript`)
|
||||
- Changed files (`account-page.js`, `avatar-image.js`): **no duplicate
|
||||
candidates**.
|
||||
|
||||
### CRAP / cyclomatic complexity (`crap4javascript`)
|
||||
All changed functions are well below the 8.0 threshold and 100% covered. Highest:
|
||||
`AccountPage._getProfileField` (CC 4, CRAP 4.0), `getDisplayAvatarUrl` (CC 3,
|
||||
CRAP 3.0), `AvatarImage` (CC 2, CRAP 2.0); the rest are CC 1.
|
||||
|
||||
### Soft Gherkin acceptance mutation (`gherkin-mutator --level soft`)
|
||||
- **`account-avatar-display.feature`**: 2 executed, **2 survived** — both are
|
||||
single-character case mutations of the example avatar URLs
|
||||
(`https://example.com/avatar.png` → `https://exaMple.com/avatar.png`). Each
|
||||
example URL is used consistently on both the setup (set-avatar-url broadcast)
|
||||
and assertion (account page displays) sides of its scenario, so the mutated
|
||||
value is still displayed correctly. These are intrinsic equivalents, not
|
||||
implementation gaps; no change warranted.
|
||||
|
||||
## Suite status
|
||||
- `psf-memo-client`: unit **298 passing**, property **43 passing**, acceptance
|
||||
**pass** (all 25 feature files, including the new `account-avatar-display`
|
||||
suite), lint **pass**, build **pass**.
|
||||
|
||||
## Handoffs sent
|
||||
- `git_handoff` to coder and refactorer (`priority: 00`) with the account-avatar
|
||||
display review commit (refreshed mutation manifest, Gherkin acceptance-mutation
|
||||
stamp, and this summary).
|
||||
- No specifier handoff: no functional or spec change in this commit (only
|
||||
tool-generated manifests/stamps and the review summary).
|
||||
|
||||
By architect.
|
||||
@@ -1,3 +1,7 @@
|
||||
# acceptance-mutation-manifest-begin
|
||||
# {"version":1,"tested_at":"2026-09-05T20:15:33.828984609Z","feature_name":"Account Avatar Display","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/account-avatar-display.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[]}
|
||||
# acceptance-mutation-manifest-end
|
||||
|
||||
# Scenarios: Account Avatar Display - 1, Account Avatar Display - 2
|
||||
#
|
||||
# When the authenticated account has an avatar URL set, the /account page
|
||||
|
||||
@@ -108,5 +108,5 @@ AccountPage.ACCOUNT_PATH = ACCOUNT_PATH
|
||||
module.exports = AccountPage
|
||||
|
||||
// mutate4javascript-manifest-begin
|
||||
// {"version":1,"tested_at":"2026-08-27T15:05:46.167Z","module_hash":"5368f9a79434a29fa2879eb387a41426fbb869348e9bcb620878622f4fd3178f","functions":[{"id":"func/AccountPage.constructor","name":"AccountPage.constructor","line":19,"end_line":23,"hash":"89f261283d2ceab1023088c80e89e48e21d1b62dbc2241a6e9fb00b5653d0607"},{"id":"func/AccountPage.getAddress","name":"AccountPage.getAddress","line":26,"end_line":28,"hash":"dd06e8414856559223a8fd5bd68193d8e04ea6264e3ac7c08e80c8dea69e2a36"},{"id":"func/AccountPage._getProfileField","name":"AccountPage._getProfileField","line":32,"end_line":38,"hash":"f9cfeda61b974259daa7204c803aa186efa79f97cb56f5af4d2b502567da7674"},{"id":"func/AccountPage.getName","name":"AccountPage.getName","line":41,"end_line":43,"hash":"fd06a52ab7c03ab8e78702d3b05851294957ae30842f5649d3dbe7014d1d423f"},{"id":"func/AccountPage.getBio","name":"AccountPage.getBio","line":46,"end_line":48,"hash":"0e52e9086ca7c1a78dfb1025977356c453bfb4ad036a418afd92ccf700b6c394"},{"id":"func/AccountPage.hasSetNameButton","name":"AccountPage.hasSetNameButton","line":51,"end_line":53,"hash":"49dc20060d4c55606057a926132f0cc5c8154548a445b299927ef68b9da86ca3"},{"id":"func/AccountPage.hasSetBioButton","name":"AccountPage.hasSetBioButton","line":56,"end_line":58,"hash":"9bfca400cd4dc62fb73911c270e5628746eaf1efbb50aa51e3bc98df4a1b05ec"},{"id":"func/AccountPage.clickSetName","name":"AccountPage.clickSetName","line":61,"end_line":63,"hash":"82ff3b1da4068cbb8b78d55a9dfbd366c78927b67c7d4aa96c4cda12e3144f38"},{"id":"func/AccountPage.clickSetBio","name":"AccountPage.clickSetBio","line":66,"end_line":68,"hash":"97464fb4db204c66fd95beacc8d1e892ae92e30ae0da1dea9dd0f0efc85077d9"}]}
|
||||
// {"version":1,"tested_at":"2026-09-05T20:13:30.792Z","module_hash":"e35d2e97af43f437efeb08523df3b6f0d636b940fe9c484eb0b41becd026a331","functions":[{"id":"func/AccountPage.constructor","name":"AccountPage.constructor","line":20,"end_line":24,"hash":"89f261283d2ceab1023088c80e89e48e21d1b62dbc2241a6e9fb00b5653d0607"},{"id":"func/AccountPage.getAddress","name":"AccountPage.getAddress","line":27,"end_line":29,"hash":"dd06e8414856559223a8fd5bd68193d8e04ea6264e3ac7c08e80c8dea69e2a36"},{"id":"func/AccountPage._getProfileField","name":"AccountPage._getProfileField","line":33,"end_line":39,"hash":"f9cfeda61b974259daa7204c803aa186efa79f97cb56f5af4d2b502567da7674"},{"id":"func/AccountPage.getName","name":"AccountPage.getName","line":42,"end_line":44,"hash":"fd06a52ab7c03ab8e78702d3b05851294957ae30842f5649d3dbe7014d1d423f"},{"id":"func/AccountPage.getBio","name":"AccountPage.getBio","line":47,"end_line":49,"hash":"0e52e9086ca7c1a78dfb1025977356c453bfb4ad036a418afd92ccf700b6c394"},{"id":"func/AccountPage.getAvatarUrl","name":"AccountPage.getAvatarUrl","line":52,"end_line":54,"hash":"aac8da7f8bbb222a9c48e48af6e667f57b1e15f9b01ee6d9781d6739af53a168"},{"id":"func/AccountPage.getDisplayAvatarUrl","name":"AccountPage.getDisplayAvatarUrl","line":58,"end_line":60,"hash":"19f807d397818eddc8323f1698a4f859a6aaa2dc00531e834a9db0949316b1a1"},{"id":"func/AccountPage.hasAvatarImage","name":"AccountPage.hasAvatarImage","line":63,"end_line":65,"hash":"d0a80731894de29b831f26935390810fde8b4dc0d2796b092692745f4ee53774"},{"id":"func/AccountPage.getAvatarImageUrl","name":"AccountPage.getAvatarImageUrl","line":68,"end_line":70,"hash":"fa449dd4836aee10077b357a25e0fa628580e9c5d925337906c0493a3fdb9e44"},{"id":"func/AccountPage.hasSetNameButton","name":"AccountPage.hasSetNameButton","line":73,"end_line":75,"hash":"49dc20060d4c55606057a926132f0cc5c8154548a445b299927ef68b9da86ca3"},{"id":"func/AccountPage.hasSetBioButton","name":"AccountPage.hasSetBioButton","line":78,"end_line":80,"hash":"9bfca400cd4dc62fb73911c270e5628746eaf1efbb50aa51e3bc98df4a1b05ec"},{"id":"func/AccountPage.hasSetAvatarUrlButton","name":"AccountPage.hasSetAvatarUrlButton","line":83,"end_line":85,"hash":"bcbba0b321844c4a3e70ee12708a59b08429ddac6e15d8be6261c44d8495cb8c"},{"id":"func/AccountPage.clickSetName","name":"AccountPage.clickSetName","line":88,"end_line":90,"hash":"82ff3b1da4068cbb8b78d55a9dfbd366c78927b67c7d4aa96c4cda12e3144f38"},{"id":"func/AccountPage.clickSetBio","name":"AccountPage.clickSetBio","line":93,"end_line":95,"hash":"97464fb4db204c66fd95beacc8d1e892ae92e30ae0da1dea9dd0f0efc85077d9"},{"id":"func/AccountPage.clickSetAvatarUrl","name":"AccountPage.clickSetAvatarUrl","line":98,"end_line":100,"hash":"eed3c7b975daad8eb4048b35161539f1efeb9731b710d1c90f8c30e52b40e6fe"}]}
|
||||
// mutate4javascript-manifest-end
|
||||
|
||||
Reference in New Issue
Block a user