Placements & Navigation
Placements control where your app’s UI appears in the NimbleBrain shell layout. Each placement maps a ui:// resource to a slot in the shell and optionally registers a route for navigation.
The shell layout has the following slots:
| Slot | Where it renders | Description |
|---|---|---|
sidebar.<group> |
Left sidebar, under the focused workspace’s Apps heading, and the workspace overview’s app grid | Your app’s entry in the workspace. Every sidebar.<group> placement renders in the one Apps group, whatever the group name. Give it a route. |
sidebar |
No nav entry for a connector | Reserved for the host’s own views (Conversations, Automations, Files). A connector’s sidebar placement with a route still registers that route. |
sidebar.bottom |
The bottom of the mobile navigation drawer | A pinned item. It does not render in the desktop sidebar. |
main |
A route, with no nav entry | A full-page view. Requires a route. Reachable by URL or when your app navigates to it. |
settings |
The connector’s settings page | Your connector’s settings component, rendered under a Settings heading on Settings → Connectors → your connector, after its connection sections and before tool permissions. See Settings sections. |
An unknown slot is dropped, never fatal.
Declaring placements
Section titled “Declaring placements”Add a placements array to your manifest’s _meta["ai.nimblebrain/host"]:
{ "_meta": { "ai.nimblebrain/host": { "host_version": "1.0", "name": "Tasks", "icon": "check-square", "placements": [ { "slot": "sidebar.apps", "resourceUri": "ui://dashboard", "priority": 30, "label": "Tasks", "icon": "check-square", "route": "tasks" }, { "slot": "settings", "resourceUri": "ui://settings" } ] } }}Priority sorting
Section titled “Priority sorting”Within a slot, placements sort by priority, ascending, then by label (or route) alphabetically. The default priority is 100.
priority: 10 → appears firstpriority: 50 → appears secondpriority: 100 → appears third (default)The sidebar shows the first four apps under Apps, then a View all link to the workspace overview, which lists every app. There is no grouping by priority band.
Route-based navigation
Section titled “Route-based navigation”A placement with a route registers a URL inside the workspace, under /w/<slug>/app/:
route value |
URL |
|---|---|
"tasks" |
/w/<slug>/app/tasks |
"my-app/reports" |
/w/<slug>/app/my-app/reports |
Clicking the app in the sidebar opens that URL and loads the placement’s resourceUri in the main area, beside the docked chat.
The primary view
Section titled “The primary view”The virtual primary resource path resolves to the first declared placement’s resourceUri, so the shell can load your main view without knowing its URI ahead of time. See UI Resources for how primary resolves.
The schema also accepts a primaryView object for back-compat, but the host does not consume it: placements are the source of truth for what is registered and routed. Declare a placement, not primaryView.
Sidebar rendering
Section titled “Sidebar rendering”Under the focused workspace, the sidebar shows:
- The host’s views: Conversations, Automations, Files, and the Inbox.
- Apps:
sidebar.<group>placements, sorted by priority, capped at four with a View all link. - Connectors: a link to the workspace’s connectors.
The group name in sidebar.<group> is not shown as a heading; every group renders under Apps. A main placement never appears here. To give your app a nav entry, declare a sidebar.apps placement with a route.
Mobile drawer
Section titled “Mobile drawer”On narrow screens the sidebar becomes a drawer. sidebar.bottom placements render at its foot, linking to their route. A sidebar.bottom placement with route: "settings" is not rendered: declare connector settings with the settings slot.
Specify icons using Lucide names in kebab-case:
{ "icon": "check-square" }{ "icon": "message-square" }{ "icon": "database" }{ "icon": "settings" }The resolver accepts kebab-case or PascalCase ("message-square" and "MessageSquare" both resolve to the MessageSquare component). If the name is not found, CircleDot is used as the fallback.
Size hints
Section titled “Size hints”The size field ("compact", "full", "auto") is accepted as a hint. The shell does not read it. A main or sidebar.<group> view fills the main area; a settings component is sized to its content.
Placement registry internals
Section titled “Placement registry internals”The PlacementRegistry is an in-memory store that tracks all active placements. It is updated when connectors are installed or uninstalled.
Every entry is either ambient (no wsId — platform apps like Conversations, Files, Automations; always present inside any workspace) or workspace-scoped (installed connectors, visible only to members of that workspace). The registry exposes a single read method, forWorkspace(wsId), which returns ambient + scoped entries merged and sorted. There is deliberately no “return everything” accessor — in a multi-tenant host, no legitimate caller wants placements unrelated to a workspace.
Registration — When a connector starts, its declared placements are registered via register(serverName, placements, wsId) against the installing workspace’s wsId. Platform apps register ambient (no wsId). The virtual primary resource path then resolves to the first registered placement’s resourceUri.
Querying — forWorkspace(wsId) returns ambient entries plus entries scoped to wsId, sorted by slot then priority (lower first).
Unregistration — When a connector is uninstalled, its placements for that workspace are removed. Other workspaces’ entries for the same connector are untouched.
// Register explicit placements, scoped to a workspaceregistry.register( "my-app", [ { slot: "sidebar", resourceUri: "ui://nav", priority: 50, label: "My App" }, { slot: "main", resourceUri: "ui://dashboard", route: "my-app" }, ], "ws_engineering",);
// Read the merged ambient + workspace-scoped listconst items = registry.forWorkspace("ws_engineering");// → sorted by slot then priorityExample: an app with a nav entry
Section titled “Example: an app with a nav entry”{ "_meta": { "ai.nimblebrain/host": { "host_version": "1.0", "name": "CRM", "icon": "users", "placements": [ { "slot": "sidebar.apps", "resourceUri": "ui://dashboard", "route": "crm", "label": "CRM", "icon": "users", "priority": 50 } ] } }}This adds CRM under the workspace’s Apps heading and to the workspace overview, and loads ui://dashboard at /w/<slug>/app/crm when clicked.