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.

Note

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's ConfirmDialog/useConfirm and prompt().
  • Context menus: MenuDropdown from @ui — never a hand-rolled floating div.
  • CSS: emit Tailwind utilities into the kubuno-module cascade 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.