/** * Hello World plugin — module entry point. * * The host calls mount(container, api) when the plugin tab is activated and * unmount(container) when it is torn down. * * api shape: * api.context — current PluginContext snapshot * api.onContextChange(cb) → unsubscribe — called whenever theme/project/session changes * api.rpc(method, path, body?) → Promise — proxied to this plugin's server subprocess */ export function mount(container, api) { // ── Build DOM ────────────────────────────────────────────────────────────── container.innerHTML = `

Hello from a plugin!

This tab is rendered by a plain ES module — no iframe.

Context

Theme
Project
Session

Server RPC

Calls this plugin's Node.js server subprocess via the RPC bridge.

Click a button to make an RPC call…
`; // ── Theme helper ────────────────────────────────────────────────────────── function applyTheme(theme) { const root = container.querySelector('#hw-root'); if (!root) return; const isDark = theme === 'dark'; root.style.background = isDark ? '#1a1a2e' : '#ffffff'; root.style.color = isDark ? '#e0e0e0' : '#1a1a2e'; const pre = container.querySelector('#hw-result'); if (pre) pre.style.background = isDark ? '#2a2a3e' : '#f0f0f4'; } // ── Render context values ───────────────────────────────────────────────── function renderContext(ctx) { const t = container.querySelector('#hw-theme'); const p = container.querySelector('#hw-project'); const s = container.querySelector('#hw-session'); if (t) t.textContent = ctx.theme || '—'; if (p) p.textContent = ctx.project ? ctx.project.name : '(none)'; if (s) s.textContent = ctx.session ? (ctx.session.title || ctx.session.id) : '(none)'; applyTheme(ctx.theme); } // Apply initial context renderContext(api.context); // Subscribe to future changes const unsubscribe = api.onContextChange(renderContext); // ── Button styles ───────────────────────────────────────────────────────── container.querySelectorAll('button').forEach((btn) => { Object.assign(btn.style, { padding: '8px 16px', border: '1px solid currentColor', borderRadius: '6px', background: 'transparent', color: 'inherit', cursor: 'pointer', fontSize: '0.85rem', opacity: '0.8', }); btn.addEventListener('mouseenter', () => { btn.style.opacity = '1'; }); btn.addEventListener('mouseleave', () => { btn.style.opacity = '0.8'; }); }); // ── RPC buttons ─────────────────────────────────────────────────────────── const resultEl = container.querySelector('#hw-result'); container.querySelector('#hw-btn-hello').addEventListener('click', async () => { resultEl.textContent = 'Loading…'; try { const data = await api.rpc('GET', '/hello'); resultEl.textContent = JSON.stringify(data, null, 2); } catch (err) { resultEl.textContent = `Error: ${err.message}`; } }); container.querySelector('#hw-btn-echo').addEventListener('click', async () => { resultEl.textContent = 'Loading…'; try { const data = await api.rpc('POST', '/echo', { greeting: 'Hello from the plugin module!' }); resultEl.textContent = JSON.stringify(data, null, 2); } catch (err) { resultEl.textContent = `Error: ${err.message}`; } }); // Store unsubscribe so unmount can clean up container._hwUnsubscribe = unsubscribe; } export function unmount(container) { if (typeof container._hwUnsubscribe === 'function') { container._hwUnsubscribe(); delete container._hwUnsubscribe; } container.innerHTML = ''; }