Skip to main content

Overview

apps/cms is the dedicated home for Tuturuuu CMS. It should feel like a consumer-grade site editing product for nontechnical editors, even though it continues to use the existing external-project implementation contracts behind the scenes.
  • It owns the CMS UI, routing, workspace picker, and root-level /{wsId}/projects linking console.
  • It remains a satellite frontend. Protected APIs still live in apps/web.
  • All CMS /api/* traffic is forwarded to apps/web.

Product Vocabulary

Normal CMS product UI should use editor-friendly terms: site, site template, connection, content, section, URL path, custom details, media, publishing, preview, and team access. Avoid exposing implementation terms such as external project, canonical, adapter, binding, slug, schema, metadata, profile data, payload, or JSON in the primary UI. When staff genuinely need exact implementation values, place them behind a collapsed “Developer details” or internal root-console advanced panel. Code, database names, API paths, tests, and operational docs can keep the existing external-project* names so route and data contracts remain stable.

Routing

  • /{wsId}: overview
  • /{wsId}/games: game-focused content library, visible only when CMS Games is enabled
  • /{wsId}/landing: landing-page builder for hero/profile/page-section edits
  • /{wsId}/library: content operations library for non-landing, non-game content
  • /{wsId}/library/entries/{entryId}: compatibility redirect that opens the library fullscreen editor dialog
  • /{wsId}/library/collections/{collectionId}: collection detail
  • /{wsId}/members: workspace member and role management
  • /{wsId}/preview: delivered-content preview
  • /{wsId}/settings: legacy compatibility redirect to /{wsId}/members
  • /internal/projects: internal site-template registry and workspace linking console
  • /play/{wsId}/webgl/{assetId}: public WebGL package player. This route does not require a CMS login, but the proxied WebGL asset API still only serves the package when the owning CMS entry is published. WebGL player iframes must stay sandboxed without allow-same-origin.
When wsId resolves to the internal/root workspace, /{wsId} and other workspace-local routes should bounce to /internal/projects instead of trying to render the bound-workspace CMS surfaces. The previous /{wsId}/content, /{wsId}/collections/..., and /{wsId}/admin CMS routes are removed. New entry points should link directly to the CMS-native route map above.

Access Model

  • Only workspaces with CMS access enabled appear in the CMS workspace picker.
  • A workspace must still satisfy the existing external-project permissions to open CMS workspace routes.
  • CMS Games is disabled by default for every workspace. Enable it from the CMS settings dialog, which writes ENABLE_CMS_GAMES=true to workspace configs and reveals the /{wsId}/games sidebar route.
  • Only platform admins can open /internal/projects.
  • Workspace member and role management lives on /{wsId}/members. Read paths resolve through CMS workspace access so editors with CMS access can load people, invites, roles, member defaults, and guest defaults without requiring direct workspace settings-route access. Write paths still respect member and role management permissions.

API Ownership

  • apps/cms rewrites /api/:path* to apps/web.
  • The rewrite target must resolve to the central web app, not the CMS app origin. Prefer INTERNAL_WEB_API_ORIGIN, NEXT_PUBLIC_WEB_APP_URL, or WEB_APP_URL for the central origin. If a deployment-level NEXT_PUBLIC_APP_URL points at cms.tuturuuu.com or the current CMS Vercel preview URL, the CMS config ignores it and falls back to https://tuturuuu.com to avoid self-rewriting /api/* requests.
  • New CMS backend behavior should be added in apps/web, then exposed through packages/internal-api.
  • The root linking console reads the workspace-indexed binding summary from GET /api/v1/admin/external-project-bindings.
  • The root linking console may load its initial server-render payload directly with the request-scoped CMS Supabase admin path after getCmsWorkspaceAccess proves root admin access. Avoid server-to-server Internal API fetches during that initial render, because production auth/origin forwarding failures turn /internal/projects into a hard RSC error page. Keep workspace-secret lookups batched; one .in('ws_id', allWorkspaceIds) query can exceed PostgREST/proxy URL limits and return 400 Bad Request in production.
  • Backend naming stays on external-project* contracts even though the product surface is branded as Tuturuuu CMS.
  • Add or update the CMS product-copy hygiene test whenever new visible CMS strings are introduced. The test should protect the consumer vocabulary while allowing collapsed developer-detail and internal-only exceptions.

Content Model

  • Collections remain the content-type container. Field definitions live in workspace_external_project_field_definitions, while entry values continue to be stored in the existing profile_data and metadata JSON payloads so delivery and sync payloads stay backward-compatible.
  • Field definitions support the manifest field contract: string, markdown, number, boolean, date, datetime, json, and string-array. Scope controls whether the value is saved under profile_data or metadata.
  • The CMS Library has a Content model section with reusable templates for Profile, Blog Posts, Gallery, Shop Products, Writing Worlds, and Social Links. Applying a template creates the collection if needed, stores the collection schema, and creates any missing field definitions without deleting CMS-only fields.
  • Landing and Library must stay visibly distinct. Landing is a page-builder surface for landing-only sections, preview-first actions, and page readiness. Library is a content operations surface for search, status, bulk publishing, collection health, and non-landing/non-game content. Keep landing, library, and games capability scopes mutually distinct in tests.
  • The Library command center is the consolidated authoring entry point for content, content models, workflow queues, and settings. Keep new CMS operations reachable from that surface before adding another isolated toolbar or settings panel.
  • Entry detail forms render schema-aware controls for the active collection field definitions. Authors should edit those typed controls instead of hand-editing raw profile_data or metadata JSON for modeled fields.
  • Body and asset affordances should be driven by the collection schema when available. Naming heuristics for legacy Yoola collections remain as compatibility fallback only.
  • External-project setup and sync import manifest profileFields and metadataFields into DB field definitions. Sync snapshots are built from DB-managed field definitions, and field removals should only be applied during an explicit destructive sync.

Yashie Bridge

  • Yashie is the proof case for the generic content model. Its manifest seeds profile, blog post, gallery, shop product, writing world, and social-link collections and field definitions.
  • This phase does not change ../yashie runtime pages. Yashie can be seeded and managed from CMS, but the public Yashie app continues to read its local static data until a later integration pass consumes CMS delivery payloads.
  • Shop product entries are catalog-only in this phase. No checkout, payment, inventory, or commerce fulfillment behavior is implied by the template.

Client Architecture

  • Keep route pages server-side only for auth and access gates, then hand off interactive CMS surfaces to client modules backed by TanStack Query.
  • Keep cms-studio-client.tsx focused on query state, mutations, and routing. Library rendering should stay split across dedicated gallery, workflow, and settings modules rather than growing back into one large TSX file.
  • Keep CMS team access clients on @tuturuuu/internal-api helpers that target external-project-aware apps/web routes. Do not call the standard workspace members or roles settings endpoints directly from apps/cms.
  • Entry detail now opens as a fullscreen dialog from the library route so the CMS keeps the library cache warm instead of forcing a separate navigation for each entry.
  • The Games route reuses the library client with a game-like collection scope and only exposes WebGL ZIP upload controls when ENABLE_CMS_GAMES is explicitly enabled.
  • When a user creates the first Games entry, CMS should automatically create the workspace Games collection and then open the new game entry instead of requiring a manual collection setup step.
  • Published game entries expose a public /play/{wsId}/webgl/{assetId} link from the WebGL package card. Keep this route login-free in the CMS proxy, and keep publication checks in the central apps/web WebGL asset route so drafts and archived entries do not leak assets. The player iframe and the served WebGL document must both use sandboxing that allows scripts but does not allow same-origin access.
  • CMS media uploads show per-file progress for cover images, gallery media, and WebGL ZIP packages. Cover and gallery media must use the external-project asset app-server upload route so Tuturuuu measures the actual bytes before writing storage objects, then store the returned Drive path on the CMS asset row. WebGL ZIP package uploads still upload directly to the signed storage URL returned by the self-hosted web API, then call the WebGL package finalize route so the ZIP can be extracted and mapped to a playable artifact. The WebGL asset-serving route must infer browser content types from the requested WebGL filename for known outputs such as index.html, .js, .jpg, .wasm, .data, and compressed .gz/.br files, because extracted storage metadata can be too generic or incorrectly recorded as text/plain. Served WebGL files must include a CSP sandbox header that omits allow-same-origin; served HTML is also adjusted for the CMS player so Unity canvases fill the iframe viewport instead of keeping the default fixed-size template layout, hide the stock Unity footer chrome, resize the canvas backing buffer to the iframe viewport, inject a route-local <base> and artifact-map URL resolver so Build/* and TemplateData/* load through the same WebGL package API route even when the Unity template creates scripts dynamically, and show in-frame resource download progress when the runtime fetches package assets.
  • CMS asset delivery must respect the workspace Drive provider. Supabase assets may use signed image transforms, while R2-backed assets should resolve through the workspace storage provider signed-read path and ignore Supabase-only transform parameters.
  • CMS asset rows may only store workspace storage paths under external-projects/; asset delivery and cleanup must not operate on paths from other workspace modules such as Drive or finance.
  • CMS does not register the shared Serwist service worker or emit the shared web app manifest. It is a Vercel-hosted satellite that proxies protected APIs to apps/web, so PWA routes owned by apps/web must not be advertised from cms.tuturuuu.com.
  • Library taxonomy edits should stay lightweight: category/tag assignment, creation, and removal can happen directly from the library dialog, while deeper editorial changes still live in the entry sidebar taxonomy card.

Local Development

  • Use bun dev:cms to run the CMS app with the shared packages and apps/web.
  • Use bun devx:cms or bun devrs:cms when you also need a reset local Supabase environment.

CI/CD

  • Preview deployments run through .github/workflows/vercel-preview-cms.yaml.
  • Production deployments run through .github/workflows/vercel-production-cms.yaml.
  • Both workflows are gated through ci-check.yml and the tuturuuu.ts workflow allowlist.
  • The Vercel project must be configured through the VERCEL_CMS_PROJECT_ID repository secret.