Reader Mastodon: follow / unfollow button on profile pages#110531
Open
Reader Mastodon: follow / unfollow button on profile pages#110531
Conversation
The Mastodon profile page needs a 'Requested' affordance for the locked-account follow-pending state. ATmosphere call sites are unaffected (default isRequested=false). Click invokes onUnfollow — Mastodon's unfollow endpoint cancels pending requests, so consumers need only one handler for both Following and Requested.
Adds MastodonViewerState, MastodonCreateFollowParams, MastodonDeleteFollowParams, MastodonFollowResponse, and extends MastodonAuthorProfile with optional viewer and is_self fields. Fields are optional during the backend rollout window so this commit can land independently of the wpcom backend changes.
…ProfileViewer Match the existing per-feature naming convention in this file (MastodonFeedItemViewer for the per-status viewer block). Pure rename — no functional changes.
Adds createMastodonFollow (POST /follows) and deleteMastodonFollow
(DELETE /follows/{account_id}) targeting the new wpcom backend
endpoints. Both return a uniform MastodonFollowResponse with the
post-action viewer block, so optimistic patches and server-state
commits use the same shape.
Mirrors followAtmosphereActorMutation / unfollowAtmosphereActorMutation shape: optimistic patch on the scoped author-profile cache entry, rollback on error, server-state commit in onSuccess (so locked-account 'requested: true' lands correctly even though we optimistically set 'following: true'). Factories accept the consumer's QueryClient per the Calypso/Dashboard split documented in client/reader/AGENTS.md.
Renders FollowButton in the SocialProfileCard headerActions slot when the backend has projected the viewer block (forwards-compat gate) and the resolved actor is not the viewer's own profile. Wires the two mutation hooks at component-level so isPending is reactive, emits Tracks events for click + error, and forwards locked-account 'requested' state into the new FollowButton prop.
Mirrors atmosphere parity — when a follow or unfollow mutation fails (rate-limited, auth-expired, upstream unavailable), dispatch errorNotice in addition to the existing Tracks event so users see why the button reverted. Clears the notice on subsequent success. Adds a regression test for the error path.
The scoped author-profile query normalizes the actor input (trim, strip leading @, lowercase) before building its cache key. The new follow / unfollow mutation factories built their key directly from the raw vars.actor, so webfinger or mixed-case actors keyed to a different cache entry than the query — leaving the optimistic patch and rollback as silent no-ops. Numeric ids worked because normalization is a no-op for digits. Adds a regression test that drives the mutation with '@alice@MASTODON.social' and asserts the patch lands on the entry seeded under the normalized 'alice@mastodon.social' key.
Mirrors the per-protocol adapter pattern documented in
client/reader/AGENTS.md and used by use-mastodon-{like,repost}-action.ts:
the pipeline-level error log lives in the client adapter, not in
packages/api-queries (which can't import calypso/lib/logstash).
Tests mock calypso/lib/logstash so the new call doesn't trigger an
unmocked-request alarm in nock.
- Validate the MastodonFollowResponse shape at the fetcher boundary so a malformed payload (missing viewer) fails loudly instead of writing viewer: undefined into the cache during the optimistic-update commit. - In follow/unfollow onSuccess, invalidate the profile query when the cache entry was evicted between onMutate and onSuccess so the authoritative server viewer (which carries requested: true for locked accounts) is refetched instead of silently dropped. - In onError, invalidate when no previous snapshot exists so the optimistic patch can't outlive the failure as a stale value. - Tighten viewer? / requested JSDoc per comment review (drop literal default object; drop the federation-queue speculation). - Add tests for the malformed-payload guard, the not_found-specific follow/unfollow copy, removeNotice on retry success, and a logToLogstash assertion.
Contributor
Jetpack Cloud Live (direct link)
Automattic for Agencies Live (direct link)
Dashboard Live (dotcom) (direct link)
|
# Conflicts: # packages/api-queries/src/reader-mastodon.ts
Contributor
|
This PR modifies the release build for the following Calypso Apps: For info about this notification, see here: PCYsg-OT6-p2
To test WordPress.com changes, run |
# Conflicts: # packages/api-core/src/reader-mastodon/types.ts # packages/api-queries/src/__tests__/reader-mastodon.test.tsx
Tighten `assertMastodonFollowResponse` to verify the `viewer` block carries the three required booleans (not just that `viewer` is an object), and throw with a distinct message so a backend-shape regression is grep-able in Logstash / Sentry rather than indistinguishable from a real 400. Add the missing optimistic-mutation test cases: - follow: rollback path when no previous snapshot exists (`onError` falls back to `invalidateQueries`) - follow: cache eviction between `onMutate` and `onSuccess` (`onSuccess` falls back to `invalidateQueries`) - unfollow: webfinger actor normalization parity with follow - FollowButton: `isRequested` precedence over `isFollowedBy`
… awaits Three fixes from code review: - Optimistically write `requested: true` (not `following: true`) when the target is locked. Without this the button paints "Following / Unfollow" for the round-trip and snaps to "Requested" on commit — a UX flip-flop that also misleads AT users mid-flight. Threads `locked` through `FollowMastodonActorVars` with a fallback to the cached profile's `locked` field for backwards-compat. - Return the `invalidateQueries` promise from both factories' `onError` and `onSuccess` fallbacks so TanStack waits for the refetch before transitioning the mutation out of `pending`. - Capture `accountId` at click time in the panel handlers so error analytics survive a profile refetch racing with the in-flight mutation. `showFollowError` now takes the captured id rather than re-reading `profile.data?.id`. Adds two regression tests covering the locked-account optimistic patch both with explicit `vars.locked` and via the cached `old.locked` fallback.
…rrors
Adversarial-pass follow-ups:
- Panel-level unfollow click test: DELETE wire call, the
`calypso_reader_mastodon_profile_unfollow_clicked` Tracks payload
(`was_requested` included), `removeNotice` clearing, the Cancel-request
variant for locked-account pending requests, and a 502-unfollow
failure asserting `action: 'unfollow'` in Tracks + Logstash plus the
`not_found` unfollow-specific copy on a 404.
- Panel-level Tracks payload assertions: `toContainEqual` on the full
`follow_clicked` props (including `was_locked`), with a separate test
exercising `was_locked: true` end-to-end.
- FollowButton: regression test for `disabled={ isPending }` on the new
Requested branch.
- api-queries: `vars.locked: false` wins over `old.locked: true`
precedence test — covers the third arm of the
`vars.locked ?? previous?.locked ?? false` fallback chain.
- Fetchers: parameterized malformed-payload coverage (null, missing
field, non-boolean values) and parameterized error-kind coverage
(`not_found`, `rate_limited`, `upstream_unavailable`, `bad_request`)
for both create and delete fetchers.
- `assertMastodonFollowResponse` now throws a real `Error` (with the
`code` property) instead of a bare object, so dev-tools rejection
logs surface a usable stack trace. Classification through
`classifyMastodonError` is unchanged.
- Tightened the `if ( ! updated )` comment in the optimistic-update
fallbacks to acknowledge both eviction and never-populated cases.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes CM-681
Backend PR: 215533-ghe-Automattic/wpcom
Proposed Changes
FollowButton(client/reader/social/follow-button.tsx) with an optionalisRequestedprop for Mastodon's locked-account pending-follow state — ATmosphere call sites are unaffected (defaultfalse).MastodonAuthorProfileViewer,MastodonFollowResponse, follow-params), fetchers (createMastodonFollow,deleteMastodonFollow), and TanStack Query mutation factories (followMastodonActorMutation,unfollowMastodonActorMutation) with optimistic-update / rollback / on-success-commit semantics, plus thecancelQueriestry/catch hardening fromclient/reader/AGENTS.md.MastodonAuthorProfilePanelviaSocialProfileCard headerActions, gated onviewer && ! is_selffor forwards-compat with backends that haven't deployed the viewer projection yet, with click + error Tracks events and anerrorNoticetoast on mutation failure (cleared on subsequent success).Why are these changes being made?
Requestedis meaningful in Mastodon (no AT Proto analogue) and a real fraction of accounts opt into it, so the locked-account state is modeled explicitly rather than collapsed into "Following".viewer?/is_self?optional fields) lets this frontend ship independently of the wpcom backend changes, with no user-visible behaviour change until the backend deploys.Testing Instructions
viewerblock onGET /reader/mastodon/connections/{id}/profile/{actor}, plusPOST /followsandDELETE /follows/{account_id}endpoints, plusread:follows+write:followsKeyring scopes). Without the backend, the forwards-compat gate keeps the button hidden — verify nothing regresses on existing Mastodon profile pages./reader/mastodon/{connectionId}/profile/{actor}for an account you don't follow → the Follow button renders. Click → button switches to "Following" optimistically, network call confirms.auth-statusflow once the backend ships theread:follows+write:followsscope additions.is_self: true).yarn test-client client/reader/social client/reader/mastodon/test/author-profile-panel.test.tsxandyarn test-packages packages/api-core/src/reader-mastodon packages/api-queries/src/__tests__/reader-mastodon.test.tsx.Pre-merge Checklist