The app manifest, cartolex-manifest/1

The interface starts from one request: GET /api/app/manifest. The shell reads it, loads the catalogues of the interface language, imports the extensions’ modules (each register(api)), renders the navigation and routes. The manifest is the contract between the app and any interface built on it.

{
  "format": "cartolex-manifest/1",
  "app": {"id": "cartolex", "name": "cartolex", "version": "1.0.1",
          "build": {"commit": "3f2a1c9e…", "date": "2026-10-06"}, "platform": "Linux 6.8.0 x86_64"},
  "branding": {"name": "cartolex", "logo": "/static/brand/logo.svg", "accent": null},
  "locales": {"available": ["en", "fr", "pt-BR"], "default": "en",
              "catalogues": {"en": ["/static/i18n/en.json"], "fr": ["/static/i18n/fr.json"],
                             "pt-BR": ["/static/i18n/pt-BR.json"]}},
  "nav": [{"id": "overview", "label": "nav.overview", "route": "/overview",
           "module": "/static/pages/overview.js", "order": 10, "placement": "main"}],
  "modules": ["/static/ext/reports/index.js"],
  "capabilities": {"collection": true, "ai_api": false, "ai_copilot": true, "hosted": false, "idle_stop": false},
  "project": {"open": true, "id": "coast-3f2a9c1e", "name": "Coastal map"},
  "security": {"csrf_header": "X-Cartolex-CSRF", "csrf_cookie": "cartolex_csrf_5b1e0c2a"}
}

Keys

key

what it holds

format

cartolex-manifest/1

app

id (cartolex), name (the host’s brand, else cartolex), the cartolex version, the build (commit and its date: the wheel’s build stamp, tools/build_stamp.py, or the checkout’s last commit; null when unknown) and, locally, the platform the app runs on (null hosted); the interface shows the version and build at the foot of the settings menu and on the About page, and puts them in a diagnostic

branding

the name and logo the interface shows, and the host’s accent per theme ({"light": "#rrggbb", "dark": …}) or null: the shell sets it as the --cx-accent custom property

locales

the interface languages, the default one, and for each language the catalogues to merge, in order: cartolex’s, then each extension’s

nav

the pages, sorted by order: id, label (a catalogue key), route (a history route the server answers with the shell), module (the ES module that renders the page), order, placement (main, settings or hidden). cartolex’s own pages are overview, people, keywords, themes, map, share (main), settings (settings), and build, method, start (the header’s project menu leads to it) and about (the logo leads to it) (hidden). A page whose module file is missing gets /static/pages/placeholder.js, and the app logs a warning once

modules

the extensions’ ES modules the shell imports before the first route

capabilities

what this app can do: collection (texts can be collected), ai_api (a key for the AI clean-up by API was given), ai_copilot (the AI clean-up outside the app, by the copilot’s bundle), hosted, idle_stop (the local app stops once no page is open: each page sends POST /api/presence every 30 s and on closing); extensions add flags of their own

project

the project the requests work on: open, its id (the key of cached state in the browser) and name

security

the header state-changing requests carry (X-Cartolex-CSRF) and the name of the cookie holding its value, which carries the app instance’s id so two apps on two loopback ports never share it

Versions

Within cartolex-manifest/1, a newer cartolex may add optional keys; an interface ignores the keys it does not know. A change an interface could misread (a key removed, a meaning changed) makes the format /2. tests/fixtures/manifest.example.json is the example interfaces are tested against, and the app’s tests check that the live manifest holds every key of it.

The schema

The schema is generated from cartolex.app.manifest.Manifest and stored with the package; python -m cartolex.app.schemas --check (run by the tests) fails when it is out of date, --write regenerates it. It is also served at GET /api/app/manifest/schema.

