mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo.git
synced 2026-09-21 16:52:01 -07:00
Review and harden set-avatar-url profile-text refactor
Merge refactorer set-avatar-url work: shared MemoProfileTextAction base with a profileTextConfig factory so MemoSetName/MemoSetBio/MemoSetAvatarUrl become thin config declarations, plus avatar URL byte-limit, round-trip, and byte budget property tests. No hardening fix required; language mutation reports no survivors and soft Gherkin acceptance mutation survivors are documented equivalents. Record the durable review report. By architect.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# Review summary: set-avatar-url
|
||||
|
||||
**Architect review of the refactorer handoff for task `set-avatar-url`.**
|
||||
|
||||
## Commits reviewed
|
||||
- `991053a` (specifier): Spec Set Avatar URL feature.
|
||||
- `7bee85a` (coder): Implement Set Avatar URL client feature — `memo-set-avatar-url.js`,
|
||||
`set-avatar-url-page.js`, account/set-avatar-url React components, acceptance handlers,
|
||||
and unit tests.
|
||||
- `131d31e` (refactorer): Merge coder set-avatar-url implementation.
|
||||
- `58e21ff01a` (refactorer): Deduplicate Memo profile-text actions and add set-avatar-url
|
||||
property tests — extracted a shared `MemoProfileTextAction` base with a `profileTextConfig`
|
||||
factory so `MemoSetName`, `MemoSetBio`, and `MemoSetAvatarUrl` each become a thin data
|
||||
declaration, removing the duplicated config and broadcast method; added property tests
|
||||
covering the avatar URL byte limit, round trips, and byte budget conservation.
|
||||
|
||||
Merged with the prior chain commits (`2eb86e2` specifier backlog update, `e321c7f` revert of
|
||||
an unrelated specifier-model change in `swarmforge.conf`).
|
||||
|
||||
## Architectural findings and fixes applied
|
||||
The refactorer's structure is sound. `MemoSetName`/`MemoSetBio`/`MemoSetAvatarUrl` are now
|
||||
thin config-driven subclasses of the shared `MemoProfileTextAction` base, which supplies the
|
||||
config factory and the broadcast-method binding; the three actions differ only in their
|
||||
protocol prefix, byte limit, error codes, and profile-store method. UI/Core separation,
|
||||
dependency direction, and information hiding all hold: the wallet and profiles store are
|
||||
injected, keeping the module free of network/UI concerns behind small adapter boundaries.
|
||||
The client remains the only component touched; no `psf-memo-db`/`psf-memo-indexer` changes.
|
||||
|
||||
No hardening fix was required. The refactorer's property tests already cover the avatar URL
|
||||
byte limit, UTF-8 round trips, byte-budget conservation, and the over-limit rejection path,
|
||||
and language mutation reports no survivors in the changed source files.
|
||||
|
||||
## Verification results
|
||||
|
||||
### Language mutation (`mutate4javascript`, differential vs manifest, `--max-workers 8`)
|
||||
Every changed testable source file is fully killed (0 survivors, 0 uncovered):
|
||||
- `psf-memo-client/src/services/memo-profile-text-action.js` — 3/3 killed (new base)
|
||||
- `memo-set-avatar-url.js`, `memo-set-bio.js`, `memo-set-name.js` — 0 sites (thin config
|
||||
wrappers; their logic lives in the fully-killed `MemoProfileTextAction`/`MemoAction` bases)
|
||||
|
||||
The React components (`app-body/account`, `app-body/set-avatar-url`, `app-body`) are JSX UI
|
||||
modules the mutation tool cannot parse; per the constitution these environmentally
|
||||
unsuitable UI modules are excluded from mutation testing, and their testable logic lives
|
||||
in the services above.
|
||||
|
||||
### DRY (`dry4javascript`)
|
||||
No duplicate candidates found in `src/services`.
|
||||
|
||||
### CRAP / cyclomatic complexity (`crap4javascript`)
|
||||
All changed functions within threshold: `profileTextConfig` CC 1, 100% branch coverage,
|
||||
CRAP 1.0. The three thin action subclasses expose no functions (config-only declarations).
|
||||
|
||||
### Soft Gherkin acceptance mutation (`gherkin-mutator --level soft`)
|
||||
**psf-memo-client** `set-avatar-url.feature`: 12 executed, **6 killed, 6 survived**.
|
||||
- Scenario 1 (valid avatar URL broadcast) `m1`,`m2`: mutated capitalization of the URL
|
||||
example survives because the assertion echoes the broadcast value from the same example
|
||||
(weak/tautological example-to-assertion connection). Specifier-side feature-quality item.
|
||||
- Scenario 3 (over-long URL rejected) `m4`,`m5`: mutated chars still leave the URL over the
|
||||
217-byte limit, so rejection is unchanged — genuine equivalents.
|
||||
- Scenario 4 (byte counter) `m9`,`m13`: mutated chars leave the byte length unchanged, so
|
||||
the remaining count is identical — genuine equivalents.
|
||||
|
||||
The sibling features touched by the shared-base refactor were also re-verified:
|
||||
`set-bio.feature` (6 killed, 6 survived) and `set-name.feature` (7 killed, 7 survived), all
|
||||
survivors being the same capitalization/same-length equivalents or tautological-assertion
|
||||
cases. No implementation changes are warranted; the equivalents are intrinsic and the
|
||||
tautological-assertion cases are specifier feature-quality improvements.
|
||||
|
||||
## Suite status
|
||||
- `psf-memo-client`: unit **74 passing**, property **11 passing**, acceptance **pass**
|
||||
(8 feature files), lint **pass**.
|
||||
|
||||
## Handoffs sent
|
||||
- No coder/refactorer handoff: the architect made no functional changes requiring follow-up
|
||||
review (only the durable report and tool-generated mutation manifests).
|
||||
- No specifier handoff: no specification changes in this commit (the feature-file mutation
|
||||
manifests are tool-generated metadata; the weak-scenario findings are recorded here for
|
||||
the specifier in the durable report).
|
||||
|
||||
By architect.
|
||||
@@ -1,3 +1,7 @@
|
||||
# acceptance-mutation-manifest-begin
|
||||
# {"version":1,"tested_at":"2026-08-27T16:58:51.862423444Z","feature_name":"Set Avatar URL","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/set-avatar-url.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[{"index":1,"name":"Set Avatar URL - 2 an empty avatar URL is rejected on the set avatar page","scenario_hash":"297768a79c84fa3e7394112d688c046be75a3f9fef80ed784b745e7b637878ee","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-27T16:58:43.073982780Z"}]}
|
||||
# acceptance-mutation-manifest-end
|
||||
|
||||
# Scenarios: Set Avatar URL - 1, Set Avatar URL - 2, Set Avatar URL - 3, Set Avatar URL - 4, Set Avatar URL - 5
|
||||
Feature: Set Avatar URL
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# acceptance-mutation-manifest-begin
|
||||
# {"version":1,"tested_at":"2026-08-27T15:07:16.579432218Z","feature_name":"Set Bio","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/set-bio.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[{"index":1,"name":"Set Bio - 2 an empty bio is rejected on the set bio page","scenario_hash":"13afeec034e9b128e3e2c2c82f392648a11407b917c33c53d13185cc9d8bf2b7","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-27T15:06:58.731115193Z"}]}
|
||||
# {"version":1,"tested_at":"2026-08-27T16:58:59.832784881Z","feature_name":"Set Bio","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/set-bio.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[{"index":1,"name":"Set Bio - 2 an empty bio is rejected on the set bio page","scenario_hash":"13afeec034e9b128e3e2c2c82f392648a11407b917c33c53d13185cc9d8bf2b7","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-27T15:06:58.731115193Z"}]}
|
||||
# acceptance-mutation-manifest-end
|
||||
|
||||
# Scenarios: Set Bio - 1, Set Bio - 2, Set Bio - 3, Set Bio - 4, Set Bio - 5
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# acceptance-mutation-manifest-begin
|
||||
# {"version":1,"tested_at":"2026-08-26T04:22:57.360768955Z","feature_name":"Set Name","feature_path":"../../specs/set-name.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[{"index":1,"name":"Set Name - 2 an empty name is rejected on the set name page","scenario_hash":"a4fbf28bd1afbffeff2f24f685299663df57e3cc4332a115fea84c08db634670","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-26T03:34:50.405131105Z"}]}
|
||||
# {"version":1,"tested_at":"2026-08-27T16:59:00.404406977Z","feature_name":"Set Name","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/set-name.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[{"index":1,"name":"Set Name - 2 an empty name is rejected on the set name page","scenario_hash":"a4fbf28bd1afbffeff2f24f685299663df57e3cc4332a115fea84c08db634670","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-27T16:59:00.404406977Z"}]}
|
||||
# acceptance-mutation-manifest-end
|
||||
|
||||
# Scenarios: Set Name - 1, Set Name - 2, Set Name - 3, Set Name - 4, Set Name - 5
|
||||
|
||||
@@ -52,3 +52,7 @@ class MemoProfileTextAction extends MemoAction {
|
||||
MemoProfileTextAction.profileTextConfig = profileTextConfig
|
||||
|
||||
module.exports = MemoProfileTextAction
|
||||
|
||||
// mutate4javascript-manifest-begin
|
||||
// {"version":1,"tested_at":"2026-08-27T16:57:59.182Z","module_hash":"101611ef1fa18260af869f9c65140a4ef35716649a5a0a64e838fe68d5114b4f","functions":[{"id":"func/profileTextConfig","name":"profileTextConfig","line":27,"end_line":40,"hash":"73d0c1953b628958acf23c16ae27f38c200bafc8568cb0daa8737ea2be2282c8"},{"id":"func/MemoProfileTextAction.constructor","name":"MemoProfileTextAction.constructor","line":45,"end_line":49,"hash":"75d5bb04a9d31690f94a3e2337cce03cbe62202bb4fd33b44949562288626605"}]}
|
||||
// mutate4javascript-manifest-end
|
||||
|
||||
@@ -37,5 +37,5 @@ MemoSetAvatarUrl.MAX_AVATAR_URL_BYTES = MAX_AVATAR_URL_BYTES
|
||||
module.exports = MemoSetAvatarUrl
|
||||
|
||||
// mutate4javascript-manifest-begin
|
||||
// {"version":1,"tested_at":"2026-08-27T16:55:55.303Z","module_hash":"473dc38c01f201fb09484ef6288c8ab86ca3457f9e3871cee279c119b65bb44a","functions":[]}
|
||||
// {"version":1,"tested_at":"2026-08-27T16:58:11.278Z","module_hash":"473dc38c01f201fb09484ef6288c8ab86ca3457f9e3871cee279c119b65bb44a","functions":[]}
|
||||
// mutate4javascript-manifest-end
|
||||
|
||||
@@ -37,5 +37,5 @@ MemoSetBio.MAX_BIO_BYTES = MAX_BIO_BYTES
|
||||
module.exports = MemoSetBio
|
||||
|
||||
// mutate4javascript-manifest-begin
|
||||
// {"version":1,"tested_at":"2026-08-27T16:55:54.606Z","module_hash":"4da70e80defe9ae983bb914d305228497f4877dd43be7f63d6858bf456cf3ab7","functions":[]}
|
||||
// {"version":1,"tested_at":"2026-08-27T16:58:18.035Z","module_hash":"4da70e80defe9ae983bb914d305228497f4877dd43be7f63d6858bf456cf3ab7","functions":[]}
|
||||
// mutate4javascript-manifest-end
|
||||
|
||||
@@ -37,5 +37,5 @@ MemoSetName.MAX_NAME_BYTES = MAX_NAME_BYTES
|
||||
module.exports = MemoSetName
|
||||
|
||||
// mutate4javascript-manifest-begin
|
||||
// {"version":1,"tested_at":"2026-08-27T16:55:54.963Z","module_hash":"4ec10f6de6698eaa32c5fa4c91b8432f910c0dfa2842742aff90ef390a219189","functions":[]}
|
||||
// {"version":1,"tested_at":"2026-08-27T16:58:25.109Z","module_hash":"4ec10f6de6698eaa32c5fa4c91b8432f910c0dfa2842742aff90ef390a219189","functions":[]}
|
||||
// mutate4javascript-manifest-end
|
||||
|
||||
Reference in New Issue
Block a user