Browse API 1 documentation
GroveAPI 1preview

Starter recipes

Copyable minimal examples for panes, composer utilities, and Roots.

Mango 3.1+View Markdown

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 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 A visible Vue 3 and TypeScript Sprig.
giphy-composer 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 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 A compact Root for commands, native cards, IRC sends, and live message observation.
weatherbot v0.2.1 Commands, cards, status badges, storage, external APIs, and channel events.
relay-formatter v0.1.2 Inbound filtering and native channel-member menu actions.
fish-root v0.1.2 Inbound and outbound filters with Keychain-backed legacy interoperability.
tus-uploader 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 and Testing and distribution before shipping.