Skip to content

feat(messages): feed view preferences — All / My / Following / Followers (#19) - #103

Merged
Adron merged 1 commit into
parity/queuefrom
issue/19-feed-view-preferences
Sep 16, 2026
Merged

Adron merged 1 commit into
parity/queuefrom
issue/19-feed-view-preferences

Conversation

@Adron

@Adron Adron commented Sep 16, 2026

Copy link
Copy Markdown
Member

Closes #19. Part of epic #17. P0.

The mechanism is confirmed from the docs, not assumed

The issue asked to "confirm the exact query contract live for the following/followers variants".
Done — and the answer is that there is no such query parameter:

  • helpapi/messages.txt: GET /api/messages takes limit, offset, onlyMine, tag. No
    following/followers parameter exists. The same page says a signed-in token personalises the
    feed
    , and for search: "scoped to your feed visibility (honors your viewingPreference)".
    That is the documentation stating the server applies the stored preference when scoping the feed.
  • helpapi/users-and-profile.txt: viewingPreference is a PATCH /api/user/update field, and the
    response is "the updated user object (same shape as GET /api/user)".
  • helpapi/following.txt: no feed mechanism at all — follow/unfollow/counts/requests only.

So: persist the preference, then reload. onlyMine=true remains the per-request mechanism for
My Messages; the other three are left to the server-side preference.

One doc/live discrepancy handled defensively: the help page's GET /api/user example shows a bare
top-level user object while live returns { "user": … }, so the PATCH response DTO tolerates both
and falls back to the requested value when the field is not echoed.

Implementation

  • Four FilterChips under the top bar, visible even when the feed is subscription-gated, so a
    user can switch away from a view they cannot see.
  • Selecting a chip PATCHes the account first, then refreshes. The selection applies
    optimistically but rolls back on failure and surfaces the mapped error, with no refresh — the
    switcher can never show a view that was never saved. The server's echoed value wins over the
    tapped one. The row disables while a save is in flight; re-tapping the current selection is a
    no-op.
  • Init reads GET /api/user before the first refresh, so the feed's very first request already
    runs under the saved view rather than flashing All Messages.
  • The preference rides along to page 2+ so a page chain stays consistent. Cursor reset comes free
    from Feed: switch to cursor (keyset) pagination #22's refresh() and is pinned by a test proving the stale cursor is discarded.

Module-dependency decision

No feature→feature dependency was created. ViewingPreference went into :core:model;
:core:network gained a partial PATCH api/user/update on the existing InterlinedListApi plus a
small ViewingPreferenceStore that :feature:messages consumes.

Flagging explicitly: :feature:profile's SettingsRepository (#33) and this
ViewingPreferenceStore are now two independent writers of the same field, each with its own
in-flight state and neither aware of the other. In practice, changing the view in Settings will not
update an already-composed feed switcher until the feed is re-entered. They should be consolidated
behind one owner — most likely a shared account-preferences module — once both surfaces settle.
Filed as a follow-up.

Verification

TDD: tests written first and confirmed red, then implemented to green.
./gradlew :app:assembleDebug testDebugUnitTest (plus a clean run) → BUILD SUCCESSFUL,
842 tests, 0 failures, 0 skipped.

New coverage: 8 in ViewingPreferenceStoreTest (every wire value both directions, the partial body
{"viewingPreference":"followers_only"}, unknown/absent fallback, the server's real 400 message);
7 in MessagesFeedViewPreferenceTest (each preference's exact query, preference + cursor on page 2,
read from GET /api/user, PATCH on save); 8 added to MessagesFeedViewModelTest (initial read,
unreadable-preference fallback, PATCH + refresh, cursor restart, rollback on failure, server value
wins, no-op re-tap, refresh keeps selection). 4 Compose tests compile, not executed (no emulator).

Known rough edge

After switching, previously cached rows stay on screen until the refresh lands (with the refresh
indicator showing), so My Messages can briefly show other authors' messages. Clearing the cache
eagerly would fix it but cause a blank flash on every switch — left as-is rather than adding the
complexity.

The feed ignored `viewingPreference` entirely and always asked for the
whole feed. It now opens on whatever the account has saved and offers the
same four choices as Settings -> View Preferences on the web.

`GET /api/messages` takes only `limit`, `offset`, `onlyMine` and `tag`
(/help/api/messages) — there is no following/followers parameter, and
search is documented as "scoped to your feed visibility (honors your
viewingPreference)". So the server scopes the feed from the saved
preference: switching PATCHes the account first and only then reloads the
feed from the top, with `onlyMine=true` remaining the request-level
mechanism for My Messages.

The switcher updates optimistically but rolls back and surfaces the error
if the PATCH fails, so it can never show a view the account never stored.
Switching resets the keyset cursor, so paging restarts under the new view.

`viewingPreference` is shared through `:core:model` plus a small
`ViewingPreferenceStore` in `:core:network` rather than by depending on
`:feature:profile` — no feature module depends on another here. That
leaves `:feature:profile`'s `SettingsRepository` and this store as two
paths to the same field; they should be consolidated behind one owner.

Closes #19
@Adron
Adron merged commit 58c2c05 into parity/queue Sep 16, 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