ce4d6025d7
Post Workflow: - PostDetail full page (/posts/:id) replaces slide panel approach - Post = 1 Caption Copy + 1 Body Copy + 1 Design + 0-1 Video - copy_type field on Translations (caption/body) - Composition endpoint returns rich data (content preview, languages, thumbnails) - Stage auto-advances on translation/artefact changes (both link and unlink) - "Translations" renamed to "Copy" in navigation - GET /api/posts/:id, /api/translations/:id, /api/artefacts/:id routes added - PostProduction: "New Post" creates → navigates to full page - CampaignDetail: click post → navigates to full page - Inline link picker (no modals) with search + rich item display - PostComposition sub-components for caption, copy, designs, video, formats, readiness Budget Allocation: - Single source of truth: BudgetEntries (Campaign.budget deprecated) - Budget mutex for race conditions - Validation at all levels (main → campaign → track, expenses) - CEO approval workflow: BudgetRequests table, public approval page - Finance page: request budget UI, budget requests section - Settings: CEO email field - All emails branded with "Rawaj —" prefix Brand Identity: - Name: Rawaj (رواج) — trending/virality - Deep teal palette (#0d9488), forest-tinted dark mode - DM Sans font, custom SVG logo - Consistent across login, sidebar, emails, public pages Approval Workflow: - Single reviewer per artefact (not multi-select) - Reviewer redirect on public review page - Server blocks submit-review without reviewer - Review URLs use APP_URL (not server URL) UI/UX: - Scroll clipping fix: Modal, TabbedModal, SlidePanel restructured to avoid overflow-y-auto clipping native select dropdowns - section-card overflow-hidden → overflow-clip - All page titles via Header.jsx (removed duplicate h1s) - CampaignDetail redesigned: prominent budget card, compact team Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
227 lines
10 KiB
Markdown
227 lines
10 KiB
Markdown
# Post Composition Redesign — Post as Orchestrator
|
|
|
|
**Date:** 2026-03-15
|
|
**Status:** Draft
|
|
|
|
## Problem
|
|
|
|
The current Post model is flat — a post has a title, description, status, attachments, and platforms. There's no structure showing that a social media post is actually a **composition** of distinct production pieces (caption, in-design copy, design assets, video). The Post detail panel is a disorganized form with tabs that don't map to how content is actually produced.
|
|
|
|
Additionally, ContentItems (from the UX overhaul pipeline) duplicates Post metadata and adds confusion about where to create content.
|
|
|
|
## Design
|
|
|
|
### Post = Orchestrator
|
|
|
|
A Post is a container that assembles independently-produced pieces into a publishable unit:
|
|
|
|
```
|
|
Post "Summer Sale Launch"
|
|
├─ Caption (text field on Post, one base version, minor platform tweaks)
|
|
├─ Copy (in-design text): linked Translation(s) — approved via Translation flow
|
|
├─ Design(s): linked Artefact(s) — approved via Artefact flow
|
|
├─ Video: linked Artefact (optional) — approved via Artefact flow
|
|
├─ Platforms: [IG, TikTok, YouTube]
|
|
└─ Format checklist: auto-derived from platforms
|
|
```
|
|
|
|
### Composition Pieces
|
|
|
|
| Piece | Storage | Approval | Notes |
|
|
|-------|---------|----------|-------|
|
|
| **Caption** | `Post.caption` field (text) | Part of final Post sign-off | The text posted WITH the content (IG caption, tweet text, etc.). One base version with minor platform tweaks (hashtags). Multilingual via existing Translation system if needed. |
|
|
| **In-design copy** | Translation record (`post_id` FK, `is_original=true`) | Translation approval flow | Text that goes INSIDE the design (overlaid on image/video). Already exists. |
|
|
| **Design(s)** | Artefact(s) linked via `post_id` FK, `type='design'` | Artefact approval flow | 1..N designs per post (carousel = multiple). Each artefact can have versions. |
|
|
| **Video** | Artefact linked via `post_id` FK, `type='video'` | Artefact approval flow | 0..1 video per post. Has its own versions/approval. |
|
|
| **Format specs** | Derived from `Post.platforms` | None (production checklist) | System maps platforms → required formats (IG→1:1, TikTok→9:16, etc.). Designer uses as a guide. |
|
|
|
|
### Platform → Format Mapping
|
|
|
|
```javascript
|
|
const PLATFORM_FORMATS = {
|
|
instagram: [
|
|
{ key: 'ig_feed', label: 'Feed (1:1)', ratio: '1:1' },
|
|
{ key: 'ig_story', label: 'Story (9:16)', ratio: '9:16' },
|
|
{ key: 'ig_reel', label: 'Reel (9:16)', ratio: '9:16' },
|
|
],
|
|
tiktok: [
|
|
{ key: 'tt_video', label: 'TikTok (9:16)', ratio: '9:16' },
|
|
],
|
|
youtube: [
|
|
{ key: 'yt_video', label: 'YouTube (16:9)', ratio: '16:9' },
|
|
{ key: 'yt_short', label: 'Short (9:16)', ratio: '9:16' },
|
|
{ key: 'yt_thumb', label: 'Thumbnail (16:9)', ratio: '16:9' },
|
|
],
|
|
facebook: [
|
|
{ key: 'fb_post', label: 'Post (1:1 or 16:9)', ratio: '1:1' },
|
|
{ key: 'fb_story', label: 'Story (9:16)', ratio: '9:16' },
|
|
],
|
|
twitter: [
|
|
{ key: 'tw_post', label: 'Post (16:9)', ratio: '16:9' },
|
|
],
|
|
linkedin: [
|
|
{ key: 'li_post', label: 'Post (1:1 or 1.91:1)', ratio: '1:1' },
|
|
],
|
|
snapchat: [
|
|
{ key: 'sc_snap', label: 'Snap (9:16)', ratio: '9:16' },
|
|
],
|
|
}
|
|
```
|
|
|
|
This is a **checklist** for the designer, not enforced entities. The Post detail panel shows "Formats needed" based on selected platforms. No separate database records per format.
|
|
|
|
### Post Status & Readiness
|
|
|
|
**Post status field** (unchanged): `idea` | `in_progress` | `in_review` | `approved` | `rejected` | `scheduled` | `published`
|
|
|
|
**Readiness is auto-computed** from pieces:
|
|
- `pieces_ready`: true when ALL linked Translations are approved AND ALL linked Artefacts are approved
|
|
- Displayed as: "Ready for sign-off" or "Waiting on: Copy (AR), Video"
|
|
|
|
**Final publish flow:**
|
|
1. All pieces get approved through their own flows
|
|
2. Post auto-shows "All pieces ready — awaiting sign-off"
|
|
3. Someone manually moves Post to `approved` or `scheduled`
|
|
4. Published when scheduled date arrives (or manually)
|
|
|
|
### ContentItems Merge
|
|
|
|
ContentItems table is removed. Its fields map to Post:
|
|
- `ContentItems.stage` → `Post.stage` (copy / translate / design / post / published)
|
|
- `ContentItems.title` → already `Post.title`
|
|
- `ContentItems.campaign_id` → already `Post.campaign_id`
|
|
- `ContentItems.brand_id` → already `Post.brand_id`
|
|
- `ContentItems.assignee_id` → already `Post.assigned_to`
|
|
|
|
Stage auto-advances based on what exists:
|
|
- Post created → stage = `copy`
|
|
- Translation linked → stage = `translate` (if multiple languages)
|
|
- Artefact (design) linked → stage = `design`
|
|
- All pieces approved → stage = `post`
|
|
- Published → stage = `published`
|
|
|
|
### Post Detail Panel — Composition View
|
|
|
|
Replace the current tabbed panel with a **composition workspace**:
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ Header: Title, Status, Brand, Campaign │
|
|
│ Platforms: [IG] [TikTok] [YouTube] │
|
|
├─────────────────────────────────────────┤
|
|
│ │
|
|
│ CAPTION │
|
|
│ ┌─────────────────────────────────────┐ │
|
|
│ │ Textarea: "🔥 Summer deals..." │ │
|
|
│ │ Platform hashtags: #summer #sale │ │
|
|
│ └─────────────────────────────────────┘ │
|
|
│ │
|
|
│ COPY (in-design text) │
|
|
│ ┌────────┐ ┌────────┐ ┌────────┐ │
|
|
│ │ EN ✓ │ │ AR ✓ │ │ FR ⏳ │ │
|
|
│ └────────┘ └────────┘ └────────┘ │
|
|
│ [Link Translation] or [Create New] │
|
|
│ │
|
|
│ DESIGNS │
|
|
│ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Slide 1 │ │ Slide 2 │ │
|
|
│ │ [thumbnail] │ │ [thumbnail] │ │
|
|
│ │ ✓ Approved │ │ ✓ Approved │ │
|
|
│ └──────────────┘ └──────────────┘ │
|
|
│ [Link Artefact] or [Create New] │
|
|
│ │
|
|
│ VIDEO (optional) │
|
|
│ ┌──────────────────────────────────┐ │
|
|
│ │ [video thumbnail] Reel v2 │ │
|
|
│ │ ⏳ In Review │ │
|
|
│ └──────────────────────────────────┘ │
|
|
│ [Link Artefact] or [Create New] │
|
|
│ │
|
|
│ FORMAT CHECKLIST │
|
|
│ ☑ IG Feed 1:1 ☑ IG Story 9:16 │
|
|
│ ☑ TikTok 9:16 ☐ YT 16:9 │
|
|
│ │
|
|
│ READINESS │
|
|
│ ● Copy: 2/3 languages approved │
|
|
│ ● Design: 2/2 approved │
|
|
│ ● Video: In review │
|
|
│ [Approve & Schedule] (disabled until │
|
|
│ all pieces ready) │
|
|
│ │
|
|
│ DISCUSSION │
|
|
│ [comments section] │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
This is a **single scrollable view**, not tabs. Each section is a collapsible card. The readiness summary at the bottom gives a clear picture of what's blocking publication.
|
|
|
|
### Schema Changes
|
|
|
|
**Post table — add:**
|
|
- `caption` (LongText) — the social media caption
|
|
- `stage` (SingleLineText) — pipeline stage: copy/translate/design/post/published
|
|
|
|
**Post table — remove:**
|
|
- `description` (deprecated — copy lives in Translations)
|
|
|
|
**Artefact table — ensure:**
|
|
- `post_id` FK already exists
|
|
- `type` field already exists (design/video/copy)
|
|
|
|
**Translation table — ensure:**
|
|
- `post_id` FK already exists
|
|
|
|
**ContentItems table:**
|
|
- Delete after migration
|
|
|
|
### Migration
|
|
|
|
1. For each ContentItem: if no Post exists with matching title + campaign_id, create a Post from it
|
|
2. Move `stage` values to the new Post.stage field
|
|
3. Relink any Translations/Artefacts that referenced ContentItem IDs
|
|
4. Drop ContentItems table (or leave empty, mark deprecated)
|
|
|
|
### API Changes
|
|
|
|
**POST /api/posts** — add `caption` field
|
|
**PATCH /api/posts/:id** — add `caption` field, auto-update `stage` based on linked pieces
|
|
**GET /api/posts/:id** — include linked Translations (with approval status), linked Artefacts (with type + approval status), computed `pieces_ready` boolean, computed `waiting_on` array
|
|
|
|
**New helper endpoint:**
|
|
**GET /api/posts/:id/composition** — returns the full composition view:
|
|
```json
|
|
{
|
|
"caption": "🔥 Summer deals...",
|
|
"copy": [
|
|
{ "id": 1, "language": "EN", "status": "approved" },
|
|
{ "id": 2, "language": "AR", "status": "approved" },
|
|
{ "id": 3, "language": "FR", "status": "in_review" }
|
|
],
|
|
"designs": [
|
|
{ "id": 10, "title": "Slide 1", "status": "approved", "thumbnail_url": "..." },
|
|
{ "id": 11, "title": "Slide 2", "status": "approved", "thumbnail_url": "..." }
|
|
],
|
|
"video": { "id": 20, "title": "Reel v2", "status": "in_review", "thumbnail_url": "..." },
|
|
"platforms": ["instagram", "tiktok", "youtube"],
|
|
"formats_needed": ["ig_feed", "ig_story", "ig_reel", "tt_video", "yt_video", "yt_short", "yt_thumb"],
|
|
"pieces_ready": false,
|
|
"waiting_on": ["Copy (FR)", "Video"]
|
|
}
|
|
```
|
|
|
|
### What Stays the Same
|
|
|
|
- Artefact approval flow (unchanged)
|
|
- Translation approval flow (unchanged)
|
|
- Post review via public link (unchanged — now reviews the full composition)
|
|
- Campaign/brand/platform selection on Posts (unchanged)
|
|
- KanbanBoard for pipeline view (unchanged — works with `stage` or `status`)
|
|
|
|
## Out of Scope
|
|
|
|
- Auto-publishing to social media platforms
|
|
- Caption AI generation
|
|
- Design template system
|
|
- Format-specific cropping tool
|
|
- Per-platform caption variations (just one caption with manual tweaks)
|