Island content
An island content plugin supplies the body of a workspace tab. Users can open it beside their current page, move it between islands, or add it to a stack of tabs. Timon supplies the frame, tab bar, resize gaps and keyboard controls.
Declare apiVersion: 8, kind: "island" and
client.slots: ["island.content"]. Keep React and @timon/ui external. No host
entry or permission is needed for a browser-only content. Data access still
requires the permissions and authentication of the API it calls.
The supplied examples/plugin-island package is a complete browser-only
template. Its manifest ID is example-island; its registered key is board,
so the content type is example-island:board. The npm package name does
not determine this ID.
Register the type
Inside apply(ctx), register a component without props:
return ctx.slots.register({
slot: 'island.content',
key: 'board',
island: {
title: 'Board',
parseParams(raw) {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null
return typeof raw.id === 'string' && /^[a-z0-9-]{1,40}$/.test(raw.id)
? { id: raw.id } : null
},
params: { id: { type: 'string', required: true, maxLength: 40 } },
singletonBy: 'id',
},
}, Board)
parseParams validates and cleans incoming or saved params; null refuses
them. params describes fields to agents composing layouts: type (string,
number, boolean), required, nullable, enum, maxLength, min, max
and description. Without params, an agent's proposal is accepted with a
warning and only parseParams judges it, when the user applies it: declare
them. Validation in the browser still runs. Persist identifiers,
never secrets or business data: params must be flat and fit within 1 KiB.
Read the actual data through your authenticated plugin API.
Registration makes the type available; it does not open a tab automatically. Return the slot disposer so unloading releases the type. Saved tabs remain as unavailable placeholders while the plugin is disabled and recover when it returns.
Read the tab context
Import useIslandContent from @timon/ui inside your component:
const { params, setParams, setTitle, close, focused, active, primary } = useIslandContent()
The hook also returns islandId, tabId and type. setParams(next) passes
through your parser before saving. setTitle(null) restores the registered
title; close() closes this tab. The hook throws outside island content.
primary is useful for an exclusive group sharing a single resource.
Inactive tabs unmount by default: clean up requests, subscriptions and other
effects. Declare keepMounted: true only when hiding the content must preserve
its mounted instance. The context's active tells a kept instance when to pause.
Open or drag content
From a button or menu already in one of your declared slots:
import { openInIsland, startIslandDrag } from '@timon/ui'
const content = { type: `${ctx.id}:board`, params: { id: 'demo' } }
const opened = openInIsland(content, { edge: 'right' })
Edges are right (default), left, top, bottom, and center (a tab in the
focused island). Narrow views open tabs. An already open identity is activated;
false means no workspace is ready or the opening was refused. Show a useful
message on refusal. Limits are 8 islands, depth 3, 12 tabs per island and 32 in total.
For a draggable source, call startIslandDrag(event, content, { label, onDropped }) in onDragStart. Set your own dataTransfer types first; they
are preserved. onDropped runs only after acceptance. Optional hold: element
keeps the workspace drop layer down while the pointer is inside a source that
has its own drop targets. Always supply a click or menu action too, for keyboard
and touch users; drag and drop is unavailable in narrow workspaces.
To bootstrap the supplied template, ask your agent to use islands_get and
islands_propose to open example-island:board with {"id":"demo"}, then
apply the proposal card. Its button opens or drags a second board. A launcher
outside island content needs another declared surface, such as a widget; list
both kinds and both slots in that case.
Other content options
IslandContentOptions and IslandParamSpec are exported by @timon-ai/sdk.
singletonBy accepts one param name or a list identifying a resource; absent,
identical params identify the same content. minWidth and minHeight default
to 280 and 160 pixels. icon is SVG markup. exclusive names a group with a
single primary instance. detach(params) supplies an action to reopen the
content in its own window. volatileParams names fields to omit from named
presets. groupBy names a param whose value groups tabs of the type in one
island (a terminal's scope); newTab(params) makes the "+" of the tab bar
return the params of one more tab; onClose(params) runs when the user closes
the tab, to end what it held.
island.content admission is exact: a .widget slot does not grant it.
The host:route type belongs to the page host. Use the slot contract instead
of creating a second content registry or a nested workspace.