Feeds and social kits
One engine, many kits. A feed is a blueprint setting (blueprint.feeds, convex/kits/social/lib.ts): a named place where people publish posts with words, photos and videos, and reply, react, repost, quote, save and follow. Every behaviour is a setting on the feed, so the same code makes:
| Kit | What it is | Feed settings that make it |
|---|---|---|
feed Posts and threads | Your own X or Threads | short posts (500), up to 4 media, nested replies, reposts, quotes |
photos Photo feed | Your own Instagram | media required, up to 10, captions (2200), flat comments, profile grid, explore |
gallery Gallery | Photos and videos in a grid, #tags as albums | media required, owner posts, others like and comment |
stories Stories | Posts that disappear after a day | expiresHours: 24, one photo or video, several reactions |
forum Forum | Topics with long threaded discussions | titles, 10000 characters, sorted by latest reply |
reviews Reviews | Star ratings with words and photos | rating, flat replies |
guestbook Guestbook | Short notes visitors leave | no media, 300 characters |
outside-feeds Outside feeds | Pull X, Bluesky, Mastodon, Instagram, Threads, YouTube, RSS | sources |
profiles Profile and followers | Profile, who to follow, activity | (no feed of its own) |
The AI or a person can change any of it: rename a feed, allow videos, switch replies from threads to comments, add ❤️😂🔥 reactions, require approval, make posts disappear, add a source. Every word is a setting too: blueprint.words["feed.<key>"] (keys in FEED_WORDS).
Feed settings #
id, name, postRoles, replyRoles, replies (off · flat · nested), media (off · optional · required), mediaKinds, maxMedia, maxLength, titles, rating, reactions (emoji, first is the main "like"), reposts, quotes, expiresHours, approval, sources.
Blocks #
feed.timeline (layouts list · cards · grid · stories; sort new · active · top; scope all · following · person · mine; tag; mediaOnly), feed.composer, feed.profile, feed.people, feed.activity, feed.search, feed.saved, feed.sources. Screens: /post/<id> (a post and its thread), /u/<handle> (a profile), /tag/<tag>.
Data #
Tables in convex/kits/social/tables.ts: feedPosts (replies keep root, so a thread loads with one index; counts are kept on the post), feedTags, feedReactions, feedSaves, socialProfiles (@handle, bio, photo, link, follower counts), follows, feedActivity, feedSourceRuns. Photos and videos are files in the space's media library (convex/media.ts, folder posts); deleting a post deletes the files uploaded for it.
Who may do what uses the space's roles: read with view; post and reply with post (and the feed's postRoles / replyRoles); react with post or play; edit your own posts; delete anyone's with edit; approve and hide with manage. Follows and profiles belong to the person, across spaces. Posting is limited to 60 posts per 10 minutes per person per space.
Outside feeds #
A source is { id, provider, target, key?, every?, paused? } on a feed. key is the name of a key in the key vault, never the key. Every 5 minutes a job pulls sources that are due (default every 60 minutes, at least 15), adds new posts once (by the provider's id), shows the original author and link, and backs off when a source keeps failing. The key is only read inside the server action that calls the provider.
| Provider | Target | Key |
|---|---|---|
| X | @handle, or a search like from:nasa #space | X API bearer token (paid API tier) |
| Bluesky | handle.bsky.social | none |
| Mastodon | @user@instance.social | optional |
me (the token's account) | Instagram API access token | |
| Threads | me | Threads API access token |
| YouTube | @channel or a channel id | YouTube Data API key |
| RSS / Atom | the feed's https link | none |
Links to private network addresses are refused. Instagram and Threads media links expire after a while on Meta's side, so old imported posts can lose their picture.
Feed recipes #
Ready settings for the kinds of feed people ask for (convex/kits/social/recipes.ts). The AI reads them in its guide and uses one when the person names the kind ("add a clips feed", "a Q&A board for my class"), then changes what they ask. feedRecipeOps(bp, recipe, { name?, id?, page?, tab? }) gives the changes that add the feed and a page for it, with ids that never clash.
| Recipe | What it makes |
|---|---|
| threads | Short posts, threads, reposts, quotes (X / Threads) |
| photos | Photos and videos with captions, cards (Instagram) |
| stories | One photo or video, gone after a day |
| clips | Short videos only, several reactions (TikTok / Reels) |
| updates | Owner-only news with titles and comments (blog, changelog) |
| board | Members post, managers approve first (class or fan wall) |
| moments | Private family or friends photos, warm reactions |
| questions | Titles and threaded answers, most helpful on top (Q&A) |
| forum | Topics, long threads, most active on top |
| links | Links with titles, upvotes and discussion (Reddit) |
| reviews | Star ratings with words and photos |
| guestbook | Short notes, no pictures |
| portfolio | Owner-only grid, #tags as collections |
What the AI knows about feeds #
convex/kits/social/guide.ts (FEEDS_GUIDE, part of the guide's kits section): the /feeds settings, that everything inside a post comes with the blocks, the feed blocks and the switcher, the feed.<key> words, the recipes, how to add a feed and its page, and how a whole social app is put together.
Taking out a kit #
When a kit is taken out, blocks on the pages that stay and show one of its feeds, collections or streams go with it (removeKitOps), so a page never points at something that's gone.