Files
marketing-app/docs/superpowers/specs/2026-03-15-post-composition-redesign.md
fahed ce4d6025d7 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>
2026-03-15 18:02:29 +03:00

10 KiB

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

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.stagePost.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:

{
  "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)