> ## Documentation Index
> Fetch the complete documentation index at: https://empty-ad9a3406.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin Communication

> How plugins talk to each other via events and hooks

## Two Mechanisms

|                   | Events               | Hooks                        |
| ----------------- | -------------------- | ---------------------------- |
| **Purpose**       | Notify               | Extend                       |
| **Return values** | No                   | Yes                          |
| **Use case**      | "Something happened" | "Add your thing to my thing" |

## Events (Pub/Sub)

### Emitting

```javascript theme={null}
api.bus.emit('todo:added', { text: 'Buy milk', count: 3 });
```

### Listening

```javascript theme={null}
api.bus.on('todo:added', (data) => {
  console.log(`Total todos: ${data.count}`);
});
```

### One-Shot Listening

```javascript theme={null}
// Fires once, then auto-removes
api.bus.once('board:ready', (data) => {
  console.log('Board ready, only fires once');
});
```

### Unsubscribing

```javascript theme={null}
function handler(data) { /* ... */ }
api.bus.on('my-event', handler);

// Later:
api.bus.off('my-event', handler);  // Must be same reference
```

### Full Example

```javascript theme={null}
// === Plugin A: todo-list ===
export function setup(api) {
  const todos = [];

  function add(text) {
    todos.push({ text, done: false });
    api.bus.emit('todo:added', { text, count: todos.length });
  }

  function toggle(index) {
    todos[index].done = !todos[index].done;
    api.bus.emit('todo:toggled', { index, done: todos[index].done });
  }
}

// === Plugin B: stats ===
export function setup(api) {
  let total = 0;
  let completed = 0;

  api.bus.on('todo:added', (data) => {
    total = data.count;
    render();
  });

  api.bus.on('todo:toggled', (data) => {
    completed += data.done ? 1 : -1;
    render();
  });
}
```

## Hooks (Extension Points)

### Registering

```javascript theme={null}
// Toolbar plugin exposes an extension point
api.registerHook('sidebar:add-section', (config) => {
  const section = document.createElement('div');
  section.innerHTML = `<h4>${config.title}</h4>`;
  sidebar.appendChild(section);
  return section;
});
```

### Using

```javascript theme={null}
// Another plugin adds a section to the sidebar
const sections = api.useHook('sidebar:add-section', {
  title: 'My Section',
  content: 'Hello from my plugin!'
});
// sections → [sectionElement, ...]
```

### Removing

```javascript theme={null}
function myHandler(config) { /* ... */ }

api.registerHook('my-hook', myHandler);
// Later (e.g., in teardown):
api.removeHook('my-hook', myHandler);
```

### Full Example

```javascript theme={null}
// === Plugin A: toolbar ===
export function setup(api) {
  const toolbar = document.createElement('div');
  toolbar.style.cssText = 'display:flex; gap:8px; padding:12px;';

  api.registerHook('toolbar:add-button', (config) => {
    const btn = document.createElement('button');
    btn.textContent = config.label;
    btn.onclick = config.action;
    toolbar.appendChild(btn);
    return btn;
  });

  // Invoke hooks after all plugins are loaded
  api.bus.once('board:allPluginsLoaded', () => {
    api.useHook('toolbar:add-button', {
      label: '🏠 Home',
      action: () => goHome()
    });
  });

  api.boardEl.appendChild(toolbar);
}

// === Plugin B: my-tools ===
export function setup(api) {
  // Wait for toolbar to register its hook
  api.bus.once('board:allPluginsLoaded', () => {
    api.useHook('toolbar:add-button', {
      label: '📊 Stats',
      action: () => showStats()
    });

    api.useHook('toolbar:add-button', {
      label: '⚙️ Settings',
      action: () => openSettings()
    });
  });
}
```

## Reacting to Lifecycle Events

### After All Plugins Load

Use `board:allPluginsLoaded` when your plugin depends on other plugins being ready:

```javascript theme={null}
export function setup(api) {
  // Register my hooks immediately
  api.registerHook('my-plugin:add-item', (config) => { /* ... */ });

  // Use other plugins' hooks after they're all loaded
  api.bus.once('board:allPluginsLoaded', () => {
    api.useHook('toolbar:add-button', {
      label: 'My Action',
      action: () => doSomething()
    });
  });
}
```

### Listening to Other Plugins' Events

```javascript theme={null}
// React when a sticky note is created
api.bus.on('sticky-note:created', ({ id }) => {
  api.notify(`New note created: ${id}`, 'info');
});

// React when a clock ticks
api.bus.on('clock:tick', ({ hours, minutes }) => {
  if (hours === 0 && minutes === 0) {
    api.notify('Midnight!', 'warning');
  }
});
```

## Best Practices

<AccordionGroup>
  <Accordion icon="lock" title="Don't import other plugins">
    Always use events or hooks — never `import('./other-plugin.js')`.
  </Accordion>

  <Accordion icon="tag" title="Use namespaced event names">
    `my-plugin:action` not `action`.
  </Accordion>

  <Accordion icon="rotate-left" title="Clean up listeners">
    Remove event listeners and hooks in your `teardown()` function:

    ```javascript theme={null}
    function handler(data) { /* ... */ }

    export function setup(api) {
      api.bus.on('my-event', handler);
      api.registerHook('my-hook', myHandler);
    }

    export function teardown() {
      api.bus.off('my-event', handler);
      api.removeHook('my-hook', myHandler);
    }
    ```
  </Accordion>

  <Accordion icon="clock" title="Use `once` for one-time setup">
    ```javascript theme={null}
    api.bus.once('board:ready', () => {
      // Initialization that should only happen once
    });
    ```
  </Accordion>
</AccordionGroup>
