Mobile Feed
Location: apps/mobile/src/app/(main)/feed/
The feed is the main discovery surface. It has 5 tabs and a keyword filter system. Feed state is persisted in MMKV so tabs retain their scroll position and last loaded content between sessions.
Files
| File | Description |
|---|---|
index.tsx | Feed root — tab bar + tab switcher |
filter.tsx | Filter presets modal (format, depth, interest) |
keywords.tsx | Keyword mute management UI |
5 Feed Tabs
| Tab | Key | API Endpoint | Description |
|---|---|---|---|
| For You | foryou | GET /posts/feed | Personalised ranked feed based on user interests |
| Following | following | GET /posts/feed?following=true | Posts from users you follow |
| Trending | trending | GET /posts/trending | High-engagement posts in last 24h |
| Circles | circles | GET /posts/feed?visibility=circles | Posts visible to your circles |
| Discover | discover | GET /discovery/candidates + GET /posts/feed?interest=... | Discovery + interest-based posts |
Feed Query Parameters
| Param | Type | Notes |
|---|---|---|
format | String? | Filter by post format |
depth | Int? | Filter by content depth 0..5 |
interestSlug | String? | Filter by primary interest |
journalism | Bool? | Show only journalism-tagged posts |
following | Bool? | Restrict to followed users |
cursor | String? | Pagination (postId cursor) |
limit | Int? | Page size (default 20) |
Filter Presets (filter.tsx)
A bottom sheet modal with:
- Format chips: All, Note, Essay, Photo, Video, Quote, Thread
- Depth slider: 0 (any) to 5 (thesis-level)
- Interest picker: Select from user's followed interests
- Journalism toggle: Show only
isJournalism = trueposts
Presets are persisted in MMKV under feed_filter_preset.
MMKV Persistence
| MMKV Key | Value | Description |
|---|---|---|
feed_active_tab | String | Last active tab key |
feed_filter_preset | JSON | Saved filter preset |
feed_scroll_<tab> | Int | Scroll position per tab |
Unified Cards
Text posts render PostCard; video and audio items render MediaCard, which now shares PostCard's header (avatar · author · interest · time · reputation) and the same Stat footer bar (react / reply / repost / bookmark) — Stat is exported from post-card as the single source of truth. This makes media first-class siblings of text posts across every screen.
Each card shows:
- Author avatar + name + persona indicator
- Interest tag with accent color
- Post title + excerpt (or media player for video/audio)
- Format badge (Note, Essay, Photo, etc.)
- Depth indicator (0..5 dots)
- Reaction count + reply count + bookmark count
- Author reputation in primary interest (level badge)
- Boost indicator (⚡) if
boostScore > 0 - Journalism badge (📰) if
isJournalism
Real video engagement. Video cards in the stream carry live react / comment / repost counts plus the viewer's reacted/reposted state from the discovery stream serializer, with optimistic toggles — not a bare caption.
Loading & Empty States
- Skeletons render while a tab's first page loads (shimmer placeholders matching card layout).
- A consistent EmptyState block (icon medallion + serif line + hint + CTA) replaces ad-hoc "No posts yet" snippets — shared from
@regulus/ui.
Active-Signal Filters
When any filter (format / depth / interest / journalism) is applied, the feed surfaces an active-signal indicator on the filter control so a narrowed feed is never mistaken for an empty one. Tapping it reopens the filter sheet; clearing restores the full feed.
Realtime Updates
When Centrifugo is connected, a feed:public channel pushes new post IDs. The feed shows a "N new posts" pill at the top. Tapping it scrolls to top and loads the new batch.