From 93e96e745b50c937322c81b8db51b12b90095e6b Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 25 Aug 2026 21:23:19 -0700 Subject: [PATCH] Add architectural review for reply-memo Review the reply-memo feature (specifier spec, coder implementation, refactorer test extraction). Kill two mutation survivors by adding tests for the hexToBytes length check and the reply page successPath wiring. Refresh mutation and acceptance mutation manifests from verification runs. By architect. --- docs/reviews/reply-memo-summary.md | 86 +++++++++++++++++++++++++++++ specs/memo-new.feature | 2 +- specs/post-memo.feature | 2 +- specs/reply-memo.feature | 4 ++ specs/set-name.feature | 2 +- src/services/account-page.js | 2 +- src/services/memo-action.js | 2 +- src/services/memo-post.js | 2 +- src/services/memo-reply.js | 4 ++ src/services/memo-set-name.js | 2 +- src/services/new-post.js | 2 +- src/services/page-controller.js | 2 +- src/services/profiles.js | 2 +- src/services/reply-thread-page.js | 4 ++ src/services/set-name-page.js | 2 +- src/services/utf8.js | 4 ++ test/unit/memo-reply.test.js | 12 ++++ test/unit/reply-thread-page.test.js | 20 +++++++ 18 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 docs/reviews/reply-memo-summary.md diff --git a/docs/reviews/reply-memo-summary.md b/docs/reviews/reply-memo-summary.md new file mode 100644 index 0000000..5608a1b --- /dev/null +++ b/docs/reviews/reply-memo-summary.md @@ -0,0 +1,86 @@ +# Architectural Review Summary — reply-memo + +## Task and commits reviewed +- Task: `reply-memo` +- Reviewed the merged branch ending at `b4508e8909` (refactorer), which carried: + - `8294b4d` — specifier Reply to a Memo Gherkin spec (`specs/reply-memo.feature`) + - `230618d` — specifier browser fix: replace Node-only `Buffer.byteLength` with a + TextEncoder-based UTF-8 byte helper (`src/services/utf8.js`) so the Set Name + byte counter works in the browser + - `d33fda8` — coder implementation (`MemoReply`, `ReplyThreadPage`, `utf8`, + acceptance handlers) + - `b4508e8` — refactorer extraction of reply tests into the shared helpers +- Merged into `swarmforge-architect` (merge commit `c0e4eba`) and processed as a batch. + +## Architectural findings and fixes applied +Reviewed UI/Core separation, dependency rule, information hiding/encapsulation, and +local code quality. + +1. **UI/Core separation (good).** All reply behavior lives in testable services + (`memo-reply.js`, `reply-thread-page.js`) free of UI/IO; the wallet and thread + are injected behind small adapter boundaries. `utf8.js` is a shared, browser-safe + byte-length helper that fixes a real browser bug (Node `Buffer` is unavailable in + the browser) and is reused by both the Set Name and Reply slices. +2. **Dependency rule (good).** `memo-reply` depends inward on `memo-action` and + `utf8`; `reply-thread-page` depends inward on `page-controller`, `memo-reply`, and + `utf8`. No low-level module reaches toward IO. +3. **Information hiding (good).** `MemoReply` extends `MemoAction` and supplies the + reply-specific `config`, `isTooLong`, and `reflect`; it overrides `reply()` to + build the raw wire payload (32-byte parent txid + UTF-8 text) because the reply + wire format differs from the plain-value broadcast. `ReplyThreadPage` extends + `PageController` and supplies `successPath`, `validationCodes`, `_setBusy`, and + `_perform`, plus a `setParent` for nested replies. The `hexToBytes`/`buildReplyPayload` + helpers are module-private, keeping the wire format hidden. +4. **Test refactoring (good).** The refactorer extended `memo-action-helpers` (extra + `extraArgs` for the parent txid, `byteBased` multi-byte tests, `assertBroadcastMsg`) + and added `registerPageSubmitTests` to `page-controller-helpers`, so the reply + tests reuse the shared registrars instead of duplicating them. Helpers stay + separate from `.test.js` files. +5. **Fix applied — mutation survivors (2).** The language mutation tool flagged two + `|| -> &&` survivors that were equivalent only because of test gaps: + - `memo-reply` `hexToBytes`: the 64-character length check was unobservable because + the only invalid-txid test used a non-hex string that the hex-parse loop also + rejected. Added a test that a wrong-length but valid-hex txid is rejected with + the length error, killing the mutation. + - `reply-thread-page` constructor `successPath`: the page was never constructed + with a `successPath`, so the `|| -> &&` wiring was unobservable. Added a test + that a configured success path is honored (navigates on success), killing the + mutation. + Both tests are behavior-preserving and close real coverage gaps. + +## Verification results +- **Unit (`node --test`):** 86/86 pass (was 84; +2 survivor-killing tests). +- **Property (`node --test test/property/*.test.js`):** 13/13 pass. +- **Acceptance (normal):** `memo-new`, `post-memo`, `set-name`, and `reply-memo` + generated suites all pass (4 suites). +- **Mutation (`mutate4javascript`, `--max-workers 8`, `--mutate-all`):** + - memo-action 5/0/0, memo-post 2/0/0, memo-set-name 2/0/0, memo-reply 8/0/0, + page-controller 7/0/0, new-post 4/0/0, set-name-page 3/0/0, reply-thread-page + 5/0/0, account-page 7/0/0, profiles 1/0/0, utf8 0/0/0 (no mutation sites). + - All testable core modules fully kill; no survivors, no uncovered. The two + `|| -> &&` survivors were killed by the added tests. +- **DRY (`dry4javascript src` and `dry4javascript test`):** no duplicate candidates + in either tree. +- **Gherkin acceptance mutation (soft):** + - `reply-memo.feature` — 13 executed, **5 killed, 8 survived**, 0 errors. + - `memo-new.feature` — 14 executed, **4 killed, 10 survived**, 0 errors. + - `post-memo.feature` — 5 executed, **0 killed, 5 survived**, 0 errors. + - `set-name.feature` — 13 executed, **6 killed, 7 survived**, 0 errors. + - Killed: byte/char `count` dithers and the empty-value boundary — values are + behaviorally connected to the counter and rejection branches. + - Survived (documented equivalents): message/name/broadcast-error text dithers are + opaque data — any non-empty value broadcasts and reflects identically, so the + mutation does not change observable behavior. + +## 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 `reply-memo`), to review the + architect commit (survivor-killing test additions + refreshed tool manifests). +- No handoff to the specifier: the architect produced no functional feature commit + (the reply-memo feature was implemented by the coder and already spec-approved). + +By architect. diff --git a/specs/memo-new.feature b/specs/memo-new.feature index b2e903f..4f34827 100644 --- a/specs/memo-new.feature +++ b/specs/memo-new.feature @@ -1,5 +1,5 @@ # acceptance-mutation-manifest-begin -# {"version":1,"tested_at":"2026-08-26T03:36:06.630079245Z","feature_name":"New Post Page","feature_path":"../../specs/memo-new.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[{"index":1,"name":"New Post Page - 2 an empty memo is rejected on the new post page","scenario_hash":"ac70dcf123f435da2b8a6c4953b0b22b8e7a5a8a74291547b954cb562cf9f339","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-26T03:34:47.703957423Z"}]} +# {"version":1,"tested_at":"2026-08-26T04:22:55.346096718Z","feature_name":"New Post Page","feature_path":"../../specs/memo-new.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[{"index":1,"name":"New Post Page - 2 an empty memo is rejected on the new post page","scenario_hash":"ac70dcf123f435da2b8a6c4953b0b22b8e7a5a8a74291547b954cb562cf9f339","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-26T03:34:47.703957423Z"}]} # acceptance-mutation-manifest-end # Scenarios: New Post Page - 1, New Post Page - 2, New Post Page - 3, New Post Page - 4, New Post Page - 5, New Post Page - 6 diff --git a/specs/post-memo.feature b/specs/post-memo.feature index ab5842c..1356cf1 100644 --- a/specs/post-memo.feature +++ b/specs/post-memo.feature @@ -1,5 +1,5 @@ # acceptance-mutation-manifest-begin -# {"version":1,"tested_at":"2026-08-26T03:35:45.131394972Z","feature_name":"Post a Memo","feature_path":"../../specs/post-memo.feature","background_hash":"d7f1a31b7651301ec01bdea9c4c990f9a032ce179aa84f8d6a78595f8a476474","implementation_hash":"unknown","scenarios":[{"index":1,"name":"Post a Memo - 2 an empty memo is rejected","scenario_hash":"4e6fea6fa5adbf7fe0eefdd9bc21a7027fe7312d2937a4fd68f1a1eb68d33a8d","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-26T03:34:49.113519078Z"}]} +# {"version":1,"tested_at":"2026-08-26T04:22:56.345139893Z","feature_name":"Post a Memo","feature_path":"../../specs/post-memo.feature","background_hash":"d7f1a31b7651301ec01bdea9c4c990f9a032ce179aa84f8d6a78595f8a476474","implementation_hash":"unknown","scenarios":[{"index":1,"name":"Post a Memo - 2 an empty memo is rejected","scenario_hash":"4e6fea6fa5adbf7fe0eefdd9bc21a7027fe7312d2937a4fd68f1a1eb68d33a8d","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-26T03:34:49.113519078Z"}]} # acceptance-mutation-manifest-end # Scenarios: Post a Memo - 1, Post a Memo - 2, Post a Memo - 3 diff --git a/specs/reply-memo.feature b/specs/reply-memo.feature index c4ff571..cb43aa6 100644 --- a/specs/reply-memo.feature +++ b/specs/reply-memo.feature @@ -1,3 +1,7 @@ +# acceptance-mutation-manifest-begin +# {"version":1,"tested_at":"2026-08-26T04:22:54.324158808Z","feature_name":"Reply to a Memo","feature_path":"../../specs/reply-memo.feature","background_hash":"fe3d19204f81f7060aa6a9f344ae4bf081e7bbacaf88fba1a001b57d0594dfb2","implementation_hash":"unknown","scenarios":[{"index":1,"name":"Reply to a Memo - 2 an empty reply is rejected","scenario_hash":"a194ab7b3698b25903bc00111192308cd21046db6fa40b6150bb4ba5156f4317","mutation_count":1,"result":{"Total":1,"Killed":1,"Survived":0,"Errors":0},"tested_at":"2026-08-26T04:22:54.324158808Z"}]} +# acceptance-mutation-manifest-end + # Scenarios: Reply to a Memo - 1, Reply to a Memo - 2, Reply to a Memo - 3, Reply to a Memo - 4, Reply to a Memo - 5 Feature: Reply to a Memo diff --git a/specs/set-name.feature b/specs/set-name.feature index 61ac958..7ca0982 100644 --- a/specs/set-name.feature +++ b/specs/set-name.feature @@ -1,5 +1,5 @@ # acceptance-mutation-manifest-begin -# {"version":1,"tested_at":"2026-08-26T03:35:47.374200272Z","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-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"}]} # acceptance-mutation-manifest-end # Scenarios: Set Name - 1, Set Name - 2, Set Name - 3, Set Name - 4, Set Name - 5 diff --git a/src/services/account-page.js b/src/services/account-page.js index 58925de..19371d6 100644 --- a/src/services/account-page.js +++ b/src/services/account-page.js @@ -53,5 +53,5 @@ AccountPage.ACCOUNT_PATH = ACCOUNT_PATH module.exports = AccountPage // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T03:33:33.460Z","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"}]} +// {"version":1,"tested_at":"2026-08-26T04:22:04.080Z","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 1e40631..c5b76a7 100644 --- a/src/services/memo-action.js +++ b/src/services/memo-action.js @@ -73,5 +73,5 @@ class MemoAction { module.exports = MemoAction // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T03:32:20.894Z","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"}]} +// {"version":1,"tested_at":"2026-08-26T04:19:37.933Z","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 8d6b16d..a17e1a7 100644 --- a/src/services/memo-post.js +++ b/src/services/memo-post.js @@ -65,5 +65,5 @@ MemoPost.MAX_MEMO_CHARS = MAX_MEMO_CHARS module.exports = MemoPost // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T03:32:36.219Z","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"}]} +// {"version":1,"tested_at":"2026-08-26T04:19:57.323Z","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-reply.js b/src/services/memo-reply.js index c5d2a14..83b7a05 100644 --- a/src/services/memo-reply.js +++ b/src/services/memo-reply.js @@ -110,3 +110,7 @@ MemoReply.MEMO_REPLY_PREFIX = MEMO_REPLY_PREFIX MemoReply.MAX_REPLY_BYTES = MAX_REPLY_BYTES module.exports = MemoReply + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-08-26T04:20:24.193Z","module_hash":"3eb068f9f90f27c5d7acf2bb5a6c517d1085bcb379123ca2d7134d4fb48a092b","functions":[{"id":"func/MemoReply.constructor","name":"MemoReply.constructor","line":36,"end_line":39,"hash":"23091c1b8f7847199bab3b54d8d81e9d8432c6da96138d72ac03fcf9426d542c"},{"id":"func/MemoReply.isTooLong","name":"MemoReply.isTooLong","line":42,"end_line":44,"hash":"2e867501d184010313ba9b27a6bb1e446df8f093514ee231b90ce77699ecbaf2"},{"id":"func/MemoReply.reply","name":"MemoReply.reply","line":48,"end_line":67,"hash":"2d7b425e350b640caf6ca821146065e9c21fe56f1e8a9d8b1f6cab5504a523c1"},{"id":"func/MemoReply.reflect","name":"MemoReply.reflect","line":70,"end_line":79,"hash":"344e1bf304a4dfd475b02824b7ddbf555da0f3ec89f73b4f6009cf0bf097fb02"},{"id":"func/buildReplyPayload","name":"buildReplyPayload","line":84,"end_line":91,"hash":"ef9ee77938593f1dbf2d168c20ea4f8dee0300f64f0fdb4f06a4ba647bb782d5"},{"id":"func/hexToBytes","name":"hexToBytes","line":94,"end_line":107,"hash":"29b401020452eabcb1b54634029d8015758b77b536be3d1ed508e9d560ac93b1"}]} +// mutate4javascript-manifest-end diff --git a/src/services/memo-set-name.js b/src/services/memo-set-name.js index 2763c55..3e3bc6c 100644 --- a/src/services/memo-set-name.js +++ b/src/services/memo-set-name.js @@ -62,5 +62,5 @@ MemoSetName.MAX_NAME_BYTES = MAX_NAME_BYTES module.exports = MemoSetName // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T03:32:46.747Z","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"}]} +// {"version":1,"tested_at":"2026-08-26T04:20:10.594Z","module_hash":"98a611f25cf764ac9f182aa9ceb60e0d6ea750d391ba26562c034ee84ef4a9ae","functions":[{"id":"func/MemoSetName.constructor","name":"MemoSetName.constructor","line":35,"end_line":38,"hash":"9407b43605444074011b1da595c9d53356352c72b0d847e00365d79ad705663a"},{"id":"func/MemoSetName.isTooLong","name":"MemoSetName.isTooLong","line":41,"end_line":43,"hash":"e25b4701e1bf64f197980310a29c195a7f3b429ea41be4f01732541c5a9b7cfc"},{"id":"func/MemoSetName.setName","name":"MemoSetName.setName","line":47,"end_line":49,"hash":"9226b63b60a573a9dfb5c7bbb1449d1a138f30b648642d345c5162d012a2cfc0"},{"id":"func/MemoSetName.reflect","name":"MemoSetName.reflect","line":52,"end_line":56,"hash":"3cfed5e8ec7acf592e07e67659b4d8e075d98bbddf79755b8a31b76fd1ae5696"}]} // mutate4javascript-manifest-end diff --git a/src/services/new-post.js b/src/services/new-post.js index b19efb8..b4437a5 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-26T03:34:10.185Z","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-26T04:21:12.008Z","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 3b31bc1..1bc5d73 100644 --- a/src/services/page-controller.js +++ b/src/services/page-controller.js @@ -61,5 +61,5 @@ class PageController { module.exports = PageController // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T03:32:57.173Z","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"}]} +// {"version":1,"tested_at":"2026-08-26T04:20:49.050Z","module_hash":"cfaf6bb208fa501d8fefcaa75c6ad1107128ea4b9baf739b43b4e803f28eef9b","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":44,"hash":"bd2acb67a3a3e33cf8ed9f61886796102c1689486a1ea6757153b2be097eb704"},{"id":"func/PageController._handleSubmitFailure","name":"PageController._handleSubmitFailure","line":49,"end_line":58,"hash":"3d9ad3eb3a25e11b8a1eef164b2757034499b85b439754bb606622f99c72d5f9"}]} // mutate4javascript-manifest-end diff --git a/src/services/profiles.js b/src/services/profiles.js index 8c26c70..2e2e62c 100644 --- a/src/services/profiles.js +++ b/src/services/profiles.js @@ -28,5 +28,5 @@ class Profiles { module.exports = Profiles // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T03:33:56.932Z","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"}]} +// {"version":1,"tested_at":"2026-08-26T04:22:27.216Z","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/reply-thread-page.js b/src/services/reply-thread-page.js index a65f480..5bb214f 100644 --- a/src/services/reply-thread-page.js +++ b/src/services/reply-thread-page.js @@ -57,3 +57,7 @@ class ReplyThreadPage extends PageController { ReplyThreadPage.REPLY_THREAD_PATH = REPLY_THREAD_PATH module.exports = ReplyThreadPage + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-08-26T04:21:44.786Z","module_hash":"6a5e24347eb65fe6f046276efeeab797895317e58ac9803800bf3eef5ddbcf90","functions":[{"id":"func/ReplyThreadPage.constructor","name":"ReplyThreadPage.constructor","line":23,"end_line":30,"hash":"b43af0c6321c8f04814d27e71ad868636921162142917594e8a9235cd7b9c926"},{"id":"func/ReplyThreadPage.setParent","name":"ReplyThreadPage.setParent","line":33,"end_line":36,"hash":"2816f5ec8d3c78101df88e2121f42d89007f3a4153f08990dc9b13c40f75d317"},{"id":"func/ReplyThreadPage.remainingCount","name":"ReplyThreadPage.remainingCount","line":39,"end_line":41,"hash":"2e6661f11eb38ed153282f6ff9d2ba0d7bd6123b4e98a5f9a68bef2da13f301c"},{"id":"func/ReplyThreadPage._setBusy","name":"ReplyThreadPage._setBusy","line":44,"end_line":46,"hash":"eab587885393bc07c55e5c7e73fdd200eac659176c2d8dcf60b8e35773a3a6cf"},{"id":"func/ReplyThreadPage._perform","name":"ReplyThreadPage._perform","line":49,"end_line":54,"hash":"640864465ca82bdc624c93b695f8da359ef19d4001e94e1432328fe3486fd14c"}]} +// mutate4javascript-manifest-end diff --git a/src/services/set-name-page.js b/src/services/set-name-page.js index 1337a61..e1b3c36 100644 --- a/src/services/set-name-page.js +++ b/src/services/set-name-page.js @@ -54,5 +54,5 @@ SetNamePage.ACCOUNT_PATH = ACCOUNT_PATH module.exports = SetNamePage // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T03:33:19.790Z","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"}]} +// {"version":1,"tested_at":"2026-08-26T04:21:29.321Z","module_hash":"5e4ff62bceef737444b487d65f85608ba84ffc0bac69791d1347f7c1dfd8a300","functions":[{"id":"func/SetNamePage.constructor","name":"SetNamePage.constructor","line":24,"end_line":30,"hash":"4ce053830f485ce8a0fba4495cd85fc5e2cac3c9db88b5cbf1e8c66a371a9752"},{"id":"func/SetNamePage.remainingCount","name":"SetNamePage.remainingCount","line":33,"end_line":35,"hash":"47c88116b836dde07c64ae4de65c89a6d702a7889e032f2df163dd84cf48083f"},{"id":"func/SetNamePage._setBusy","name":"SetNamePage._setBusy","line":38,"end_line":40,"hash":"a0947ed899e0def1f6ae243197d409f5603c4820467f0e0fafdcb4deadb3a92b"},{"id":"func/SetNamePage._perform","name":"SetNamePage._perform","line":43,"end_line":48,"hash":"8614f25b06dcb21b81ab94b4a4611c5e3719ebf4ed7ab13e13976ab0d5938b27"}]} // mutate4javascript-manifest-end diff --git a/src/services/utf8.js b/src/services/utf8.js index 52d4531..b70e2c8 100644 --- a/src/services/utf8.js +++ b/src/services/utf8.js @@ -13,3 +13,7 @@ function byteLength (str) { } module.exports = { byteLength } + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-08-26T04:22:38.473Z","module_hash":"7f91541c49f2b6f421e8d4158bfe808b35a5449534cb26c567162fce6fec64bf","functions":[{"id":"func/byteLength","name":"byteLength","line":11,"end_line":13,"hash":"973c9dadcd1d8bbd53587252443880db13c8be3639fa74ce3c69a08ea358c8e2"}]} +// mutate4javascript-manifest-end diff --git a/test/unit/memo-reply.test.js b/test/unit/memo-reply.test.js index 319e7fc..0a1c8ea 100644 --- a/test/unit/memo-reply.test.js +++ b/test/unit/memo-reply.test.js @@ -101,3 +101,15 @@ test('replying with an invalid parent txid reports a clear error', async () => { ) assert.equal(wallet.broadcasts.length, 0) }) + +test('replying with a wrong-length but valid-hex parent txid is rejected', async () => { + const wallet = fakeWallet() + const memoReply = new MemoReply({ wallet }) + + // 10 hex characters are valid hex but not the required 64-character txid. + await assert.rejects( + memoReply.reply('hello memo', 'a'.repeat(10)), + (err) => /64-character hex/i.test(err.message) + ) + assert.equal(wallet.broadcasts.length, 0) +}) diff --git a/test/unit/reply-thread-page.test.js b/test/unit/reply-thread-page.test.js index 2e1f828..1b1263c 100644 --- a/test/unit/reply-thread-page.test.js +++ b/test/unit/reply-thread-page.test.js @@ -16,6 +16,7 @@ const assert = require('node:assert/strict') const MemoReply = require('../../src/services/memo-reply') const ReplyThreadPage = require('../../src/services/reply-thread-page') +const { fakeWallet } = require('../helpers/fake-wallet') const { registerPageControllerTests, registerPageSubmitTests } = require('./page-controller-helpers') const { buildPage } = require('./page-build-helpers') @@ -111,3 +112,22 @@ test('replying to a nested reply uses the selected parent txid', async () => { assert.equal(store.replies[0].parentTxid, nestedTxid) assert.equal(store.replies[0].text, 'hello nested') }) + +test('the reply page navigates to a configured success path on success', async () => { + const wallet = fakeWallet() + const thread = fakeThread() + const memoReply = new MemoReply({ wallet, thread }) + const navigations = [] + const page = new ReplyThreadPage({ + memoReply, + navigate: (p) => navigations.push(p), + successPath: '/custom-path', + parentTxid: PARENT_TXID + }) + page.setInput('hello memo') + + const result = await page.submit() + + assert.equal(result.ok, true) + assert.deepEqual(navigations, ['/custom-path']) +})