export type VMStatus = "idle" | "loading" | "ready" | "empty" | "error"; export type VMState = | { status: "idle"; data?: undefined; error?: undefined } | { status: "loading"; data?: T; error?: undefined } | { status: "ready"; data: T; error?: undefined } | { status: "empty"; data?: undefined; error?: undefined } | { status: "error"; data?: T; error: { messageKey: string; code?: string } }; export type ActionCallbacks = { readonly onCreate?: (draft?: Partial) => void | Promise; readonly onUpdate?: (item: T) => void | Promise; readonly onDelete?: (item: T) => void | Promise; readonly onRefresh?: () => void | Promise; }; export type I18nLabel = { readonly labelKey: string; readonly descriptionKey?: string; }; export type RegistryGate = { readonly permission?: string; readonly flag?: string; }; export type SidebarRegistryItem = I18nLabel & RegistryGate & { readonly id: string; readonly href: string; readonly icon?: string; readonly children?: readonly SidebarRegistryItem[]; }; export type BreadcrumbRegistryItem = I18nLabel & RegistryGate & { readonly id: string; readonly href?: string; }; export type MobileRegistryItem = I18nLabel & RegistryGate & { readonly id: string; readonly href: string; readonly priority?: number; }; export type PortalRegistryItem = I18nLabel & RegistryGate & { readonly id: string; readonly slot: string; readonly order?: number; }; export type ShellRegistries = { readonly sidebar?: readonly SidebarRegistryItem[]; readonly breadcrumbs?: readonly BreadcrumbRegistryItem[]; readonly mobile?: readonly MobileRegistryItem[]; readonly portals?: readonly PortalRegistryItem[]; }; // --- Subframe-Adapter contract (M015) --- /** Swappable UI implementation: renders a serializable component model to HTML. */ export type UiRenderer = (model: Model) => string; export type DataTableColumn = I18nLabel & { readonly key: string; readonly align?: "left" | "right" | "center" }; export type DataTableRow = { readonly id: string; readonly cells: Readonly> }; /** Domain View-Model a loader builds for a tabular list. */ export type DataTableVM = { readonly caption?: I18nLabel; readonly columns: readonly DataTableColumn[]; readonly rows: readonly DataTableRow[]; readonly emptyLabelKey?: string; }; /** Normalized component prop-model the UI implementation renders. */ export type DataTableModel = { readonly captionKey?: string; readonly columns: readonly { readonly key: string; readonly labelKey: string; readonly align: "left" | "right" | "center" }[]; readonly rows: readonly DataTableRow[]; readonly emptyLabelKey: string; readonly rowCount: number; }; export function ready(data: T): VMState { return { status: "ready", data }; } export function empty(): VMState { return { status: "empty" }; } export function assertI18nLabel(label: I18nLabel): I18nLabel { if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(label.labelKey)) { throw new Error(`Registry labels must use i18n keys: ${label.labelKey}`); } if (label.descriptionKey && !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(label.descriptionKey)) { throw new Error(`Registry descriptions must use i18n keys: ${label.descriptionKey}`); } return label; } // Domain View-Models (M013-Konvention: src//*.vm.ts) export * from "./walking-skeleton/walking-skeleton.vm.js"; export * from "./auth/session-list.vm.js"; export * from "./auth/select-tenant.vm.js"; export * from "./files/file-browser.vm.js"; export * from "./files/file-detail.vm.js"; export * from "./files/file-upload.vm.js"; export * from "./tasks/task-list.vm.js"; export * from "./tasks/task-detail.vm.js";