/**
* 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.
GET /hello
POST /echo
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 = '';
}