Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
4.9 KiB
Surfaces
Dynamic surface lifecycle. Core surfaces are registered at startup.
Extension surfaces are uploaded by admins and served from the
surface_registry table.
User Endpoints
List Enabled Surfaces
GET /surfaces
Returns enabled surfaces with minimal nav info (no source or enabled
fields — these are admin concerns).
{
"surfaces": [
{ "id": "chat", "title": "Chat", "route": "/" },
{ "id": "editor", "title": "Editor", "route": "/editor" },
{ "id": "hello-dashboard", "title": "Hello Dashboard", "route": "/s/hello-dashboard" }
]
}
Auth: Authenticated.
Admin Endpoints
List All Surfaces
GET /admin/surfaces
Returns all surfaces including disabled ones. Each entry is a full Surface Registry Object.
{
"surfaces": [
{ "id": "chat", "title": "Chat", "manifest": {...}, "enabled": true, "source": "core", "installed_at": "...", "updated_at": "..." },
{ "id": "hello-dashboard", "title": "Hello Dashboard", "manifest": {...}, "enabled": false, "source": "extension", "installed_at": "...", "updated_at": "..." }
]
}
Auth: Platform admin.
Get Surface
GET /admin/surfaces/:id
Returns full surface details including manifest.
Response: A single Surface Registry Object.
Errors:
404— surface not found (or store error).
Auth: Platform admin.
Install Surface
POST /admin/surfaces/install
Content-Type: multipart/form-data
Field: file — a .surface or .zip archive (zip containing
manifest.json + static assets). The manifest declares id, title,
route, required data loaders, scripts, and CSS.
Size limit: 50 MB.
Manifest validation: id and title are required.
Errors:
400— no file, wrong extension, too large, invalid zip, missing or invalid manifest, missingid/title.409— surface ID conflicts with an existing core surface.
Auth: Platform admin.
Enable / Disable
PUT /admin/surfaces/:id/enable
PUT /admin/surfaces/:id/disable
Disabled surfaces redirect to / and hide from navigation.
Undisableable surfaces: chat and admin cannot be disabled
(returns 400).
Errors:
400— attempting to disablechatoradmin.404— surface not found.
Auth: Platform admin.
Delete Surface
DELETE /admin/surfaces/:id
Removes the surface, its DB row, and its extracted static assets. Core surfaces cannot be deleted.
Errors:
400— attempting to delete a core surface.404— surface not found.
Auth: Platform admin.
Defense-in-depth: The store layer also enforces
WHERE source = 'extension' on delete, so even if the handler check
were bypassed, core surfaces are protected.
Surface Registry Object
{
"id": "hello-dashboard",
"title": "Hello Dashboard",
"manifest": { "route": "/s/hello-dashboard", "scripts": [...], "css": [...] },
"enabled": true,
"source": "core|extension",
"installed_at": "...",
"updated_at": "..."
}
Internal Store Methods
The SurfaceRegistryStore interface exposes methods beyond what the
HTTP API uses directly:
Seed(ctx, id, title, source, manifest)— upserts a surface at startup. Does not overwrite theenabledflag (admin toggle survives restarts). Called by the page engine'sSeedSurfaces().ListEnabled(ctx) → []string— returns IDs of all enabled surfaces. Used by the page engine for nav rendering (EnabledSurfaceIDs()), not exposed via HTTP.
Page Routes
Extension surfaces are served at /s/:slug via RenderExtensionSurface().
The page engine does a runtime DB lookup — no server restart needed after install.
Static assets are served from /surfaces/:id/ (nginx location block in
both unified and split deployment). In unified mode, the Go server
handles this route with path-traversal protection. In split deployment,
nginx serves directly from /data/surfaces/.
Surface Archive Format
A .surface file is a zip archive containing:
manifest.json — required (must have "id" and "title")
js/ — optional (extracted to /data/surfaces/{id}/js/)
css/ — optional (extracted to /data/surfaces/{id}/css/)
assets/ — optional (icons, images, etc.)
script.star — optional (Starlark entry point, v0.38.0)
star/ — optional (Starlark submodules, v0.38.0)
The archive may have a single top-level directory prefix (e.g.,
my-surface/manifest.json) — the installer strips it when locating
manifest.json and when extracting js/, css/, assets/, star/
directories and bare .star files. Files outside these directories
(e.g., script.js at root) are not extracted.
See EXTENSION-SURFACES.md for the full authoring guide including manifest schema, platform API, and CSS custom properties.