Clips — Technical Reference
Where this lives
Backend
apps/backend/graphql/types/post.type.js—clips(limit: Int, offset: Int): [Post!]!, documented inline as "Posts of type='clip' (uploaded as a single video, no images) - backs the full-screen vertical Clips feed"apps/backend/graphql/resolvers/post.resolver.js—clipsresolver callspostManager.getClips; for anonymous callers (!context.user) it passespublicOnly: trueapps/backend/managers/post-managers/post.manager.js—getClips— delegates topostAccessService.getByType('clip', options)apps/backend/data-access-services/post/post.access-service.js—getByType(type, options)— whenpublicOnlyis set (logged-out browsing) it additionally restricts tovisibility: 'public',isPublished: true, and excludes posts from private accounts (creator.isPrivate: false)
A clip is not a distinct model — it's a Post with type: 'clip', set at creation time when the post has exactly one video attachment and no images. There is no separate "Clip" GraphQL type; clips returns ordinary Post objects.
Frontend
apps/frontend-nextjs/src/page-components/ClipsPage.tsx— the authenticated full-screen vertical feed; queriesclips, and reuses the same like/save/repost mutationsPostCard.tsxuses (hasUserLikedPost/likePost,isPostSaved/savePost,hasUserReposted/repostPost/undoRepost)apps/frontend-nextjs/src/page-components/PublicClipsPage.tsx— logged-out view: an infinitely-scrolling grid of public clips (queriesclips, relying on the resolver'spublicOnlyfiltering); tapping a tile opensPublicPostViewer, and interactions route to/loginapps/frontend-nextjs/src/components/public/BrowseGate.tsx— picksClipsPagevsPublicClipsPagebased onuseAuth().isAuthenticatedapps/frontend-nextjs/src/app/clips/page.tsx—/clipsroute; rendersBrowseGatewithClipsPage/PublicClipsPageapps/frontend-nextjs/src/components/Navigation.tsx— desktop sidebar link to Clips (ClipIcon), placed right after Explore; also the center item of the mobile bottom barapps/frontend-nextjs/src/components/PostModal.tsx— opened fromClipsPage.tsxto handle commenting on a clip, same modal used by the regular feedapps/frontend-nextjs/src/components/stories/CreateClipModal.tsx— a built, working video-first composer (file picker or getUserMedia/MediaRecorder capture, caption, audience picker, callscreatePost). Fixed this session: it used to only be imported by a Storybook story (StoriesFeature.stories.tsx), unreachable from any real page (an earlier floating "+" button onClipsPage.tsxthat opened it had been removed, commit835c2de6, "drop redundant Clips upload FAB", in favor of creating clips through the regular post composer).ClipsPage.tsxnow imports it directly and renders it behind ashowCreateClipstate flag, opened by a "Create clip" FAB (top-right,Plusicon, shown only whenauthUseris set)
Technical implementation checklist
-
clipsquery — wired end-to-end;ClipsPage.tsx(authenticated) andPublicClipsPage.tsx(logged-out, public-only) - Like / unlike — wired; shares
PostCard.tsx'shasUserLikedPost/likePostmutations - Save / unsave — wired; shares
isPostSaved/savePostmutations - Repost / undo repost — wired; shares
hasUserReposted/repostPost/undoRepostmutations - Comment — wired via the shared
PostModal.tsx - Dedicated Clips creation UI (record/upload camera flow) — Fixed this session:
CreateClipModal.tsxis fully built and now mounted onClipsPage.tsxbehind a "Create clip" FAB; clips can still also be produced by the regular post composer (CreatePostModal.tsx) when a single video is attached
Feed query
query GetClips($limit: Int, $offset: Int) {
clips(limit: $limit, offset: $offset) {
id
text
userId
createdAt
user { id username profilePicture }
media { id mediaUrl mediaType width height }
}
}
ClipsPage.tsx follows the same optimistic-toggle pattern as PostCard.tsx for like/save/repost — it queries the current state (hasUserLikedPost, isPostSaved, hasUserReposted) per visible clip and updates local state immediately on tap, before the mutation resolves.