# Starter recipes

These examples are intentionally small: plain HTML and JavaScript, no dependencies, no build step. Each directory can be loaded directly with **Load Unpacked (Dev)…** in a development build of Mango on macOS.

Use a reverse-DNS package identifier you control before distributing an extension. The identifiers below are examples only.

## A visible channel Sprig

Choose this when the user needs a persistent custom interface attached to one conversation.

```text
hello-pane/
├── manifest.json
└── index.html
```

`manifest.json`:

```json
{
  "grove": 1,
  "id": "com.example.grove.hello-pane",
  "name": "Hello Pane",
  "version": "0.1.0",
  "entry": "index.html",
  "presentation": {
    "surfaces": ["channelPane"],
    "defaultSurface": "channelPane",
    "defaultSize": { "width": 340, "height": 300 }
  },
  "capabilities": ["channel.read", "channel.send"],
  "hostPermissions": []
}
```

`index.html`:

```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello Pane</title>
<button id="hello">Say hello</button>
<ol id="messages"></ol>
<script>
  const messages = document.querySelector('#messages')

  grove.on('message', message => {
    const item = document.createElement('li')
    item.textContent = `${message.from}: ${message.text}`
    messages.append(item)
  })

  document.querySelector('#hello').addEventListener('click', () => {
    grove.send('Hello from Grove')
  })
</script>
</html>
```

Start it from a channel. New live messages appear in the list, and the button sends through Mango to that bound channel.

## A composer utility

Choose this when a temporary interface should return text to the message draft without sending automatically.

```text
quick-insert/
├── manifest.json
└── index.html
```

`manifest.json`:

```json
{
  "grove": 1,
  "id": "com.example.grove.quick-insert",
  "name": "Quick Insert",
  "version": "0.1.0",
  "entry": "index.html",
  "presentation": {
    "surfaces": ["composer"],
    "defaultSurface": "composer",
    "defaultSize": { "width": 420, "height": 240 }
  },
  "contributions": {
    "commands": [
      { "name": "greet", "summary": "Add a greeting to the draft" }
    ]
  },
  "capabilities": ["commands.register"],
  "hostPermissions": []
}
```

`index.html`:

```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quick Insert</title>
<button id="insert">Insert greeting</button>
<button id="cancel">Cancel</button>
<script>
  document.querySelector('#insert').addEventListener('click', () => {
    grove.composer.complete('Hello from Grove')
  })

  document.querySelector('#cancel').addEventListener('click', () => {
    grove.composer.close()
  })

  grove.on('command', invocation => {
    if (invocation.command === 'greet') grove.composer.complete('Hello from Grove')
  })
</script>
</html>
```

Open it from the composer's `+` menu, or type `/greet` to launch it on demand. **Insert greeting** and the command both close the utility and place the text in the current draft for review.

## A headless command Root

Choose this when Mango's native command and card UI is enough and the extension needs no pane of its own.

```text
hello-root/
├── manifest.json
└── index.html
```

`manifest.json`:

```json
{
  "grove": 1,
  "id": "com.example.grove.hello-root",
  "name": "Hello Root",
  "version": "0.1.0",
  "entry": "index.html",
  "presentation": {
    "surfaces": ["headless"],
    "scope": "server"
  },
  "capabilities": ["commands.register"],
  "hostPermissions": []
}
```

`index.html`:

```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Hello Root</title>
<script>
  grove.commands.register([{
    name: 'hello',
    summary: 'Show a Grove greeting'
  }])

  grove.on('command', invocation => {
    if (invocation.command !== 'hello') return
    grove.card({
      id: 'hello',
      title: 'Hello from Grove',
      body: `The command ran in ${invocation.target ?? 'Mango'}.`
    })
  })
</script>
</html>
```

Start the Root for a network, then type `/hello` in a conversation on that network. Mango renders the returned card above the composer.

## Grow from the smallest example

- Add an optional `settings.html` only when the extension needs configuration. Settings pages receive no network access.
- Add `hostPermissions` only for the exact HTTPS or WSS origins the runtime contacts.
- Add capabilities one feature at a time and test the consent flow after each change.
- Use `textContent`, not `innerHTML`, for IRC-controlled text.
- Keep a Root's work bounded and reconnect after lifecycle changes; it is client-side WebKit, not an always-on server.

## Complete public examples

The maintained examples below are public source repositories. Links target the release tags reviewed with these API 1 docs. Visit the [Mediaware EU repositories](https://github.com/orgs/Mediaware-EU/repositories) for newer releases, and use a release commit SHA when an immutable source reference is required.

| Example | What it demonstrates |
| --- | --- |
| [hello-vue v0.1.0](https://github.com/Mediaware-EU/hello-vue/tree/v0.1.0) | A visible Vue 3 and TypeScript Sprig. |
| [giphy-composer v0.1.10](https://github.com/Mediaware-EU/giphy-composer/tree/v0.1.10) | A composer utility, manifest-declared commands, settings page, Keychain configuration, direct API requests, focus, and draft completion. |
| [immich-composer v0.1.0](https://github.com/Mediaware-EU/immich-composer/tree/v0.1.0) | A self-hosted service, brokered OAuth with PKCE, native non-CORS requests, Keychain sessions, binary previews, and composer uploads. |
| [command-lab-root v0.1.0](https://github.com/Mediaware-EU/command-lab-root/tree/v0.1.0) | A compact Root for commands, native cards, IRC sends, and live message observation. |
| [weatherbot v0.2.1](https://github.com/Mediaware-EU/weatherbot/tree/v0.2.1) | Commands, cards, status badges, storage, external APIs, and channel events. |
| [relay-formatter v0.1.2](https://github.com/Mediaware-EU/relay-formatter/tree/v0.1.2) | Inbound filtering and native channel-member menu actions. |
| [fish-root v0.1.2](https://github.com/Mediaware-EU/fish-root/tree/v0.1.2) | Inbound and outbound filters with Keychain-backed legacy interoperability. |
| [tus-uploader v0.2.2](https://github.com/Mediaware-EU/tus-uploader/tree/v0.2.2) | An application-scoped upload-provider Root, settings, bounded file reads, progress, and resumable TUS streaming. |

Use these after the minimal recipe works. Start with [Package and manifest](package-manifest.md) and [Testing and distribution](testing-and-distribution.md) before shipping.
