7.2 KiB
Feed Tabs — Architect Review
By architect.
Task and commits reviewed
- Task:
feed-tabs(refactorer handoffmerge_and_process refactorer 4b8b4ed499, delivered as a one-itemBATCH). - Merged
swarmforge-refactorer(fast-forward fromde3f1c8). Commits:f3a41deSpecify merged posts feed with Recent/Following tabs (specifier)d1372daMerge Following feed into the posts page as Recent/Following tabs (coder)4b8b4edRefactor feed tabs: simplify mode loading, add feed-tabs properties (refactorer)
- Architect review commit:
e4bda31256(view/controller encapsulation + test consolidation + tool-written mutation and acceptance manifests). - Verification record
git_sha:e4bda31256(client). - Records/summary commit: this commit.
The feature merges the old /posts/following route into /posts/recent as a
row of two mode buttons. On first open the page asks
GET /follow/following/:addr; if the viewer follows at least one account it
selects Following, otherwise Recent. Switching tabs resets to the first page.
The /posts/following route and navbar item are removed. The new
FeedTabsPage service composes the existing RecentFeedPage and
FollowingFeedPage controllers behind injected memoDb/wallet, and the React
shell (components/app-body/posts/index.js) renders it. Client-only read
feature; no DB or indexer change.
Architectural review
- UI/Core separation (good).
FeedTabsPageis pure coordinator logic insrc/services/withmemoDbandwalletinjected; it opens no network, DOM, or wallet IO itself. The React shell constructsMemoDband the controller and maps the snapshot into view state. Acceptance drives the controller directly through the fake memo-db, so core behavior is tested with no UI or IO. - Dependency rule (good). The view depends on the service; the service
depends on the two page controllers and on the
MemoDbadapter through its constructor. No high-level module reaches around the injected adapter, and the adapter does not reach back into the controller. - Information hiding (one fix). The controller exposes a tested
getState()snapshot, but the view was still readingpage.posts,page.pagination,page.mode,page.emptyBecauseNoFollows, andpage.offsetdirectly. RoutedshowPagethroughgetState()so the controller is the single source of feed state (see Fixes). - Local code quality (one fix). The two default-mode unit tests were structurally identical and DRY reported them (score 1.00). Consolidated into a table-driven pair with a shared assertion helper. Added initial-state tests to kill mutation survivors the refactorer left (see Fixes).
- Delegation, not duplication (accepted).
FeedTabsPageowns paging (nextPage/previousPage/canLoadMore) and delegates post loading to the two sub-pages. It computesemptyBecauseNoFollowsitself fromhasFollows, which is the correct merged-page semantics (the message shows only when the viewer follows no one).FollowingFeedPagestill exposes its ownemptyBecauseNoFollows, but that is the older/posts/followingcontract and is now unused by the tabs flow; it is retained only becausespecs/following-feed.featurestill specifies it. Not changed here (specifier owns retiring that feature); noting it as the one remaining conceptual overlap. - Module boundaries (good). The deleted
following-feedcomponent left no dangling imports;FollowingFeedPageremains as the internal delegate used byFeedTabsPage, andmemo-db.jsgained a thingetFollowingadapter method next to the existinggetFollowingFeed.
Fixes applied
- View consumes the controller snapshot.
posts/index.jsshowPagenow readspage.getState()instead of five internal fields, keeping the controller's representation hidden behind its public accessor. The injectedmemoDb(needed for author-profile lookup) is still passed explicitly. - Killed four mutation survivors with initial-state tests. The refactorer's
first mutation run left
constructor.offset = 0,constructor.hasFollows = false,constructor.emptyBecauseNoFollows = false, and thecanLoadMore() ?? falsedefault surviving because no test observed an unloaded controller. Addedconstructor starts in a neutral, unloaded stateandcanLoadMore is false before any page has been loaded. Final mutation: 22 killed, 0 survived, 0 uncovered. - Removed a local test duplicate. Extracted
assertOnlySelectedFeedLoadedand folded the two default-mode tests into one table so DRY reports no candidate.
No production behavior changes were needed. The controller structure, injected dependencies, and boundaries were accepted.
Verification results
Record (pinned to the review commit e4bda31256):
| File | Component |
|---|---|
docs/reviews/feed-tabs-verification.json |
psf-memo-client |
- Language mutation (
mutate-file.sh src/services/feed-tabs-page.js --max-workers 8, run twice: the differential pass selected 0 changed sites, so the wrapper auto-reran with--mutate-all): 22 killed, 0 survived, 0 uncovered (22 total/covered, 0 uncovered). - DRY (
dry4javascript, scoped to the changed production files and the changed unit/property tests): no duplicate candidates found. The first run flagged one local duplicate in the unit test file (two default-mode tests, score 1.00); removing it cleared the report. A broad whole-client DRY run was not needed and, per standing precedent, its pre-existing step-handler and test-setup boilerplate is not part of this task. - Cyclomatic complexity / CRAP (
crap4javascript src/services/feed-tabs-page.js): maximum CRAP 5.0 at 100% coverage (openCC 5);_isEmptyFollowing/_loadMode/_normalizeTabCC 3; every other function CC ≤ 2. All functions are below the 6.0 target. - Soft Gherkin acceptance mutation (
gherkin-mutator --level soft --workers 8),psf-memo-client/specs/feed-tabs.feature: 42 total, 0 killed, 42 survived, 0 errors. Every survivor is an intrinsic equivalent: a single-character case/character substitution of an example value (followed_text,followed_txid,followee,other,other_text,other_txid) used consistently on both the setup and assertion sides of its scenario, so the scenario still passes. This is the documented behavior for this read-only feature; no implementation gap is implied.gherkin-mutatorwrote anacceptance-mutation-manifestwith"scenarios":[](each scenario has an equivalent survivor, so none is recorded) and no# mutation-stamp; committed as tool-written.
Suite status
verify.sh client result: pass at git_sha e4bda31256:
- unit: 506 pass / 0 fail
- property: 121 pass / 0 fail
- acceptance: all 37 generated suites passed
- lint: ok
- build: ok
Handoffs
- End-of-chain
git_handofftospecifier(priority 50) to mergeswarmforge-architectintomaster, using this records/summary commit. git_handofftocoderandrefactorer(priority 00) with the review commite4bda31256for follow-up review: the view now consumesgetState(), and the default-mode tests were consolidated with added initial-state coverage; the rest is tool-written mutation and acceptance manifests.