YYayaw
Docs

Architecture

High-level architecture and main technical modules.

Overview

Yayaw uses the Next.js App Router with locale-prefixed routes (/en, /fr). The product is organized around an authenticated multi-tenant dashboard, a public CMS runtime, and a server-only control plane for trusted automation.

Key reader paths:

Main Building Blocks

Frontend and Routing

  • App routes in src/app
  • Locale middleware and routing in src/i18n and src/lib/middleware
  • Dashboard UI blocks under src/blocks
  • Shared route/navigation configuration drives sidebar, command menu, breadcrumbs, and route visibility.

Dashboard Naming Convention

  • Page title defines the page topic (for example: Media, Billing, Authorization).
  • Block title defines what entity is managed and implies the action (for example: Assets manager).
  • Block description explains, in one short sentence, what users can do in that block.

Authentication and Authorization

  • Better Auth configuration in src/config/better-auth.config.ts
  • Auth server integration in src/lib/server/services/auth/auth-drizzle.ts
  • Better Auth API keys are enabled for control-plane access with explicit control-plane:read, control-plane:write, control-plane:publish, and control-plane:admin permissions
  • Better Auth admin capabilities include impersonation and SCIM provisioning (@better-auth/scim)
  • Organization invitation onboarding is passkey-first and uses hashed invite onboarding tokens stored outside the Better Auth invitation table
  • Organization role synchronization in src/lib/server/services/authz
  • Internal authorization model stays in Yayaw (Permix + Drizzle):
    • groups and memberships
    • explicit scoped bindings in group_role_bindings
    • role policies in role_policies
  • Explainable authorization engine in src/lib/server/authz:
    • authorizeDetailed(...) for allow/deny + decision chain
    • can(...) as the boolean facade used by routes and actions
  • Admin AuthZ custom server actions for list/detail/bulk/simulate/explain/audit in src/lib/server/actions/authz/authorization-admin-actions.ts

