{
  "title": "App Spec",
  "description": "AppSpec is the root project manifest (Core §4.4). A Workspace MAY contain\nmore than one App; all Apps in a workspace run simultaneously, mounted at\ntheir own RootURL.\n",
  "type": "object",
  "properties": {
    "access": {
      "$ref": "#/$defs/AppAccess",
      "description": "@schema {example: \"private\", enum: [\"private\", \"public\"], description: \"Auth axis: private (default, secure by default) | public — orthogonal to app_renderer\"}"
    },
    "app_renderer": {
      "description": "Chrome archetype (frontend/05-app-kinds.md): sidebar-nav | topnav | no-nav — no-nav means truly no navigation",
      "type": "string",
      "enum": [
        "sidebar-nav",
        "topnav",
        "no-nav"
      ],
      "examples": [
        "no-nav"
      ]
    },
    "auth": {
      "$ref": "#/$defs/AppAuth",
      "description": "Auth overrides the auth screens (login, setup, change-password,",
      "nullable": true
    },
    "auth_config_ref": {
      "description": "Per-App auth strategy config (kind: Config)",
      "type": "string"
    },
    "chrome": {
      "$ref": "#/$defs/AppChrome",
      "description": "Chrome fine-tunes which shell chrome elements render (frontend/",
      "nullable": true
    },
    "confirm": {
      "$ref": "#/$defs/AppConfirm",
      "description": "Confirm sets App-wide default confirmation dialogs for mutating",
      "nullable": true
    },
    "consumes": {
      "description": "cross-app interfaces needed → grant request",
      "type": "array",
      "items": {
        "$ref": "#/$defs/AppConsume"
      }
    },
    "datastores": {
      "description": "Datastores is the App-level App Registry selection — the App Registry",
      "type": "object",
      "additionalProperties": {},
      "examples": [
        "db: pg-main"
      ]
    },
    "favicon": {
      "description": "Browser tab icon: spec-root-relative module asset ref to an image file (contains \\\"/\\\"). Optional — falls back to an image `logo`, else the built-in favicon",
      "type": "string",
      "examples": [
        "workspaces/kedai-teras.svg"
      ]
    },
    "intake": {
      "$ref": "#/$defs/IntakeSpec",
      "description": "Intake declares protection for this App's ANONYMOUS intake surface",
      "nullable": true
    },
    "logo": {
      "description": "Brand mark: a lucide icon name (e.g. \\\"package\\\") OR a spec-root-relative module asset ref to an image file (contains \\\"/\\\", e.g. \\\"modules/\u003cmodule\u003e/assets/\u003cfile\u003e.svg\\\")",
      "type": "string",
      "examples": [
        "modules/cafe-master/assets/brand/logo.svg"
      ]
    },
    "menu": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/MenuItem"
      }
    },
    "modules": {
      "description": "Modules mounted by this App — manifests outside these modules are excluded from the App bundle",
      "type": "array",
      "items": {
        "type": "string"
      },
      "examples": [
        "[clinic, pharmacy]"
      ]
    },
    "page_transition": {
      "description": "Page-to-page navigation animation (View Transitions API), scoped to the page content area: none | fade (default) | slide | slide-up | scale",
      "type": "string",
      "enum": [
        "none",
        "fade",
        "slide",
        "slide-up",
        "scale"
      ],
      "examples": [
        "fade"
      ]
    },
    "persist_backend": {
      "description": "Entity persist backend (backend/04-persist-backend.md), e.g. jsonb-persist",
      "type": "string",
      "examples": [
        "jsonb-persist"
      ]
    },
    "publishes": {
      "description": "cross-app interfaces offered",
      "type": "array",
      "items": {
        "$ref": "#/$defs/AppInterface"
      }
    },
    "registered_views": {
      "description": "Extra views this App exposes beyond its menu: exactly one of entity (module/entity) or view (module/name) per entry",
      "type": "array",
      "items": {
        "$ref": "#/$defs/RegisteredViewDecl"
      },
      "examples": [
        "[{view: cafe-order/menu-catalog}, {entity: cafe-master/menu-item}]"
      ]
    },
    "renderers": {
      "description": "Renderers maps a VisualSpecKind name → renderer for the whole App",
      "type": "object",
      "additionalProperties": {}
    },
    "root_url": {
      "description": "Mount prefix inside the workspace: \\\"/\\\" or any \\\"/path\\\" — unique per workspace; reserved segments (_ui, api, _admin, assets, health, login, register, _ws, print) are rejected",
      "type": "string",
      "examples": [
        "/app/klinik"
      ],
      "pattern": "^(/|/[^/]+(/[^/]+)*)$"
    },
    "stack_family": {
      "description": "Shell implementation (frontend/03-renderer-kind.md), e.g. react-shadcn",
      "type": "string",
      "examples": [
        "react-shadcn"
      ]
    },
    "theme_ref": {
      "description": "Theme kind name applied per-App (frontend/05-app-kinds.md §6)",
      "type": "string",
      "examples": [
        "ocean-blue"
      ]
    },
    "title": {
      "description": "Human-readable display name (spaces allowed) — brand bar + document.title; metadata.name stays the machine identifier",
      "type": "string",
      "examples": [
        "Acme Corp Portal"
      ]
    },
    "vendor": {
      "description": "Vendor is optional — the publishing vendor identity, only required",
      "type": "string",
      "examples": [
        "acme-corp"
      ]
    },
    "version": {
      "description": "Version is optional — only meaningful when publishing the App to the",
      "type": "string",
      "examples": [
        "1.0.0"
      ]
    },
    "workspaces": {
      "description": "Optional workspace mount allowlist — absent = all workspaces; [] = staged (mounted nowhere); [slug,...] = only those workspaces",
      "type": "array",
      "items": {
        "type": "string"
      },
      "examples": [
        "[cafe, kopi]"
      ],
      "nullable": true
    }
  },
  "required": [
    "root_url"
  ],
  "additionalProperties": false
}