@agent-plane/ui-shell
v0.6.1
Published
Shared top-nav React component + PageLayout + canonical stylesheet used by the agent-plane host SPA and every extension UI bundle. Pure-presentational; props-driven; no Zustand/store coupling.
Readme
@agent-plane/ui-shell
Shared top-nav React component for the agent-plane host SPA and every extension UI bundle.
Why this package exists
Before this package, every extension UI bundle hand-rolled a smaller clone of the host's top-bar. The bundles drifted: each one chose a different subset of kernel links, and the operator saw a different shell from each domain. This package extracts the rendering so all bundles share one source of truth.
Contract
- Pure presentational. No Zustand, no react-query, no module-level state. Every value is a prop.
- Peer deps: React 19, react-router-dom 7. Consumers must already use them.
- No environment-specific defaults. This package stays public-npm safe: nothing internal-URL or secret leaks in.
/ui/extensions/*paths render as plain anchors (cross-SPA navigation), withaccount_idappended. All other paths render as react-router-domNavLinks.
Consumers (and how to consume)
Pin the exact version (no ^, no ~). Each consumer also runs a
lockfile-integrity check (scripts/check-ui-shell-pin.*) as a merge
blocker.
// package.json
{
"dependencies": {
"@agent-plane/ui-shell": "0.1.0"
}
}import { AppNavBar } from '@agent-plane/ui-shell';
import type { BadgeProvider } from '@agent-plane/ui-shell';
const badges: BadgeProvider = {
countFor(path, key) {
if (key === 'inbox_unread') return store.inboxCount;
if (path === '/work') return store.activityCount;
return 0;
},
};
return (
<AppNavBar
brand="Email Agent"
groupedItems={[{ path: '/conversations', label: 'Chat' }, inboxNavItem!]}
items={[
{ path: '/work', label: 'Work' },
{ path: '/graph/studio', label: 'Graph' },
{ path: '/profiles', label: 'Profiles' },
...packNavItems,
]}
pluginMenu={pluginMenuFromApi}
excludePluginPath={inboxNavItem?.path}
accountId={accountId}
forceActive={[{ forItem: '/projects', whenPathStartsWith: '/clients' }]}
badgeProvider={badges}
trailingItems={[{ path: '/docs', label: 'Docs' }]}
rightSlot={<UserMenuButton />}
/>
);Tests
The package ships behaviour parity tests under test/AppNavBar.test.tsx.
Six named cases — each is a merge blocker for any change here:
parity_anchor_for_ui_extensions_pathsparity_account_id_appended_on_plugin_linksparity_active_link_clients_maps_to_projectsparity_active_link_exact_matchparity_badge_count_renders_when_provider_returns_positiveparity_plugin_menu_self_filter
Run with npm test.
Release
See RELEASE.md.
