@cosmicdrift/kumiko-framework 0.398.0 → 0.400.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/AGENTS.md +33 -19
  2. package/agents/api/dev-server-testing.md +23 -0
  3. package/agents/api/framework-db.md +277 -0
  4. package/agents/api/framework-engine.md +1810 -0
  5. package/agents/api/framework-errors.md +119 -0
  6. package/agents/api/framework-event-store.md +99 -0
  7. package/agents/api/framework-seeding.md +26 -0
  8. package/agents/api/framework-ui-types.md +542 -0
  9. package/agents/api/renderer-web.md +1450 -0
  10. package/agents/api/testing-helpers.md +189 -0
  11. package/agents/api/testing-stack.md +144 -0
  12. package/agents/api/types.md +5124 -0
  13. package/agents/api.md +20 -0
  14. package/agents/examples/derived-fields-sql.md +251 -0
  15. package/agents/examples/documents.md +161 -0
  16. package/agents/examples/inspection-checklist.md +807 -0
  17. package/agents/examples.md +3 -0
  18. package/agents/features-index.md +178 -0
  19. package/agents/features.md +9 -1
  20. package/dist/api/entity-read-access.d.ts +2 -0
  21. package/dist/api/entity-read-access.js +46 -2
  22. package/dist/api/server-boot-checks.js +2 -2
  23. package/dist/api/server-routes.js +3 -1
  24. package/dist/db/derived-field-sql.d.ts +23 -0
  25. package/dist/db/derived-field-sql.js +115 -0
  26. package/dist/db/derived-related-sql.d.ts +31 -0
  27. package/dist/db/derived-related-sql.js +137 -0
  28. package/dist/db/driver-row-coercion.d.ts +1 -0
  29. package/dist/db/driver-row-coercion.js +13 -0
  30. package/dist/db/eagerload.d.ts +3 -1
  31. package/dist/db/eagerload.js +4 -6
  32. package/dist/db/event-store-executor-context.js +1 -1
  33. package/dist/db/event-store-executor-read-paging.js +1 -1
  34. package/dist/db/event-store-executor-read-search.d.ts +3 -0
  35. package/dist/db/event-store-executor-read-search.js +17 -9
  36. package/dist/db/event-store-executor-read-sort.d.ts +9 -2
  37. package/dist/db/event-store-executor-read-sort.js +27 -14
  38. package/dist/db/event-store-executor-read-where.d.ts +0 -5
  39. package/dist/db/event-store-executor-read-where.js +3 -9
  40. package/dist/db/event-store-executor-read.js +23 -4
  41. package/dist/db/event-store-executor-write-steps.d.ts +6 -1
  42. package/dist/db/event-store-executor-write-steps.js +20 -1
  43. package/dist/db/event-store-executor-write.js +8 -8
  44. package/dist/db/money.d.ts +4 -0
  45. package/dist/db/money.js +34 -29
  46. package/dist/db/parent-ref-clause.d.ts +1 -0
  47. package/dist/db/parent-ref-clause.js +18 -8
  48. package/dist/db/read-gate-clause.d.ts +7 -0
  49. package/dist/db/read-gate-clause.js +188 -0
  50. package/dist/db/read-ownership.d.ts +9 -0
  51. package/dist/db/read-ownership.js +12 -0
  52. package/dist/engine/boot-validator/boot-check.d.ts +3 -1
  53. package/dist/engine/boot-validator/boot-check.js +7 -11
  54. package/dist/engine/boot-validator/boot-context.d.ts +26 -0
  55. package/dist/engine/boot-validator/boot-context.js +57 -0
  56. package/dist/engine/boot-validator/boot-options.d.ts +33 -0
  57. package/dist/engine/boot-validator/boot-options.js +1 -0
  58. package/dist/engine/boot-validator/boot-phases-app.d.ts +5 -0
  59. package/dist/engine/boot-validator/boot-phases-app.js +103 -0
  60. package/dist/engine/boot-validator/boot-phases.d.ts +11 -0
  61. package/dist/engine/boot-validator/boot-phases.js +140 -0
  62. package/dist/engine/boot-validator/boot-report.d.ts +6 -0
  63. package/dist/engine/boot-validator/boot-report.js +47 -0
  64. package/dist/engine/boot-validator/boot-rule.d.ts +12 -0
  65. package/dist/engine/boot-validator/boot-rule.js +14 -0
  66. package/dist/engine/boot-validator/boot-warnings.d.ts +2 -0
  67. package/dist/engine/boot-validator/boot-warnings.js +20 -0
  68. package/dist/engine/boot-validator/derived-sql-fields.d.ts +4 -0
  69. package/dist/engine/boot-validator/derived-sql-fields.js +184 -0
  70. package/dist/engine/boot-validator/entity-handler.d.ts +3 -0
  71. package/dist/engine/boot-validator/entity-handler.js +6 -3
  72. package/dist/engine/boot-validator/entity-list-screens.js +3 -1
  73. package/dist/engine/boot-validator/field-conditions.d.ts +2 -0
  74. package/dist/engine/boot-validator/field-conditions.js +178 -0
  75. package/dist/engine/boot-validator/i18n-keys.d.ts +2 -2
  76. package/dist/engine/boot-validator/i18n-keys.js +1 -3
  77. package/dist/engine/boot-validator/index.d.ts +5 -34
  78. package/dist/engine/boot-validator/index.js +24 -204
  79. package/dist/engine/boot-validator/pii-retention.js +2 -7
  80. package/dist/engine/boot-validator/projection-list-screens.js +19 -1
  81. package/dist/engine/boot-validator/read-gate.d.ts +2 -0
  82. package/dist/engine/boot-validator/read-gate.js +54 -0
  83. package/dist/engine/boot-validator/screens-forms.js +19 -6
  84. package/dist/engine/boot-validator/screens-kind-entity-list.js +8 -6
  85. package/dist/engine/boot-validator/screens-lists.js +8 -0
  86. package/dist/engine/boot-validator/zod-shape.js +4 -2
  87. package/dist/engine/build-app-schema.js +11 -5
  88. package/dist/engine/embedded-derived.d.ts +8 -6
  89. package/dist/engine/embedded-derived.js +25 -7
  90. package/dist/engine/entity-handlers.d.ts +1 -1
  91. package/dist/engine/entity-handlers.js +17 -18
  92. package/dist/engine/entity-list-runtime-options.d.ts +5 -1
  93. package/dist/engine/entity-list-runtime-options.js +103 -4
  94. package/dist/engine/factories.d.ts +5 -5
  95. package/dist/engine/factories.js +3 -5
  96. package/dist/engine/field-access.d.ts +2 -0
  97. package/dist/engine/field-access.js +48 -1
  98. package/dist/engine/field-state.d.ts +12 -0
  99. package/dist/engine/field-state.js +67 -0
  100. package/dist/engine/handler-helpers.d.ts +3 -0
  101. package/dist/engine/handler-helpers.js +7 -1
  102. package/dist/engine/index.d.ts +2 -1
  103. package/dist/engine/index.js +2 -1
  104. package/dist/engine/ownership.d.ts +1 -1
  105. package/dist/engine/ownership.js +9 -5
  106. package/dist/engine/registry-state.d.ts +1 -0
  107. package/dist/engine/registry-state.js +1 -1
  108. package/dist/engine/schema-builder.js +3 -0
  109. package/dist/engine/strip-unreadable-related-lists.d.ts +4 -0
  110. package/dist/engine/strip-unreadable-related-lists.js +80 -0
  111. package/dist/entrypoint/index.js +1 -0
  112. package/dist/entrypoint/internals.d.ts +2 -0
  113. package/dist/files/assert-file-refs-belong-to-tenant.js +26 -6
  114. package/dist/files/bind-pending-file-refs.d.ts +3 -2
  115. package/dist/files/bind-pending-file-refs.js +11 -12
  116. package/dist/files/bind-referenced-files.write.js +5 -5
  117. package/dist/files/create-file-ref-from-bytes.d.ts +2 -0
  118. package/dist/files/create-file-ref-from-bytes.js +2 -0
  119. package/dist/files/create-file-ref.d.ts +2 -0
  120. package/dist/files/create-file-ref.js +39 -22
  121. package/dist/files/dispose-removed-file-refs.js +5 -8
  122. package/dist/files/file-field-slots.d.ts +10 -0
  123. package/dist/files/file-field-slots.js +53 -0
  124. package/dist/files/file-ref-entity.d.ts +1 -1
  125. package/dist/files/file-ref-table.d.ts +1 -1
  126. package/dist/files/file-routes.js +30 -9
  127. package/dist/files/index.d.ts +1 -0
  128. package/dist/files/index.js +1 -0
  129. package/dist/files/load-save-file-refs.js +3 -4
  130. package/dist/files/record-read-check.js +2 -30
  131. package/dist/files/record-write-check.d.ts +2 -0
  132. package/dist/files/record-write-check.js +18 -3
  133. package/dist/files/upload-field-resolution.d.ts +4 -1
  134. package/dist/files/upload-field-resolution.js +57 -12
  135. package/dist/pipeline/dispatch-batch.js +6 -5
  136. package/dist/pipeline/dispatch-write.js +2 -1
  137. package/dist/pipeline/write-result-masking.js +2 -1
  138. package/dist/schema-cli/validate-command.js +46 -12
  139. package/dist/schema-cli.js +1 -1
  140. package/dist/ui-types/index.d.ts +2 -1
  141. package/dist/ui-types/index.js +2 -1
  142. package/package.json +3 -3
  143. package/src/changes.json +67 -0
