From 9caff9c4c330c8bc218a7fc9383e027c70f1a82c Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 25 Aug 2026 20:01:43 -0700 Subject: [PATCH] Remove memo action constructor duplication and persist mutation state Move per-action config into a static config object on MemoPost and MemoSetName and have the MemoAction base constructor read it, eliminating the DRY-flagged duplicate constructor pattern. Commit the differential and acceptance mutation manifests written by the approved tools during the architect verification pass for the set-name feature. By architect. --- docs/reviews/set-name-summary.md | 81 ++++++++++++++++++++++++++++++++ specs/set-name.feature | 4 ++ src/services/account-page.js | 4 ++ src/services/memo-action.js | 21 ++++++--- src/services/memo-post.js | 17 ++++--- src/services/memo-set-name.js | 19 +++++--- src/services/new-post.js | 2 +- src/services/page-controller.js | 4 ++ src/services/profiles.js | 4 ++ src/services/set-name-page.js | 4 ++ 10 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 docs/reviews/set-name-summary.md diff --git a/docs/reviews/set-name-summary.md b/docs/reviews/set-name-summary.md new file mode 100644 index 0000000..c379acb --- /dev/null +++ b/docs/reviews/set-name-summary.md @@ -0,0 +1,81 @@ +# Architectural Review Summary — set-name + +## Task and commits reviewed +- Task: `set-name` +- Reviewed the merged branch ending at `d79b9f04bd` (refactorer), which carried: + - `bb9d00a` — specifier Set Name Gherkin spec (`specs/set-name.feature`) + - `bd2eac5` — coder implementation (MemoSetName, SetNamePage, AccountPage, + Profiles store, React views, acceptance handlers) + - `d79b9f0` — refactorer extraction of `MemoAction` and `PageController` base + classes plus set-name property tests +- Merged into `swarmforge-architect` (fast-forward) and processed as a batch. + +## Architectural findings and fixes applied +Reviewed UI/Core separation, dependency rule, information hiding/encapsulation, +and local code quality. + +1. **Base-class extraction (good).** `MemoAction` (shared by `MemoPost` and + `MemoSetName`) owns `validate`/`broadcast`/`_throwIfInvalid`; subclasses + supply only the protocol-specific `isTooLong`/`reflect` and their config. + `PageController` (shared by `NewPostPage` and `SetNamePage`) owns + `setInput`/`submit`/`_handleSubmitFailure`; subclasses supply + `successPath`, `validationCodes`, `_setBusy`, and `_perform`. This removes + structural duplication between the two memo actions and the two page + controllers while keeping dependency direction inward. +2. **UI/Core separation (good).** All behavior lives in testable services free + of UI/IO; the React views (`set-name`, `account`, `app-body`, `nav-menu`) + are thin shells that inject wallet/profiles/navigate adapters. The shared + `Profiles` session store keeps the Set Name and Account pages in sync + without leaking persistence structures across the boundary. +3. **Acceptance handlers (good).** `acceptance/lib/handlers.js` uses regex + parameter capture as the default style and shares one `world` across the + memo-post, new-post, set-name, and account features; the fake wallet + `sendOpReturn` signature was corrected to the public minimal-slp-wallet API. +4. **Fix applied — constructor duplication (DRY).** The language DRY tool + flagged a score=1.00 duplicate between the `MemoPost` and `MemoSetName` + constructors (identical config-assignment shape). Moved the per-action + config into a static `config` object on each subclass and had the base + `MemoAction` constructor read `this.constructor.config`. This eliminates + the duplicate constructor pattern while keeping the config values + subclass-specific and readable. DRY now reports no duplicate candidates. + +## Verification results +- **Unit (`node --test`):** 56/56 pass. +- **Property (`npm run test:property`):** 13/13 pass (set-name validation + classification, byte-counter conservation, setInput round-trip, broadcast + failure never navigates). +- **Acceptance (normal):** `memo-new`, `post-memo`, and `set-name` generated + suites all pass (11 + 6 + 11 scenarios). +- **Mutation (`mutate4javascript`, `--max-workers 8`, `--mutate-all`):** + - `memo-action.js` — killed 5 / survived 0 / uncovered 0 + - `memo-post.js` — killed 2 / survived 0 / uncovered 0 + - `memo-set-name.js` — killed 2 / survived 0 / uncovered 0 + - `page-controller.js` — killed 7 / survived 0 / uncovered 0 + - `set-name-page.js` — killed 3 / survived 0 / uncovered 0 + - `account-page.js` — killed 7 / survived 0 / uncovered 0 + - `profiles.js` — killed 1 / survived 0 / uncovered 0 + - `new-post.js` — killed 0 / survived 0 / uncovered 0 (manifest current) + - Manifests refreshed for all refactored/new files. +- **DRY (`dry4javascript src`):** no duplicate candidates (constructor + duplication removed). +- **Gherkin acceptance mutation (soft):** `set-name.feature` — 13 executed, + **6 killed, 7 survived**, 0 errors (1 scenario/1 mutation reused from the + clean empty-name scenario). + - Killed: byte-counter `count` values and the empty-name boundary — values + are behaviorally connected. + - Survived (documented equivalents): name-text dithers (m1, m2, m10) are + opaque data — any non-empty name broadcasts and reflects identically; and + over-length name dithers (m4, m5, m6, m14) remain over-length, so the + length-rejection and zero-count branches are unchanged. +- Property tests run separately via `npm run test:property`. + +## Suite status +- Unit + property + acceptance all pass; source-level mutation fully kills all + testable core modules. Gherkin acceptance mutation survivors are documented + equivalents. + +## Handoffs sent +- `git_handoff` → coder, refactorer (priority `00`, task `set-name`), to review + the architect commit (constructor DRY fix + refreshed tool manifests). + +By architect. diff --git a/specs/set-name.feature b/specs/set-name.feature index 8c152bb..7a245c0 100644 --- a/specs/set-name.feature +++ b/specs/set-name.feature @@ -1,3 +1,7 @@ +# acceptance-mutation-manifest-begin +# {"version":1,"tested_at":"2026-08-26T03:00:49.110540817Z","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:00:28.196490363Z"}]} +# acceptance-mutation-manifest-end + # Scenarios: Set Name - 1, Set Name - 2, Set Name - 3, Set Name - 4, Set Name - 5 Feature: Set Name diff --git a/src/services/account-page.js b/src/services/account-page.js index d1f6c01..457e172 100644 --- a/src/services/account-page.js +++ b/src/services/account-page.js @@ -51,3 +51,7 @@ AccountPage.SET_NAME_PATH = SET_NAME_PATH AccountPage.ACCOUNT_PATH = ACCOUNT_PATH module.exports = AccountPage + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-08-26T02:57:14.578Z","module_hash":"956a690653185cdbda205b7ee5124f905241d47660357f40e32ee2a9f2340ea9","functions":[{"id":"func/AccountPage.constructor","name":"AccountPage.constructor","line":18,"end_line":22,"hash":"89f261283d2ceab1023088c80e89e48e21d1b62dbc2241a6e9fb00b5653d0607"},{"id":"func/AccountPage.getAddress","name":"AccountPage.getAddress","line":25,"end_line":27,"hash":"dd06e8414856559223a8fd5bd68193d8e04ea6264e3ac7c08e80c8dea69e2a36"},{"id":"func/AccountPage.getName","name":"AccountPage.getName","line":31,"end_line":37,"hash":"63f3f003cea554075f50c92062da81de964fc5cedefbf871843d9dd871aaed17"},{"id":"func/AccountPage.hasSetNameButton","name":"AccountPage.hasSetNameButton","line":40,"end_line":42,"hash":"49dc20060d4c55606057a926132f0cc5c8154548a445b299927ef68b9da86ca3"},{"id":"func/AccountPage.clickSetName","name":"AccountPage.clickSetName","line":45,"end_line":47,"hash":"82ff3b1da4068cbb8b78d55a9dfbd366c78927b67c7d4aa96c4cda12e3144f38"}]} +// mutate4javascript-manifest-end diff --git a/src/services/memo-action.js b/src/services/memo-action.js index d14e561..6ea7ff6 100644 --- a/src/services/memo-action.js +++ b/src/services/memo-action.js @@ -2,13 +2,9 @@ Shared base for Memo protocol actions that broadcast an OP_RETURN transaction through a wallet and reflect the result on an injected store. - Subclasses supply the protocol-specific pieces: - prefix - hex protocol prefix (e.g. 0x6d02 for a post) - walletRequiredMsg - error message when no wallet is present - lengthMessage - error text for an over-length value - emptyMessage - error text for an empty value - lengthCode - error code for an over-length value - validationCode - error code for an empty value + Subclasses supply the protocol-specific pieces, either as a static + `config` object (prefix, walletRequiredMsg, lengthMessage, emptyMessage, + lengthCode, validationCode) or as methods: isTooLong(value) - true when the value exceeds the action's limit reflect(txid, value) - record the broadcast result on the injected store */ @@ -16,6 +12,13 @@ class MemoAction { constructor (deps = {}) { this.wallet = deps.wallet + const cfg = this.constructor.config + this.prefix = cfg.prefix + this.walletRequiredMsg = cfg.walletRequiredMsg + this.lengthMessage = cfg.lengthMessage + this.emptyMessage = cfg.emptyMessage + this.lengthCode = cfg.lengthCode + this.validationCode = cfg.validationCode } // Validate a candidate value. @@ -68,3 +71,7 @@ class MemoAction { } module.exports = MemoAction + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-08-26T02:59:30.088Z","module_hash":"9f6ac3a351ce499162bd5f350ac2eec15f7a4a88450334b49cb1c445b83b0eea","functions":[{"id":"func/MemoAction.constructor","name":"MemoAction.constructor","line":13,"end_line":22,"hash":"881f01aa2a258bcbc4750b69dc303a03139b6368decb2e10e667dc2f23f5ea80"},{"id":"func/MemoAction.validate","name":"MemoAction.validate","line":26,"end_line":36,"hash":"b8598a392b3a65b5f1fe329048a041a087ef0735806fd03f42fe0cf7e19ef7fc"},{"id":"func/MemoAction.broadcast","name":"MemoAction.broadcast","line":40,"end_line":59,"hash":"07853c0eec474cae372e901db388b62b50020db0aff1f63bb587b9e494f4ede5"},{"id":"func/MemoAction._throwIfInvalid","name":"MemoAction._throwIfInvalid","line":62,"end_line":70,"hash":"dafb785969f30b0fa347c8e699e4bf3302ce3a9ef0d3481f1ffa5c276992c808"}]} +// mutate4javascript-manifest-end diff --git a/src/services/memo-post.js b/src/services/memo-post.js index c0b2cd7..00b49a4 100644 --- a/src/services/memo-post.js +++ b/src/services/memo-post.js @@ -22,15 +22,18 @@ const MEMO_POST_PREFIX = '6d02' const MAX_MEMO_CHARS = 217 class MemoPost extends MemoAction { + static config = { + prefix: MEMO_POST_PREFIX, + walletRequiredMsg: 'Memo post requires a wallet.', + lengthMessage: `Memo is too long. Maximum is ${MAX_MEMO_CHARS} characters.`, + emptyMessage: 'Memo must not be empty.', + lengthCode: 'memo_length', + validationCode: 'memo_validation' + } + constructor (deps = {}) { super(deps) this.feed = deps.feed - this.prefix = MEMO_POST_PREFIX - this.walletRequiredMsg = 'Memo post requires a wallet.' - this.lengthMessage = `Memo is too long. Maximum is ${MAX_MEMO_CHARS} characters.` - this.emptyMessage = 'Memo must not be empty.' - this.lengthCode = 'memo_length' - this.validationCode = 'memo_validation' } // A memo is over-length when it exceeds the character limit. @@ -62,5 +65,5 @@ MemoPost.MAX_MEMO_CHARS = MAX_MEMO_CHARS module.exports = MemoPost // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T02:54:15.221Z","module_hash":"7060e3997af5f6340180385c1e96e055614280683bffdeb737e49c37ad6ce946","functions":[{"id":"func/MemoPost.constructor","name":"MemoPost.constructor","line":25,"end_line":34,"hash":"f1843343deb758b304364862c2c58af3a8a5b03e3d8a70f3056c8679e869a9ed"},{"id":"func/MemoPost.isTooLong","name":"MemoPost.isTooLong","line":37,"end_line":39,"hash":"833f9f66eae849df0248d0c696c95c767a121b394b1c728bc3ec675668dff5de"},{"id":"func/MemoPost.post","name":"MemoPost.post","line":43,"end_line":45,"hash":"26d8e84b520fae1928f7f72215e6ed95f7219c14266c99b710e2af5373cb6faf"},{"id":"func/MemoPost.reflect","name":"MemoPost.reflect","line":48,"end_line":56,"hash":"87e2168a71309a572c60b63f382dd6f681cb28ed543090dbde399e29556cfcdf"}]} +// {"version":1,"tested_at":"2026-08-26T02:59:50.005Z","module_hash":"ef34e1b3318b764dab099f693855e5f57704d60b2173e99c146bdbf34b6dd8c5","functions":[{"id":"func/MemoPost.constructor","name":"MemoPost.constructor","line":34,"end_line":37,"hash":"527e19b059e463a67be214a5c77c0ce4261ffadb58b9546b422be543c1df292d"},{"id":"func/MemoPost.isTooLong","name":"MemoPost.isTooLong","line":40,"end_line":42,"hash":"833f9f66eae849df0248d0c696c95c767a121b394b1c728bc3ec675668dff5de"},{"id":"func/MemoPost.post","name":"MemoPost.post","line":46,"end_line":48,"hash":"26d8e84b520fae1928f7f72215e6ed95f7219c14266c99b710e2af5373cb6faf"},{"id":"func/MemoPost.reflect","name":"MemoPost.reflect","line":51,"end_line":59,"hash":"87e2168a71309a572c60b63f382dd6f681cb28ed543090dbde399e29556cfcdf"}]} // mutate4javascript-manifest-end diff --git a/src/services/memo-set-name.js b/src/services/memo-set-name.js index 5f9fb3f..1e3af95 100644 --- a/src/services/memo-set-name.js +++ b/src/services/memo-set-name.js @@ -22,15 +22,18 @@ const MEMO_SET_NAME_PREFIX = '6d01' const MAX_NAME_BYTES = 77 class MemoSetName extends MemoAction { + static config = { + prefix: MEMO_SET_NAME_PREFIX, + walletRequiredMsg: 'Memo set name requires a wallet.', + lengthMessage: `Name is too long. Maximum is ${MAX_NAME_BYTES} bytes.`, + emptyMessage: 'Name must not be empty.', + lengthCode: 'name_length', + validationCode: 'name_validation' + } + constructor (deps = {}) { super(deps) this.profiles = deps.profiles - this.prefix = MEMO_SET_NAME_PREFIX - this.walletRequiredMsg = 'Memo set name requires a wallet.' - this.lengthMessage = `Name is too long. Maximum is ${MAX_NAME_BYTES} bytes.` - this.emptyMessage = 'Name must not be empty.' - this.lengthCode = 'name_length' - this.validationCode = 'name_validation' } // A name is over-length when it exceeds the byte limit. @@ -56,3 +59,7 @@ MemoSetName.MEMO_SET_NAME_PREFIX = MEMO_SET_NAME_PREFIX MemoSetName.MAX_NAME_BYTES = MAX_NAME_BYTES module.exports = MemoSetName + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-08-26T03:00:01.475Z","module_hash":"7df00da6f6ac452ed14c70ec73f07ac6e7a9c9e50f105f3fb3fcb969e409b567","functions":[{"id":"func/MemoSetName.constructor","name":"MemoSetName.constructor","line":34,"end_line":37,"hash":"9407b43605444074011b1da595c9d53356352c72b0d847e00365d79ad705663a"},{"id":"func/MemoSetName.isTooLong","name":"MemoSetName.isTooLong","line":40,"end_line":42,"hash":"c19efae68fd1faed8e63e106a9dbad10872853879870fe49df16968f1c8a3641"},{"id":"func/MemoSetName.setName","name":"MemoSetName.setName","line":46,"end_line":48,"hash":"9226b63b60a573a9dfb5c7bbb1449d1a138f30b648642d345c5162d012a2cfc0"},{"id":"func/MemoSetName.reflect","name":"MemoSetName.reflect","line":51,"end_line":55,"hash":"3cfed5e8ec7acf592e07e67659b4d8e075d98bbddf79755b8a31b76fd1ae5696"}]} +// mutate4javascript-manifest-end diff --git a/src/services/new-post.js b/src/services/new-post.js index f3fe80a..3279411 100644 --- a/src/services/new-post.js +++ b/src/services/new-post.js @@ -68,5 +68,5 @@ NewPostPage.RECENT_FEED_PATH = RECENT_FEED_PATH module.exports = NewPostPage // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T02:54:16.145Z","module_hash":"45d772cfe40b8a34018092abbe924c01cbde741c5cbaa737a283ba35214a99ff","functions":[{"id":"func/NewPostPage.constructor","name":"NewPostPage.constructor","line":23,"end_line":33,"hash":"686653c181941b27c19cd8c1f9fa7b3fee50db6e19c1e39d0db79e6bfbe52a81"},{"id":"func/NewPostPage.addMenuLink","name":"NewPostPage.addMenuLink","line":36,"end_line":39,"hash":"bac97164d2d70bfdb946c4d54e67983c43cad093bac558f1d169e1739ca97137"},{"id":"func/NewPostPage.hasMenuLink","name":"NewPostPage.hasMenuLink","line":42,"end_line":44,"hash":"7e1abf5d0833aaf3b3da3a024de9eb2b80da900b2930e832c7e92d77e0e50344"},{"id":"func/NewPostPage.remainingCount","name":"NewPostPage.remainingCount","line":47,"end_line":49,"hash":"521ce4ed841f62099529b327f2245a9e94c09af2f67ed5d91839e52607bcea37"},{"id":"func/NewPostPage._setBusy","name":"NewPostPage._setBusy","line":52,"end_line":54,"hash":"af4c51d75a9bbfc4d8c2566414ee950704d83831ad915ee04eea8bf2fab65a3e"},{"id":"func/NewPostPage._perform","name":"NewPostPage._perform","line":57,"end_line":62,"hash":"e66f893a4913b5d6f3cc4fcc1ad58e21557b4a316f0150050c961e3ece237795"}]} +// {"version":1,"tested_at":"2026-08-26T02:57:53.801Z","module_hash":"45d772cfe40b8a34018092abbe924c01cbde741c5cbaa737a283ba35214a99ff","functions":[{"id":"func/NewPostPage.constructor","name":"NewPostPage.constructor","line":23,"end_line":33,"hash":"686653c181941b27c19cd8c1f9fa7b3fee50db6e19c1e39d0db79e6bfbe52a81"},{"id":"func/NewPostPage.addMenuLink","name":"NewPostPage.addMenuLink","line":36,"end_line":39,"hash":"bac97164d2d70bfdb946c4d54e67983c43cad093bac558f1d169e1739ca97137"},{"id":"func/NewPostPage.hasMenuLink","name":"NewPostPage.hasMenuLink","line":42,"end_line":44,"hash":"7e1abf5d0833aaf3b3da3a024de9eb2b80da900b2930e832c7e92d77e0e50344"},{"id":"func/NewPostPage.remainingCount","name":"NewPostPage.remainingCount","line":47,"end_line":49,"hash":"521ce4ed841f62099529b327f2245a9e94c09af2f67ed5d91839e52607bcea37"},{"id":"func/NewPostPage._setBusy","name":"NewPostPage._setBusy","line":52,"end_line":54,"hash":"af4c51d75a9bbfc4d8c2566414ee950704d83831ad915ee04eea8bf2fab65a3e"},{"id":"func/NewPostPage._perform","name":"NewPostPage._perform","line":57,"end_line":62,"hash":"e66f893a4913b5d6f3cc4fcc1ad58e21557b4a316f0150050c961e3ece237795"}]} // mutate4javascript-manifest-end diff --git a/src/services/page-controller.js b/src/services/page-controller.js index 7f83ce1..7b46c14 100644 --- a/src/services/page-controller.js +++ b/src/services/page-controller.js @@ -57,3 +57,7 @@ class PageController { } module.exports = PageController + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-08-26T02:56:41.826Z","module_hash":"3799cba6a1b2af39fb7e570336328abc7216f01ed2af7071a2a0d345469f7fae","functions":[{"id":"func/PageController.constructor","name":"PageController.constructor","line":13,"end_line":18,"hash":"09ba0e480cbc1213c699f45c7b6ef59e584dce4e8d4f1ab0adf5094435eba06f"},{"id":"func/PageController.setInput","name":"PageController.setInput","line":21,"end_line":24,"hash":"595484662b7ca07ef5eef5cebbff06309242552d9b4d15687df02f260ba88244"},{"id":"func/PageController.submit","name":"PageController.submit","line":29,"end_line":42,"hash":"6e9c336a13eb33e355b92a1a1f82ba6ac480c9125dc51d433a0b1acfb01d5d39"},{"id":"func/PageController._handleSubmitFailure","name":"PageController._handleSubmitFailure","line":47,"end_line":56,"hash":"3d9ad3eb3a25e11b8a1eef164b2757034499b85b439754bb606622f99c72d5f9"}]} +// mutate4javascript-manifest-end diff --git a/src/services/profiles.js b/src/services/profiles.js index 7cd33d0..d3a47ab 100644 --- a/src/services/profiles.js +++ b/src/services/profiles.js @@ -26,3 +26,7 @@ class Profiles { } module.exports = Profiles + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-08-26T02:57:34.069Z","module_hash":"6a13673cbcae9c1dc6a497b7214409f415a403b9c6eaefd04776950fb8b64768","functions":[{"id":"func/Profiles.constructor","name":"Profiles.constructor","line":13,"end_line":15,"hash":"d13fcf15cca167093fca3cb89c2482d1fbbfac5afad666e4b9cf47a440aa8394"},{"id":"func/Profiles.setName","name":"Profiles.setName","line":17,"end_line":20,"hash":"5a36c6e237798608de0bedd8744b75000c0eec5c0a6a64c870a25bcdf20aed21"},{"id":"func/Profiles.getName","name":"Profiles.getName","line":22,"end_line":25,"hash":"2fcb9d84687ea0f3b24f3e34c0a72eda55a4e869b87e08a7f4551321a4166198"}]} +// mutate4javascript-manifest-end diff --git a/src/services/set-name-page.js b/src/services/set-name-page.js index cf5ded9..5fccc00 100644 --- a/src/services/set-name-page.js +++ b/src/services/set-name-page.js @@ -51,3 +51,7 @@ SetNamePage.SET_NAME_PATH = SET_NAME_PATH SetNamePage.ACCOUNT_PATH = ACCOUNT_PATH module.exports = SetNamePage + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-08-26T02:57:01.182Z","module_hash":"398fed9fb3ec02fb64027d97c4d0110f667014fd86102413e03c92b26cdbb03c","functions":[{"id":"func/SetNamePage.constructor","name":"SetNamePage.constructor","line":23,"end_line":29,"hash":"4ce053830f485ce8a0fba4495cd85fc5e2cac3c9db88b5cbf1e8c66a371a9752"},{"id":"func/SetNamePage.remainingCount","name":"SetNamePage.remainingCount","line":32,"end_line":34,"hash":"a6aac7215cf5bcbbfbab23f8edfa3c665bcf0ddf6db1d83601c9c23b9de56ad6"},{"id":"func/SetNamePage._setBusy","name":"SetNamePage._setBusy","line":37,"end_line":39,"hash":"a0947ed899e0def1f6ae243197d409f5603c4820467f0e0fafdcb4deadb3a92b"},{"id":"func/SetNamePage._perform","name":"SetNamePage._perform","line":42,"end_line":47,"hash":"8614f25b06dcb21b81ab94b4a4611c5e3719ebf4ed7ab13e13976ab0d5938b27"}]} +// mutate4javascript-manifest-end