Data Layer

  • Drizzle setup in src/lib/db
  • Schemas organized by domain:
    • Better Auth schema
    • Authorization schema
    • System schema
  • Dashboard sidebar parents are navigable section dashboards. /dashboard/content is the CMS health dashboard; it summarizes active organization content inventory, publication status, media storage, CMS data, and AI generation jobs before users drill into dedicated catalog screens. /dashboard/admin, /dashboard/organization, /dashboard/settings, and /dashboard/test are also valid section roots, so sidebar parents never point at hidden or missing pages. The dashboard shell uses shadcn breadcrumbs and a shared section navigation bar derived from the filtered sidebar hierarchy; the section bar lists child routes for the active dashboard section only.
  • Organization media library in system schema:
    • media_folders and media_assets are organization-scoped
    • assets are stored in public object storage (media bucket by default) with persisted publicUrl
    • S3-compatible storage supports MinIO, S3, and R2 style deployments
    • visual assets can store generated WebP thumbnails for library display; missing thumbnails are backfilled during media library listing and do not count toward user upload quota
    • server actions always enforce scope: { orgId } and reject cross-organization mutations
    • read/list is available to organization members, upload/edit/delete requires manager or admin role
    • upload quotas are plan-driven from billing runtime config (per-file and total organization storage)
    • page-builder image generation stores OpenAI WebP outputs through the same media upload/persist pipeline
  • Reusable sections catalog in system schema:
    • ui_section_registry_items stores stable built-in, global, and organization section identity
    • ui_section_revisions stores validated SectionDefinitionV1 JSON + diagnostics
    • ui_section_publications stores lifecycle status and the latest published revision pointer
    • section definitions store renderer ids, recipes, component references, and bindings only
    • ordinary pages use unpinned PageSection references so they follow the latest published section; strict contextual design drafts use server-issued sectionRevisionId pins so review and publication resolve the exact immutable section revisions that were visually approved
    • AI section creation uses a reusable plan workbench shell:
      • src/components/ai/plan-workbench/*
      • section adapter drawer: src/blocks/dashboard/content/sections/block-ai-create-drawer.tsx
      • streaming plan endpoint: src/app/api/ai/blocks/plan/route.ts
      • persisted runs/jobs endpoints (resume/poll/cancel): src/app/api/ai/blocks/plan/jobs/*
      • persisted storage tables:
        • ui_block_ai_threads
        • ui_block_ai_runs
        • ui_block_ai_run_events
      • runner abstraction: src/lib/server/services/blocks/block-ai-plan-runner.ts
      • AI prompt contract includes explicit design-policy instructions (component reuse + project-consistent layout patterns)
      • finalize action with explicit missing-import approvals: createBlockFromAiPlanAction
    • AI creation entry points are guarded by the ai-components-enabled site setting so the UI and server actions disable together.
  • Hybrid pages catalog in system schema:
    • ui_page_registry_items stores page identity, scope/channel, path, and metadata
    • ui_page_revisions stores versioned PuckPageDocumentV1 + diagnostics
    • Page AI generation uses durable Postgres state:
      • ui_page_ai_runs
      • ui_page_ai_run_events
      • APIs under src/app/api/ai/pages/runs/*
      • Vercel Queues can wake the hosted worker route; bun run worker:page-ai polls the same DB state for self-hosted long-lived worker deployments
    • ui_page_publications stores lifecycle status and published revision pointer
    • dashboard UX is split into list/table then dedicated editor route per page
    • editor is direct Puck + shadcn (no legacy fallback path)
    • save model is autosave + publish/archive with optimistic locking (baseRevisionId)
    • conflict handling is explicit (conflict response) with no automatic merge/rebase
    • runtime reads published Puck documents directly
    • global pages resolve from /[locale]/[...slug]
    • organization member pages resolve from /[locale]/o/[orgSlug]/[[...slug]]
    • publish is blocked on diagnostics errors and reserved-route collisions
  • Global registry approval policy in system schema:
    • ui_registry_templates stores approved shadcn-compatible aliases + URL templates
    • import pipelines resolve external aliases only from approved templates
    • admin management page is available at /dashboard/admin/registry-templates
  • Generated server actions in src/lib/server/actions/database/generated

Observability and Feature Flags

  • PostHog server/client integration in src/lib/posthog and src/providers
  • Dynamic flags in src/lib/flags
  • Managed flag defaults declared in src/config/flags.config.ts and seeded by src/lib/scripts/seed.ts
  • Admin site settings are backed by code-managed feature_flags rows
  • Custom Flags are generated from non-managed feature_flags rows and only affect runtime behavior when code reads their slug explicitly
  • Runtime auth capabilities resolved in src/config/authentication-runtime.config.ts
  • Dashboard analytics are driven by a server-only view-model in src/lib/server/services/dashboard:
    • /dashboard/admin is the superadmin platform health surface with Stripe revenue, PostHog audience, registered-user growth, billing risk, and admin actions. The admin sidebar parent is a direct link to this route.
    • /dashboard is a permission-aware navigation home built from visible sidebar section roots and quick links, without duplicating organization health or provider metrics
    • organization owners/admins/managers see organization billing, seats, content inventory, usage, and management actions on organization-scoped section dashboards
    • organization members see operational organization activity and safe shortcuts only on the sections they can access
    • database, PostHog, and Stripe metric aggregates use the shared Next.js Data Cache for 5 minutes, keyed by route, timeframe, organization, and access scope, while session and authorization decisions remain per-request
    • CMS page view analytics read cms_page_viewed events for active-organization pages, and include global pages only for actors with global page:manage, including top-page rankings for /dashboard/content
    • provider-level fallbacks keep one failed metric source from breaking the page
    • deployment status is provider-backed: Vercel deployment reads remain available, while self-hosted runtimes can provide static deployment metadata through DEPLOYMENT_* variables
    • PostHog events registered from the app include organization_id when an active organization is available, enabling organization-scoped analytics
  • Dynamic project data models are controlled through MCP and stored as generated native Postgres tables for operational read/write performance. Their declarations, publications, and deployment history live in system registry tables, while generated table DDL is additive and server-generated. Record CRUD/query runs through the same control plane against deployed tables with model-validated payloads, bounded filters, and parameterized SQL values. Admin > Dynamic Data is the static control-plane surface for inspecting models, querying deployed table rows, and managing record payloads when the actor has dynamic-data:manage. Product-facing dashboard sections are declared by MCP through ui.nav.section and render at /dashboard/{section}/dynamic-data without code changes once visible deployed models are assigned to that section. All dashboard reads and writes still resolve the model resource and run through scoped dynamic-data authorization server-side. The dashboard model list includes a bounded no-code draft form for creating native-table model drafts from names, slugs, scope, navigation section, and scalar fields; publication and deployment still go through the same validated dynamic-data service/MCP pipeline before any generated DDL is applied. Model managers can also publish the latest ready draft from the dashboard model list, open the generated deployment plan for the published revision, and apply that reviewed plan from the model detail; those actions call the same plan, publish, and deploy services as MCP and stay gated by dynamic-data:manage. That list includes models visible through broad global/organization grants and through resource-scoped model grants, and per-model manage access controls the generated record actions. Extension metadata is visible in the model list and model detail so product bundles can be inspected by extension key, capabilities, tags, and entity bindings without opening raw JSON. For deployed models, model detail and generated runtime actions are rendered from the same normalized runtime manifest returned by MCP, so operators inspect installed routes and ui.runtimeActions instead of unpublished draft UI. Model definitions can include validated ui.table metadata so MCP callers can tune Yayaw Table column renderers, order, visibility, sorting, calculations, and table/kanban/gallery presentation without changing repository code. They can also include validated ui.form metadata that compiles into the existing Yayaw Table form builder for record create/edit drawers or modals, including field order, visibility, labels, helper copy, read-only state, and safe widget hints. ui.table.rowActions can declare row-level dashboard actions that call deployed dynamic runtime routes through server-side record reloading, bounded literal/record/system bindings, Yayaw dynamic-data authorization, and the existing runtime executor, so product control actions can be added by MCP without custom React code. ui.detail.summary can declare a selected-record header, badges, and key/value fields from safe literal, record, or system bindings, while ui.detail.relatedRecords can declare read-only related-record panels that reuse Yayaw Table against target dynamic models. Related panels use server-side source record reloads, target model authorization, and parameterized native-table filters, so product admin detail pages can grow by MCP configuration instead of repository-specific components.
  • Session, page-view, and high-volume event data remain analytics-provider traffic rather than MCP-routed writes; MCP can configure and query analytics surfaces, but it should not be the hot ingestion path.
  • Runtime analytics event emission is controlled through managed feature flags in /dashboard/admin/flags. cms-page-view-analytics-enabled gates CMS page view events without adding a separate analytics route-configuration surface.

Deployment Providers

Yayaw's first portable self-host target is Docker standalone Next.js with Postgres, S3-compatible object storage, a database-backed Page AI worker, and a reverse proxy that preserves Host and X-Forwarded-* headers. Vercel remains a supported provider for hosting, queues, deployment metrics, and project-domain verification, but those capabilities are no longer assumed by the core runtime.

Runtime provider seams:

  • storage: STORAGE_PROVIDER=s3
  • organization public domains: PUBLIC_DOMAIN_PROVIDER=vercel|manual-dns
  • Page AI wake-up: PAGE_AI_QUEUE_DRIVER=direct|vercel-queue|db-worker
  • deployment metadata: Vercel runtime variables or static DEPLOYMENT_* variables

Postgres + Drizzle is the source of truth for application data. S3-compatible object storage is the media persistence layer.

Control Plane

  • Production MCP endpoint: src/app/api/mcp/route.ts
  • Local stdio launcher: src/lib/scripts/mcp/yayaw-mcp-server.ts
  • Key management script: src/lib/scripts/mcp/control-plane-key.ts
  • Shared typed operation registry: src/lib/server/services/control-plane
  • Audit storage: control_plane_audit_events
  • Production access requires the control-plane-mcp-enabled site setting, a valid Better Auth API key or OAuth access token, control-plane permissions, and underlying Yayaw authorization
  • Write tools require reason; optimistic publish flows require expectedRevisionId; destructive archive tools require confirm: true

Future feature rule:

  • Treat operational, content, configuration, publish, audit, and status workflows as control-plane candidates by default
  • Put reusable business logic behind explicit actor-aware service entrypoints
  • Keep UI server actions, MCP tools, CLI scripts, and optional HTTP APIs as thin adapters over the same service logic
  • Add typed MCP tools/resources, audit coverage, docs, LLM source updates, and focused tests in the same change when a feature becomes control-plane-capable
  • Document intentional exclusions when a feature remains UI-only

Feature Flag Contract

For product features, flags must control:

  • runtime behavior (plugins/services/actions)
  • UI exposure (cards, actions, navigation entry points)

This avoids partial disables where backend behavior is off but UI links remain visible.

Documentation Architecture

  • Human docs: Fumadocs pages under content/docs
  • LLM docs source of truth: content/llm/llm-source.md
  • Generated assistant docs:
    • AGENTS.md
    • GEMINI.md
    • .github/copilot-instructions.md

When changing product behavior, update the relevant English feature doc first, then update content/llm/llm-source.md when assistant behavior or architectural source-of-truth changes. Regenerate assistant docs with:

bun run docs:llm:generate