Skip to main content

TL;DR

Tuturuuu CMS now lives in the dedicated apps/cms satellite app.
  • apps/cms owns the CMS authoring UI, workspace picker, overview/content/preview/members routes, and the root /{wsId}/projects linking console (reached as /internal/projects for the root workspace).
  • apps/web remains the only backend/API owner for CMS and external-project traffic.
  • apps/cms rewrites /api/* to apps/web, so proxy behavior and bot protection stay centralized.

Product Vocabulary

The visible CMS experience is a site/content product, not an implementation console. Bound-workspace UI should say site, site template, connection, content, section, URL path, custom details, media, publishing, preview, people, invitations, and team access. Avoid implementation terms in normal UI copy, including external project, canonical, adapter, binding, slug, schema, metadata, profile data, payload, and JSON. If operators need exact implementation values, show them only in collapsed Developer details panels or internal root-console advanced controls. Keep code, API routes, database objects, migration names, and integration docs on the existing external-project* contracts.

Route Model

The route tree below mirrors apps/cms/src/app/[locale]/(dashboard)/[wsId]. Workspace navigation is defined in apps/cms/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx. Every route is workspace-scoped under /{wsId}; the root workspace uses the internal slug (it resolves to ROOT_WORKSPACE_ID via normalizeWorkspaceId).

Root workspace

  • /{wsId}/projects (reached as /internal/projects): internal site-template registry, workspace-indexed connection list, and connection audit history. The internal workspace is not a bindable content workspace, so its overview, content, and /settings routes redirect into /{wsId}/projects.

Bound workspaces

  • /{wsId}: overview / continue-editing home.
  • /{wsId}/content: primary content operations surface (search, status filtering, publishing, reusable content).
  • /{wsId}/content/collections/{collectionId}: collection (section) detail.
  • /{wsId}/content/entries/{entryId}: entry (content) detail.
  • /{wsId}/pages: page-builder surface for landing-style sections.
  • /{wsId}/media: media library.
  • /{wsId}/products: product catalog content.
  • /{wsId}/storefront: storefront configuration.
  • /{wsId}/taxonomy: taxonomy (terms, categories, tags).
  • /{wsId}/games: game content surface, shown only when CMS Games is enabled for the workspace (getCmsGamesEnabled).
  • /{wsId}/preview: site preview.
  • /{wsId}/members: workspace member search, invite, removal, role assignment, and role/default-permission management.
  • /{wsId}/settings: redirect-only route. It sends the internal workspace to /{wsId}/projects and every other workspace to /{wsId}/members; CMS has no standalone settings screen.

Root Linking Console

  • Treat /{wsId}/projects (the root workspace’s /internal/projects) as a workspace-first control room.
  • The main left panel is the searchable workspace list.
  • Each row shows the current connection state, site type, site template, and most recent change signal.
  • The selected workspace panel owns connect and disconnect actions.
  • The site-template registry is secondary and stays on the same page for create/edit flows.
  • Binding audits stay anchored to the selected workspace instead of living as a detached global feed.

Workspace CMS

  • Use /{wsId} as the concise starting point for continue-editing tasks, launch readiness, attention queues, recent activity, and connected-site context.
  • Use /{wsId}/content for broader content operations: search, status filtering, publishing queues, collection health, and reusable content.
  • Use /{wsId}/pages for focused landing-style page edits, page readiness, and preview-first section work.
  • Use /{wsId}/media, /{wsId}/products, /{wsId}/storefront, and /{wsId}/taxonomy for the corresponding media, catalog, storefront, and taxonomy surfaces.
  • Use /{wsId}/games for game content when CMS Games is enabled for the workspace.
  • Use /{wsId}/members for routine collaborator management without leaving CMS.
  • Keep collection configuration on /{wsId}/content/collections/{collectionId} and entry authoring on /{wsId}/content/entries/{entryId}.
  • Use /{wsId}/preview as the delivered-content surface backed by the existing preview delivery route.
  • CMS has no standalone settings screen; /{wsId}/settings is a redirect-only route to /{wsId}/members (or /{wsId}/projects on the internal workspace).

Member Management In CMS

  • Workspace member search, invite, remove, and role-assignment controls now have a dedicated /{wsId}/members sidebar route.
  • The members surface also absorbs the essentials of the workspace roles page so CMS operators can manage named roles and default permissions without leaving CMS.
  • This keeps standalone CMS usable without forcing operators back into the main platform app for routine collaborator changes.
  • CMS members data must load through external-project-aware apps/web API routes and @tuturuuu/internal-api helpers. Reads should authorize by CMS workspace access so app-session users do not hit workspace settings-route 403s; writes still respect member and role management permissions.
  • Use CMS copy in this area: team access, people, invites, access levels, member defaults, guest defaults, and Advanced access.

Implementation Notes

  • Keep CMS as a pure satellite frontend. Do not move API ownership into apps/cms.
  • Continue adding or extending client helpers in packages/internal-api for CMS-facing API calls.
  • Keep preview sourced from the existing delivery route instead of introducing a second preview payload contract.
  • Backend/database naming remains external-project*; the product surface is what changed.
  • Protect CMS-owned product copy with the CMS copy hygiene test, and add explicit admin/developer-detail exceptions only when a staff workflow truly needs an implementation value.
  • Translation validation now treats null leaves as invalid so missing shared keys fail checks instead of silently passing parity.
  • External app admin dashboards should call POST /api/v1/workspaces/{wsId}/external-projects/setup with their sync manifest before diff/apply. The route uses the existing app-coordination token and requires manage access. If the workspace is already bound to an active canonical project for the same adapter, setup preserves that binding while refreshing the project schema and field definitions; otherwise it creates the canonical {adapter}-main project if needed and binds the configured workspace without requiring production Supabase keys in the external app.
  • External apps can mark manifest assets with metadata.publicPath, metadata.localAssetPath, or a relative sourceUrl such as /media/hero.png.
  • Before diff/apply, run the public-folder linker so the manifest stores deterministic Drive paths under external-projects/{adapter}/{collectionSlug}/{entrySlug}/{filename}.
  • Diff/apply routes are workspace-scoped. They may persist manifest schema into the bound workspace’s collections and field definitions, but they must not update canonical_external_projects or other root/canonical registry state.
  • Before apply, upload those files through the external-project asset app-server upload route. Abort the apply if a local public asset is missing so CMS never receives storage paths for objects that were not uploaded.
  • Asset storagePath/storage_path values must stay under external-projects/. Do not point CMS assets at Drive, finance, or other workspace storage namespaces; delivery and admin cleanup only operate on assets that satisfy the CMS namespace invariant.

Team Guidance

  • Link into the CMS-native route map directly.
  • Prefer the root /internal/projects console (the root workspace’s /{wsId}/projects) for site-template and workspace-link operations.
  • Keep bound-workspace collaborator management on the dedicated /{wsId}/members route, and do not surface the root projects console inside non-root workspace sidebars.
  • Update both apps/cms/messages/en.json and apps/cms/messages/vi.json, then run bun i18n:sort whenever CMS copy changes.