# Post Rendering Flow Documentation This document traces the complete data flow and rendering pipeline for opening and displaying a post in the pm-pics application, from the PhotoGrid component through to the CompactMediaViewer. --- ## Overview The post rendering system follows this component chain: ``` PhotoGrid → Post.tsx → CompactRenderer → CompactMediaViewer ``` Each layer transforms and enriches the data, handling navigation state, media types, and rendering options. --- ## 1. Entry Point: PhotoGrid Component **File**: [PhotoGrid.tsx](../src/components/PhotoGrid.tsx) ### Data Source PhotoGrid receives media items from two possible sources: 1. **Custom Pictures** (props): Pre-loaded media items passed directly 2. **Feed Data** (hook): Fetched via `useFeedData` hook with parameters: - `source`: 'home' | 'collection' | 'tag' | 'user' | 'widget' - `sourceId`: Identifier for the source - `sortBy`: 'latest' | other sort options - `categorySlugs`: Optional category filtering ### Data Structure: MediaItemType ```typescript interface MediaItemType { id: string; picture_id?: string; title: string; description: string | null; image_url: string; thumbnail_url: string | null; type: MediaType; meta: any | null; likes_count: number; created_at: string; user_id: string; comments: { count: number }[]; // Enriched fields author?: UserProfile; job?: any; responsive?: any; versionCount?: number; } ``` ### Navigation Data Setup When a user clicks on a media item (line 258-274): ```typescript const handleMediaClick = (mediaId: string, type: MediaType, index: number) => { // Special handling for internal pages if (type === 'page-intern') { navigate(`/user/${username}/pages/${slug}`); return; } // Update navigation context with current index setNavigationData({ ...navigationData, currentIndex: index }); // Navigate to post view navigate(`/post/${mediaId}`); } ``` **Navigation Context** includes: - `posts`: Array of post metadata (id, title, image_url, user_id, type) - `currentIndex`: Position in the feed - `source`: Where the user came from - `sourceId`: Source identifier --- ## 2. Post Container: Post.tsx **File**: [Post.tsx](../src/pages/Post.tsx) ### Data Fetching Strategy The `fetchMedia` function (lines 477-674) implements a **3-tier fallback strategy**: #### Tier 1: Fetch as Post ```typescript const postData = await db.fetchPostById(id); ``` Returns a `PostItem` with: - Post metadata (title, description, settings) - Array of `pictures` (sorted by position) - User information #### Tier 2: Fetch as Picture ```typescript const pictureData = await db.fetchPictureById(id); ``` If found and has `post_id`, fetches the full post. Otherwise creates a **pseudo-post** with single picture. #### Tier 3: Fetch as Page ```typescript const pageData = await db.fetchPageById(id); ``` Creates a pseudo-post for internal page content with `type: 'page-intern'`. ### Version Resolution After fetching, the system resolves image versions (lines 478-526): ```typescript const resolveVersions = async (items: any[]) => { // 1. Extract root IDs (parent_id || id) const rootIds = items.map(i => i.parent_id || i.id); // 2. Fetch all related versions const allVersions = await db.fetchRelatedVersions(rootIds); // 3. Preserve original positions // 4. Sort by position, then created_at // 5. Deduplicate by ID } ``` This ensures that: - AI-generated variations are grouped with originals - User-selected versions are displayed - Position order is maintained ### State Management Key state variables: ```typescript const [post, setPost] = useState(null); const [mediaItems, setMediaItems] = useState([]); const [mediaItem, setMediaItem] = useState(null); // Currently displayed const [viewMode, setViewMode] = useState<'compact' | 'article' | 'thumbs'>('compact'); ``` ### View Mode Synchronization View mode is synchronized with URL parameters (lines 82-124): ```typescript // Initialize from URL const viewParam = searchParams.get('view'); if (viewParam === 'article' || viewParam === 'compact' || viewParam === 'thumbs') { return viewParam; } // Sync state to URL useEffect(() => { const newParams = new URLSearchParams(searchParams); newParams.set('view', viewMode); setSearchParams(newParams, { replace: true }); }, [viewMode]); ``` ### Data Types #### PostItem ```typescript interface PostItem { id: string; title: string; description: string | null; user_id: string; created_at: string; updated_at: string; pictures?: PostMediaItem[]; settings?: Json; meta?: Json; isPseudo?: boolean; // For single pictures without posts type?: string; // 'page-intern' for internal pages } ``` #### PostMediaItem ```typescript interface PostMediaItem { id: string; title: string; description: string | null; image_url: string; thumbnail_url: string | null; user_id: string; type: MediaType; created_at: string; position: number | null; post_id: string | null; parent_id: string | null; // For versions meta: Json | null; likes_count: number; visible: boolean; is_liked?: boolean; // Enriched by user context renderKey: string; // Unique key for React rendering } ``` --- ## 3. Renderer Selection: CompactRenderer **File**: [CompactRenderer.tsx](../src/pages/Post/renderers/CompactRenderer.tsx) ### Props Interface ```typescript interface PostRendererProps { post: PostItem; authorProfile: UserProfile; mediaItems: PostMediaItem[]; mediaItem: PostMediaItem; // Currently selected // State isOwner: boolean; isLiked: boolean; likesCount: number; currentImageIndex: number; // Media-specific videoPlaybackUrl?: string; videoPosterUrl?: string; versionImages: ImageFile[]; // Callbacks onMediaSelect: (item: PostMediaItem) => void; onExpand: (item: PostMediaItem) => void; onLike: () => void; onDeletePicture: (id: string) => void; // ... many more action handlers // Navigation navigationData: any; handleNavigate: (direction: 'next' | 'prev') => void; // Edit mode isEditMode?: boolean; localPost?: { title: string; description: string }; localMediaItems?: any[]; // ... edit-related handlers } ``` ### Layout Strategy CompactRenderer implements a **responsive 2-column layout**: #### Desktop (lg breakpoint): - **Left Column**: Media viewer + filmstrip - **Right Column**: Header + details (scrollable) #### Mobile: - **Stacked Layout**: Header → Media feed → Details ### Component Breakdown ``` CompactRenderer ├── CompactPostHeader (mobile top, desktop right) │ ├── User info, title, description │ ├── View mode switcher │ └── Action menu (edit, delete, export) │ ├── Left Column (Desktop) │ ├── CompactMediaViewer (main media display) │ └── CompactFilmStrip (thumbnail navigation) │ ├── MobileGroupedFeed (Mobile only) │ └── Vertical scrolling media cards │ └── Right Column (Desktop) ├── CompactPostHeader (repeated) └── CompactMediaDetails ├── Like/comment stats ├── Description ├── Comments section └── Action buttons ``` --- ## 4. Media Display: CompactMediaViewer **File**: [CompactMediaViewer.tsx](../src/pages/Post/renderers/components/CompactMediaViewer.tsx) ### Media Type Handling The viewer handles **6 distinct media types**: #### 1. Internal Videos (`video`, `video-intern`) ```typescript ``` #### 2. TikTok Videos (`tiktok`) ```typescript