Files
kandev/apps/backend/cmd/plugin-fixture/fixture-package/ui/bundle.js
T
João Salavisa 330afb7901 feat(plugins): add main-top-bar slot for the default app top bar (#1841)
* feat(plugins): add main-top-bar slot for the default app top bar

Plugins could only contribute to the session/chat top bar via the
chat-top-bar slot. Add a main-top-bar slot, mounted on the default
app top bar (Home / Kanban / Tasks) across desktop, tablet, and
mobile headers, so plugins can surface workspace-scoped status or
actions outside of a task session.

slotProps: { workspaceId, workspaceLabel, currentPage } — no task or
session ids, since the bar isn't scoped to a task.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: narrow main-top-bar currentPage slot prop to a literal union

Addresses Claude Code Review feedback on PR #1841: MainTopBarSlotProps.currentPage
was typed as a plain string even though every caller only ever passes
"kanban" | "tasks" (matching KanbanHeaderProps.currentPage). Narrow the
plugin-facing contract to match so plugin authors get real type safety.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(e2e): cover the main-top-bar plugin slot

Extend the kandev-plugin-e2e fixture bundle to also register a
main-top-bar slot component, and assert it renders on the default
app top bar (Home) alongside the existing task-sidebar coverage.

Verified locally: apps/web e2e plugins.spec.ts passes, plugin-fixture
Go tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 17:18:34 +01:00

82 lines
2.4 KiB
JavaScript

/**
* Native JS UI bundle for the kandev-plugin-e2e fixture plugin
* (docs/plans/plugins/PLUGIN-API.md). A self-contained ES module — no
* imports, no bundled React — that calls `window.registerKandevPlugin(id,
* plugin)` at evaluation time and, on `initialize(registry, host)`,
* registers a nav item, a top-level route, a `task-sidebar` slot component,
* a `main-top-bar` slot component, and a `task.created` WS handler. Uses
* only host.React/host.jsx.
*
* The task-created counter lives in module scope (not component state) with
* a tiny listener set, so it survives across route navigations (the page
* component unmounts/remounts as the user navigates away and back).
*/
(function () {
var moduleCount = 0;
var listeners = new Set();
function emit() {
listeners.forEach(function (fn) {
fn(moduleCount);
});
}
function incrementCount() {
moduleCount += 1;
emit();
}
function useCounter(React) {
var state = React.useState(moduleCount);
var count = state[0];
var setCount = state[1];
React.useEffect(function () {
setCount(moduleCount);
listeners.add(setCount);
return function () {
listeners.delete(setCount);
};
}, []);
return count;
}
window.registerKandevPlugin("kandev-plugin-e2e", {
initialize: function (registry, host) {
var React = host.React;
var jsx = host.jsx;
function PluginPage() {
var count = useCounter(React);
return jsx(
"div",
{ id: "hello-plugin-page-root" },
jsx("h1", { id: "hello-plugin-page" }, "Hello E2E"),
jsx("span", { id: "hello-task-counter" }, String(count)),
);
}
function SidebarSlot() {
return jsx("div", { id: "hello-sidebar" }, "Hello E2E sidebar");
}
function MainTopBarSlot(props) {
var slotProps = props.slotProps || {};
return jsx("span", { id: "hello-main-top-bar" }, "Hello " + slotProps.currentPage);
}
registry.registerNavItem({
id: "e2e-hello",
label: "Hello E2E",
path: "/plugins/e2e-hello",
section: "main",
});
registry.registerRoute("/plugins/e2e-hello", PluginPage);
registry.registerComponent("task-sidebar", SidebarSlot);
registry.registerComponent("main-top-bar", MainTopBarSlot);
registry.registerWsHandler("task.created", function () {
incrementCount();
});
},
});
})();