Skip to content

feat: viewer_fid viewer_context and thread_hash on v2 casts - #43

Merged
CassOnMars merged 1 commit into
farcasterorg:mainfrom
backmeupplz:viewer-context-thread-hash
Oct 30, 2026
Merged

CassOnMars merged 1 commit into
farcasterorg:mainfrom
backmeupplz:viewer-context-thread-hash

Conversation

@backmeupplz

@backmeupplz backmeupplz commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Two fields Neynar returns on cast objects that the v2 API leaves empty today. A client building reply threads needs both: whether the viewer follows each author and already liked or recast each cast, and which conversation a reply belongs to. Without them it makes extra requests for every cast on the page.

viewer_fid

viewer_fid was already parsed in dispatch but never used. It now works on:

  • /v2/farcaster/feed/following, /v2/farcaster/feed (following and trending), /v2/farcaster/feed/trending and its aliases for_you and topic
  • /v2/farcaster/feed/channels, /v2/farcaster/feed/parent_urls
  • /v2/farcaster/feed/user/casts
  • /v2/farcaster/cast, /v2/farcaster/cast/bulk, /v2/farcaster/casts
  • /v2/farcaster/cast/conversation (every cast in the tree)

With it, each cast gets viewer_context: {"liked": bool, "recasted": bool}. The author and each mentioned profile get viewer_context: {"following": bool, "followed_by": bool, "blocking": bool, "blocked_by": bool}. These are the CastViewerContext and ViewerContext types already in types.rs. blocking/blocked_by come from block links, the same links /v2/farcaster/block/list reads.

Each value is one point lookup (ReactionStore::get_reaction_add, LinkStore::get_link_add), behind two new HubQueryHandler methods, has_reaction and has_link. That is 2 lookups per cast and 4 per user. Without viewer_fid nothing is looked up and viewer_context is left out, as before.

thread_hash

thread_hash is always on, set in message_to_cast, so every endpoint that returns casts has it:

  • a top-level cast (no parent, or a parent URL) has its own hash, with no lookup;
  • a reply has the hash of the top-most ancestor, found by following parent_cast_id through get_cast_by_hash with the parent's FID. That is one direct cast store read per level, so a depth-d reply costs d reads;
  • if an ancestor is missing, or the chain is longer than 64 hops (MAX_THREAD_HOPS), the field is left out, as it is today.

There is no cache. Ancestors don't change, so one could be added later if conversation endpoints show the cost.

Tests

  • test_thread_hash_walks_to_the_root: a root, a reply and a reply to the reply all get the root's hash. A missing ancestor gives none. A self-parented cast stops at the hop limit.
  • test_viewer_context_only_with_viewer_fid: /cast with viewer_fid returns the cast's and users' viewer_context from the viewer's likes, follows and blocks. Without viewer_fid there is no viewer_context on the cast, the author or mentioned profiles.
  • test_has_link_and_has_reaction (server_tests): the real store lookups across two shards. A follow and a like are found. A reverse follow, a block and a removed recast are not.

cargo test --lib: everything passes except 4 storage tests (test_storage_slot_from_rent_event, test_storage_slot_with_mix_of_units, test_user_with_low_total_storage_cannot_lend, test_commit_username_proof_messages). Those fail the same way on current main. cargo fmt --all --check is clean, and clippy reports nothing in the new code. I haven't run it on a synced node.

With `viewer_fid`, the cast endpoints fill Neynar's `viewer_context` on
each cast (liked, recasted) and on its author and mentioned profiles
(following, followed_by, blocking, blocked_by), using point lookups in
the reaction and link stores. Without it the response has no
viewer_context, as before.

Every cast now carries `thread_hash`: its own hash for a top-level cast,
else the root found by walking parent cast ids in the cast store (at
most 64 hops), omitted when an ancestor is missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
backmeupplz added a commit to backmeupplz/quilcast that referenced this pull request Oct 22, 2026
…f reach

- A thread that starts with the gap gets a ⋯ bubble in the avatar column
  and room above, so it reads as the top of that thread instead of the end
  of the post above; a gap in the middle has more room too.
- A reply to a parent already shown above is left out, so one conversation
  doesn't show twice in a row.
- Ready for Hypersnap PR #43 (farcasterorg/hypersnap#43): the parent is
  fetched with viewer_fid, its author's viewer_context.following skips the
  follow lookup, and thread_hash fetches the root directly; until haatz
  runs it, the old lookups are used (now at most 2 steps above the parent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CassOnMars
CassOnMars merged commit 5c0c791 into farcasterorg:main Oct 30, 2026
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.

3 participants