@@ -0,0 +1,1450 @@
1
+ <!-- Generated by `bun run gen:agent-docs` from packages/renderer/src/web/index.ts. Do not edit. -->
2
+
3
+ # @cosmicdrift/kumiko-renderer/web
4
+
5
+ Import from `@cosmicdrift/kumiko-renderer/web`. Types used here but not exported by any entry point are in `api/types.md`.
6
+
7
+ ## Functions
8
+
9
+ ```ts
10
+ function ActionMenu(props: ActionMenuProps): ReactNode;
11
+ function AiTextArea(props: AiTextAreaProps): ReactNode; // Multi-line variant of {@link AiTextField}.
12
+ function AiTextField(props: AiTextFieldProps): ReactNode; // Single-line AI text field — ghost-text completion + correct/translate/ rewrite toolbar.
13
+ function AppLayout(props: AppLayoutProps): ReactNode;
14
+ function Avatar(props: AvatarProps): ReactNode;
15
+ function BareFormProvider(props: { children: ReactNode; }): ReactNode;
16
+ function BooleanField(props: BooleanFieldProps): ReactNode; // Checkbox/Switch = Field(layout:"inline") + Input(kind:"boolean").
17
+ function Card(props: CardProps): ReactNode;
18
+ function ChoiceCards<T extends string>(props: { value: T | null; options: { value: T; title: string; description?: string; aside?: string; disabled?: boolean; }[]; onChange: (value: T) => void; ariaLabel?: string; testId?: string; }): ReactNode; // Card-styled radio group for mutually exclusive choices that need a description or price line.
19
+ function CollapsibleSection(props: { title: string; defaultOpen?: boolean; children: ReactNode; testId?: string; }): ReactNode; // Collapsible section using native <details> with React-controlled state; otherwise the open attribute resets to its initial value on every parent re-render.
20
+ function ComparisonTable<Col>(props: { columns: Col[]; columnHeader: (col: Col, index: number) => string; columnKey: (col: Col, index: number) => string; metrics: ComparisonMetric<Col>[]; metricLabel: string; testId?: string; className?: string; card?: boolean; }): ReactNode; // Transposed comparison table: row = metric, column = variant, with the best column optionally highlighted for each metric.
21
+ function DateField(props: DateFieldProps): ReactNode; // Date field = Field + Input(kind:"date"), ISO yyyy-mm-dd.
22
+ function DefaultAppShell(props: DefaultAppShellProps): ReactNode;
23
+ function DetailList(props: { rows: { id?: string; label: string; value: ReactNode; emphasize?: boolean; }[]; testId?: string; }): ReactNode; // Read-only key-value list for detail views (dimmed label on the left, value on the right).
24
+ function DispatcherProvider(props: DispatcherProviderProps): ReactNode;
25
+ function Drawer(props: DrawerProps): ReactNode; // Slide-in panel beside a list (e.g. mail reader next to the inbox) — thin wrapper over the Sheet primitive with header/body/footer slots so screens skip per-route Radix boilerplate.
26
+ function DropdownMenuCheckboxItem(props: ComponentPropsWithoutRef<typeof Primitive.CheckboxItem>): ReactNode;
27
+ function DropdownMenuContent(props: ComponentPropsWithoutRef<typeof Primitive.Content> & { portalContainer?: HTMLElement | null; }): ReactNode;
28
+ function DropdownMenuItem(props: ComponentPropsWithoutRef<typeof Primitive.Item>): ReactNode;
29
+ function DropdownMenuLabel(props: ComponentPropsWithoutRef<typeof Primitive.Label>): ReactNode;
30
+ function DropdownMenuSeparator(props: ComponentPropsWithoutRef<typeof Primitive.Separator>): ReactNode;
31
+ function EditorPanel(props: EditorPanelProps): ReactNode;
32
+ function EmptyState(props: { icon?: ReactNode; title: string; description?: string; action?: ReactNode; testId?: string; }): ReactNode; // Empty state with an optional icon and CTA, using the dashed-box style of entityList empty states as a standalone widget for custom screens.
33
+ function ErrorState(props: { error: DispatcherError; onRetry?: () => void; testId?: string; }): ReactNode; // Error state based on the Banner primitive, with a retry button.
34
+ function FeedList(props: { rows: FeedRow[]; emptyContent?: ReactNode; testId?: string; }): ReactNode; // Non-tabular compact list (e.g. "upcoming appointments") — primary text plus an optional right-aligned trailing value for each row.
35
+ function FileField(props: FileFieldProps): ReactNode; // File upload = Field + Input(kind:"file"|"image"), based on FileRef.
36
+ function FloatingPanel(props: FloatingPanelProps): ReactNode; // Non-modal panel floating above the page: draggable by its header, resizable from all edges and corners, clamped to the viewport, geometry optionally remembered.
37
+ function FormScreenShell(props: { children: ReactNode; className?: string; testId?: string; maxWidth?: FormScreenShellWidth; fillHeight?: boolean; }): ReactNode;
38
+ function HeaderActionGroup(props: HeaderActionGroupProps): ReactNode;
39
+ function Icon(props: { name: NavIconKey; className?: string; }): ReactNode;
40
+ function InfinityList<TData = unknown, TRow = Readonly<Record<string, unknown>>>(props: InfinityListProps<TData, TRow>): ReactNode; // Cursor-paginated scroll list (mail inbox, activity feeds) — loads the next page when the end sentinel becomes visible (IntersectionObserver), instead of a pager bar like QueryTable/entityList.
41
+ function KumikoLink(props: KumikoLinkProps): ReactNode;
42
+ function KumikoScreen(props: KumikoScreenProps): ReactNode;
43
+ function LanguageMenuItems(props: LanguageMenuItemsProps): ReactNode; // Locale choices as dropdown entries, for a user menu that carries the shell controls.
44
+ function LanguageSwitcher(props: LanguageSwitcherProps): ReactNode;
45
+ function LiveEventsProvider(props: LiveEventsProviderProps): ReactNode;
46
+ function LoadingState(props: { rows?: number; className?: string; testId?: string; }): ReactNode; // Loading state with skeleton rows styled like cards; `rows` controls the height.
47
+ function LocaleProvider(props: LocaleProviderProps): ReactNode;
48
+ function MiniStat(props: { label: string; value: string; tone?: StatTone; emphasize?: boolean; testId?: string; labelTestId?: string; valueTestId?: string; }): ReactNode; // Compact metric tile (without an icon) — for dense KPI grids.
49
+ function ModeSwitch<T extends string>(props: { value: T | null; options: { value: T; label: string; count?: number; }[]; onChange: (value: T) => void; ariaLabel?: string; variant?: ModeSwitchVariant; className?: string; testId?: string; }): ReactNode; // Segmented control for mutually exclusive modes—the prominent alternative to the buried <select>.
50
+ function NavProvider(props: NavProviderProps): ReactNode;
51
+ function NavTree(props: NavTreeProps): ReactNode;
52
+ function NumberField(props: NumberFieldProps): ReactNode; // Number field = Field + Input(kind:"number"); avoids repeating the id/name/value/onChange wiring at each screen.
53
+ function PageSection(props: PageSectionProps): ReactNode;
54
+ function PdfPreview(props: { source: PdfPreviewSource; testId?: string; }): ReactNode;
55
+ function PhotoSlots(props: PhotoSlotsProps): ReactNode; // Guided photo capture: one tile per requested shot in a two-column grid, each its own camera/gallery picker, filled tiles show the thumbnail.
56
+ function PlainContentEditor(props: ContentEditorProps): ReactNode;
57
+ function PlanCard(props: PlanCardProps): ReactNode;
58
+ function PlanGrid(props: PlanGridProps): ReactNode;
59
+ function PrimitivesProvider(props: PrimitivesProviderProps): ReactNode;
60
+ function ProfileMenu(props: ProfileMenuProps): ReactNode;
61
+ function ProgressBar(props: { value: number; tone?: ProgressTone; size?: "default" | "thin"; ariaLabel?: string; className?: string; testId?: string; }): ReactNode; // Progress bar, `value` 0..1 (clamped).
62
+ function ProgressList(props: { rows: ProgressListRow[]; emptyContent?: ReactNode; testId?: string; }): ReactNode; // Label/value header and progress bar for each entry (for example, loan repayment progress).
63
+ function PublicShell(props: PublicShellProps): ReactNode; // One header and footer for anonymous pages; `focus` narrows to a task column, `card` centers a single card (sign-in).
64
+ function QueryTable<TData = unknown>(props: QueryTableProps<TData>): ReactNode;
65
+ function RangeField(props: RangeFieldProps): ReactNode; // Slider = Field + Input(kind:"range").
66
+ function RenderEdit<TValues extends FormValues, TCtx = unknown>(rawProps: RenderEditProps<TValues, TCtx>): ReactNode;
67
+ function RenderField(props: RenderFieldProps): ReactNode;
68
+ function RenderList(props: RenderListProps): ReactNode;
69
+ function ResizableHandle(props: ResizablePrimitive.SeparatorProps & { withHandle?: boolean; }): JSX.Element;
70
+ function ResizablePanel(props: ResizablePrimitive.PanelProps): JSX.Element;
71
+ function ResizablePanelGroup(props: ResizablePrimitive.GroupProps): JSX.Element;
72
+ function ResultPanel(props: { title: string; subtitle?: string; empty?: boolean; emptyText?: ReactNode; rows?: { label: string; value: ReactNode; emphasize?: boolean; }[]; footer?: ReactNode; children?: ReactNode; testId?: string; }): ReactNode; // Result section for a calculator: SectionCard with an empty state (Banner) or a metric list (DetailList) plus optional extras (table, notes).
73
+ function ResultTable<Row>(props: { columns: ResultColumn<Row>[]; rows: Row[]; rowKey: (row: Row, index: number) => string; testId?: string; className?: string; card?: boolean; footer?: ResultTableFooterRow[]; }): ReactNode; // Static, prop-driven result table for calculated rows (tranches, scenarios).
74
+ function ScreenWidthProvider(props: { width: FormWidth | undefined; children: ReactNode; }): ReactNode;
75
+ function SectionCard(props: { title: string; subtitle?: string; action?: ReactNode; footer?: ReactNode; children: ReactNode; testId?: string; }): ReactNode; // Section with a title and optional action slot (range switcher, filter) — replaces repeated `<section className={CARD}>…<h2>` blocks in custom screens.
76
+ function SelectField(props: SelectFieldProps): ReactNode; // Select field = Field + Input(kind:"select").
77
+ function SideBySideTable(props: { caption: string; rowHeaderLabel: string; columns: SideBySideTableColumn[]; rows: SideBySideTableRow[]; rowHeaderWidth?: number; testId?: string; }): ReactNode; // Small read-only grid with real table semantics (`th scope`) whose cells are arbitrary React nodes — for side-by-side comparisons like "from the document" vs.
78
+ function Sidebar(props: SidebarProps): ReactNode;
79
+ function SidebarBrand(props: SidebarBrandProps): ReactNode;
80
+ function SidebarMenu(props: React.ComponentProps<"ul">): React.JSX.Element;
81
+ function SidebarMenuButton(props: React.ComponentProps<"button"> & { asChild?: boolean; isActive?: boolean; tooltip?: string | React.ComponentProps<typeof TooltipContent>; } & VariantProps<typeof sidebarMenuButtonVariants>): React.JSX.Element;
82
+ function SidebarMenuItem(props: React.ComponentProps<"li">): React.JSX.Element;
83
+ function SidebarPanel(props: SidebarPanelProps): ReactNode; // Renders its children into the second sidebar column when the shell offers one.
84
+ function SidebarProvider(props: React.ComponentProps<"div"> & { defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; }): React.JSX.Element;
85
+ function SidebarUser(props: SidebarUserProps): ReactNode;
86
+ function Sparkline(props: { points: number[]; className?: string; }): ReactNode;
87
+ function Stack(props: StackProps): ReactNode;
88
+ function StatCard(props: { icon?: ReactNode; label: string; value: string; sub?: string; tone?: StatTone; accentColor?: string; delta?: StatDelta; trend?: string; spark?: number[]; testId?: string; children?: ReactNode; }): ReactNode; // Icon-bearing metric tile with an optional delta chip, trend line, and sparkline.
89
+ function StatusBadge(props: { tone: StatusBadgeTone; children: ReactNode; className?: string; testId?: string; }): ReactNode; // Pill badge with a status dot.
90
+ function StatusBarChart(props: { entries: StatusBarEntry[]; ariaLabel: string; startLabel?: string; endLabel?: string; highlightLast?: boolean; dense?: boolean; testId?: string; }): ReactNode; // Status bar strip (e.g. 90-day uptime): variable-height bars with gradient fade + tick line on top; the last entry gets a "now" accent stripe.
91
+ function StepBar(props: { steps: string[]; currentIndex: number; compactLabel: string; onStepSelect?: (index: number) => void; doneSteps?: boolean[]; selectableSteps?: "done" | "all"; narrowLayout?: "label" | "steps"; orientation?: "horizontal" | "vertical"; heading?: string; description?: string; subtitles?: (string | undefined)[]; upNext?: { heading: string; title: string; subtitle?: string; }; testId?: string; compactTestId?: string; }): ReactNode; // Wizard step overview — numbered chips with a connector line between them.
92
+ function TextField(props: TextFieldProps): ReactNode; // Text field = Field + Input(kind:"text").
93
+ function TextareaField(props: TextareaFieldProps): ReactNode; // Multi-line text field = Field + Input(kind:"textarea").
94
+ function ThemeMenuItem(props: ThemeMenuItemProps): ReactNode; // Theme toggle as a dropdown entry, for a user menu that carries the shell controls.
95
+ function ThemeToggle(props: ThemeToggleProps): ReactNode;
96
+ function TimeseriesChart(props: { points: TimeseriesPoint[]; windowStartMs: number; windowEndMs: number; tone?: StatusTone; ariaLabel: string; axisLabels?: { start: string; mid?: string; end: string; }; xAxis?: TimeseriesXAxis; yAxis?: TimeseriesYAxis; height?: TimeseriesHeight; markers?: ChartMarker[]; formatMarkerTime?: (atMs: number) => string; referenceLines?: TimeseriesReferenceLine[]; emptyContent?: ReactNode; testId?: string; }): ReactNode; // Time-series line chart (smoothed line with area fill).
97
+ function ToastProvider(props: ToastProviderProps): ReactNode;
98
+ function TokensProvider(props: TokensProviderProps): ReactNode;
99
+ function Topbar(props: TopbarProps): ReactNode;
100
+ function UploadZone(props: UploadZoneProps): ReactNode; // Drop zone + multi-file picker with a per-file status row (uploading/done/ error).
101
+ function UserRolesProvider(props: UserRolesProviderProps): ReactNode;
102
+ function WorkspaceShell(props: WorkspaceShellProps): ReactNode;
103
+ function WorkspaceSwitcher(props: WorkspaceSwitcherProps): ReactNode; // Requires an ancestor `SidebarProvider` (see `../ui/sidebar`) — this component renders `SidebarMenuButton`, which calls `useSidebar()` internally and throws ("useSidebar must be used within a SidebarProvider.") when rendered outside one.
104
+ function applyTokensToCssVars(_tokens: Tokens): void; // Historically wrote tokens to :root as CSS variables.
105
+ function buildFileUploadFormData(input: FileUploadFormDataInput): FormData;
106
+ function buildNavRegistrySlice(schema: FeatureSchema, allowedNavQns?: ReadonlySet<string>): NavRegistrySlice;
107
+ function buildNavRegistrySliceForApp(app: AppSchema, allowedNavQns?: ReadonlySet<string>): NavRegistrySlice;
108
+ function clearRememberedListState(): void; // Forgets all remembered list state and the identity marker.
109
+ function cn(...inputs: ClassValue[]): string; // Standard shadcn helper: `clsx` combines conditional classes, while `tailwind-merge` resolves conflicts (for example, `px-2 px-4` → `px-4`).
110
+ function createBrowserDraftStorage(options?: CreateBrowserDraftStorageOptions): DraftStorage; // Default DraftStorage when createKumikoApp boots without one.
111
+ function createBrowserLocaleResolver(options?: CreateBrowserLocaleResolverOptions): LocaleResolver; // Default resolver when createKumikoApp is booted without a `locale` option.
112
+ function createEventSourceLiveEvents(options?: CreateEventSourceLiveEventsOptions): LiveEventSubscriber; // Returns an EventSource-backed `LiveEventSubscriber`.
113
+ function createKumikoApp(options?: CreateKumikoAppOptions): { root: Root; };
114
+ function createPublicSurface(options: CreatePublicSurfaceOptions): { root: Root; };
115
+ function createStaticLocaleResolver(options?: { locale?: string; timeZone?: string; }): LocaleResolver; // Default resolver for apps without their own i18n layer.
116
+ function downloadFileRef(fileRefId: string, options?: { fileName?: string; }): Promise<void>;
117
+ function fetchFileRefBlob(fileRefId: string): Promise<{ blob: Blob; fileName: string | undefined; }>;
118
+ function fileNameFromContentDisposition(header: string | null): string | undefined;
119
+ function filterAppSchemaNavsByAllowlist(schema: AppSchema, allowedNavQns: ReadonlySet<string>, reparentOverrides?: ReadonlyMap<string, NavReparentOverride>): AppSchema;
120
+ function filterByAccess(workspaces: WorkspaceSchema[], userRoles: string[] | undefined): WorkspaceSchema[];
121
+ function formatMoney(amountMinor: number, currency: string, locale?: string, fractionDigits?: number): string;
122
+ function formatPath(target: ScreenTarget): string;
123
+ function parsePath(pathname: string, hasWorkspaces?: boolean): NavRoute | undefined;
124
+ function parseTargetFromSearchParams(params: Readonly<Record<string, string>>): TargetRef | undefined; // Parses the current search parameters back into a TargetRef.
125
+ function postWithDownload(dispatcher: Dispatcher, type: string, payload: unknown): Promise<DispatcherError | null>;
126
+ function qualifyScreenId(featureName: string, screenId: string): string;
127
+ function readLoadedBuild(): KumikoBuild | undefined;
128
+ function reparentAppSchemaNavs(schema: AppSchema, overrides: ReadonlyMap<string, NavReparentOverride>): AppSchema;
129
+ function resolveDefaultId(visible: WorkspaceSchema[], preferredShortId: string | undefined): string | undefined;
130
+ function saveBlobAsFile(fileName: string, blob: Blob, options?: { bom?: boolean; }): void;
131
+ function smoothPath(pts: ReadonlyArray<{ x: number; y: number; }>): string; // Quadratic midpoint technique: smooths the zigzag line without overshoot (~5 lines instead of a Catmull-Rom/Bezier fit).
132
+ function synthesizeActionFormEntity(fields: Readonly<Record<string, FieldDefinition>>): EntityDefinition; // Builds a minimal EntityDefinition from the inline fields of an ActionForm or SecretMint screen.
133
+ function synthesizeActionFormScreen(screen: ActionFormScreenDefinition | SecretMintScreenDefinition): EntityEditScreenDefinition; // Converts an ActionFormScreenDefinition or SecretMintScreenDefinition to the EntityEditScreen shape expected by RenderEdit.
134
+ function uploadFileRef(file: Blob, target?: FileRefTarget): Promise<string>; // Posts one file to the stack's file endpoint (double-submit CSRF) and returns the new FileRef id.
135
+ function useBrowserNavApi(options?: { hasWorkspaces?: boolean; basePath?: string; features?: FeatureSchema[]; }): NavApi; // React hook that builds a NavApi from browser history.
136
+ function useBrowserTokensApi(): TokensApi; // Builds a TokensApi for the browser.
137
+ function useDispatchTarget(): (target: TargetRef) => void; // Recommended production dispatch.
138
+ function useDispatcher(): Dispatcher;
139
+ function useDispatcherStatus(): DispatcherStatus;
140
+ function useDraft<T extends object>(defaults: T, options?: { idPrefix?: string; }): { draft: T; patch: (changes: Partial<T>) => void; reset: () => void; field: <K extends keyof T>(name: K) => { id: string; name: string; value: T[K]; onChange: (v: T[K]) => void; }; }; // Form state for calculator screens: a draft object plus `patch` for partial updates, and `field(name)` that provides `{ id, name, value, onChange }` props ready for field widgets (NumberField/MoneyField/PercentField).
141
+ function useForm<TValues extends FormValues, TCtx = unknown>(options: UseFormOptions<TValues, TCtx>): UseFormResult<TValues>;
142
+ function useIsNarrowViewport(): boolean;
143
+ function useIsViewportBelow(breakpointPx: number): boolean;
144
+ function useLiveEvents(): LiveEventSubscriber; // Hook for a useQuery-like consumer.
145
+ function useLocale(): LocaleResolver; // Provides the current LocaleResolver and automatically subscribes to locale changes — the calling component re-renders when the language changes.
146
+ function useNav(): NavApi;
147
+ function usePrimitives(): PrimitivesRegistry;
148
+ function useQuery<TData = unknown>(type: string, payload: unknown, options?: UseQueryOptions): UseQueryResult<TData>;
149
+ function useResolvers(): ReadonlyMap<string, ResolverComponent>;
150
+ function useStore<T>(store: Store<T>): T;
151
+ function useStoreSelector<T, S>(store: Store<T>, select: (snapshot: T) => S, equals?: (a: S, b: S) => boolean): S;
152
+ function useToast(): ToastApi;
153
+ function useTokenController(): TokensApi; // Full API including theme toggling, for a dark/light switch button.
154
+ function useTokens(): Tokens; // Read-only tokens hook.
155
+ function useTranslation(): (key: string, params?: Readonly<Record<string, unknown>>) => string; // `t("key", params)` resolves in this order: 1.
156
+ function useUserRoles(): string[] | undefined;
157
+ ```
158
+
159
+ ## Constants
160
+
161
+ ```ts
162
+ const BROWSER_LOCALE_STORAGE_KEY: "kumiko:locale";
163
+ const DropdownMenu: FC<Primitive.DropdownMenuProps>;
164
+ const DropdownMenuPortal: FC<Primitive.DropdownMenuPortalProps>;
165
+ const DropdownMenuTrigger: ForwardRefExoticComponent<Primitive.DropdownMenuTriggerProps & RefAttributes<HTMLButtonElement>>;
166
+ const MoneyField: typeof NumberField;
167
+ const PercentField: typeof NumberField;
168
+ const STATUS_TONE_TEXT: Record<StatusTone, string>;
169
+ const cssVarTokens: Tokens; // Tokens with var-string values.
170
+ const defaultPrimitives: CorePrimitives;
171
+ const defaultTokens: Tokens; // Default tokens—identical to `cssVarTokens` (variable-string references).
172
+ const lightTokens: Tokens;
173
+ ```
174
+
175
+ ## Classes
176
+
177
+ ```ts
178
+ class FileDownloadFailedError extends Error {
179
+ constructor(status: number);
180
+ }
181
+ class FileUploadFailedError extends Error { // Carries the server's own `error` message when the upload endpoint sent one.
182
+ constructor(message: string, fromServer: boolean, httpStatus?: number, code?: string);
183
+ }
184
+ ```
185
+
186
+ ## Types
187
+
188
+ ```ts
189
+ type ActionMenuProps = {
190
+ trigger: ReactNode; // Free-form Trigger-Component — Avatar, IconButton, Pill, Custom.
191
+ triggerLabel?: string; // aria-label for the trigger wrapper.
192
+ items: MenuItemDef[];
193
+ align?: "start" | "center" | "end"; // Where to anchor the popup relative to the trigger.
194
+ minWidth?: string; // Minimum width for the content.
195
+ testId?: string;
196
+ };
197
+ interface AiTextAreaProps extends AiTextFieldBaseProps {
198
+ rows?: number;
199
+ }
200
+ interface AiTextFieldProps extends AiTextFieldBaseProps {}
201
+ type AppLayoutProps = {
202
+ topbar?: ReactNode; // Optional.
203
+ sidebar?: ReactNode;
204
+ children: ReactNode;
205
+ testId?: string;
206
+ fill?: boolean; // Viewport-fit shell.
207
+ className?: string; // Optional classes appended to the root (for a custom background, etc.).
208
+ mainClassName?: string; // Optionaler Klassen-Append an `<main>`.
209
+ };
210
+ interface AppPrimitives {} // Extension point for app-owned primitives.
211
+ interface AppTokens {} // Extension point for app-specific token categories.
212
+ type AvatarProps = {
213
+ id: string; // Stable identifier—the source for the background-color hash.
214
+ label: string; // Full name or email—the initials are extracted from this value.
215
+ size?: AvatarSize;
216
+ testId?: string;
217
+ };
218
+ type AvatarSize = "sm" | "md" | "lg";
219
+ type BannerProps = { // Banner for an inline message or page state (for example, "Loading…" or "Screen not found").
220
+ variant?: "error" | "info" | "loading" | "warning" | "primary"; // "error" for alerts (conflict, network error), "info" for neutral placeholders (not-found, loading), "loading" for load states, "warning" for a non-blocking but attention-grabbing notice (e.g. secretMint's "this is shown only once"), "primary" for a call-to-action notice (neutral background, accent in the primary color; pair it with `title` and `actions`).
221
+ title?: ReactNode; // Bold heading above `children`, inside the text column.
222
+ titleTestId?: string;
223
+ children: ReactNode;
224
+ actions?: ReactNode; // Optional—additional buttons or elements to the right of the text (for example, "Neu laden").
225
+ padded?: boolean; // Adds outer spacing around the banner for page states where it renders alone in the main area (KumikoScreen "not-found", "loading", etc.).
226
+ testId?: string;
227
+ id?: string; // Set when a `<Field>` wraps this Banner as its control (no actual input exists for the label to target — see render-field.tsx's unsupported-field-type fallback).
228
+ };
229
+ interface BooleanFieldProps extends FieldBase {
230
+ value: boolean;
231
+ onChange: (v: boolean) => void;
232
+ }
233
+ type ButtonProps = ButtonBaseProps & ({ children: ReactNode; } | { children?: undefined; icon: NavIconKey; ariaLabel: string; }); // Standard button.
234
+ type CardProps = { // Generic card chrome (border/background/radius/shadow plus header/body/footer) as a primitive, providing one implementation for all card surfaces.
235
+ slots?: CardSlots;
236
+ options?: CardOptions;
237
+ children?: ReactNode; // Body content in the normal case; header and footer are supplied through `slots`.
238
+ className?: string;
239
+ testId?: string;
240
+ dataAttributes?: DataAttributes;
241
+ };
242
+ type ClientFeatureDefinition = {
243
+ name: string;
244
+ providers?: ComponentType<{ children: ReactNode; }>[]; // Context providers wrapped around the complete renderer tree.
245
+ gates?: ComponentType<{ children: ReactNode; }>[]; // Screen decorators placed between the provider stack and the shell or screen render.
246
+ translations?: TranslationsByLocale; // Default translations supplied by the feature for its UI strings.
247
+ components?: Readonly<Record<string, ComponentType>>; // Custom screen components, mapped by screenId.
248
+ columnRenderers?: Readonly<Record<string, ColumnRendererComponent>>; // Column renderer components, mapped by renderer name.
249
+ extensionSectionComponents?: Readonly<Record<string, ExtensionSectionComponent>>; // Extension-section components, mapped by `__component` name.
250
+ clientActions?: Readonly<Record<string, ClientActionHandler>>; // Handlers for RowAction `kind: "clientAction"`, keyed by the action name `<feature>:<kebab>`.
251
+ navProviders?: Readonly<Record<string, TreeChildrenSubscribe>>; // Nav providers for the single nav visual-tree merge: they attach dynamic children to a static `r.nav({ provider: true })` node.
252
+ navEntities?: Readonly<Record<string, string[]>>; // SSE entity lists per nav ID for live refresh of navProviders, analogous to `treeEntities`.
253
+ navProvidersFromCollections?: (collections: QualifiedContentCollection[]) => CollectionNavProviders; // Nav providers derived from the app schema — called with every `r.contentCollection()` the app declares.
254
+ contentEditors?: Partial<Record<ContentEditorFormat, ContentEditorComponent>>; // Content editor components — map `contentFormat` ("plain" | "document") → React component.
255
+ resolvers?: Readonly<Record<string, ComponentType<{ target: TargetRef; onClose: () => void; }>>>; // Editor resolver components keyed by featureId:action.
256
+ };
257
+ type ColorTokens = {
258
+ background: string;
259
+ foreground: string;
260
+ card: string;
261
+ cardForeground: string;
262
+ popover: string;
263
+ popoverForeground: string;
264
+ primary: string;
265
+ primaryForeground: string;
266
+ secondary: string;
267
+ secondaryForeground: string;
268
+ muted: string;
269
+ mutedForeground: string;
270
+ accent: string;
271
+ accentForeground: string;
272
+ destructive: string;
273
+ destructiveForeground: string;
274
+ border: string;
275
+ input: string;
276
+ ring: string;
277
+ };
278
+ interface ComparisonMetric<Col> {
279
+ label: string;
280
+ value: (col: Col, index: number) => ReactNode;
281
+ bestIndex?: (cols: Col[]) => number; // Index of the best column for this row (highlighted); -1 = none.
282
+ }
283
+ type CorePrimitives = {
284
+ Button: ComponentType<ButtonProps>;
285
+ Banner: ComponentType<BannerProps>;
286
+ Field: ComponentType<FieldProps>;
287
+ Input: ComponentType<InputProps>;
288
+ DataTable: ComponentType<DataTableProps>;
289
+ EmbeddedListInput?: ComponentType<EmbeddedListInputProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
290
+ Form: ComponentType<FormProps>;
291
+ Section: ComponentType<SectionProps>;
292
+ Card: ComponentType<CardProps>;
293
+ Grid: ComponentType<GridProps>;
294
+ GridCell: ComponentType<GridCellProps>;
295
+ Text: ComponentType<TextProps>;
296
+ Heading: ComponentType<HeadingProps>;
297
+ Dialog: ComponentType<DialogProps>;
298
+ Modal: ComponentType<ModalProps>;
299
+ Drawer?: ComponentType<DrawerProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
300
+ Lightbox: ComponentType<LightboxProps>;
301
+ ConfigSourceBadge: ComponentType<ConfigSourceBadgeProps>;
302
+ ConfigCascadeView: ComponentType<ConfigCascadeViewProps>;
303
+ Link: ComponentType<LinkProps>;
304
+ Progress?: ComponentType<ProgressProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
305
+ StepBar?: ComponentType<StepBarProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
306
+ WizardStepGroup?: ComponentType<WizardStepGroupProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
307
+ Tabs?: ComponentType<TabsProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
308
+ StatusBadge?: ComponentType<StatusBadgeProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
309
+ Metric?: ComponentType<MetricProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
310
+ JsonView?: ComponentType<JsonViewProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
311
+ FileValueList?: ComponentType<FileValueListProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
312
+ FillContainer?: ComponentType<FillContainerProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
313
+ PageHeader?: ComponentType<PageHeaderProps>; // Optional — additive rollout, see StickyActionBar.
314
+ MetricBand?: ComponentType<MetricBandProps>; // Optional — additive rollout, see StickyActionBar.
315
+ ActionOverflowMenu?: ComponentType<ActionOverflowMenuProps>; // Optional: without an implementation, callers with >2 header/row actions fall back to today's all-buttons-inline rendering.
316
+ SecretReveal?: ComponentType<SecretRevealProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
317
+ StickyActionBar?: ComponentType<StickyActionBarProps>; // Optional (unlike the other Core-Primitives) so existing partial CorePrimitives mocks in tests keep compiling — additive rollout of a new primitive shouldn't force every test double to grow a stub.
318
+ CopyButton?: ComponentType<CopyButtonProps>; // Optional — additive rollout, see StickyActionBar.
319
+ ShareButton?: ComponentType<ShareButtonProps>; // Optional — additive rollout, see StickyActionBar.
320
+ PromoPanel?: ComponentType<PromoPanelProps>; // Optional — additive rollout, see StickyActionBar.
321
+ };
322
+ type CoreTokens = {
323
+ color: ColorTokens;
324
+ radius: RadiusTokens;
325
+ font: FontTokens;
326
+ shadow: ShadowTokens;
327
+ spacing: SpacingTokens;
328
+ };
329
+ type CreateBrowserDraftStorageOptions = {
330
+ storagePrefix?: string; // sessionStorage-key prefix, one entry per screenId gets appended.
331
+ };
332
+ type CreateBrowserLocaleResolverOptions = {
333
+ storageKey?: string; // localStorage key used to persist the active locale.
334
+ defaultLocale?: string; // Fallback when neither localStorage nor navigator.language provides a locale.
335
+ normalizeLocale?: (tag: string) => string | undefined; // Maps a stored or browser-reported tag onto one of the app's locales; `undefined` rejects it (a stored tag then falls through to navigator.language, the browser tag to `defaultLocale`).
336
+ };
337
+ type CreateEventSourceLiveEventsOptions = {
338
+ url?: string; // URL of the SSE endpoint.
339
+ onSessionEnded?: () => void; // Fires once per connection when the session ended: the server refused the SSE handshake, or opened the stream with a `session-ended` frame (stale auth cookie downgraded to anonymous).
340
+ timers?: LiveEventTimers; // Reconnect-timer seam; defaults to the global setTimeout/clearTimeout.
341
+ random?: () => number; // Jitter source in [0, 1); defaults to Math.random.
342
+ now?: () => number; // Clock for synthetic resync events; defaults to Date.now.
343
+ };
344
+ type CreateKumikoAppOptions = {
345
+ onError?: BoundaryErrorHandler; // Called when the screen or root error boundary catches an error, after it was logged with console.error — attach telemetry here.
346
+ reportClientErrors?: boolean; // POST caught render errors and unhandled window errors to `/api/client-errors` (default true).
347
+ schema?: AppSchema | FeatureSchema; // App schema.
348
+ rootId?: string;
349
+ dispatcher?: Dispatcher;
350
+ schemaUrl?: string; // URL of the authenticated schema endpoint, fetched when no `schema` is given.
351
+ draftStorage?: DraftStorage; // RenderEdit's create-mode draftId storage — where a same-tab reload finds which of several parallel create-sessions on a screen to resume.
352
+ screenQn?: string;
353
+ translate?: Translate;
354
+ onRowClick?: (row: ListRowViewModel, entityName: string) => void; // Row-click fallback for entities with no declared `detailFor` screen — a `detailFor` screen always wins over this option.
355
+ shell?: (props: { children: ReactNode; schema: AppSchema; }) => ReactNode; // App shell.
356
+ primitives?: Partial<PrimitivesRegistry>;
357
+ clientFeatures?: ClientFeatureDefinition[]; // Client extensions supplied by features.
358
+ locale?: LocaleResolver; // App-level LocaleResolver.
359
+ navAdapter?: (options?: { hasWorkspaces?: boolean; features?: FeatureSchema[]; }) => NavApi; // Navigation adapter — a React hook that returns a NavApi instance (route + navigate + hrefFor).
360
+ basePath?: string; // URL prefix the app is mounted under (for example "/admin").
361
+ screenWidth?: FormWidth; // App-wide default width for form/detail screens that don't set their own `layout.width`.
362
+ };
363
+ type CreatePublicSurfaceOptions = {
364
+ routes: PublicRoute[];
365
+ fallback?: ReactNode; // Rendered when no route.path matches the current path.
366
+ rootId?: string;
367
+ locale?: LocaleResolver;
368
+ primitives?: Partial<PrimitivesRegistry>;
369
+ dispatcher?: Dispatcher; // Dispatcher for handler calls (e.g. an anonymous deletion request).
370
+ clientFeatures?: ClientFeatureDefinition[]; // Feature client extensions — ONLY `providers` + `translations` are stacked.
371
+ shell?: (props: { children: ReactNode; }) => ReactNode; // Page chrome around the matched content (apex/marketing layout).
372
+ formality?: Formality; // Wording for every page of this surface.
373
+ };
374
+ type DataTableProps = DataTableTableBlock & {
375
+ columns: ListColumnViewModel[];
376
+ rows: ListRowViewModel[];
377
+ rowTone?: (row: ListRowViewModel) => SelectOptionTone | undefined; // Tints a row.
378
+ onRowClick?: (row: ListRowViewModel) => void;
379
+ sort?: DataTableSort | null; // Current sorting, or null when unsorted.
380
+ onSortChange?: (next: DataTableSort | null) => void; // Called with the next sort state after a header click.
381
+ rowActions?: DataTableRowAction[]; // Per-row actions rendered in a right-hand column as inline buttons (≤2 actions) or a kebab dropdown (>2).
382
+ rowActionMode?: DataTableRowActionMode; // How the row-action column renders: - `"adaptive"` (default): with `onRowClick` all actions except the `rowClick` one go in the kebab (no kebab or column if none remain); without it the primary action is a link button and the rest go in the kebab (a single action gets no kebab).
383
+ bulk?: DataTableBulk; // Present: row checkboxes plus a selection bar that runs these actions once per selected row.
384
+ emptyState?: ReactNode; // Custom empty-state content (e.g. Icon + Heading + CTA-Button).
385
+ toolbarTitle?: ReactNode; // Optional title slot at the left of the toolbar, typically the screen title ("Items", "Bestellungen").
386
+ toolbarStart?: ReactNode; // Toolbar slot in the middle, typically a search input.
387
+ toolbarEnd?: ReactNode; // Toolbar slot on the right, typically for a + New button, filters, or a view switch.
388
+ filterFacets?: DataTableFacet[]; // Faceted filter dropdowns for select/boolean fields.
389
+ filterValues?: Readonly<Record<string, string[]>>; // Currently selected values for each facet field, supplied by URL state.
390
+ onFilterChange?: (field: string, values: string[]) => void; // Toggling a facet value: the caller updates URL state and refetches.
391
+ onFilterReset?: () => void; // Clears all active facets.
392
+ dateRangeFacets?: DataTableDateRangeFacet[]; // Time-range filters beside the facet dropdowns.
393
+ onDateRangeChange?: (field: string, bound: "from" | "to", value: string) => void; // One bound changed ("" clears it) → caller writes URL state + refetch.
394
+ pager?: {
395
+ page: number;
396
+ limit: number;
397
+ total: number;
398
+ onPageChange: (next: number) => void;
399
+ pageSizeOptions?: number[]; // Offered page sizes; with `onPageSizeChange` the footer shows a "N per page" select.
400
+ onPageSizeChange?: (next: number) => void;
401
+ }; // Pagination state and callback.
402
+ toolbarDescription?: string; // Hint at the left of the toolbar (relatedList tab description).
403
+ itemNoun?: (count: number) => string; // Noun for the footer count ("1–19 of 19 leases", "1 position"), resolved for the given count.
404
+ onReachEnd?: () => void; // Infinite-scroll callback.
405
+ loadingMore?: boolean;
406
+ hasMore?: boolean; // When false and `onReachEnd` is set, the renderer shows an end-of-list message instead of the sentinel.
407
+ testId?: string;
408
+ onCellChange?: (rowId: string, field: string, value: unknown) => void; // Wires an edit path into a component column-renderer: `DataTableCell` passes this down as `ColumnRendererProps.onChange` for that cell, bound to `(rowId, column.field)`.
409
+ getRowTestId?: (row: ListRowViewModel) => string; // Overrides the row `data-testid` (default `row-${row.id}`) — for a screen that already has its own DOM-test/e2e naming scheme.
410
+ getCellTestId?: (row: ListRowViewModel, field: string) => string; // Overrides the cell `data-testid` (default `cell-${row.id}-${field}`).
411
+ chromeless?: boolean; // Drops the table's own rounded/border/card background — for a host that already provides the surrounding boundary (a tab panel) and would otherwise show a card nested inside that boundary.
412
+ scrollBody?: boolean; // Fills the available height of its flex container and scrolls rows internally instead of growing with row count — for a host that would otherwise have a long table stretch the whole page, or a short table leave dead space below it (a tab panel).
413
+ screenPadding?: boolean; // Uses the shared screen padding (wider bottom inset) instead of the table's symmetric embedded inset — for a table that IS the screen body (entityList/projectionList), so a list screen ends at the same footer distance as a form or custom screen.
414
+ expandedRowIds?: ReadonlySet<string>; // Ids of the rows whose expansion area is currently open (controlled by the host; several rows can be open at once).
415
+ onToggleRowExpanded?: (rowId: string) => void; // User toggled a row's expand control.
416
+ renderExpandedRow?: (row: ListRowViewModel) => ReactNode; // Content of the area under an expanded row.
417
+ };
418
+ interface DateFieldProps extends FieldBase {
419
+ value: string;
420
+ onChange: (v: string) => void;
421
+ min?: string;
422
+ max?: string;
423
+ }
424
+ type DefaultAppShellProps = {
425
+ brand: ReactNode; // Header slot at the top of the sidebar — workspace identity (logo, name, plan tag).
426
+ schema: AppSchema | FeatureSchema; // Schema (AppSchema or legacy FeatureSchema) passed through to NavTree; sidebar entries are generated automatically from schema.navs (per-feature).
427
+ user?: DefaultAppShellUser; // Current user — drives navigation role gating.
428
+ sidebarActions?: ReactNode; // Icon row between the brand and NavTree — typically a Search trigger, ThemeToggle, or TenantSwitcher.
429
+ navBadges?: ReadonlyMap<string, ReactNode>; // Runtime badges per navigation item (bare nav-id → ReactNode), passed through to NavTree.
430
+ headerActions?: ReactNode; // Right-aligned slot in the topbar (next to the breadcrumb) — for ThemeToggle, global actions, and similar controls.
431
+ sidebarFooter?: ReactNode; // Footer slot at the bottom of the sidebar — profile row, help link, or plan banner.
432
+ fill?: boolean; // Viewport-fit shell.
433
+ children: ReactNode; // Screen content rendered inside SidebarInset.
434
+ };
435
+ type DispatcherProviderProps = {
436
+ dispatcher: Dispatcher;
437
+ children: ReactNode;
438
+ };
439
+ type DrawerProps = {
440
+ open: boolean;
441
+ onOpenChange: (open: boolean) => void;
442
+ side?: "left" | "right" | "top" | "bottom";
443
+ title?: ReactNode;
444
+ description?: ReactNode;
445
+ footer?: ReactNode;
446
+ children: ReactNode;
447
+ testId?: string;
448
+ showCloseButton?: boolean; // Header close (X).
449
+ variant?: "floating" | "flush"; // Panel treatment.
450
+ belowHeader?: boolean; // `variant="flush"` only: dock the panel below the app's ShellHeader (offset top by `--shell-header-height`, height shrunk to match) instead of covering it.
451
+ width?: number | string; // Panel width for `side="left"|"right"` (ignored for top/bottom and in the narrow-viewport layout).
452
+ resize?: {
453
+ defaultWidthPx?: number;
454
+ minWidthPx?: number;
455
+ maxWidthPx?: number;
456
+ }; // Opt-in drag-to-resize + maximize toggle (left/right sides only).
457
+ backdrop?: {
458
+ blurPx?: number;
459
+ dimPercent?: number;
460
+ }; // Backdrop behind the panel.
461
+ modal?: boolean; // `false` runs the drawer as a panel beside the page instead of over it: no overlay, no focus trap, and the page behind stays interactive, so text there can be selected and copied.
462
+ panelClassName?: string; // Extra classes for the panel, merged over the built-in ones.
463
+ fillBody?: boolean; // The body becomes a plain flex column with no padding or scrolling of its own, for children that bring their own scroll area and pinned footer.
464
+ };
465
+ type EditorPanelProps = {
466
+ resolvers: ReadonlyMap<string, ResolverComponent>;
467
+ };
468
+ type FeedRow = {
469
+ id: string;
470
+ primary: string;
471
+ trailing?: string;
472
+ };
473
+ type FieldProps = {
474
+ id: string;
475
+ label: string;
476
+ required?: boolean;
477
+ issues?: FieldIssue[];
478
+ labelAppendix?: ReactNode;
479
+ fieldAppendix?: ReactNode;
480
+ appendixPlacement?: "above-control" | "below-control"; // Where `fieldAppendix` sits relative to the control.
481
+ description?: ReactNode; // Help text directly under the label, above the control.
482
+ status?: {
483
+ tone: StatusTone;
484
+ label: string;
485
+ testId?: string;
486
+ }; // Status pill at the right end of the label row (e.g. "Saved", "Not set").
487
+ children: ReactNode;
488
+ layout?: "stacked" | "inline" | "row"; // Label layout.
489
+ accent?: boolean; // Marks a field whose value is set at this level (differs from the inherited one).
490
+ hideLabel?: boolean; // Collapses the visible label to `sr-only` — stays associated via `htmlFor` (no separate `aria-label` needed).
491
+ changed?: boolean; // Marks the field as edited relative to its saved value — the label row shows a "changed" marker.
492
+ testId?: string;
493
+ };
494
+ interface FileFieldProps extends FieldBase {
495
+ value: string | null; // FileRef UUID of the stored file, or null.
496
+ onChange: (fileId: string | null) => void;
497
+ accept?: string[];
498
+ variant?: "file" | "image"; // "image" shows a preview; "file" shows only the file name.
499
+ entityType?: string; // Binds the upload to a specific entity field; the `/api/files` endpoint then validates `accept`/`maxSize` server-side against that field's definition.
500
+ fieldName?: string;
501
+ }
502
+ type FileRefTarget = Pick<FileUploadFormDataInput, "entityType" | "entityId" | "fieldName" | "subField" | "actionForm">;
503
+ type FloatingPanelGeometry = {
504
+ x: number;
505
+ y: number;
506
+ width: number;
507
+ height: number;
508
+ };
509
+ type FloatingPanelProps = {
510
+ title: ReactNode; // Header content; the header is the move handle.
511
+ headerActions?: ReactNode; // Header buttons (dock, minimize, close).
512
+ children: ReactNode;
513
+ storageKey?: string; // localStorage key; without it the geometry isn't persisted.
514
+ defaultGeometry?: Partial<FloatingPanelGeometry>; // Default 400x560, bottom-right with a 24px margin.
515
+ minWidthPx?: number;
516
+ minHeightPx?: number;
517
+ maxWidthPx?: number; // Default: viewport width minus twice the margin.
518
+ maxHeightPx?: number; // Default: viewport height minus twice the margin.
519
+ testId?: string;
520
+ };
521
+ type FontTokens = {
522
+ sans: string;
523
+ heading: string;
524
+ mono: string;
525
+ };
526
+ type FormProps = { // Submit wrapper.
527
+ onSubmit: (e?: FormEvent) => void;
528
+ focusFirstErrorKey?: number; // Bumped by the host each time a submit fails on field errors; the form then moves focus to its first invalid control.
529
+ children: ReactNode;
530
+ title?: ReactNode;
531
+ subtitle?: ReactNode; // Optional muted subtitle below the title (e.g. "Add a new item to your catalog"), providing context in the form header rather than only a label.
532
+ actions?: ReactNode;
533
+ secondaryActions?: ReactNode; // Secondary, record-related actions — rendered on the left on desktop, on their own row below the primary action on a narrow viewport.
534
+ testId?: string;
535
+ width?: FormWidth; // Max width of the form container.
536
+ stickyActions?: boolean; // Pins `actions` to the viewport bottom on narrow screens (`<640px`) instead of normal document flow, so it stays reachable when a virtual keyboard shrinks the visible viewport.
537
+ headerRegion?: ReactNode; // Extra content rendered above the card, inside the same page padding the card itself sits in — for a host that needs its own header region (title/metrics/tabs) to share the form's left padding and width instead of rendering as unpadded siblings before it.
538
+ titleAction?: ReactNode; // Compact content on the right of the form title, same row — status chips or allowance badges that belong to the screen, not to a field.
539
+ fillHeight?: boolean; // Sizes the form to fill its container's height (instead of the page's natural content height) so a single scrolling child — a lone relatedList tab's table — can scroll internally instead of stretching the whole page.
540
+ chromeless?: boolean; // Drops the surrounding card (bg/border/rounded, title/subtitle block, footer border) so sections+footer render as siblings of `headerRegion` on the page background instead of inside a nested card — a tabbed projectionDetail's tab content is a sibling of the head card, not its child (bedienkonzept A1), so it must look like a standalone screen instead of a form embedded in another card.
541
+ screenForm?: boolean; // Set by RenderEdit for entityEdit/actionForm screens that fill the shell height: the form renders without a surrounding card, in a padded scroll surface with a fixed-width form column.
542
+ unsavedCount?: number; // Number of unsaved changes, shown in the pinned footer; 0 shows the quiet "No unsaved changes" status.
543
+ summary?: {
544
+ title: string;
545
+ subtitle?: string;
546
+ }; // Drawer-hosted forms: context box above the fields.
547
+ sectionNav?: FormSectionNavItem[]; // Titled sections for the "on this page" navigation next to a `screenForm`.
548
+ sideRail?: ReactNode; // Leading column next to the scroll surface of a `screenForm` (wizard step navigation).
549
+ };
550
+ type GridCellProps = { // Span wrapper for a child within a grid.
551
+ span?: number;
552
+ width?: FieldCellWidth; // Only read inside a `flow` grid.
553
+ children: ReactNode;
554
+ };
555
+ type GridProps = { // Column-based layout.
556
+ columns: number | "auto";
557
+ children: ReactNode;
558
+ testId?: string;
559
+ maxRows?: number; // Rows visible before the grid becomes vertically scrollable.
560
+ flow?: boolean; // Fields flow left to right in wrapping rows sized by `GridCell.width` instead of equal-width tracks (screen forms and drawers).
561
+ list?: boolean; // One full-width row per child, separated by hairlines — the field rows of a `settings-list` section.
562
+ ariaLabelledBy?: string; // Id of the element naming this grid (a Field label).
563
+ };
564
+ type HeaderActionGroupProps = {
565
+ children: ReactNode;
566
+ className?: string;
567
+ };
568
+ type HeadingProps = { // Heading with two roles: `page` is a page title (web: h1), while `section` is a group subheading (web: h2 with uppercase and muted styling).
569
+ variant?: "page" | "section";
570
+ children: ReactNode;
571
+ testId?: string;
572
+ };
573
+ type InfinityListProps<TData = unknown, TRow = Readonly<Record<string, unknown>>> = {
574
+ query: string; // Dispatcher-Query-Type (`<feature>:query:<entity>:<verb>`).
575
+ payload?: Readonly<Record<string, unknown>>;
576
+ pageSize?: number; // Rows per page — sent as `limit` in the query payload.
577
+ rows: (data: TData) => TRow[];
578
+ nextCursor: (data: TData) => string | null; // Pull the next-page cursor from the result; `null` means last page.
579
+ rowId: (row: TRow, index: number) => string; // Must derive from row content (e.g. `row.id`), not from `index` — a live refresh reorders rows (new/changed rows move to the front).
580
+ renderRow: (row: TRow) => ReactNode;
581
+ emptyState?: ReactNode;
582
+ className?: string;
583
+ testId?: string;
584
+ live?: boolean; // Subscribe to SSE events for the entity parsed from `query` (`<feature>:query:<entity>:<verb>`) and refetch the first page on any create/update/delete/restore event, merging it into the already-accumulated rows instead of collapsing them — see `useQuery`'s `live` option for the same convention.
585
+ selectedId?: string | null; // Row id (as returned by `rowId`) whose position `onSelectionChange` reports.
586
+ onSelectionChange?: (selection: InfinityListSelection) => void; // Fires when the selected row's position or neighbours change, e.g. for a "message 4 of 6" header with previous/next buttons.
587
+ };
588
+ type InfinityListSelection = { // Position of the selected row among the rows loaded so far.
589
+ index: number;
590
+ total: number;
591
+ prevId: string | null;
592
+ nextId: string | null;
593
+ hasMore: boolean;
594
+ };
595
+ type InputProps =
596
+ | {
597
+ kind: "text";
598
+ id: string;
599
+ name: string;
600
+ value: string;
601
+ onChange: (v: string) => void;
602
+ autoFocus?: boolean;
603
+ disabled?: boolean;
604
+ required?: boolean;
605
+ hasError?: boolean;
606
+ placeholder?: string; // Hint text shown when the field is empty.
607
+ autoComplete?: string; // Browser autofill / native keyboard hint.
608
+ testId?: string; // Sets `data-testid` on the actual `<input>` so app tests can address the element directly (getByTestId + readOnly/disabled assertions).
609
+ readOnly?: boolean; // Read-only input (for example, a generated free-tier slug).
610
+ icon?: FieldIconKey; // Closed FieldIconKey vocabulary (FIELD_ICONS registry, renderer/web).
611
+ prefix?: string; // Resolved inline text before/after the input (EditFieldSpec.prefix/suffix).
612
+ suffix?: string;
613
+ fill?: InputFillAction;
614
+ dataAttributes?: DataAttributes;
615
+ onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void; // Raw keydown access (e.g. Enter-to-submit at the field instead of the surrounding Form).
616
+ }
617
+ | {
618
+ kind: "code"; // Fixed-length numeric one-time code.
619
+ id: string;
620
+ name: string;
621
+ value: string;
622
+ onChange: (v: string) => void;
623
+ length: number;
624
+ onComplete?: (code: string) => void;
625
+ autoFocus?: boolean;
626
+ disabled?: boolean;
627
+ required?: boolean;
628
+ hasError?: boolean;
629
+ testId?: string;
630
+ autoComplete?: string; // Default "one-time-code".
631
+ }
632
+ | {
633
+ kind: "email";
634
+ id: string;
635
+ name: string;
636
+ value: string;
637
+ onChange: (v: string) => void;
638
+ disabled?: boolean;
639
+ required?: boolean;
640
+ hasError?: boolean;
641
+ placeholder?: string;
642
+ autoComplete?: string; // Default "email".
643
+ testId?: string;
644
+ }
645
+ | {
646
+ kind: "month"; // Month picker; the value is `YYYY-MM` (empty string when unset).
647
+ id: string;
648
+ name: string;
649
+ value: string;
650
+ onChange: (v: string) => void;
651
+ disabled?: boolean;
652
+ required?: boolean;
653
+ hasError?: boolean;
654
+ testId?: string;
655
+ }
656
+ | {
657
+ kind: "password";
658
+ id: string;
659
+ name: string;
660
+ value: string;
661
+ onChange: (v: string) => void;
662
+ disabled?: boolean;
663
+ required?: boolean;
664
+ hasError?: boolean;
665
+ placeholder?: string; // Hint text shown when the field is empty (analog to kind:"text").
666
+ autoComplete?: "current-password" | "new-password"; // "current-password" for login, "new-password" for reset/signup—browser password managers use this to decide whether to save the value.
667
+ trailingAction?: TrailingInputAction; // Icon button inside the field's trailing edge (clear a stored value, undo a pending removal).
668
+ testId?: string;
669
+ }
670
+ | {
671
+ kind: "number";
672
+ id: string;
673
+ name: string;
674
+ value: number | "";
675
+ onChange: (v: number | undefined) => void;
676
+ disabled?: boolean;
677
+ required?: boolean;
678
+ hasError?: boolean;
679
+ placeholder?: string; // Hint text shown when the field is empty (analog to kind:"text").
680
+ testId?: string;
681
+ icon?: FieldIconKey; // Closed FieldIconKey vocabulary (FIELD_ICONS registry, renderer/web).
682
+ step?: number | "any"; // `<input step>`.
683
+ locale?: string; // BCP-47 tag for the grouped display and the decimal separator when typing.
684
+ grouping?: boolean; // `false` shows the value without a thousands separator (e.g. a year).
685
+ integer?: boolean; // Integer-only entry (bigInt): fractional drafts are not emitted via `onChange`.
686
+ unit?: string; // Resolved display suffix (static or from a sibling field) — never part of the numeric value.
687
+ prefix?: string;
688
+ suffix?: string;
689
+ fill?: InputFillAction;
690
+ }
691
+ | {
692
+ kind: "range";
693
+ id: string;
694
+ name: string;
695
+ value: number;
696
+ onChange: (v: number) => void;
697
+ min: number;
698
+ max: number;
699
+ step?: number;
700
+ disabled?: boolean;
701
+ required?: boolean;
702
+ hasError?: boolean;
703
+ }
704
+ | {
705
+ kind: "boolean";
706
+ id: string;
707
+ name: string;
708
+ value: boolean;
709
+ onChange: (v: boolean) => void;
710
+ disabled?: boolean;
711
+ required?: boolean;
712
+ hasError?: boolean;
713
+ }
714
+ | {
715
+ kind: "file" | "image";
716
+ id: string;
717
+ name: string;
718
+ value: string | null; // FileRef UUID (stored file) or null (no upload).
719
+ onChange: (fileId: string | null) => void; // Implementations must offer their own remove control that calls `onChange(null)`: FileListField relies on it to drop a file from an `images`/`files` field.
720
+ disabled?: boolean;
721
+ required?: boolean;
722
+ hasError?: boolean;
723
+ accept?: string[]; // Erlaubte Extensions/MIME (z.B.
724
+ maxSize?: string;
725
+ entityType?: string; // For the upload POST `/api/files`: the endpoint validates maxSize/accept against the field definition for (entityType, fieldName).
726
+ uploadActionForm?: string; // Screen QN of the actionForm the field belongs to; sent as `actionForm` instead of `entityType`, so the upload stays pending for that form's field.
727
+ entityId?: string; // Must be sent as the `entityId` part of the upload so the FileRef is attached to the record (qr overlays and access guards need it).
728
+ fieldName?: string;
729
+ imageVariant?: string; // Only for kind:"image" — variant name for the preview (`/api/files/:id/variant/:name`).
730
+ capture?: "environment" | "user"; // Only for kind:"image" — forwarded to the file input's `capture` attribute so a phone opens the camera instead of the file picker.
731
+ directory?: boolean; // Web-only folder picker: the picked files (with `webkitRelativePath`) go to `onFilesPicked` and nothing is uploaded.
732
+ onFilesPicked?: (files: PickedFile[]) => void;
733
+ pickLabel?: string; // Translated label of the pick button; absent → the upload/change default.
734
+ testId?: string; // Hidden file input gets `${testId}-input`, the pick button `${testId}-pick`.
735
+ }
736
+ | {
737
+ kind: "files-dropzone"; // Drop zone for a `files` / `images` field: the value is the list of uploaded FileRef ids, the implementation offers multi-pick, drag and drop and a remove control per file.
738
+ id: string;
739
+ name: string;
740
+ value: string[];
741
+ onChange: (fileIds: string[]) => void;
742
+ disabled?: boolean;
743
+ required?: boolean;
744
+ hasError?: boolean;
745
+ accept?: string[];
746
+ maxSize?: string;
747
+ maxCount?: number;
748
+ entityType?: string; // Upload target, same meaning as on the `file` kind.
749
+ uploadActionForm?: string;
750
+ entityId?: string;
751
+ fieldName?: string;
752
+ testId?: string;
753
+ }
754
+ | {
755
+ kind: "date";
756
+ id: string;
757
+ name: string;
758
+ value: string;
759
+ onChange: (v: string | undefined) => void;
760
+ locale?: string; // Locale for display and input parsing.
761
+ min?: string; // Datumsgrenzen als ISO `yyyy-mm-dd` — begrenzen Picker (Jahres- Dropdown-Range + ausgegraute Tage).
762
+ max?: string;
763
+ disabled?: boolean;
764
+ required?: boolean;
765
+ hasError?: boolean;
766
+ }
767
+ | {
768
+ kind: "select";
769
+ id: string;
770
+ name: string;
771
+ value: string;
772
+ onChange: (v: string) => void;
773
+ options: string[] | { value: string; label: string; description?: string; group?: string; disabled?: boolean; }[]; // Allowed values.
774
+ disabled?: boolean;
775
+ required?: boolean;
776
+ hasError?: boolean;
777
+ radioVariant?: "list" | "card"; // `"card"` renders the radio group as bordered cards with label plus optional description per option and always uses the radio group, regardless of option count or `display`.
778
+ display?: "radio" | "dropdown"; // Requested presentation.
779
+ optionTones?: Readonly<Partial<Record<string, SelectOptionTone>>>; // Tone per option value; the selected segment of a radio presentation takes its colors.
780
+ }
781
+ | ({ kind: "combobox"; id: string; name: string; options: { value: string; label: string; }[]; disabled?: boolean; required?: boolean; hasError?: boolean; placeholder?: string; searchPlaceholder?: string; emptyText?: string; onSearchChange?: (q: string) => void; loading?: boolean; onCreate?: () => void; createLabel?: string; } & ({ multiple?: false; value: string; onChange: (v: string) => void; } | { multiple: true; value: string[]; onChange: (v: string[]) => void; }))
782
+ | {
783
+ kind: "money";
784
+ id: string;
785
+ name: string;
786
+ value: number | ""; // Internal format: cents/minor units as an integer (for example, 1299 for 12,99 EUR).
787
+ onChange: (v: number | undefined) => void;
788
+ currency?: string; // ISO-4217 currency code, for example "EUR" / "USD" / "CHF".
789
+ locale?: string; // Locale for number formatting.
790
+ disabled?: boolean;
791
+ required?: boolean;
792
+ hasError?: boolean;
793
+ }
794
+ | {
795
+ kind: "timestamp";
796
+ id: string;
797
+ name: string;
798
+ value: string; // ISO-8601 datetime string.
799
+ onChange: (v: string | undefined) => void;
800
+ wallClock?: boolean; // true = locatedTimestamp (wall-clock without an offset; the server validates z.iso.datetime({local:true})).
801
+ locale?: string; // Locale for displaying and parsing the date portion.
802
+ min?: string; // Date bounds as ISO datetimes; they constrain the calendar to day-level granularity.
803
+ max?: string;
804
+ disabled?: boolean;
805
+ required?: boolean;
806
+ hasError?: boolean;
807
+ }
808
+ | {
809
+ kind: "locatedTimestamp";
810
+ id: string;
811
+ name: string;
812
+ value: { at: string; tz: string; utc?: string; } | ""; // Located-timestamp value: wall-clock `at` (without an offset) plus an IANA `tz`.
813
+ onChange: (v: { at: string; tz: string; } | undefined) => void;
814
+ locale?: string; // Locale for the date portion.
815
+ min?: string; // Date bounds as ISO datetimes; they constrain the calendar.
816
+ max?: string;
817
+ disabled?: boolean;
818
+ required?: boolean;
819
+ hasError?: boolean;
820
+ }
821
+ | {
822
+ kind: "tz";
823
+ id: string;
824
+ name: string;
825
+ value: string; // IANA zone name, e.g. "Europe/Berlin".
826
+ onChange: (v: string | undefined) => void;
827
+ disabled?: boolean;
828
+ required?: boolean;
829
+ hasError?: boolean;
830
+ }
831
+ | {
832
+ kind: "textarea";
833
+ id: string;
834
+ name: string;
835
+ value: string;
836
+ onChange: (v: string) => void;
837
+ rows?: number; // Visible rows.
838
+ disabled?: boolean;
839
+ required?: boolean;
840
+ hasError?: boolean;
841
+ placeholder?: string; // Hint text shown when the field is empty (analog to kind:"text").
842
+ readOnly?: boolean; // Read-only Textarea.
843
+ onSubmitShortcut?: () => void; // Ctrl/Cmd+Enter submits instead of inserting a newline.
844
+ dataAttributes?: DataAttributes;
845
+ onKeyDown?: (event: KeyboardEvent<HTMLTextAreaElement>) => void; // Raw keydown access (e.g. plain-Enter-submits/Shift+Enter-newline at the field instead of the surrounding Form).
846
+ }
847
+ ;
848
+ type KumikoBuild = {
849
+ id: string;
850
+ builtAt: string;
851
+ };
852
+ type KumikoLinkProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> & {
853
+ to: NavTarget;
854
+ };
855
+ type KumikoScreenProps = {
856
+ schema: FeatureSchema;
857
+ qn: string;
858
+ translate?: Translate;
859
+ entityId?: string;
860
+ onRowClick?: (row: ListRowViewModel, entityName: string) => void;
861
+ onCopyLink?: () => Promise<void> | void;
862
+ };
863
+ type LanguageMenuItemsProps = {
864
+ locales: LocaleOption[];
865
+ label?: string;
866
+ };
867
+ type LanguageSwitcherProps = {
868
+ locales: LocaleOption[]; // Selectable locales.
869
+ icon?: ReactNode; // Icon slot left of the button label.
870
+ label?: string; // aria-label + title of the trigger.
871
+ triggerContent?: "code" | "label" | "icon-only"; // What the trigger shows next to the icon: the uppercase code (default), the active locale's label, or the icon only.
872
+ variant?: "default" | "chip"; // `default`: icon plus code/label button.
873
+ testId?: string;
874
+ };
875
+ type LiveEvent = {
876
+ type: string;
877
+ data: {
878
+ id?: string; // Absent on anonymous connections: the signal would reveal hidden rows.
879
+ aggregateType: string;
880
+ eventType: string;
881
+ version?: number; // Absent on anonymous connections, like `id`.
882
+ createdAt: string;
883
+ };
884
+ };
885
+ type LiveEventSubscriber = (entityName: string, listener: (event: LiveEvent) => void) => () => void; // Subscribes to live events for an entity (aggregateType) and returns the unsubscribe function.
886
+ type LiveEventsProviderProps = {
887
+ children: ReactNode;
888
+ value: LiveEventSubscriber;
889
+ };
890
+ type LocaleOption = {
891
+ code: string; // BCP-47 code, e.g. "de", "en-US", "fr-CA".
892
+ label: string; // Human-readable label shown in the dropdown.
893
+ };
894
+ type LocaleProviderProps = {
895
+ resolver: LocaleResolver;
896
+ fallbackBundles?: TranslationsByLocale[]; // Default bundles supplied by feature plugins.
897
+ fallbackLocale?: string; // Falls back to fallbackLocale when neither the current-locale nor the key lookup hits in a plugin bundle.
898
+ children: ReactNode;
899
+ };
900
+ type MenuItemDef =
901
+ | {
902
+ kind: "item";
903
+ id: string;
904
+ label: string;
905
+ icon?: ReactNode; // Optional icon displayed before the label (16px).
906
+ shortcut?: string; // Display-only shortcut hint on the right (monospace, muted).
907
+ onSelect: () => void;
908
+ disabled?: boolean;
909
+ variant?: "default" | "danger"; // "danger" renders the label in red, typically for sign-out or delete actions.
910
+ }
911
+ | {
912
+ kind: "separator";
913
+ }
914
+ | {
915
+ kind: "label";
916
+ label: string;
917
+ }
918
+ ;
919
+ type ModeSwitchVariant = "outline" | "pill";
920
+ type NavApi = {
921
+ route: NavRoute | undefined; // Current route — `undefined` when the URL is at the root / there's no route selected.
922
+ navigate: (target: NavTarget, options?: NavigateOptions) => void; // Push a new route.
923
+ replace: (target: NavTarget, options?: NavigateOptions) => void; // Replace the current route in place.
924
+ hrefFor: (target: NavTarget, options?: NavigateOptions) => string; // Build the href a click on {target} would produce.
925
+ searchParams: Readonly<Record<string, string>>; // Snapshot of the current search params (browser: `?key=value` pairs after the path).
926
+ setSearchParams: (updates: Readonly<Record<string, string | null>>) => void; // Merges updates into the current search params.
927
+ };
928
+ type NavProviderProps = {
929
+ children: ReactNode;
930
+ value: NavApi;
931
+ };
932
+ type NavReparentOverride = {
933
+ parent: string;
934
+ order: number;
935
+ };
936
+ type NavRoute = {
937
+ workspaceId?: string;
938
+ screenId: string;
939
+ entityId?: string;
940
+ };
941
+ type NavTarget = ScreenTarget | ObjectTarget;
942
+ type NavTreeProps = {
943
+ schema: AppSchema | FeatureSchema;
944
+ user?: {
945
+ id: string;
946
+ roles: string[];
947
+ };
948
+ testId?: string;
949
+ allowedNavQns?: ReadonlySet<string>;
950
+ navBadges?: ReadonlyMap<string, ReactNode>;
951
+ };
952
+ interface NumberFieldProps {
953
+ label: string;
954
+ id: string;
955
+ name: string;
956
+ value: number | undefined;
957
+ onChange: (v: number | undefined) => void;
958
+ required?: boolean;
959
+ disabled?: boolean;
960
+ hideLabel?: boolean;
961
+ testId?: string;
962
+ grouping?: boolean; // `false` renders without thousands separators (e.g. a year).
963
+ }
964
+ type PdfPreviewSource =
965
+ | {
966
+ base64: string;
967
+ }
968
+ | {
969
+ url: string;
970
+ }
971
+ ;
972
+ type PhotoSlotSpec = {
973
+ id: string;
974
+ label: string; // The shot this slot asks for ("Front, angled") — the empty slot's caption and the filled thumbnail's alt text.
975
+ previewUrl?: string; // Set once the app holds the photo; the slot then shows the thumbnail.
976
+ badge?: string; // Corner tag on a filled slot, e.g. "Cover photo".
977
+ };
978
+ type PhotoSlotsProps = {
979
+ slots: PhotoSlotSpec[];
980
+ onUpload: (slotId: string, file: File) => Promise<void>; // Stores the photo for `slotId`.
981
+ pickHint?: ReactNode; // Second line on an empty slot, e.g. "Camera or gallery".
982
+ accept?: string[];
983
+ capture?: "environment" | "user"; // Opens that camera directly on phones; unset keeps camera and gallery.
984
+ featureFirst?: boolean; // The first slot spans the full row (cover photo).
985
+ disabled?: boolean;
986
+ testId?: string;
987
+ };
988
+ type PlanCardActionSlot = {
989
+ label: string;
990
+ onClick: () => void;
991
+ disabled?: boolean;
992
+ };
993
+ type PlanCardLinkSlot = {
994
+ label: string;
995
+ href: string;
996
+ target?: "_blank";
997
+ };
998
+ type PlanCardPrice = {
999
+ amount: string;
1000
+ period?: string;
1001
+ prefix?: string; // Shown before the amount, e.g. "from" when prices differ per provider.
1002
+ note?: string; // Small line under the price row, e.g. "plus VAT".
1003
+ };
1004
+ type PlanCardProps = {
1005
+ title: string;
1006
+ description?: string;
1007
+ price?: PlanCardPrice | null; // null renders the price-unavailable state; undefined renders no price row.
1008
+ features?: ReactNode[];
1009
+ current?: boolean;
1010
+ cta?: PlanCardActionSlot;
1011
+ ctaLink?: PlanCardLinkSlot; // Link-styled cta (e.g. contact sales); wins over `cta` when both are set.
1012
+ secondaryAction?: PlanCardActionSlot;
1013
+ variant?: "default" | "compact"; // compact: one-off purchases (e.g. credit packs) — title+price in one row, no feature emphasis, smaller padding.
1014
+ testId?: string;
1015
+ };
1016
+ type PlanGridProps = {
1017
+ children: ReactNode;
1018
+ testId?: string;
1019
+ };
1020
+ type PrimitivesProviderProps = {
1021
+ children: ReactNode;
1022
+ value: PrimitivesRegistry;
1023
+ };
1024
+ type PrimitivesRegistry = CorePrimitives & AppPrimitives;
1025
+ type ProfileMenuItem = MenuItemDef;
1026
+ type ProfileMenuProps = {
1027
+ user: {
1028
+ id: string;
1029
+ label: string; // Display name or email — the source for initials.
1030
+ };
1031
+ items: ProfileMenuItem[];
1032
+ testId?: string;
1033
+ };
1034
+ type ProgressListRow = {
1035
+ id: string;
1036
+ label: string;
1037
+ value: string;
1038
+ fraction: number | null; // null renders no bar (nothing to measure against, e.g. an unlimited cap).
1039
+ tone?: "default" | "danger"; // danger colors bar and value.
1040
+ sub?: string; // Small line under the bar, e.g. "42 % repaid".
1041
+ };
1042
+ type PublicRoute = {
1043
+ path: string; // Exact window.location.pathname match (matched once on mount; every apex page is a full-page reload, no SPA router).
1044
+ component: ReactNode;
1045
+ };
1046
+ type PublicShellProps = PublicShellCommonProps & ({ variant: "marketing"; notice?: ReactNode; } | { variant: "focus"; progress?: ReactNode; } | { variant: "card"; });
1047
+ type PublicShellVariant = "marketing" | "focus" | "card";
1048
+ type QueryTableColumn = {
1049
+ field: string;
1050
+ label: string; // Translated header label (resolving the i18n key is the caller's responsibility).
1051
+ type?: string; // Field type for cell formatting.
1052
+ };
1053
+ type QueryTableProps<TData = unknown> = {
1054
+ query: string; // Dispatcher-Query-Type (`<feature>:query:<entity>:<verb>`).
1055
+ payload?: unknown;
1056
+ live?: boolean; // SSE invalidation — refetches on entity events (useQuery live mode).
1057
+ columns: QueryTableColumn[];
1058
+ rows?: (data: TData) => Readonly<Record<string, unknown>>[]; // Extracts rows from the query result.
1059
+ rowId?: (row: Readonly<Record<string, unknown>>, index: number) => string; // Row ID for keys and row clicks.
1060
+ onRowClick?: DataTableProps["onRowClick"];
1061
+ rowActions?: DataTableProps["rowActions"];
1062
+ emptyState?: ReactNode; // Empty content — defaults to the standard list empty state.
1063
+ toolbarTitle?: ReactNode;
1064
+ toolbarEnd?: ReactNode;
1065
+ testId?: string;
1066
+ };
1067
+ type RadiusTokens = {
1068
+ sm: string;
1069
+ md: string;
1070
+ lg: string;
1071
+ xl: string;
1072
+ };
1073
+ interface RangeFieldProps extends FieldBase {
1074
+ value: number;
1075
+ onChange: (v: number) => void;
1076
+ min: number;
1077
+ max: number;
1078
+ step?: number;
1079
+ }
1080
+ type RenderEditChangeState<TValues extends FormValues> = {
1081
+ values: TValues;
1082
+ changes: Partial<TValues>;
1083
+ dirty: boolean;
1084
+ valid: boolean;
1085
+ submitting: boolean;
1086
+ };
1087
+ type RenderEditControls<TValues extends FormValues> = {
1088
+ patch: (partial: Partial<TValues>) => void;
1089
+ validate: () => boolean;
1090
+ getValues: () => TValues;
1091
+ submit: () => Promise<void>; // Runs the same pipeline the built-in save button runs: validation, `customSubmit`/`writeCommand`, extension-section persistence, draft discard, state rebase.
1092
+ next: () => void; // Wizard only (no-op elsewhere): validates the current step and advances, like the built-in Next button.
1093
+ back: () => void; // Wizard only (no-op elsewhere): goes back one step without validating.
1094
+ };
1095
+ type RenderEditProps<TValues extends FormValues, TCtx = unknown> = {
1096
+ screen: EntityEditScreenDefinition;
1097
+ i18nScreenId?: string; // Screen id used for the `screen:<id>.title`/`.subtitle` lookups and as the title fallback, when `screen.id` is synthetic (e.g. a secretMint confirm form keeps its own id for the draft key).
1098
+ entity: EntityDefinition;
1099
+ featureName: string;
1100
+ initial: TValues;
1101
+ entityId?: string | null; // Real entity id for extension-section mounts (set-value UI).
1102
+ extensionInitialValues?: Readonly<Record<string, unknown>>; // Already-persisted extension values (e.g. `record.customFields`) for extension-section mounts.
1103
+ writeCommand?: string; // Standard single-write submit path.
1104
+ customSubmit?: (snapshot: FormSnapshot<TValues>) => Promise<SubmitResult<unknown>>; // Override for the submit pipeline.
1105
+ translate?: Translate;
1106
+ ctx?: TCtx;
1107
+ schema?: z.ZodType;
1108
+ onSubmit?: (result: SubmitResult<unknown>, values: TValues) => void; // `values` are the form values at the moment the submit succeeded.
1109
+ payloadMode?: "values" | "changes";
1110
+ buildPayload?: (snapshot: FormSnapshot<TValues>) => unknown;
1111
+ serverFieldPathPrefix?: string; // Prefix to strip from server validation issue paths before mapping them onto form fields — see SubmitConfig.serverFieldPathPrefix.
1112
+ onDelete?: () => Promise<DispatcherError | void> | DispatcherError | void; // Returning the rejected write's error shows it in the form-error banner; returning nothing means the delete went through.
1113
+ onCancel?: () => void;
1114
+ onReload?: () => void;
1115
+ refreshToken?: number; // Bumped by the host after it refetched `initial`.
1116
+ onDirtyChange?: (dirty: boolean) => void; // Fires when the form gains or loses unsaved input (field changes or a dirty extension section).
1117
+ onCopyLink?: () => Promise<void> | void; // Copy-link action — only set in update mode (create mode has no entity id yet, hence no permalink).
1118
+ actions?: RenderEditAction[]; // Header action buttons, rendered before the built-in copy-link/ delete/cancel/save controls.
1119
+ menuActions?: RenderEditAction[]; // Record actions declared with `placement: "menu"`, resolved like `actions`.
1120
+ onRelatedListDrawerAction?: (action: RowActionDrawer, initialValues: Readonly<Record<string, unknown>> | undefined) => void; // Opens a relatedList section row's drawer-kind action.
1121
+ submitLabel?: string; // i18n key for the submit button.
1122
+ submitWithoutChanges?: boolean; // Set by submitPrefilled screens: defined shows the submit button even when no field is editable (all hidden or read-only); `true` also enables it while the form is unchanged.
1123
+ submitVariant?: "primary" | "danger"; // Visual style of the submit button (actionForm `submitStyle`).
1124
+ footerActions?: ActionFormFooterAction[]; // Extra footer buttons (actionForm `footerActions`): each sets its `patch` on the form values, then submits through the normal validate + write path.
1125
+ summary?: {
1126
+ title: string;
1127
+ subtitle?: string;
1128
+ }; // Context box above the drawer form (title + optional subtitle, already resolved).
1129
+ labelAppendix?: (fieldName: string) => ReactNode | undefined; // Per-field extra content inline after the label (e.g. ConfigSourceBadge).
1130
+ fieldAppendix?: (fieldName: string) => ReactNode | undefined; // Per-field extra content below the input (e.g. ConfigCascadeView).
1131
+ fieldAppendixPlacement?: "above-control" | "below-control"; // Placement of every `fieldAppendix` relative to its control.
1132
+ fieldDescription?: (fieldName: string) => ReactNode | undefined; // Per-field help text rendered under the label (translated by the caller).
1133
+ fieldAccent?: (fieldName: string) => boolean; // Marks fields whose value is set at this level; drawn by `settings-list` rows.
1134
+ markChangedFields?: boolean; // Edit-mode "changed" markers also for screens without an entity id (configEdit loads its values from a query, not a record).
1135
+ onChange?: (state: RenderEditChangeState<TValues>) => void; // Controlled mode: fires on every values-snapshot change (typing, `patch(...)` from outside) with the current values.
1136
+ onControlsReady?: (controls: RenderEditControls<TValues>) => void; // Controlled mode: called once after mount, hands the caller `patch`/`validate`/`getValues` bound to this RenderEdit instance — addressable from outside without a remount.
1137
+ fields?: string[]; // Renders only these fields (by `field` name from the layout) — section order, title, and visibility still come from the layout, so the caller doesn't duplicate its shape.
1138
+ disabled?: boolean; // Locked state: every rendered field and the submit button go visibly inactive, no write possible.
1139
+ viewMode?: boolean; // Read-only view of an existing record (`EntityEditScreenDefinition.readOnly`): every field is disabled, no submit, discard or dirty footer, and the title resolves `screen:<id>.view.title` first.
1140
+ hideActions?: boolean; // Renders the fields without RenderEdit's own action bar (save, cancel, delete, copy-link).
1141
+ valueDisplay?: "form" | "text"; // "form" (default) — every field renders as its Input widget, disabled when `field.readOnly`, unchanged behavior.
1142
+ hideSectionTitles?: boolean; // Suppresses every section's own title (fields-section header, relatedList Section title) AND the form's own top-level title/subtitle — for a host that already renders that label itself elsewhere (e.g. a tab strip whose label duplicates it, or a page-level header above RenderEdit).
1143
+ fillScreenHeight?: boolean; // Screen body fills the shell height: the form gets `fillHeight` and `stickyActions` (sections scroll, footer pinned).
1144
+ leadContent?: ReactNode; // Rendered first inside the form, above the sections, in the same column and rhythm — for content that belongs to the form but is no field (e.g. the secret a confirm step refers to).
1145
+ submitBlocked?: boolean; // Blocks submit (button and Enter) while fields stay editable — unlike `disabled`, which locks the whole form.
1146
+ formDerivation?: FormDerivationScreen; // actionForm `derive`: derived fields are recomputed into the form state on every change and render read-only.
1147
+ actionFormScreenQn?: string; // QN of the actionForm screen this form renders; its file fields upload as pending for that screen.
1148
+ dirtyFooter?: boolean; // Show the unsaved-changes footer (count, Discard, "Save changes") even without an entity id or a screen-height form — for hosts that edit an existing server-side record through `customSubmit` (configEdit, also when embedded as a dashboard panel).
1149
+ validateOnChange?: boolean; // Validate the edited field right after each change instead of only on submit, so a bounds violation shows at the field while the Save button looks enabled.
1150
+ headerRegion?: ReactNode | ((headerSlot: ReactNode | undefined) => ReactNode); // Extra content rendered above the card, sharing its left padding and width — for a host with its own header region (title/metrics/tabs) that would otherwise render as unpadded siblings before RenderEdit.
1151
+ buildSectionActions?: (actions: RowAction[]) => RenderEditAction[] | undefined; // resolves an `EditFieldsSection`/`EditExtensionSection`/ `EditRelatedListSection`/`EditWriteFormSection`'s own `actions` into already-bound buttons, rendered in that section's title row — same split as `onCopyLink`/header `actions`: RenderEdit has no dispatcher/nav context of its own, so the caller (which does) resolves each RowAction against the record being edited.
1152
+ };
1153
+ type RenderFieldProps = {
1154
+ field: EditFieldViewModel;
1155
+ issues?: FieldIssue[];
1156
+ onChange: (val: unknown) => void;
1157
+ onSuggest?: (value: string) => void; // Seeds an empty optionsQuery select with the query's default row as a baseline value (not a change).
1158
+ featureName?: string; // Relevant only for type:"reference": the feature name for the lookup query QN (`<feature>:query:<refEntity>:list`).
1159
+ labelAppendix?: ReactNode; // Optional content rendered after the label (for example, ConfigSourceBadge).
1160
+ fieldAppendix?: ReactNode; // Optional content rendered after the input (for example, ConfigCascade).
1161
+ appendixPlacement?: FieldProps["appendixPlacement"];
1162
+ description?: ReactNode;
1163
+ status?: FieldProps["status"];
1164
+ allIssues?: Readonly<Record<string, FieldIssue[]>>; // Flat issues-by-path map (FormSnapshot.errors) — only relevant for type:"embedded" with embeddedListCells, to bucket row-/cell-issues (`${field}.${rowIndex}` / `${field}.${rowIndex}.${cellField}`).
1165
+ valueDisplay?: "form" | "text"; // "form" (default) renders every field as its editable Input, disabled when `field.readOnly` — unchanged behavior.
1166
+ row?: Readonly<Record<string, unknown>>; // Current form values, keyed by field name.
1167
+ changed?: boolean; // Marks the field as modified against the loaded record (edit mode only).
1168
+ layout?: FieldProps["layout"];
1169
+ accent?: boolean;
1170
+ fillDisabled?: boolean; // The surrounding form is locked as a whole: the fill button stays visible but inert, unlike a single readOnly field whose fill button keeps working.
1171
+ hideLabel?: boolean; // Label stays for screen readers only (sr-only).
1172
+ };
1173
+ type RenderListProps = DataTableTableBlockProps & {
1174
+ screen: EntityListScreenDefinition;
1175
+ entity: Pick<EntityDefinition, "fields" | "derivedFields">;
1176
+ rows: Readonly<Record<string, unknown>>[];
1177
+ featureName: string;
1178
+ translate?: Translate;
1179
+ onRowClick?: (row: ListRowViewModel) => void;
1180
+ emptyState?: ReactNode; // Overrides the default empty box.
1181
+ onCreate?: () => void; // Adds a "+ New" button to the toolbar and uses it as the CTA in the default empty box.
1182
+ createLabel?: string; // Label for the + New button.
1183
+ searchable?: boolean; // Enables a search input in the toolbar.
1184
+ searchPlaceholder?: string; // Placeholder for the search input.
1185
+ description?: string; // Hint at the left of the toolbar (already translated).
1186
+ emptyContent?: ListEmptyStateContent; // Author-declared replacement for the default empty box (already translated).
1187
+ narrowed?: boolean; // Search or filters changed the result; the empty box then says "no matches".
1188
+ defaultFilterActive?: boolean; // Nothing was narrowed by the user, but a default filter is still on; the empty box then offers "show all" instead of "create the first one".
1189
+ itemNounKey?: string; // Plural-forms key for the footer count noun; default is the entity's `:noun` convention key.
1190
+ searchValue?: string; // Current search term (from URL state / the parent).
1191
+ onSearchChange?: (next: string) => void; // Called when the debounced search term changes.
1192
+ sort?: ListSort | null; // Current sorting, or null for the server's default order.
1193
+ onSortChange?: (next: ListSort | null) => void; // Called with the next sort state after a header click.
1194
+ pager?: {
1195
+ page: number;
1196
+ limit: number;
1197
+ total: number;
1198
+ onPageChange: (next: number) => void;
1199
+ }; // Pager props for pagination="pages".
1200
+ onReachEnd?: () => void; // Infinite-scroll wiring for pagination="infinite".
1201
+ loadingMore?: boolean;
1202
+ hasMore?: boolean;
1203
+ rowActions?: DataTableRowAction[]; // Per-row actions in resolved form.
1204
+ rowActionMode?: DataTableRowActionMode; // How the row-action column renders (see `DataTableProps.rowActionMode`).
1205
+ bulk?: DataTableProps["bulk"]; // Forwarded to `DataTableProps.bulk` / `rowTone`.
1206
+ rowTone?: DataTableProps["rowTone"];
1207
+ toolbarActions?: ToolbarActionButton[]; // Toolbar actions in the list header in resolved form.
1208
+ filterFacets?: DataTableFacet[]; // Faceted filter dropdowns.
1209
+ filterValues?: Readonly<Record<string, string[]>>; // Currently selected facet values by field (from URL state).
1210
+ onFilterChange?: (field: string, values: string[]) => void; // Toggles a facet value (sets URL state and refetches).
1211
+ onFilterReset?: () => void; // Reset aller aktiven Facets.
1212
+ dateRangeFacets?: DataTableDateRangeFacet[]; // Time-range filters — forwarded to the DataTable.
1213
+ onDateRangeChange?: (field: string, bound: "from" | "to", value: string) => void; // One bound changed (set URL state + refetch).
1214
+ chromeless?: boolean; // Forwarded to `DataTableProps.chromeless` — drops the table's own card frame for a host that already provides one (relatedList in a tabs-mode section).
1215
+ scrollBody?: boolean; // Forwarded to `DataTableProps.scrollBody` — bounds the table height and scrolls rows internally instead of growing the page (relatedList in a tabs-mode section).
1216
+ screenPadding?: boolean; // Forwarded to `DataTableProps.screenPadding` — `true`: the table carries the shared screen padding because it is the screen body (entityList/ projectionList).
1217
+ expandedRowIds?: ReadonlySet<string>; // Forwarded to `DataTableProps.expandedRowIds` / `onToggleRowExpanded` / `renderExpandedRow` (entityList `expandableRow`).
1218
+ onToggleRowExpanded?: (rowId: string) => void;
1219
+ renderExpandedRow?: (row: ListRowViewModel) => ReactNode;
1220
+ };
1221
+ type ResolverComponent = ComponentType<{ target: TargetRef; onClose: () => void; }>;
1222
+ interface ResultColumn<Row> {
1223
+ header: string;
1224
+ align?: "left" | "right";
1225
+ cell: (row: Row) => ReactNode;
1226
+ }
1227
+ type SectionProps = { // Titled group of fields.
1228
+ id?: string; // DOM id — the scroll target of a form's "on this page" navigation.
1229
+ title?: string; // Optional; without a title, the Section renders only the grouping and no header.
1230
+ subtitle?: ReactNode; // Optional muted subtitle below the title, providing section context rather than only a label (analogous to `FormProps.subtitle`).
1231
+ children: ReactNode;
1232
+ actions?: ReactNode; // Rendered top-right in the title row — never a footer.
1233
+ layout?: "stacked" | "settings-list"; // "settings-list": title + subtitle form a left header column and the children (field rows) the right one on `md:` and up.
1234
+ variant?: "default" | "destructive"; // "destructive" marks the Section as a warning/danger area (e.g. account deletion, restrict processing) — border color only, no content change.
1235
+ testId?: string;
1236
+ icon?: IconKey; // Rendered left of the title, `text-muted-foreground`, same optical size as the title.
1237
+ };
1238
+ interface SelectFieldProps extends FieldBase {
1239
+ value: string;
1240
+ onChange: (v: string) => void;
1241
+ options: string[] | { value: string; label: string; }[];
1242
+ }
1243
+ type ShadowTokens = {
1244
+ card: string;
1245
+ };
1246
+ type SideBySideTableCell = {
1247
+ content: ReactNode;
1248
+ colSpan?: number; // Spans this cell over following columns (a value that is identical on both sides shown once).
1249
+ };
1250
+ type SideBySideTableColumn = {
1251
+ id: string;
1252
+ label: string;
1253
+ };
1254
+ type SideBySideTableRow = {
1255
+ id: string;
1256
+ header: ReactNode;
1257
+ cells: SideBySideTableCell[];
1258
+ tone?: "warn"; // Highlights a row that needs attention (e.g. differing values).
1259
+ };
1260
+ type SidebarBrandProps = {
1261
+ name: string; // Workspace/app name (bold, first line).
1262
+ plan?: string; // Second line — plan, tagline, or tenant (small, dimmed).
1263
+ logo?: ReactNode; // The tile typically contains a Lucide icon.
1264
+ collapsible?: boolean; // Shows the ChevronsUpDown icon only when the app wraps the brand in a dropdown.
1265
+ };
1266
+ type SidebarProps = {
1267
+ header?: ReactNode; // Header area at the top — typically a workspace avatar, name, and badge.
1268
+ actions?: ReactNode; // Action cluster between the header and nav — typically a "+ Neu" button and search icon.
1269
+ children: ReactNode; // NavTree and free-form sections (e.g. "Your teams" header and sub-items).
1270
+ footer?: ReactNode; // Footer area at the bottom — typically an invite link, help, and plan banner.
1271
+ testId?: string;
1272
+ };
1273
+ type SidebarUserProps = {
1274
+ name: string;
1275
+ email?: string;
1276
+ avatar?: string; // Avatar URL.
1277
+ };
1278
+ type SpacingTokens = {
1279
+ card: string;
1280
+ };
1281
+ type StatDelta = {
1282
+ value: string;
1283
+ direction: "up" | "down";
1284
+ tone?: StatTone;
1285
+ };
1286
+ type StatTone = DashboardStatTone;
1287
+ type StatusBadgeTone = StatusTone | "accent"; // `accent` is a brand-colored call-out (e.g. "3 suggestions"), not a status — hence not part of `StatusTone`, which toasts and select options share.
1288
+ type StatusBarEntry = {
1289
+ key: string; // Stable key (z.B.
1290
+ level: number; // Bar height from 0 to 1 (e.g. operational=1, degraded=0.75, outage=0.25).
1291
+ tone: StatusTone;
1292
+ label?: string; // Tooltip text (<title>), supplied translated by the caller.
1293
+ };
1294
+ type StatusTone = "ok" | "warn" | "bad" | "critical" | "muted";
1295
+ interface TextFieldProps extends FieldBase {
1296
+ value: string;
1297
+ onChange: (v: string) => void;
1298
+ placeholder?: string;
1299
+ autoComplete?: string;
1300
+ }
1301
+ type TextProps = { // Semantic text.
1302
+ variant?: "body" | "small" | "code" | "required-mark" | "muted";
1303
+ decorative?: boolean; // Pure ornament (e.g. a separator glyph): hidden from assistive tech.
1304
+ children: ReactNode;
1305
+ testId?: string;
1306
+ };
1307
+ interface TextareaFieldProps extends FieldBase {
1308
+ value: string;
1309
+ onChange: (v: string) => void;
1310
+ rows?: number;
1311
+ }
1312
+ type ThemeMenuItemProps = {
1313
+ lightIcon?: ReactNode;
1314
+ darkIcon?: ReactNode;
1315
+ autoIcon?: ReactNode;
1316
+ titleInDark?: string;
1317
+ titleInLight?: string;
1318
+ titleForAuto?: string;
1319
+ testId?: string;
1320
+ };
1321
+ type ThemeMode = "light" | "dark";
1322
+ type ThemePreference = ThemeMode | "auto"; // "auto" follows the OS color scheme live; light/dark are explicit choices.
1323
+ type ThemeToggleProps = {
1324
+ lightIcon?: ReactNode; // Icon for the step to light mode.
1325
+ darkIcon?: ReactNode; // Icon for the step to dark mode.
1326
+ autoIcon?: ReactNode; // Icon for the step to auto mode.
1327
+ titleInDark?: string; // Title/aria-label for the step to light mode.
1328
+ titleInLight?: string; // Title/aria-label for the step to dark mode.
1329
+ titleForAuto?: string; // Title/aria-label for the step to auto mode.
1330
+ testId?: string;
1331
+ };
1332
+ type TimeseriesHeight = // Pixel height, or one height per viewport class.
1333
+ | number
1334
+ | {
1335
+ narrow: number;
1336
+ wide: number;
1337
+ narrowBelowPx?: number;
1338
+ }
1339
+ ;
1340
+ type TimeseriesPoint = {
1341
+ atMs: number;
1342
+ value: number | null; // null = outage/no value → falls to the baseline for a visible drop.
1343
+ };
1344
+ type TimeseriesReferenceLine = {
1345
+ value: number; // Same unit as the point values (e.g. a p95 or SLO threshold in ms).
1346
+ label: string; // Translated by the caller.
1347
+ tone?: StatusTone;
1348
+ };
1349
+ type TimeseriesXAxis = {
1350
+ ticks: number; // Number of evenly spaced time labels (min 2).
1351
+ format: (atMs: number) => string;
1352
+ };
1353
+ type TimeseriesYAxis = {
1354
+ ticks: number; // Number of gridlines including the zero baseline (min 2).
1355
+ format?: (value: number) => string; // Label text per tick value (unit, locale).
1356
+ unit?: string; // Ready-to-display unit (e.g. "ms"), shown once above the tick labels so `format` can stay unit-free.
1357
+ };
1358
+ type ToastOptions = {
1359
+ title: string;
1360
+ description?: string;
1361
+ variant?: ToastVariant;
1362
+ docsUrl?: string;
1363
+ docsLinkLabel?: string;
1364
+ requestId?: string;
1365
+ };
1366
+ type ToastProviderProps = {
1367
+ children: ReactNode;
1368
+ };
1369
+ type ToastVariant = StatusTone;
1370
+ type Tokens = CoreTokens & AppTokens;
1371
+ type TokensApi = {
1372
+ tokens: Tokens;
1373
+ mode: ThemeMode; // The scheme currently applied (resolved from the preference when it is "auto").
1374
+ preference?: ThemePreference; // Optional so hand-built TokensApi values (tests, custom providers) stay valid: absent means a plain light/dark implementation, read as `mode`.
1375
+ setPreference?: (preference: ThemePreference) => void;
1376
+ setMode: (mode: ThemeMode) => void; // Explicit choice: leaves "auto".
1377
+ toggleMode: () => void; // Explicit flip of the resolved mode: leaves "auto".
1378
+ };
1379
+ type TokensProviderProps = {
1380
+ children: ReactNode;
1381
+ value: TokensApi;
1382
+ };
1383
+ type TopbarProps = {
1384
+ start?: ReactNode;
1385
+ center?: ReactNode;
1386
+ end?: ReactNode;
1387
+ testId?: string;
1388
+ };
1389
+ type TranslationBundle = Readonly<Record<string, TranslationValue>>; // Map of i18n key → TranslationValue.
1390
+ type TranslationsByLocale = Readonly<Record<string, TranslationBundle>>; // Map from locale code (BCP-47, e.g. `"de"`, `"en-US"`) → bundle.
1391
+ type UploadZoneProps = {
1392
+ onUpload: (file: File) => Promise<string | undefined | void>; // Uploads a single file (POST + optional follow-up mutation).
1393
+ title: ReactNode;
1394
+ hint?: ReactNode;
1395
+ accept?: string[];
1396
+ multiple?: boolean; // Allow multiple files per pick/drop.
1397
+ capture?: "environment" | "user"; // Forwarded to the file input: a phone opens that camera directly instead of offering camera, gallery and files.
1398
+ disabled?: boolean;
1399
+ testId?: string;
1400
+ describeError?: (cause: unknown) => string; // Turns a failed upload into the row's error text.
1401
+ onRemove?: (value: string) => void; // Controlled use, for a form field: a string returned by `onUpload` becomes the row's value and done rows get a remove button that calls this with it.
1402
+ doneValues?: string[]; // The values the owner currently holds.
1403
+ };
1404
+ type UseFormOptions<TValues extends FormValues, TCtx = unknown> = FormControllerOptions<TValues, TCtx>;
1405
+ type UseFormResult<TValues extends FormValues> = {
1406
+ controller: FormController<TValues>;
1407
+ snapshot: FormSnapshot<TValues>;
1408
+ };
1409
+ type UseQueryOptions = {
1410
+ enabled?: boolean;
1411
+ live?: boolean;
1412
+ refetchIntervalMs?: number;
1413
+ concurrency?: number;
1414
+ };
1415
+ type UseQueryResult<TData> = {
1416
+ data: TData | null;
1417
+ error: DispatcherError | null;
1418
+ loading: boolean;
1419
+ refetch: () => Promise<void>;
1420
+ };
1421
+ type UserRolesProviderProps = {
1422
+ roles: string[] | undefined;
1423
+ children: ReactNode;
1424
+ };
1425
+ type WorkspaceShellProps = {
1426
+ brand: ReactNode; // Branding / logo on the left side of the topbar.
1427
+ schema: AppSchema | FeatureSchema; // Schema with `workspaces` populated — the engine builds this from registry.getAllWorkspaces() + getWorkspaceNavs().
1428
+ topbarActions?: ReactNode; // Optional topbar end-slot — TenantSwitcher / ThemeToggle / UserMenu.
1429
+ user?: WorkspaceShellUser; // Current user.
1430
+ initialWorkspaceId?: string; // Initial active workspace short id ("admin").
1431
+ sidebarFooter?: ReactNode; // Footer slot at the bottom of the sidebar — profile row, help link, build information.
1432
+ navBadges?: ReadonlyMap<string, ReactNode>; // Runtime badge slot per nav leaf (bare nav id), same as DefaultAppShell.
1433
+ fill?: boolean; // Viewport-fit shell.
1434
+ children: ReactNode; // Screen content.
1435
+ };
1436
+ type WorkspaceShellUser = {
1437
+ id: string;
1438
+ roles: string[];
1439
+ };
1440
+ type WorkspaceSwitcherProps = {
1441
+ workspaces: WorkspaceSchema[];
1442
+ activeId: string;
1443
+ onSelect: (workspaceQn: string) => void;
1444
+ testId?: string;
1445
+ };
1446
+ ```
1447
+
1448
+ Also exported here, documented in `api/framework-engine.md`: `NavIconKey`
1449
+
1450
+ Also exported here, documented in `api/framework-ui-types.md`: `AppSchema`, `FeatureSchema`, `WorkspaceSchema`