Skip to content

fix(people): "Mutual connections" rendered nothing — /mutual returns counts, not users - #162

Merged
Adron merged 2 commits into
mainfrom
issue-160-mutual-counts
Sep 24, 2026
Merged

Adron merged 2 commits into
mainfrom
issue-160-mutual-counts

Conversation

@Adron

@Adron Adron commented Sep 16, 2026

Copy link
Copy Markdown
Member

No description provided.

> ⚠️ **Stacked on #141** (`issue-94-handle-lookup`), which also touches `PeopleView.xaml`. Merge that first.
> Note #138 (`issue-95-follow-pagination`) independently edits `PeopleView.xaml` just below this block, so whichever of the two merges second needs a small reconcile — different hunks, so it should be trivial.

## The feature never worked

`the-gaps.md` records "Mutual connections on a profile — chips that open that user" as shipped in session 6. **It has never displayed anything.** Found by the contract-test suite (#126 / PR #159), confirmed independently.

`GET /api/follow/{userId}/mutual` returns **counts**:

```
GET /api/follow/c65092fa-…/mutual                        -> {"mutualFollowers":0,"mutualFollowing":0}
GET /api/follow/15e3d575-…/mutual                        -> {"mutualFollowers":1,"mutualFollowing":1}
GET /api/follow/15e3d575-…/mutual?otherUserId=c65092fa-… -> {"mutualFollowers":1,"mutualFollowing":1}
```

The client asked for an array under a `mutual` key, which doesn't exist:

```
old path: TryGetProperty("mutual") = False  -> empty list, no error
```

`GetUserArrayAsync` tolerates an absent array by design, so this became `HasMutuals == false`, the section collapsed, and **nothing reported a problem**.

## The API can't support the built feature

Both documented parameter forms return the same counts object. **There is no endpoint that lists mutual users**, so clickable chips are not buildable. Rather than leave a signature the API can't honour, `GetMutualAsync` → **`GetMutualCountsAsync`** returning a real `MutualFollowCounts`, and the profile shows the counts. The "no user-list endpoint exists" finding is recorded on both the model and the method so the chips don't get reinstated.

## Audited the sibling keys while here

This is the second bug in two days where a tolerant "absent array → empty list" read hid an API mismatch (the other, #144, at least threw). So I checked every remaining `GetUserArrayAsync` key against live payloads:

| key | live | verdict |
|---|---|---|
| `requests` | `{"requests":[]}` | ✅ correct |
| `followers` | `{"followers":[…],"pagination":{…}}` | ✅ correct (#138) |
| `following` | `{"following":[…],"pagination":{…}}` | ✅ correct (#138) |
| `mutual` | `{"mutualFollowers":…}` | ❌ **this bug** |

`mutual` was the only wrong one. The helper now carries a comment warning that a wrong key here fails as *"feature renders nothing"* rather than as an error, and telling the next person to confirm the array exists in a live payload.

## Verification

```
real, non-zero   followers=1 following=1 hasAny=True  summary=1 mutual following · 1 mutual followers
real, zero       followers=0 following=0 hasAny=False summary=(null)
ALL CHECKS PASSED
```

Zero counts hide the block rather than rendering "0 mutual following · 0 mutual followers".

`dotnet build` green in Debug and Release.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
PRBODY
fix(people): "Mutual connections" rendered nothing — /mutual returns counts

the-gaps.md records mutual-connection chips as shipped in session 6. They have
never displayed anything. Found by the contract-test suite (#126), confirmed
independently.

GET /api/follow/{userId}/mutual returns counts, not users:

  GET /api/follow/15e3d575-…/mutual  -> {"mutualFollowers":1,"mutualFollowing":1}

but the client asked GetUserArrayAsync for an array under a `mutual` key:

  TryGetProperty("mutual") = False  -> empty list, no error

That helper tolerates an absent array by design, so the mismatch became
HasMutuals == false, the section collapsed, and nothing reported a problem.

The API cannot support the feature as built: both documented parameter forms
(bare, and with otherUserId) return the same counts object, and there is no
endpoint that lists mutual users. So rather than keep a
Task<List<FollowUser>> signature the API can't honour, GetMutualAsync becomes
GetMutualCountsAsync returning a typed MutualFollowCounts, and the profile shows
counts instead of chips. The "no user-list endpoint exists" finding is recorded
on both the model and the method so the chips aren't reinstated.

This is the second bug in two days where a tolerant "absent array -> empty list"
read hid an API mismatch (#144 was the other, and that one at least threw), so I
audited every remaining GetUserArrayAsync key against live payloads:

  requests  -> {"requests":[]}                          correct
  followers -> {"followers":[…],"pagination":{…}}        correct
  following -> {"following":[…],"pagination":{…}}        correct
  mutual    -> {"mutualFollowers":…}                     THIS BUG

`mutual` was the only wrong one. The helper now warns that a wrong key here
fails as "feature renders nothing" rather than as an error.

Verified by deserializing both real shapes: non-zero surfaces a summary, zero
hides the block rather than rendering "0 mutual following · 0 mutual followers".
Build green in Debug and Release.

Closes #160

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Adron
Adron changed the base branch from issue-94-handle-lookup to main September 24, 2026 06:13
Conflict in InterlinedApiClient.Follow.cs between this branch's
GetMutualCountsAsync and #138's GetFollowersPageAsync/GetFollowingPageAsync.
Both additive — kept both.

Also removed the old GetMutualAsync, which survived the merge. That is the
method this PR exists to replace: it asks GetUserArrayAsync for a 'mutual' key
the endpoint never returns, so it silently yields an empty list and the profile's
mutual-connections section renders nothing. Leaving it in place would invite
reuse of a known-broken call. Verified no caller outside this file referenced it.

Debug and Release both build clean. The contract suite's /mutual probe is
unaffected — it asserts the raw payload shape rather than going through the
removed method.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Adron
Adron merged commit 48056ca into main Sep 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant