feat: post composition redesign + budget allocation + brand identity (Rawaj)

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>
This commit is contained in:
fahed
2026-03-15 18:02:29 +03:00
parent e1d1c392eb
commit ce4d6025d7
50 changed files with 2616 additions and 229 deletions
@@ -0,0 +1,405 @@
# Post Composition Redesign — Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Transform Posts from flat entities into composition orchestrators that assemble Caption + Copy (Translations) + Designs (Artefacts) + Video into a publishable unit with auto-computed readiness.
**Architecture:** Add `caption` and `stage` fields to Posts table. New `/api/posts/:id/composition` endpoint aggregates linked Translations + Artefacts with approval statuses. PostDetailPanel is rewritten as a composition workspace (single scroll, no tabs). Platform→format mapping is a client-side constant.
**Tech Stack:** Express.js, NocoDB, React, Tailwind CSS
**Spec:** `docs/superpowers/specs/2026-03-15-post-composition-redesign.md`
---
## File Structure
**Server:**
- Modify: `server/server.js` — add `caption`/`stage` to Posts TEXT_COLUMNS, new composition endpoint, update POST/PATCH handlers
- Create: `server/post-composition.js` — helper to compute composition, readiness, and stage auto-advance
**Client — New:**
- Create: `client/src/components/PostCompositionPanel.jsx` — the new composition workspace (replaces PostDetailPanel usage)
- Create: `client/src/components/PostCompositionCaption.jsx` — caption section
- Create: `client/src/components/PostCompositionCopy.jsx` — linked translations section
- Create: `client/src/components/PostCompositionDesigns.jsx` — linked design artefacts section
- Create: `client/src/components/PostCompositionVideo.jsx` — linked video artefact section
- Create: `client/src/components/PostCompositionFormats.jsx` — platform format checklist
- Create: `client/src/components/PostCompositionReadiness.jsx` — readiness summary + sign-off
- Create: `client/src/utils/platformFormats.js` — PLATFORM_FORMATS constant
**Client — Modify:**
- Modify: `client/src/pages/PostProduction.jsx` — use PostCompositionPanel instead of PostDetailPanel
- Modify: `client/src/pages/CampaignDetail.jsx` — same
- Modify: `client/src/i18n/en.json` / `ar.json` — new i18n keys
**Client — Keep (unchanged):**
- `PostDetailVersions.jsx`, `PostDetailPlatforms.jsx`, `PostDetailApproval.jsx`, `PostDetailAttachments.jsx` — kept for backward compat, old PostDetailPanel still importable
---
## Chunk 1: Server — Schema + Composition Endpoint
### Task 1: Add caption and stage to Posts schema
**Files:**
- Modify: `server/server.js` — TEXT_COLUMNS for Posts (~line 520)
- [ ] **Step 1: Add new columns to TEXT_COLUMNS**
Add to the Posts array in TEXT_COLUMNS:
```javascript
{ name: 'caption', uidt: 'LongText' },
{ name: 'stage', uidt: 'SingleLineText' },
```
- [ ] **Step 2: Update POST /api/posts to accept caption and stage**
In the POST handler, add `caption` and `stage` to the create payload:
```javascript
caption: caption || '',
stage: stage || 'copy',
```
- [ ] **Step 3: Update PATCH /api/posts/:id to accept caption**
Add `caption` to the allowed update fields.
- [ ] **Step 4: Commit**
```bash
git add server/server.js
git commit -m "feat: add caption and stage fields to Posts schema"
```
### Task 2: Create post-composition helper
**Files:**
- Create: `server/post-composition.js`
- [ ] **Step 1: Create the helper module**
```javascript
// server/post-composition.js
const nocodb = require('./nocodb');
// Compute full composition for a post
async function getPostComposition(postId) {
const post = await nocodb.get('Posts', postId);
if (!post) return null;
// Linked translations (copy)
const allTranslations = await nocodb.list('Translations', {
where: `(post_id,eq,${postId})`,
limit: 100,
});
const copy = allTranslations.map(t => ({
id: t.Id,
language: t.language,
status: t.status || 'draft',
is_original: t.is_original,
title: t.title,
}));
// Linked artefacts (designs + video)
const allArtefacts = await nocodb.list('Artefacts', {
where: `(post_id,eq,${postId})`,
limit: 100,
});
const designs = allArtefacts
.filter(a => (a.type || 'design') === 'design')
.map(a => ({
id: a.Id,
title: a.title,
status: a.status || 'draft',
thumbnail_url: a.thumbnail_url || null,
}));
const videoArtefact = allArtefacts.find(a => a.type === 'video');
const video = videoArtefact ? {
id: videoArtefact.Id,
title: videoArtefact.title,
status: videoArtefact.status || 'draft',
thumbnail_url: videoArtefact.thumbnail_url || null,
} : null;
// Platforms and formats
let platforms = [];
try { platforms = JSON.parse(post.platforms || '[]'); } catch { platforms = post.platform ? [post.platform] : []; }
// Readiness
const waitingOn = [];
const copyNotApproved = copy.filter(c => c.status !== 'approved');
if (copyNotApproved.length > 0) waitingOn.push(...copyNotApproved.map(c => `Copy (${c.language})`));
const designsNotApproved = designs.filter(d => d.status !== 'approved');
if (designsNotApproved.length > 0) waitingOn.push(...designsNotApproved.map(d => `Design: ${d.title}`));
if (video && video.status !== 'approved') waitingOn.push('Video');
const piecesReady = copy.length > 0 && waitingOn.length === 0;
return {
caption: post.caption || '',
copy,
designs,
video,
platforms,
pieces_ready: piecesReady,
waiting_on: waitingOn,
};
}
// Auto-compute stage from linked pieces
function computeStage(composition) {
const { copy, designs, video, pieces_ready } = composition;
if (pieces_ready) return 'post';
if (designs.length > 0 || video) return 'design';
if (copy.length > 1 || copy.some(c => !c.is_original)) return 'translate';
return 'copy';
}
module.exports = { getPostComposition, computeStage };
```
- [ ] **Step 2: Commit**
```bash
git add server/post-composition.js
git commit -m "feat: add post composition helper (readiness, stage auto-compute)"
```
### Task 3: Add composition API endpoint
**Files:**
- Modify: `server/server.js` — add GET /api/posts/:id/composition
- [ ] **Step 1: Add the endpoint**
After the existing GET /api/posts/:id route, add:
```javascript
app.get('/api/posts/:id/composition', requireAuth, async (req, res) => {
try {
const { getPostComposition } = require('./post-composition');
const composition = await getPostComposition(req.params.id);
if (!composition) return res.status(404).json({ error: 'Post not found' });
res.json(composition);
} catch (err) {
console.error('Composition error:', err);
res.status(500).json({ error: 'Failed to load composition' });
}
});
```
- [ ] **Step 2: Auto-update stage on PATCH /api/posts/:id**
In the existing PATCH handler, after saving, re-compute and update stage:
```javascript
const { getPostComposition, computeStage } = require('./post-composition');
const composition = await getPostComposition(req.params.id);
if (composition) {
const newStage = computeStage(composition);
await nocodb.update('Posts', Number(req.params.id), { stage: newStage });
}
```
- [ ] **Step 3: Also auto-update post stage when Translation or Artefact status changes**
In PATCH /api/translations/:id and PATCH /api/artefacts/:id — if the record has a `post_id`, re-compute the post's stage after saving.
- [ ] **Step 4: Commit**
```bash
git add server/server.js
git commit -m "feat: add /posts/:id/composition endpoint + stage auto-update"
```
---
## Chunk 2: Client — Platform Formats + Composition Sub-Components
### Task 4: Create platform formats constant
**Files:**
- Create: `client/src/utils/platformFormats.js`
- [ ] **Step 1: Create the file**
```javascript
export 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)', 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)', ratio: '1:1' },
],
snapchat: [
{ key: 'sc_snap', label: 'Snap (9:16)', ratio: '9:16' },
],
}
export function getFormatsForPlatforms(platforms = []) {
const formats = []
const seen = new Set()
for (const p of platforms) {
for (const f of (PLATFORM_FORMATS[p] || [])) {
if (!seen.has(f.key)) { seen.add(f.key); formats.push(f) }
}
}
return formats
}
```
- [ ] **Step 2: Commit**
```bash
git add client/src/utils/platformFormats.js
git commit -m "feat: add platform format mapping constant"
```
### Task 5: Create composition sub-components
**Files:**
- Create: `client/src/components/PostCompositionCaption.jsx`
- Create: `client/src/components/PostCompositionCopy.jsx`
- Create: `client/src/components/PostCompositionDesigns.jsx`
- Create: `client/src/components/PostCompositionVideo.jsx`
- Create: `client/src/components/PostCompositionFormats.jsx`
- Create: `client/src/components/PostCompositionReadiness.jsx`
Each is a small focused component (~40-80 lines) rendering one section of the composition workspace.
- [ ] **Step 1: Caption section**
PostCompositionCaption.jsx — textarea for the social media caption. Props: `caption`, `onChange`, `disabled`.
- [ ] **Step 2: Copy section**
PostCompositionCopy.jsx — shows linked translations as language pills with status icons. Props: `copy` (array from composition), `onLink` (opens translation picker), `onCreate` (creates new translation for this post). Each pill is clickable to open the TranslationDetailPanel.
- [ ] **Step 3: Designs section**
PostCompositionDesigns.jsx — shows linked design artefacts as thumbnail cards with status badges. Props: `designs` (array), `onLink`, `onCreate`, `onOpen` (opens ArtefactDetailPanel). Shows "+ Add Design" button.
- [ ] **Step 4: Video section**
PostCompositionVideo.jsx — shows linked video artefact (0 or 1) as a card. Props: `video` (object or null), `onLink`, `onCreate`, `onOpen`.
- [ ] **Step 5: Formats checklist**
PostCompositionFormats.jsx — reads `platforms` from the post, computes needed formats via `getFormatsForPlatforms()`, renders as a checkbox list. This is informational only — checkboxes are manual (designer checks off what they've produced). No database storage for checked state (tracked visually only).
- [ ] **Step 6: Readiness summary**
PostCompositionReadiness.jsx — shows bullet list of what's ready and what's blocking. Props: `piecesReady`, `waitingOn` (array of strings), `onSignOff` (callback for approve/schedule button). Sign-off button disabled until `piecesReady` is true.
- [ ] **Step 7: Commit**
```bash
git add client/src/components/PostComposition*.jsx
git commit -m "feat: add composition sub-components (caption, copy, designs, video, formats, readiness)"
```
---
## Chunk 3: Client — Main Composition Panel + Page Integration
### Task 6: Create PostCompositionPanel
**Files:**
- Create: `client/src/components/PostCompositionPanel.jsx`
- [ ] **Step 1: Build the panel**
This is a SlidePanel-based component (like existing detail panels) but with a composition layout instead of tabs:
```
Header (title input, status, brand, campaign, platforms, assigned_to, close/save/delete)
─────────
Scrollable body:
PostCompositionCaption
PostCompositionCopy
PostCompositionDesigns
PostCompositionVideo
PostCompositionFormats
PostCompositionReadiness
CommentsSection
```
Key behavior:
- On mount: fetches composition via `GET /api/posts/:id/composition`
- Caption changes are saved with the post (dirty tracking + save button)
- Copy/Design/Video sections have "Link existing" and "Create new" actions
- "Link existing" opens a small picker modal (list of unlinked translations/artefacts)
- "Create new" calls the create API with `post_id` pre-set, then refreshes composition
- Readiness section shows sign-off button (sets post status to `approved`)
- Each section is a collapsible card (use CollapsibleSection component)
- [ ] **Step 2: Add i18n keys**
Add to en.json and ar.json:
- `post.caption`, `post.captionPlaceholder`, `post.copy`, `post.copyInDesign`, `post.designs`, `post.video`, `post.formatChecklist`, `post.formatsNeeded`, `post.readiness`, `post.allPiecesReady`, `post.waitingOn`, `post.signOff`, `post.approveAndSchedule`, `post.linkExisting`, `post.createNew`, `post.addDesign`, `post.addVideo`, `post.linkTranslation`, `post.noCopyLinked`, `post.noDesignsLinked`, `post.noVideoLinked`
- [ ] **Step 3: Commit**
```bash
git add client/src/components/PostCompositionPanel.jsx client/src/i18n/en.json client/src/i18n/ar.json
git commit -m "feat: add PostCompositionPanel — composition workspace"
```
### Task 7: Wire up PostCompositionPanel in pages
**Files:**
- Modify: `client/src/pages/PostProduction.jsx`
- Modify: `client/src/pages/CampaignDetail.jsx`
- [ ] **Step 1: Update PostProduction.jsx**
Replace `PostDetailPanel` import and usage with `PostCompositionPanel`. The panel receives the same props (post, onClose, onSave, onDelete, brands, teamMembers, campaigns) plus the new composition data fetching happens inside the panel.
- [ ] **Step 2: Update CampaignDetail.jsx**
Same — replace PostDetailPanel with PostCompositionPanel for post detail views.
- [ ] **Step 3: Commit**
```bash
git add client/src/pages/PostProduction.jsx client/src/pages/CampaignDetail.jsx
git commit -m "feat: wire PostCompositionPanel into PostProduction and CampaignDetail"
```
### Task 8: Final verification
- [ ] **Step 1: Build check**
```bash
cd client && npx vite build --logLevel error
```
- [ ] **Step 2: Manual test checklist**
1. Open a post → composition panel shows caption, copy, designs, video, formats, readiness
2. Edit caption → save → caption persists
3. Link an existing translation → appears in copy section with status
4. Link an existing artefact → appears in designs section with thumbnail
5. Create new design artefact from panel → auto-linked to post
6. Select platforms → format checklist updates
7. Approve all pieces → readiness shows "All pieces ready"
8. Sign off → post status changes to approved
9. Stage auto-advances as pieces are linked
- [ ] **Step 3: Commit any fixes**
@@ -0,0 +1,226 @@
# 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)