Projects layer — full roadmap
Status: live development Author: Frederike + Claude Last updated: 2026-06-04 Scope: the complete picture of the Projects layer — what shipped, what's pending, and the order we tackle it.
Companion to projects-layer.md (the Phase 1 spec). This doc is forward-looking; the other is historical record of the foundation.
Concept recap (so it stays out of muscle memory)
User (you)
└── Organization (the company / account) ← "workspace" in UI
└── Project (brand / initiative) ← TIRIDA, Nxtconnect AI, etc.
├── Channels (per-project social accounts)
├── Sources (per-project content ingestion: CDN / URL / Supabase / Notion / ...)
├── Voice (per-project brand voice + style — fed by brand hub OR manual entry)
└── Content (items, packages, generations — all project-scoped)
Plus per-user (NOT per-project — they complement voice):
├── Writing Style (writing_samples, default_image_prompt)
└── Reference Library (logos, reference images)
What's shipped (slice A through Phase 4)
| Slice | What landed |
|---|---|
| A | Project model, /api/projects CRUD, resolveProject middleware |
| B | useProjectStore + ProjectSwitcher + X-Project-ID header |
| C | project_id columns on platform_accounts, sources, items, packages + backfill of 11 channels + filtered list endpoints |
| C+ | CORS allow X-Project-ID (hotfix) |
| C+ | Sync-worker propagates source.project_id → item.project_id |
| E | OAuth project-stamping (TikTok/YouTube/IG/Twitter/LinkedIn) — new channels land in the active project |
| Voice phase 2 | ProjectVoice schema + ProjectStore.GetVoice/SetVoice + ToPromptBlock() |
| Voice phase 3 | Brand hub adapter (POST /api/projects/{id}/sync-voice, GET /voice) + configurable BRAND_HUB_BASE_URL |
| Voice phase 4 (autopilot) | autopilot_topics.project_id + ContentGenerator loads voice + voice wins over UserStyle's redundant fields |
| Fix | autopilot Create now includes org_id (pre-existing NOT NULL bug masked until Phase 4 touched the INSERT) |
What's pending — the 4-step plan
Step 1: Project Settings page + manual voice UI (recommended next)
A Settings tab inside each project. Two reasons we need it:
- You want to see logos on projects, rename projects (e.g. "Fastbrainer" → "FastBrainer"), tweak voice fields the brand hub got slightly wrong
- SaaS clients without a brand hub need some way to enter voice manually — that's the only blocker between us and selling this
Fields:
- Project name (rename)
- Logo upload (stored in S3, displayed in ProjectSwitcher next to the name)
brand_hub_slugoverride (when project slug ≠ hub slug, e.g.frederike-falke↔frederikefalke)- "Sync from brand hub" button (calls existing
/sync-voiceendpoint) - Voice editing — see UX approach below
Voice editing UX (the "freeform + AI extraction + ask for missing" approach):
- User pastes a freeform brand brief (1-2 paragraphs is fine) OR clicks "Sync from brand hub"
- Backend extracts the 9 structured fields via a Claude call (new endpoint
POST /api/projects/{id}/voice/extract-from-text) - UI shows extracted fields inline — each editable, marked with a confidence indicator
- For fields the AI couldn't extract or marked low-confidence, the UI prompts targeted follow-ups ("Who's your audience?", "What should the AI avoid?")
- Final review + Save
Power users can paste a brief and be done in 30 seconds. Completionists can scroll the same form and fill manually. Brand-hub users get pre-filled fields they can tweak.
Doesn't replace Writing Style or Reference Library — those stay per-user and continue to provide concrete writing samples + reference images that complement the per-project voice.
Effort estimate: ~4-6h.
Step 2: Transform pipeline voice integration
Today only the autopilot generator uses project voice. The transform pipeline (worker handlePipelineTransform) processes source-fetched content_items into platform-ready content_packages — this is the pipeline that runs every Tirida character through AI to produce platform variations.
Change: when transforming, load content_item.project_id → project.voice → ToPromptBlock() and inject into the transform prompt. ProjectVoice.image_style_by_type is especially useful here because content_type is known at transform time (story vs character-spotlight vs world).
Effort: ~30 min once we're in the file.
Step 3: Onboarding flow polish
A guided /onboarding route for new users / new projects:
1. Create your first project (project name + optional logo upload)
2. Connect channels (OAuth buttons, scoped to project)
3. Add a source (optional: CDN URL, Supabase, etc.)
4. Set your brand voice (paste a brief OR sync from hub)
5. Create your first autopilot topic (with sane defaults)
6. Done — see your dashboard
Each step has a "Skip for now" — users can come back. The onboarding flow uses the same endpoints as the rest of the dashboard, just packaged with progress UX.
Effort: ~3-4h.
Step 4: Documentation refresh
The Phase 1 spec (projects-layer.md) covers slices A-C only. Out of date.
Action:
- Update
projects-layer.mdto mark slices A-E + voice phases as shipped (link to this roadmap for forward-looking work) - Add
voice-system.mdcovering the voice schema, brand hub adapter, manual entry flow, and layering with UserStyle/ReferenceLibrary - Add
brand-hub-integration.mdcovering the API contract with Creative AI Lab brand hub + how to point at a different hub (env var) - Refresh workspace/organizations.md to clarify Org vs. Project (the terms are currently fuzzy in user-facing docs)
Effort: ~1h.
Decisions on file
These were made during implementation and are worth preserving here:
- Voice wins for overlapping fields (vs. UserStyle's
BrandVoiceDescription/WordsToAvoid/DefaultImagePrompt). UserStyle'sWritingSamples+DefaultReferenceImagesalways included as concrete examples. - Org per company, Project per brand (not Org per brand). One user can have multiple orgs (multi-tenant) but typically has one. Within an org, multiple projects model the brand portfolio.
- Topic project-scoping is real (column on autopilot_topics) — not just passed at generation time. Existing topic ("AI in Business") needs manual reassignment.
- Source types stay open via
source_type(b2_manifest, supabase, github, notion, web_scraper, rss_feed). Clients pointing at their own CDNs work today via b2_manifest with their base_url. - Brand hub is one feed, not the only one.
BRAND_HUB_BASE_URLenv var lets SaaS clients point at their own hub. Manual entry covers everyone else. - NOT NULL constraint on project_id is deferred (Slice D) until
admin@example.comlegacy test data is cleaned up.
What's also deferred (but worth tracking)
| Item | Why deferred | When |
|---|---|---|
| Super-admin cross-project dashboard | observability only, not user value | when you have multiple clients |
| Per-project member roles | no team to invite yet | when you hire someone |
brand VARCHAR cleanup on content_packages | mechanical, works fine as legacy | indefinitely |
| Slice D (NOT NULL on project_id) | legacy test data | after admin@example.com cleanup |
Open UX questions (not blocking — but track)
- Logo flow from project → channel posts (decided 2026-06-05):
- Logo lives at the project level in brand assets (NOT per-user) — every team member working on the project sees the same logo, ready to use.
- When a SaaS client adds a future team member with scoped access ("can only see TIRIDA project"), they inherit the project's logo + brand voice automatically — no per-user setup.
- UI toggle per content_type (or per source) for whether to watermark generated visuals with the logo. Off by default for portraits, on for landscape/wide shots — exact defaults TBD when the toggle lands.
- Implementation: extend project.settings to hold
brand_assets.logo_url(auto-populated from brand hub if connected, else uploaded via project settings). Image transformer reads from there when watermark flag is on.
primary_color/secondary_colorfrom brand hub — we receive them but don't use them yet. Where? Possibly: dashboard theming per active project, or generated content templates.enabled_content_types— voice has it (e.g. TIRIDA enablesstory,character-spotlight,world, etc.). Should the autopilot UI surface those as topic-suggestion seeds? Or restrict topic creation to those types?
These get answered as the UI work surfaces them — not designing now.