Groundfloor Docs

Manifest and Routes

App manifest schema, route registration, and publish metadata.

Audience: Federated app developers and Shell host team
Stored in: Control Plane Postgres (apps.manifest_json)
Validated by: app/domain/entities/shell_manifest.py when app_kind = shell_federated

The manifest is the contract between Portal, Control Plane bootstrap API, and the Shell host. Extra keys are allowed until the joint full schema is locked.


Identity: slug vs appId

FieldWherePurpose
slugApp row (apps.slug)Portal URLs, bootstrap ?slug=, Shell path /apps/{slug}/…
manifest.appIdManifest JSONModule Federation scope name
app.idUUIDAuthenticated APIs, release storage keys

Convention: keep slug and appId identical unless you have a deliberate reason not to. Bootstrap uses slug; webpack remotes use appId.

appId pattern: ^[a-zA-Z][a-zA-Z0-9._-]{0,62}$


Required fields (Phase 1)

FieldTypeDescription
versionstringSchema version, e.g. "1.0"
appIdstringFederation scope (see pattern above)
namestringDisplay name (max 120 chars)
routesarrayNavigation entries (see below)
themeobjectShell chrome colors

Route object

FieldRequiredDescription
pathYesRoute segment under the app (e.g. home, settings)
titleYesSidebar / nav label
iconNoIcon name (Shell maps to lucide or design system)
layoutNoFuture layout blocks; default []

Shell URL convention:

/apps/{slug}/{route.path}

Example: slug my-app, route path page-1/apps/my-app/page-1

The federated remote must export a module matching the route contract your Shell host expects (see Shell starter-kit CONTEXT.md).

Theme object

FieldRequiredDescription
primaryColorYesCSS hex, e.g. #2563EB
accentColorYesCSS hex, e.g. #7C3AED
modeNo"light" or "dark" (default "light")

Shell applies these to CSS variables / design tokens for app chrome around the federated remote.


Set on publish

FieldTypeDescription
remoteUrlstring (URL)Full URL to remoteEntry.js after Portal publish

Empty before first publish. Shell uses SHELL_REMOTE_DEV_URL in dev when empty.


Optional fields (pass-through)

FieldPurpose
displayNameAlternate display string
descriptionApp blurb
iconApp-level icon
collectionsHints for data collections the app uses (nav / docs)

Draft fields (joint schema session — not validated yet)

These may appear in manifests today but are not enforced by Control Plane validation. Documented for alignment with future Coderunner/Shell unified schema:

FieldPurpose
runtimepython | nodejs | dotnet | nextjs
build.install / build.startCoderunner build commands
env_requiredSecret keys the app expects
resourcesCPU/memory/replica hints

See docs/HANDOVER-AMIN.md Ask 5 for the proposed merged shape.


Sample manifest

From Portal apps/customer/src/lib/apps/manifest.ts:

{
  "version": "1.0",
  "appId": "my-app-2",
  "name": "My App 2",
  "displayName": "My App 2",
  "description": "Starter remote; Shell federated app.",
  "icon": "Sparkles",
  "theme": {
    "primaryColor": "#2563EB",
    "accentColor": "#7C3AED",
    "mode": "light"
  },
  "collections": [],
  "routes": [
    {
      "path": "page-1",
      "title": "Page 1",
      "icon": "FileText",
      "layout": []
    }
  ],
  "remoteUrl": ""
}

Register and update (Portal / API)

Register:

POST /v1/workspaces/{workspace_id}/apps
Authorization: Bearer {token}
Content-Type: application/json

{
  "name": "My App",
  "slug": "my-app",
  "app_kind": "shell_federated",
  "manifest": {  }
}

Patch manifest:

PATCH /v1/workspaces/{workspace_id}/apps/{app_id}
Authorization: Bearer {token}
Content-Type: application/json

{ "manifest": {  } }

Validation runs on register/patch for shell_federated apps. Invalid appId or missing required fields return 422.


Publish flow (updates remoteUrl)

Publishing does not replace the whole manifest — it adds/updates remoteUrl and creates an app_releases row.

  1. POST …/apps/{app_id}/releases/upload-url — presigned PUT for bundle
  2. Client PUTs remoteEntry.js or .zip
  3. POST …/apps/{app_id}/releases/{release_id}/finalize — extracts zip if needed, writes remoteUrl into manifest

Bootstrap returns the latest published release plus current manifest.


Module Federation mapping

const scope = config.manifest.appId;
const entry = config.manifest.remoteUrl; // full URL to remoteEntry.js

// remotes[scope] = `${base}@${entry}`  — see 02-shell-bootstrap.md

Remote package name / exposed modules must match what Shell imports (starter-kit documents the exact export names).


Validation errors (common)

ErrorFix
appId pattern mismatchStart with letter; use [a-zA-Z0-9._-] only
Missing routes or empty route path/titleAdd at least one route for Shell nav
Missing theme.primaryColorInclude full theme object
Bootstrap 400 not shell_federatedRe-register with correct app_kind

On this page