{
  "$defs": {
    "Accent": {
      "additionalProperties": false,
      "description": "The accent of each theme, ``#rrggbb`` (4.5:1 against the theme's background).",
      "properties": {
        "dark": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Dark"
        },
        "light": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Light"
        }
      },
      "title": "Accent",
      "type": "object"
    },
    "AppInfo": {
      "additionalProperties": false,
      "description": "The application: ``id`` and ``name`` (the host's brand, or cartolex), its version, the\nbuild (``None`` when unknown) and, locally, the system it runs on (for a diagnostic).",
      "properties": {
        "build": {
          "anyOf": [
            {
              "$ref": "#/$defs/BuildStamp"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "id": {
          "title": "Id",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        },
        "platform": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Platform"
        },
        "version": {
          "title": "Version",
          "type": "string"
        }
      },
      "required": [
        "id",
        "name",
        "version"
      ],
      "title": "AppInfo",
      "type": "object"
    },
    "BrandingInfo": {
      "additionalProperties": false,
      "description": "The name and logo the interface shows, and the accent when a host sets one.",
      "properties": {
        "accent": {
          "anyOf": [
            {
              "$ref": "#/$defs/Accent"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        },
        "logo": {
          "title": "Logo",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        }
      },
      "required": [
        "name",
        "logo"
      ],
      "title": "BrandingInfo",
      "type": "object"
    },
    "BuildStamp": {
      "additionalProperties": false,
      "description": "The code running: its commit and the commit's date (``YYYY-MM-DD``).",
      "properties": {
        "commit": {
          "title": "Commit",
          "type": "string"
        },
        "date": {
          "title": "Date",
          "type": "string"
        }
      },
      "required": [
        "commit",
        "date"
      ],
      "title": "BuildStamp",
      "type": "object"
    },
    "Capabilities": {
      "additionalProperties": true,
      "description": "What this app can do; extensions add flags of their own (extra keys).",
      "properties": {
        "ai_api": {
          "description": "the AI clean-up by API (a key was given)",
          "title": "Ai Api",
          "type": "boolean"
        },
        "ai_copilot": {
          "description": "the AI clean-up outside the app: the copilot bundle (export, then import)",
          "title": "Ai Copilot",
          "type": "boolean"
        },
        "collection": {
          "description": "collecting texts from bibliographic services",
          "title": "Collection",
          "type": "boolean"
        },
        "hosted": {
          "description": "a hosted service (many projects, a host's sign-in)",
          "title": "Hosted",
          "type": "boolean"
        },
        "idle_stop": {
          "default": false,
          "description": "the app stops when no page is open: pages say they are (POST /api/presence)",
          "title": "Idle Stop",
          "type": "boolean"
        }
      },
      "required": [
        "collection",
        "ai_api",
        "ai_copilot",
        "hosted"
      ],
      "title": "Capabilities",
      "type": "object"
    },
    "Locales": {
      "additionalProperties": false,
      "description": "The interface languages, the default, and each one's catalogues, merged in order.",
      "properties": {
        "available": {
          "items": {
            "type": "string"
          },
          "title": "Available",
          "type": "array"
        },
        "catalogues": {
          "additionalProperties": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "title": "Catalogues",
          "type": "object"
        },
        "default": {
          "title": "Default",
          "type": "string"
        }
      },
      "required": [
        "available",
        "default",
        "catalogues"
      ],
      "title": "Locales",
      "type": "object"
    },
    "NavItem": {
      "additionalProperties": false,
      "description": "A page: its id, label key, route, module, order and placement.",
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "label": {
          "title": "Label",
          "type": "string"
        },
        "module": {
          "title": "Module",
          "type": "string"
        },
        "order": {
          "title": "Order",
          "type": "integer"
        },
        "placement": {
          "default": "main",
          "enum": [
            "main",
            "settings",
            "hidden"
          ],
          "title": "Placement",
          "type": "string"
        },
        "route": {
          "title": "Route",
          "type": "string"
        }
      },
      "required": [
        "id",
        "label",
        "route",
        "module",
        "order"
      ],
      "title": "NavItem",
      "type": "object"
    },
    "ProjectInfo": {
      "additionalProperties": false,
      "description": "The project the requests work on: open or not, its id and name.",
      "properties": {
        "id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Id"
        },
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Name"
        },
        "open": {
          "title": "Open",
          "type": "boolean"
        }
      },
      "required": [
        "open"
      ],
      "title": "ProjectInfo",
      "type": "object"
    },
    "Security": {
      "additionalProperties": false,
      "description": "The header state-changing requests send, and the cookie holding its value.\n\nThe cookie's name carries the app instance's id (two apps on two loopback\nports never share a cookie); cartolex always sends it.",
      "properties": {
        "csrf_cookie": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Csrf Cookie"
        },
        "csrf_header": {
          "title": "Csrf Header",
          "type": "string"
        }
      },
      "required": [
        "csrf_header"
      ],
      "title": "Security",
      "type": "object"
    }
  },
  "$id": "https://cartolex.github.io/schemas/app/1/manifest.schema.json",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "description": "``GET /api/app/manifest``: the contract the interface starts from.",
  "properties": {
    "app": {
      "$ref": "#/$defs/AppInfo"
    },
    "branding": {
      "$ref": "#/$defs/BrandingInfo"
    },
    "capabilities": {
      "$ref": "#/$defs/Capabilities"
    },
    "format": {
      "const": "cartolex-manifest/1",
      "default": "cartolex-manifest/1",
      "title": "Format",
      "type": "string"
    },
    "locales": {
      "$ref": "#/$defs/Locales"
    },
    "modules": {
      "items": {
        "type": "string"
      },
      "title": "Modules",
      "type": "array"
    },
    "nav": {
      "items": {
        "$ref": "#/$defs/NavItem"
      },
      "title": "Nav",
      "type": "array"
    },
    "project": {
      "$ref": "#/$defs/ProjectInfo"
    },
    "security": {
      "$ref": "#/$defs/Security"
    }
  },
  "required": [
    "app",
    "branding",
    "locales",
    "nav",
    "modules",
    "capabilities",
    "project",
    "security"
  ],
  "title": "Manifest",
  "type": "object"
}