Skip to content

Sidebar Tab API

JavaScript API injected by the Canopi host as window.canopi.

Initialization

javascript
const { registerTab, bridge, permissions, storage, user, theme, diagnostics } = window.canopi;

console.log(canopi.version); // e.g. "1.0.0"

TypeScript: include docs/types/canopi-sdk.d.ts from the repo.

registerTab()

Register a tab with the sidebar host.

javascript
const tab = canopi.registerTab({
  id: 'archive-assistant',
  title: 'Archive',
  icon: 'archive-icon',
  position: 'right', // 'left' | 'right'

  render: async (container, context) => {
    container.innerHTML = '<button id="go">Archive</button>';
  },

  onActivate: async (context) => { /* tab selected */ },
  onDeactivate: async (context) => { /* tab hidden */ },
  onDestroy: () => { /* cleanup */ },

  badge: {
    getValue: async () => 3,
    updateInterval: 30000,
  },
});

TabContext

FieldTypeDescription
containerHTMLElementMount point for tab UI
isActivebooleanTab currently visible
userIdstring | nullAuthenticated user
permissionsPermissionStatusGranted scopes

Bridge communication

Request/response over the host bridge:

javascript
// Get current page URL
const url = await bridge.request('page:getUrl');

// Send notification (no response)
bridge.notify('tab:ready', { tabId: 'archive-assistant' });

// Listen for host events
bridge.on('theme:changed', (payload) => {
  console.log('Theme:', payload.mode);
});

Permissions

javascript
const status = await permissions.check('archive:wayback');
if (!status.granted) {
  await permissions.request('archive:wayback');
}

Storage

javascript
// Local (tab-scoped)
await storage.local.set('lastArchive', url);
const last = await storage.local.get('lastArchive');

// Sync (cross-device, when available)
await storage.sync.set('prefs', { service: 'wayback' });

User context

javascript
const profile = await user.getProfile();
// { id, displayName, avatarUrl, ... }

const prefs = await user.getPreferences();

Theme

javascript
const mode = theme.getMode(); // 'light' | 'dark'
theme.onChange((mode) => applyStyles(mode));

Diagnostics

javascript
diagnostics.info('archive', 'Started archive job', { url });
diagnostics.error('archive', 'Failed', { error: err.message });

Error handling

javascript
try {
  await bridge.request('archive:wayback', { url });
} catch (err) {
  if (err.code === 'PERMISSION_DENIED') {
    await permissions.request('archive:wayback');
  }
}

Full spec: docs/SIDEBAR_TAB_SDK_API.md in the repo.

Canopi — Metaweb coordination layer for community-aware presence and messaging.