docs: address spec review — add data models, routing, migration plan

Resolves 15 reviewer issues: ContentItems table schema, Copy vs
Translation distinction (is_original flag), explicit stage tracking,
routing scheme with old-route redirects, campaign brief column types,
tasks global view, animation approach (CSS only), phased rollout,
public routes unchanged, My Tasks widget data sources.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-11 17:23:36 +03:00
parent 18785ed901
commit 7ace32a070
@@ -39,11 +39,11 @@ Settings ← absorbs Brands, Assets as tabs
|-----------------|-------------| |-----------------|-------------|
| Posts (page) | Content → Posts tab | | Posts (page) | Content → Posts tab |
| Calendar (page) | Content → Posts tab → Calendar view toggle | | Calendar (page) | Content → Posts tab → Calendar view toggle |
| Translations (page) | Content → Copy tab + Translations tab | | Translations (page) | Content → Copy tab (originals, `is_original=true`) + Translations tab (translated versions, `is_original=false`) |
| Artefacts (page) | Content → Design tab | | Artefacts (page) | Content → Design tab |
| Assets (page) | Settings → Assets tab | | Assets (page) | Settings → Assets tab |
| Brands (page) | Settings → Brands tab | | Brands (page) | Settings → Brands tab |
| Tasks (page) | Projects → Tasks tab inside project detail | | Tasks (page) | Projects → Tasks tab inside project detail. Unlinked tasks accessible via a global "My Tasks" widget on Dashboard + a "All Tasks" view inside the Projects page (not just per-project). The standalone `/tasks` route redirects to `/projects?tab=tasks`. |
| Budgets (page) | Finance → Budgets tab | | Budgets (page) | Finance → Budgets tab |
## 2. Content Page — Unified Pipeline ## 2. Content Page — Unified Pipeline
@@ -68,12 +68,49 @@ The content production pipeline is: **Copy → Translate → Design → Post →
- Campaign grouping: cards from the same campaign share a visual group (shared top border or header) - Campaign grouping: cards from the same campaign share a visual group (shared top border or header)
- "New Content" button creates a content item starting at Copy stage - "New Content" button creates a content item starting at Copy stage
### Content Item — The Linking Model ### Content Item — Data Model
A **Content Item** is the thread connecting all stages:
- Has a title (the post concept/idea) A **Content Item** is a new NocoDB table (`ContentItems`) that threads all pipeline stages together.
- Optionally linked to a Campaign
- Has linked records: copy entries, translations, design artefacts, post **ContentItems table schema:**
- Each linked record has its own approval status | Column | NocoDB Type | Description |
|--------|-----------|-------------|
| `Id` | AutoNumber (PK) | NocoDB default |
| `title` | SingleLineText | Post concept / idea name |
| `stage` | SingleLineText | Current pipeline stage: `copy`, `translate`, `design`, `post`, `published` |
| `campaign_id` | Number (FK) | Optional link to Campaigns table |
| `brand_id` | Number (FK) | Brand association |
| `assignee_id` | Number (FK) | Current stage assignee |
| `created_by` | SingleLineText | Creator user ID |
**Stage is stored explicitly**, not derived. Advancing stage is a manual action (drag on kanban or "Advance" button) that also triggers the approval flow for the new stage. This keeps queries simple and avoids complex derivation logic.
**FK linkage — existing tables get a `content_item_id` column:**
- `Translations` table → `content_item_id` (Number, FK) — for both original copy and translations
- `Artefacts` table → `content_item_id` (Number, FK) — for designs/videos
- `Posts` table → `content_item_id` (Number, FK) — for the assembled post
These use the existing `FK_COLUMNS` pattern (Number columns, added via `ensureFKColumns()`).
**Copy vs Translation distinction:** Both live in the `Translations` table. A copy entry has `is_original: true` (new Boolean column). The Copy tab filters to `is_original = true`, the Translations tab filters to `is_original = false`. No new table needed.
**Server FK_COLUMNS additions:**
```js
FK_COLUMNS = {
...existing,
Translations: [...existing, 'content_item_id'],
Artefacts: [...existing, 'content_item_id'],
Posts: [...existing, 'content_item_id'],
}
```
**Server TEXT_COLUMNS additions:**
```js
TEXT_COLUMNS = {
...existing,
Translations: [...existing, { name: 'is_original', uidt: 'Checkbox' }],
}
```
### Detail Panel ### Detail Panel
- Pipeline breadcrumb at top: `Copy ✅ → Translate ✅ → Design ⏳ → Post ○` - Pipeline breadcrumb at top: `Copy ✅ → Translate ✅ → Design ⏳ → Post ○`
@@ -95,19 +132,47 @@ Not every content item needs a campaign or full pipeline. Users can:
- Create copy without linking to a campaign - Create copy without linking to a campaign
- The pipeline is the recommended flow, not enforced - The pipeline is the recommended flow, not enforced
### Routing Scheme
| Route | Content |
|-------|---------|
| `/content` | Default → Pipeline tab |
| `/content/pipeline` | Pipeline kanban |
| `/content/copy` | Copy tab (original texts) |
| `/content/translations` | Translations tab |
| `/content/design` | Design tab (artefacts) |
| `/content/posts` | Posts tab (kanban/list/calendar) |
**Old route redirects:** `/posts``/content/posts`, `/translations``/content/translations`, `/artefacts``/content/design`, `/calendar``/content/posts?view=calendar`. Redirects ensure existing bookmarks and shared links continue to work.
**Public review routes remain unchanged:** `/review/:token`, `/review-post/:token`, `/review-translation/:token` are not affected by this reorganization.
## 3. Campaign Brief Enhancement ## 3. Campaign Brief Enhancement
### Current State ### Current State
Campaigns exist but are mostly containers for posts. The campaign detail has a calendar timeline view. Campaigns exist but are mostly containers for posts. The campaign detail has a calendar timeline view.
### Enhanced Campaign Brief ### Enhanced Campaign Brief
Campaign detail page becomes a proper strategic document: Campaign detail page becomes a proper strategic document.
- **Brief section**: goals (awareness/engagement/conversions), target audience, key messages **New columns on Campaigns table:**
- **Metrics targets**: reach, impressions, engagement rate, conversion targets | Column | NocoDB Type | Description |
|--------|-----------|-------------|
| `goals` | SingleLineText | Comma-separated from: `awareness`, `engagement`, `conversions`, `brand_building`, `lead_generation` |
| `target_audience` | LongText | Free text description of target audience |
| `key_messages` | LongText | Free text key messages / talking points |
| `reach_target` | Number | Target reach count |
| `engagement_target` | Number | Target engagement rate (stored as percentage × 100) |
| `conversion_target` | Number | Target conversion count |
| `approval_status` | SingleLineText | `draft`, `pending_approval`, `approved`, `rejected` |
| `approved_by` | SingleLineText | User ID who approved |
| `approved_at` | SingleLineText | ISO timestamp of approval |
**Campaign detail sections:**
- **Brief section**: goals (multi-select chips), target audience, key messages
- **Metrics targets**: reach, engagement rate, conversions (numeric inputs)
- **Timeline**: keep existing calendar timeline (no changes) - **Timeline**: keep existing calendar timeline (no changes)
- **Budget**: link to finance/budget allocation - **Budget**: link to finance/budget allocation
- **Approval gate**: campaign must be approved before content work begins - **Approval gate**: campaign must be approved (`approval_status = approved`) before "Create Content" button appears
- **Content items**: cards showing linked content items with pipeline progress indicators - **Content items**: cards showing linked content items with pipeline progress indicators
### Campaign → Content Flow ### Campaign → Content Flow
@@ -141,6 +206,15 @@ Dashboard feels messy and doesn't answer "what needs my attention?"
### Principle ### Principle
Dashboard answers: **"What needs my attention right now?"** — not just display stats. Dashboard answers: **"What needs my attention right now?"** — not just display stats.
### "My Tasks" Widget — Data Sources
The "My Tasks" widget aggregates items assigned to the current user from:
- **Content items** where `assignee_id = currentUser` and stage needs action
- **Approval requests** pending the user's review (from any stage)
- **Tasks** assigned to the user (from Projects)
- **Issues** assigned to the user
Sorted by: overdue first, then by due date ascending, then by creation date.
## 5. Consistency Standards ## 5. Consistency Standards
### Page Header Pattern ### Page Header Pattern
@@ -166,7 +240,7 @@ Every page uses the same header layout:
- Pipeline breadcrumb at top (for content items) - Pipeline breadcrumb at top (for content items)
- Tab order: Details → Activity → Approval (consistent) - Tab order: Details → Activity → Approval (consistent)
- Save: always top-right in header - Save: always top-right in header
- Delete: always inside ⋯ menu (never standalone button) - Delete: moved from standalone header button to ⋯ overflow menu (intentional change from current Artefacts pattern — reduces accidental deletes, consistent placement)
### Cards (Kanban/Grid) ### Cards (Kanban/Grid)
- Single `KanbanCard` component used everywhere (already exists — enforce it) - Single `KanbanCard` component used everywhere (already exists — enforce it)
@@ -189,11 +263,11 @@ Every page uses the same header layout:
| Element | Animation | | Element | Animation |
|---------|-----------| |---------|-----------|
| Route changes | Fade + slide-up (150ms ease-out) | | Route changes | Fade + slide-up (150ms ease-out) |
| Detail panel open | Spring slide-in from right (slight overshoot) | | Detail panel open | CSS `cubic-bezier(0.34, 1.56, 0.64, 1)` slide-in from right (simulates spring overshoot without a library) |
| Kanban drag | Card lifts with shadow + slight rotation (2deg), drop zone pulses | | Kanban drag | Card lifts with shadow + slight rotation (2deg), drop zone pulses |
| Status badge change | Color crossfade (not instant swap) | | Status badge change | Color crossfade (not instant swap) |
| View toggle (list↔grid) | Crossfade between views | | View toggle (list↔grid) | Crossfade between views |
| Toasts | Spring slide-in from top-right, stack with spacing | | Toasts | CSS `cubic-bezier(0.34, 1.56, 0.64, 1)` slide-in from top-right, stack with spacing |
| Tab active indicator | Slides to follow selection | | Tab active indicator | Slides to follow selection |
### Hover & Interaction States ### Hover & Interaction States
@@ -218,7 +292,7 @@ Every page uses the same header layout:
- Tighter heading letter-spacing across the board - Tighter heading letter-spacing across the board
### Empty States — Premium ### Empty States — Premium
- Illustrated line-art SVG icons (matching app aesthetic) instead of plain Lucide icons - Illustrated line-art SVG icons from [Iconoir](https://iconoir.com/) library (matching app's line-art aesthetic) instead of plain Lucide icons. Fallback: compose simple illustrations from multiple Lucide icons if Iconoir doesn't have a match.
- Subtle gradient background behind icon circle - Subtle gradient background behind icon circle
- Friendly, helpful copy ("No content yet — start by writing some copy") - Friendly, helpful copy ("No content yet — start by writing some copy")
@@ -228,7 +302,42 @@ Every page uses the same header layout:
- Activity feed: staggered fade-in (50ms between items) - Activity feed: staggered fade-in (50ms between items)
- Metric card hover: gentle pulse on accent border - Metric card hover: gentle pulse on accent border
## 7. Files Impacted ## 7. Animation Approach
All animations use **CSS only** — no motion library dependency (no framer-motion, no react-spring). Spring-like effects approximated with `cubic-bezier(0.34, 1.56, 0.64, 1)`. All animations respect `prefers-reduced-motion: reduce` — when enabled, transitions are instant (duration: 0ms) and all decorative animations are disabled.
## 8. Migration & Compatibility
### Route Redirects
Old routes redirect to new locations via React Router `<Navigate>`:
- `/posts``/content/posts`
- `/calendar``/content/posts?view=calendar`
- `/translations``/content/translations`
- `/artefacts``/content/design`
- `/assets``/settings?tab=assets`
- `/brands``/settings?tab=brands`
- `/tasks``/projects?tab=tasks`
- `/budgets``/finance?tab=budgets`
### Data Migration
- New `ContentItems` table created via `ensureRequiredTables()` on server restart
- New columns (`content_item_id`, `is_original`) added via `FK_COLUMNS` / `TEXT_COLUMNS` — auto-created on restart
- Campaign brief columns added via `TEXT_COLUMNS` — auto-created on restart
- **No data migration needed for existing records** — existing posts/translations/artefacts continue to work without a content_item_id (they're standalone)
### Rollout Strategy
Phased implementation — each phase is independently deployable:
1. **Phase 1**: Nav reorganization + consistency standards (no data model changes)
2. **Phase 2**: Content page with tabs (restructure existing pages as tab sub-views)
3. **Phase 3**: Content Item model + pipeline (new table, FK linkages, pipeline kanban)
4. **Phase 4**: Campaign brief enhancement
5. **Phase 5**: Dashboard redesign
6. **Phase 6**: Premium polish (animations, glass effects, typography)
### Public Routes
Unchanged: `/review/:token`, `/review-post/:token`, `/review-translation/:token` continue to work as-is.
## 9. Files Impacted
### Navigation ### Navigation
- `client/src/components/Sidebar.jsx` — rewrite nav structure - `client/src/components/Sidebar.jsx` — rewrite nav structure
@@ -264,7 +373,9 @@ Every page uses the same header layout:
### Server ### Server
- `server/server.js` — content item model, campaign brief fields, pipeline stage tracking - `server/server.js` — content item model, campaign brief fields, pipeline stage tracking
- New columns in NocoDB: content_item_id linkages, campaign brief fields (goals, metrics, audience, approval_status) - New `ContentItems` table in `REQUIRED_TABLES`
- New columns via `FK_COLUMNS`: `content_item_id` on Translations, Artefacts, Posts
- New columns via `TEXT_COLUMNS`: `is_original` on Translations, campaign brief fields on Campaigns (goals, target_audience, key_messages, reach_target, engagement_target, conversion_target, approval_status, approved_by, approved_at)
### i18n ### i18n
- `client/src/i18n/en.json` — new keys for Content page, pipeline stages, dashboard widgets, campaign brief - `client/src/i18n/en.json` — new keys for Content page, pipeline stages, dashboard widgets, campaign brief