# Interface plugins

Add pages, panels, slash menu items, window overlays and settings sections to the Loam window with a UI plugin.

A UI plugin adds parts to the Loam window: pages that open in a tab, panels in the sidebars or the status bar, items in the `/` menu of a note, and views that float over the window. Everything it registers is removed when the plugin turns off. Keep the data and the commands in a logic module, so the CLI and agents can do the same work without a window.

## What a UI plugin can add

- `registerView(ViewType(...))`: a page that opens in a tab.
- `registerPanel(Panel(...))`: a panel in the `.leading` or `.trailing` sidebar, or in the `.statusBar`.
- `addSidebarLink(SidebarLink(...))`: a row in the navigation at the top of the left sidebar.
- `addSlashItem(SlashItem(...))`: an item in the `/` menu of notes. It does nothing when no editor is active.
- `addWindowOverlay(WindowOverlay(...))`: a view over the bottom-trailing corner of the window.
- `addSettingsSection(SettingsSection(...))`: a section of the plugin page in Settings.
- `registerCommand(Command(...))`: an action for the palette, the CLI and agents.

## A minimal UI plugin

This is the `activateUI` function that `loam plugin new word-panel --ui` generates. It adds a panel on the right side and a command. A UI plugin conforms to `UIPlugin`, and `activateUI` runs on the main actor:

**Sources/WordPanel/WordPanelPlugin.swift**

```swift
@MainActor
public func activateUI(context: PluginContext) throws {
    let counter = NoteCounter(vault: context.vault)
    try context.registerPanel(Panel(
        id: "word-panel.panel",
        title: "Word Panel",
        symbol: "hand.wave",
        slot: .trailing
    ) {
        AnyView(WordPanelPanel(notes: counter.count()))
    })
    try context.registerCommand(Command(
        id: "word-panel.hello",
        title: "Word Panel: say hello",
        summary: "Says hello and how many notes the vault has. No arguments."
    ) { _ in
        await MainActor.run { "Hello from Word Panel. Notes in this vault: \(counter.count())." }
    })
}
```

The view is SwiftUI. To refresh it when notes change, listen for `VaultChanged` and reload.

## Reach the window through contracts

A UI plugin reaches the window only through the services in its context:

- `context.workspace`: opens a resource, for example `workspace.open(.file(path), placement: .replace)`. The placements are `replace`, `newTab` and `beside`.
- `context.quickPick`: a filterable list in a popup.
- `context.toasts?.show(_:isError:)`: a short message about work that ends on its own.
- `context.editor`: the Markdown editor. It is nil until an editor plugin is active, so list `editor` in `optionalRequires` to be restarted when it comes.

## Style and permissions

- Take colours from the `themeTokens` environment value, and animations from `Motion` in PluginKitUI. Honour Reduce Motion.
- The generated `plugin.json` of a UI plugin already sets `"permissions": ["ui", "vault.read"]`. Add `vault.write` to `permissions` only if a view writes notes.

## Check it in the app

The app loads a rebuilt library only after a restart. Rebuild the plugin, then restart Loam on the vault:

```bash
loam --vault ~/Notes plugin build word-panel
loam --vault ~/Notes app relaunch
```

`loam app` talks to the window that has the vault open, so the app must be open on it. Open the right sidebar with `loam app run sidebar.toggle-trailing`, then take a picture of the window with `loam app shot <file.png>` and look at it.

> **Tip:** For AI agents: `loam guide ui` lists the registration calls. `loam guide app` lists the commands that drive the open window.
