Frontend (SDK / UI)
A module frontend is built with React 19 + TypeScript + Vite, then loaded at runtime by the host. Sharing happens through @kubuno/* libs resolved via an import map.
The entry point (entry.ts) #
The host loads /modules/<id>/entry.js and calls the exported register() function. That is where you declare your (lazy) routes, sidebar, toolbar, search and slots:
import { lazy } from 'react'
import { RouteRegistry, SlotRegistry, SDK_VERSION,
useSidebarStore, useToolbarStore } from '@kubuno/sdk'
import './i18n'
import './index.css'
export const sdkVersion = SDK_VERSION
export function register() {
RouteRegistry.register('calendar', lazy(() => import('./CalendarApp')))
RouteRegistry.register('calendar/:view', lazy(() => import('./CalendarApp')))
useSidebarStore.getState().register({ moduleId: 'calendar', routePrefix: '/calendar', /* … */ })
useToolbarStore.getState().register({ moduleId: 'calendar', routePrefix: '/calendar', /* … */ })
SlotRegistry.register('app-dialogs', 'calendar', CalendarWorker)
}External specifiers #
At runtime, react, zustand, @kubuno/sdk and @ui are marked external in Vite: the host resolves them through its import map to its single instances. The npm packages are only used for building and typechecking.
The @ui specifier is not the package name (@kubuno/ui): it is an alias declared in tsconfig.json (the paths field).
Non-negotiable UX rules #
- Never use browser dialogs (
alert/confirm/prompt) → use the SDK'sConfirmDialog/useConfirmandprompt(). - Context menus:
MenuDropdownfrom@ui— never a hand-rolled floating div. - CSS: emit Tailwind utilities into the
kubuno-modulecascade layer so you never override the host shell. Per-module accents go through[data-module="<id>"].
The full list of components and stores is in the SDK & UI reference.