The Manifest
An OpenIslands app is a typed manifest: a JSON declaration of reusable visual islands bound to typed data contracts built from files you own. You edit the manifest. You never write rendering code; the runtime owns that. The payoff is durability: a manifest is small, declarative, and validated, so an agent can maintain it for months without the dashboard rotting.
The skeleton
Every manifest has the same spine: a version, a title, the datasets it reads, and the pages that render them.
{
"version": 1,
"title": "Finance Overview",
"icon": "wallet", // optional, the app's tile icon
"datasets": {
"net_worth": { "source": "data/net_worth_monthly.csv" } // a file you own (see Data Contracts)
},
"pages": [
{
"id": "overview", // one sidebar entry per page
"icon": "house",
"islands": [
{
"type": "metric.kpi",
"title": "Net worth",
"dataset": "net_worth", // names a dataset above
"value": "net_worth_eur", // a field that must exist in it
"compareTo": "prev",
"format": "eur",
"span": 3
}
]
}
]
}That single KPI island, fed its data, renders exactly like this. The docs run the real renderer, not a screenshot:
Net worth
Pages and groups
Each page is one entry in the left sidebar (a single-page app stays chrome-free). A page
holds either a flat list of islands or tabbed groups, never both. Groups render
as tabs under the page header, deep-linked via ?group=<id>:
{
"id": "holdings",
"groups": [
{ "id": "positions", "title": "Positions", "islands": [ /* ... */ ] },
{ "id": "activity", "title": "Activity", "islands": [ /* ... */ ] }
]
}A page may also declare shared filters rendered in the page header — a daterange over a
date column, or a select that narrows a categorical column (single or multiple, its choices
drawn from the column's live distinct values) — each re-querying every island bound to the
filtered column at once. See the Manifest Reference for the full shape.
Spans and the grid
Every page (and every group) is a 12-column grid. An island's span is how many columns it
takes, from 1 to 12. Each island type has a minimum span below which it stops being
legible: a table.grid needs 5, most charts need 4, a metric.kpi needs 2. Set a span
below the minimum and validate rejects it with a named error like "span 1 is below the
minimum 4 for timeseries.line." The runtime also floors spans responsively, so a tile never
renders narrower than its usable width on a small screen.
Fail loudly
The manifest's job is to make a broken dashboard impossible to ship silently. Every
island binds to named fields, and validate (and the agent's propose_edit) checks each one
against the live, DuckDB-inferred columns of the data:
- Bind to a field that exists → the island renders.
- Bind to a field that doesn't → the build fails and names the page, the island, and the missing field. You never get a silently-wrong chart pointed at a column that isn't there.
That check is the safety net the whole design rests on. Keep the manifest declarative, with no data transforms inside island configs (shaping lives in the data layer), and the net stays taut.
Where to go next
- Data Contracts: datasets, SQL transforms, and the binding check in detail.
- Islands: every built-in island and its required fields, with live previews.
- Manifest Reference: the exhaustive, schema-generated field list.