Content OS

Apps — Index

Two shipped applications, one database, one pipeline running between them.

AppCodeWhoSize
Admin Consoleapp/Content teams, reviewers, admins43 screens, 113 API routes, 4 workers
Client Portalclient-app/The client, reviewing work1 screen, 5 API routes

A third app, the roadmap viewer (roadmap-app/), renders these docs. It has no pipeline role.


How they fit together

The client portal is not a separate product — it is the human approval gate in the middle of the admin app's pipeline.

ADMIN                                CLIENT                      ADMIN
─────                                ──────                      ─────
sources
   |
fetch + enrich          ──────▶  content_news_items
   |                                    |
   |                            Discovery tab
   |                            approve (Y) / skip (N)
   |                                    |
   |                            content_script_queue
   |                            content_generation_jobs
   |                              { origin: "client" }
   |                                    |
generation worker  ◀────────────────────┘
   |
   └──────────────────────▶  content_scripts
                                        |
                                Scripts tab
                                approve / send back
                                        |
                            content_scripts.status  ──────▶  calendar
                                                                 |
                            Calendar tab  ◀───────────────────────┘
                              (read-only)

They meet in MongoDB, never over HTTP. The portal reads and writes the same content_* collections directly; there is no API call between the two apps.


What differs

AdminClient
AuthEmail + password against content_adminsOne env-provided credential, CLIENT_*
Session cookieAdmin cookiecontent_engine_client_token, 15 days
TenantsMany, switchableOne, resolved from the session
BlogsFull studio, CMS deliveryNot visible at all
ScriptsWrite, refine, version, scheduleRead four sections, approve or reject
Production notesVisibleStripped at the data layer before they reach the browser

That last row is worth keeping: toClientVersions() drops on-screen text, b-roll, editing notes and scores at the data boundary rather than hiding them in the UI, so a future component change cannot leak them.


Shared and not shared

Shared: MongoDB, JWT_SECRET, the content_* collection schema, and the tenant ownership chain (customerId is the authorization source of truth in both).

Not shared: code. script-sections.ts exists as a deliberate hand-copied read-only fork in the portal, because the two apps build from separate Dockerfiles with no workspace linkage. Index creation is owned by the admin app alone.


The gap between them

The client can reject, but cannot say why. Skip and send-back both carry no reason, so the admin side learns that something was rejected and never what was wrong with it — which also means nothing downstream can learn from it. See Learning Agent, whose whole job would be reading exactly that signal.

Source: roadmap/apps/index.md