@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,542 @@
1
+ <!-- Generated by `bun run gen:agent-docs` from packages/framework/src/ui-types/index.ts. Do not edit. -->
2
+
3
+ # @cosmicdrift/kumiko-framework/ui-types
4
+
5
+ Import from `@cosmicdrift/kumiko-framework/ui-types`. Types used here but not exported by any entry point are in `api/types.md`.
6
+
7
+ ## Functions
8
+
9
+ ```ts
10
+ function applyFormDerivations(values: unknown, screen: FormDerivationScreen): unknown; // Recomputes every derived head field of an action form from its inputs.
11
+ function availableSelectOptions(field: SelectOptionRules, values: Record<string, unknown>): string[]; // Options of a select field that are offered for the given row values, in declaration order.
12
+ function cardColumnRoles<C extends CardRoleColumn>(columns: C[]): CardColumnRoles<C>;
13
+ function computeDerivedCellValue(op: NumericDerivedOp, values: (number | undefined)[]): number | undefined; // Computes a derived cell from its source values.
14
+ function evalFieldCondition(cond: FieldCondition, values: Record<string, unknown>): boolean; // Evaluates a declarative FieldCondition against the current row/form values.
15
+ function findMissingConditionalRequirements(fields: Readonly<Record<string, FieldDefinition>>, values: Record<string, unknown>, previous?: Record<string, unknown>): string[]; // Paths of fields (and embedded sub-fields) whose `requiredWhen` holds while the value is missing.
16
+ function findUnavailableSelectOptions(fields: Readonly<Record<string, FieldDefinition>>, values: Record<string, unknown>, previous?: Record<string, unknown>): UnavailableSelectOption[]; // Select values that violate a `conditionalOptions` rule.
17
+ function formalLocaleTag<Locale extends string>(locale: Locale): `${Locale}-x-formal`;
18
+ function isExtensionEditSection(section: EditSectionSpec): section is EditExtensionSection;
19
+ function isFieldRequired(def: RequiredState, values: Record<string, unknown>): boolean;
20
+ function isFieldVisible(def: FieldStateConditions, values: Record<string, unknown>): boolean;
21
+ function isFormDerivationInputMode(screen: FormDerivationScreen, values: unknown, field: string): boolean; // True when `field` is a `listSum` without `whenEmpty` whose list has no rows: the sum has nothing to add up, so the user types the value.
22
+ function isFormatSpec(r: unknown): r is FormatSpec;
23
+ function isMissingRequiredValue(value: unknown): boolean;
24
+ function isRecordLocked(lockedWhen: FieldCondition | undefined, row: Readonly<Record<string, unknown>>): boolean;
25
+ function isUiAccessGranted(access: AccessRule | undefined, userRoles: string[] | undefined): boolean;
26
+ function isUnchangedValue(a: unknown, b: unknown): boolean;
27
+ function isUnusedFallbackOperand(screen: FormDerivationScreen, values: unknown, field: string): boolean; // True when `field` is only read as a `whenEmpty` operand of `listSum` derivations whose lists all have rows: nothing reads it, so it is locked rather than inviting an input that is silently dropped.
28
+ function isWriteFormEditSection(section: EditSectionSpec): section is EditWriteFormSection;
29
+ function resolveNavParentScreen(screens: ScreenDefinition[], detail: ScreenDefinition, getId: (screen: ScreenDefinition) => string): ScreenDefinition | undefined;
30
+ function resolveTranslationValue(value: TranslationValue, locale: string, params?: Readonly<Record<string, unknown>>): string; // Resolve a TranslationValue to display text: a plural entry picks its CLDR category via `Intl.PluralRules` from `params.count`, falling back to `other` when `count` is missing, non-finite, or `locale` is invalid.
31
+ function roundDerivedCellValue(value: number, target: DerivedCellRoundingTarget): number; // Rounds a derived cell's computed value to the precision its target sub-field declares — commercial rounding (round-half-away-from-zero, correct for signed minor-unit money).
32
+ function translationValueOtherText(value: TranslationValue): string; // Text-only reading of a TranslationValue — the CLDR `other` form for a plural entry, or the string as-is.
33
+ function withDerivedCells(row: unknown, derived: Readonly<Record<string, EmbeddedDerivedCellDef>>, targets: Readonly<Record<string, DerivedCellRoundingTarget>>): unknown; // Recomputes every derived cell of an embedded-list row from its raw values, overwriting whatever the client sent instead of merely checking it — the server is the authority for derived cells.
34
+ ```
35
+
36
+ ## Constants
37
+
38
+ ```ts
39
+ const ACTION_FORM_ENTITY: "__action-form__"; // Pseudo-entity for actionForm field labels (renderer action-form-shim).
40
+ const ACTION_FORM_PSEUDO_ENTITY: "__action-form__";
41
+ const CARD_META_MAX: 3;
42
+ const LIST_ROW_META_COLUMNS: Readonly<Record<string, ListRowMetaColumnType>>;
43
+ const LIST_ROW_META_REFERENCES: Readonly<Record<string, ListRowMetaReference>>;
44
+ const MAX_BULK_SELECTION: 100; // Most rows one selection can hold; the loaded page caps it further.
45
+ const NO_WIDGET_FIELD_TYPES: FieldDefinition["type"][];
46
+ const PROJECTION_DETAIL_ENTITY: "__projection-detail__"; // Pseudo-entity for projectionDetail field labels (renderer projection-detail-shim).
47
+ const REFERENCE_LOOKUP_SOURCES: Readonly<Record<string, ReferenceLookupSource>>;
48
+ const SYSTEM_REFERENCE_LABELS: Readonly<Record<string, SystemReferenceLabel>>;
49
+ const UNRESOLVED_REFERENCE_LABEL_KEYS: Readonly<Record<string, string>>;
50
+ const WRITE_FORM_SECTION_ENTITY: "__write-form-section__"; // Pseudo-entity for a projectionDetail writeForm section's own fieldDefs (distinct from PROJECTION_DETAIL_ENTITY — the section's fields aren't drawn from the host record's display entity).
51
+ ```
52
+
53
+ ## Types
54
+
55
+ ```ts
56
+ type ActionFormFooterAction = {
57
+ id: string; // Stable id (kebab-case), used for the test id.
58
+ label: string; // i18n key.
59
+ icon?: IconKey;
60
+ variant?: "primary" | "secondary" | "danger"; // Button style.
61
+ patch: Readonly<Record<string, string | number | boolean | null>>; // Form values set before the submit.
62
+ };
63
+ type ActionFormLoad = {
64
+ query: string; // Query QN that returns the record.
65
+ idParam?: string; // Payload key carrying the record id (default "id").
66
+ singleton?: boolean; // The query picks the row from the caller's tenant/session context, so the form loads without a record id and the query is called WITHOUT `idParam` (same semantics as `ProjectionDetailScreenDefinition.singleton`).
67
+ };
68
+ type ActionFormRedirect = { // Redirect target plus the success-payload field carrying the navigation id.
69
+ screen: string; // Same target forms as the string `redirect`: short screen ID (same-feature) or a fully-qualified cross-feature screen QN.
70
+ idFrom: string; // Flat field name in the handler's success payload, e.g. "leaseId".
71
+ tab?: string; // Section id to open on the target `projectionDetail` with `layout.mode: "tabs"` (the `tab` search param, same as `ToolbarAction` navigate `tab`).
72
+ };
73
+ type ActionFormSuccessFact = { // One key figure of the success box, read from the handler's success payload.
74
+ field: string; // Flat field name in the handler's success payload.
75
+ label: string; // Label (i18n key).
76
+ };
77
+ type ActionFormSuccessLink = Omit<ActionFormRedirect, "idFrom"> & { // A redirect target rendered as a link in the success box.
78
+ idFrom?: string; // Success-payload field carrying the navigation id.
79
+ label: string; // Link text (i18n key).
80
+ };
81
+ type AppSchema = {
82
+ features: FeatureSchema[];
83
+ workspaces?: WorkspaceSchema[];
84
+ };
85
+ type CustomScreenDefinition = {
86
+ id: string;
87
+ type: "custom";
88
+ nav?: ScreenNavSugar;
89
+ detailFor?: string;
90
+ description?: string;
91
+ agent?: AgentScreenHints;
92
+ renderer: PlatformComponent;
93
+ routes?: CustomScreenRoute[];
94
+ listScreenId?: string; // Parent list screen for breadcrumb when this detail is not in nav.
95
+ access?: AccessRule;
96
+ dormant?: boolean; // This screen is registered without a self-owned `r.nav()` entry by design — an app opts in by navving it explicitly (e.g. a settings-area self-service screen).
97
+ visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
98
+ fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
99
+ };
100
+ type DashboardChartMarkerKind = { // Look of the markers whose `kind` matches: a dashed guide line through the plot plus a colored pin.
101
+ tone?: DashboardChartTone;
102
+ color?: string;
103
+ label?: string; // i18n key; with `markerLegend: "legend"` the chart legend lists the markers of this kind under it.
104
+ };
105
+ type DashboardChartPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
106
+ kind: "chart";
107
+ id: string;
108
+ label: string;
109
+ chart: DashboardChartKind;
110
+ query: string;
111
+ subtitle?: string; // i18n key under the title, e.g. "7 days, by creation day".
112
+ seriesTones?: Readonly<Record<string, DashboardChartTone>>; // Series/segment/line key -> tone; unmapped keys fall back to a palette.
113
+ seriesColors?: Readonly<Record<string, string>>; // Series/segment/line key -> raw CSS color (e.g. "var(--color-debt)"); wins over `seriesTones`.
114
+ valueFormat?: DashboardCurrencyFormat; // Formats values as currency (minor units): y ticks, legend totals and tooltips on stacked-bars/segment-bars/stacked-area, only the y ticks on timeseries.
115
+ unit?: string; // i18n key of a unit label.
116
+ scrollable?: boolean; // Only for chart "stacked-area": fixed width per bucket, plot scrolls horizontally.
117
+ markerKinds?: Readonly<Record<string, DashboardChartMarkerKind>>; // Only for chart "stacked-area": marker `kind` -> look.
118
+ legendTotals?: boolean; // Only for chart "stacked-area": false hides the per-series sums in the legend (they mean nothing for balances).
119
+ ranges?: DashboardChartRanges; // Only for chart "stacked-area": range switch in the panel header.
120
+ brush?: boolean; // Only for chart "stacked-area": scrubber under the plot to drag the visible window; starts at today.
121
+ dateFormat?: "day" | "month"; // Only for chart "stacked-area": date format of axis and marker labels.
122
+ initialWindow?: "default-range" | "from-today"; // Only for chart "stacked-area": window the uncontrolled chart starts in.
123
+ markerLegend?: "list" | "legend"; // Only for chart "stacked-area": "legend" drops the numbered marker list; pins become plain dots with a tooltip and the legend lists marker kinds (see `markerKinds[].label`).
124
+ markerLabels?: boolean; // Only for chart "stacked-area": true writes each marker label vertically next to its guide line on wide panels; narrow panels keep the pins.
125
+ };
126
+ type DashboardCurrencyFormat = { // Chart/stat values are minor units, formatted with the user locale.
127
+ kind: "currency";
128
+ currency: string;
129
+ fractionDigits?: number; // Defaults to the currency's own decimals.
130
+ };
131
+ type DashboardDateParam = { // Epoch ms, rendered as a medium date in the user time zone.
132
+ kind: "date";
133
+ atMs: number;
134
+ };
135
+ type DashboardFeedPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
136
+ kind: "feed";
137
+ id: string;
138
+ label: string;
139
+ query: string;
140
+ };
141
+ type DashboardFilterDefinition = {
142
+ id: string;
143
+ label: string;
144
+ kind: "select";
145
+ placeholder?: string;
146
+ allLabel?: string; // i18n key for the "(all)" entry.
147
+ options?: { value: string; label: string; }[];
148
+ optionsQuery?: string; // Query-Result-Contract: `{ rows: { value: string; label: string }[] }`.
149
+ };
150
+ type DashboardListPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
151
+ kind: "list";
152
+ id: string;
153
+ label: string;
154
+ query: string;
155
+ columns: ListColumnSpec[];
156
+ };
157
+ type DashboardMoneyParam = { // Minor units (like Money fields), formatted with the user locale.
158
+ kind: "money";
159
+ amountMinor: number;
160
+ currency: string;
161
+ };
162
+ type DashboardPanelDefinition = DashboardStatPanel | DashboardStatGroupPanel | DashboardChartPanel | DashboardListPanel | DashboardFeedPanel | DashboardProgressListPanel | DashboardTextPanel | DashboardCustomPanel | DashboardScreenPanel;
163
+ type DashboardPanelEmptyState = {
164
+ span?: DashboardPanelSpan;
165
+ emptyLabel?: string;
166
+ emptyHint?: string; // Second line under `emptyLabel`.
167
+ };
168
+ type DashboardPanelQueryOptions = {
169
+ params?: Readonly<Record<string, unknown>>; // Static, author-set query parameters merged on top of the screen's dynamic filterParams — lets a panel pin a value (e.g. a status facet) the screen-wide filter doesn't cover, without needing its own query.
170
+ ignoreScreenFilter?: boolean; // The screen `filter` value is NOT merged into this panel's query (a tenant picker must not hit sources without a tenant column).
171
+ };
172
+ type DashboardPanelSpan = "half" | "full";
173
+ type DashboardPanelVisibility = {
174
+ query: string;
175
+ field: string;
176
+ eq: string | number | boolean | null;
177
+ };
178
+ type DashboardProgressListPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
179
+ kind: "progress-list";
180
+ id: string;
181
+ label: string;
182
+ query: string;
183
+ };
184
+ type DashboardProgressTone = "default" | "danger";
185
+ type DashboardRefresh = // Polling of the record and all panel queries, never faster than DASHBOARD_MIN_REFRESH_SECONDS.
186
+ | {
187
+ seconds: number;
188
+ }
189
+ | {
190
+ field: string;
191
+ }
192
+ ;
193
+ type DashboardRelativeTimeParam = { // Epoch ms, rendered relative to now ("5 minutes ago", "in 30 seconds") in the user locale; the text keeps ticking while it is on screen.
194
+ kind: "relativeTime";
195
+ atMs: number;
196
+ };
197
+ type DashboardScopeDefinition = {
198
+ badge: string;
199
+ notice?: string;
200
+ };
201
+ type DashboardScreenDefinition = {
202
+ id: string;
203
+ type: "dashboard";
204
+ nav?: ScreenNavSugar;
205
+ detailFor?: string;
206
+ description?: string;
207
+ agent?: AgentScreenHints;
208
+ dormant?: boolean; // Screen has no nav entry by design (opened via link or navved by the app); exempts it from the nav-area boot check.
209
+ visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
210
+ fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
211
+ panels: DashboardPanelDefinition[];
212
+ record?: DashboardRecordBinding; // Binds the dashboard to one record via a URL search param, see DashboardRecordBinding.
213
+ actions?: RowAction[]; // Header actions on the bound record (writeHandler, navigate, clientAction).
214
+ refresh?: DashboardRefresh;
215
+ filter?: DashboardFilterDefinition;
216
+ timeRange?: DashboardTimeRangeDefinition; // Segmented control in the toolbar; the value lives in URL search param `id` and is merged into EVERY panel query (also ignoreScreenFilter ones).
217
+ scope?: DashboardScopeDefinition; // Marks the screen as spanning more than the caller's own scope.
218
+ showUpdatedAt?: boolean; // Show the "Updated at" timestamp in the toolbar.
219
+ slots?: ScreenSlots;
220
+ access?: AccessRule;
221
+ };
222
+ type DashboardScreenPanel = {
223
+ kind: "screen";
224
+ id: string;
225
+ screen: string; // Same-feature short id or cross-feature QN `<feature>:screen:<id>`.
226
+ label?: string;
227
+ visibleWhen?: DashboardPanelVisibility;
228
+ fallback?: string; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` embedded in this panel's place when `visibleWhen` is unmet or its query fails (nothing while it loads).
229
+ chromeless?: boolean; // Render the embedded form without its own card frame and padding, aligned to the dashboard's page grid like a standalone settings screen.
230
+ };
231
+ type DashboardStatGroupPanel = DashboardPanelGate & {
232
+ kind: "stat-group";
233
+ id: string;
234
+ label?: string;
235
+ subtitle?: string; // i18n key, a line under `label`; the boot validator requires `label`.
236
+ span?: DashboardPanelSpan;
237
+ stats: DashboardStatPanel[];
238
+ };
239
+ type DashboardStatPanel = DashboardPanelQueryOptions & DashboardPanelGate & {
240
+ kind: "stat";
241
+ id: string; // Stable id: kebab-case, unique within the panel set.
242
+ label: string; // Display text (i18n key).
243
+ query: string;
244
+ valueField: string;
245
+ subField?: string;
246
+ toneField?: string;
247
+ valueFormat?: DashboardValueFormat; // Formats a numeric value as currency (minor units) or, with `relativeTime`, as a ticking "5 minutes ago" from epoch ms; ignored for string values.
248
+ tone?: DashboardStatTone; // Fixed tone when `toneField` is unset or yields none; not applied while the value is 0.
249
+ sparklineField?: string; // Record field holding `{ atMs, value | null }[]` (MetricResult.points), drawn as a sparkline next to the value.
250
+ deltaField?: string; // Delta chip (e.g. "↓23 %") next to the label.
251
+ deltaDirectionField?: string;
252
+ deltaToneField?: string;
253
+ icon?: PlatformComponent; // Static icon next to the label.
254
+ accentColor?: string; // Static CSS color value (e.g. "var(--color-debt)") for the icon chip, passed straight to the tile without a registry lookup.
255
+ };
256
+ type DashboardText = string | DashboardI18nText; // Plain string is displayed as-is; DashboardI18nText is translated by the renderer.
257
+ type DashboardTextBlock =
258
+ | {
259
+ kind: "heading";
260
+ text: string;
261
+ }
262
+ | {
263
+ kind: "paragraph";
264
+ text: string;
265
+ emphasis?: "muted" | "strong"; // Default "muted", like body copy inside panels.
266
+ }
267
+ | {
268
+ kind: "list";
269
+ items: string[];
270
+ }
271
+ | {
272
+ kind: "code";
273
+ code: string;
274
+ }
275
+ | {
276
+ kind: "hint";
277
+ text: string;
278
+ }
279
+ | {
280
+ kind: "link";
281
+ text: string; // Link label (i18n key).
282
+ screen: string; // Same-feature short id or cross-feature QN `<feature>:screen:<id>`.
283
+ roles?: string[]; // Show the link only to users holding one of these roles.
284
+ }
285
+ ;
286
+ type DashboardTextPanel = DashboardPanelGate & {
287
+ kind: "text";
288
+ id: string;
289
+ label?: string; // Panel title (i18n key).
290
+ span?: DashboardPanelSpan;
291
+ blocks: DashboardTextBlock[];
292
+ };
293
+ type DashboardTextParam = string | number | DashboardI18nText | DashboardMoneyParam | DashboardDateParam | DashboardRelativeTimeParam; // Nested DashboardI18nText params resolve first (e.g. a duration "{years} {months}" with one plural key each); numbers stay raw so plural rules can read `count`.
294
+ type DashboardTimeRangeDefinition = {
295
+ id: string;
296
+ options: { value: string; label: string; }[]; // `label` is an i18n key.
297
+ default: string;
298
+ };
299
+ type DerivedCellRoundingTarget = {
300
+ type: EmbeddedSubFieldDef["type"];
301
+ scale?: number;
302
+ };
303
+ type EditExtensionSection = {
304
+ id?: string; // Kebab-case tab id, used as the `?tab=` value.
305
+ kind: "extension";
306
+ title: string;
307
+ component: PlatformComponent;
308
+ contributesToFormSubmit?: boolean; // When true, the extension registers via `useExtensionFormSubmit` and the surrounding form's Save button should activate for its dirty state.
309
+ entityName?: string; // Overrides the host-derived entity name passed to the mounted component.
310
+ countField?: string; // Record field rendered as a count badge in the tab label when the enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
311
+ actions?: RowAction[]; // Rendered top-right in the section's Card title row — same navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`, evaluated against the same record.
312
+ };
313
+ type EditFieldsSection = EditFieldsSectionBase & ({ fields: EditFieldSpec[]; } | { groups: EditFieldGroup[]; }); // Either a flat `fields` grid or titled `groups` cards, never both: the groups branch has no `fields` key, so a raw `.fields` read on a section does not compile — use `sectionFieldSpecs`.
314
+ type EditLayout = {
315
+ sections: EditSectionSpec[];
316
+ width?: FormWidth; // Default "4xl" (a centered column, narrower than list chrome), unless the app overrides it via `createKumikoApp({ screenWidth })`.
317
+ mode?: "single" | "wizard" | "tabs"; // Default "single".
318
+ variant?: "form" | "settings-list"; // "settings-list" lays each section out as a band with a title column on the left and one hairline-separated row per field (label + help left, control right) — the familiar settings pattern, for screens that are a list of independent preferences rather than one record form.
319
+ draft?: boolean; // Persists in-progress wizard state as a resumable draft instead of discarding it on navigation away.
320
+ wizard?: {
321
+ aside?: {
322
+ upNext: true;
323
+ };
324
+ }; // Wizard layouts only.
325
+ };
326
+ type EditWriteFormSection = {
327
+ id?: string; // Kebab-case tab id, used as the `?tab=` value.
328
+ kind: "writeForm";
329
+ title?: string;
330
+ description?: string;
331
+ columns?: number;
332
+ icon?: IconKey;
333
+ fieldDefs: Readonly<Record<string, FieldDefinition>>; // Field-type map for this section's own form — same shape as `ActionFormScreenDefinition.fields`.
334
+ fields: EditFieldSpec[]; // Rendered fields, in order — same shape as `EditFieldsSection.fields`.
335
+ handler: string; // Write-handler QN dispatched on submit.
336
+ submitLabel?: string; // i18n-key for the submit button.
337
+ actions?: RowAction[]; // Rendered top-right in the section's Card title row — same navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`, evaluated against the same record.
338
+ };
339
+ type EmbeddedDerivedCellDef = ({ op: "multiply" | "sum" | "subtract" | "percent"; from: string[]; } | { op: "inRange"; from: string[]; inside: string; outside: string; }) & { // A cell computed from other cells of the same row.
340
+ when?: Exclude<FieldCondition, boolean>; // Row-level condition on a sibling sub-field.
341
+ };
342
+ type EmbeddedListEditSpec = {
343
+ readOnlyCells?: string[]; // Sub-fields shown as read-only cells; every other cell stays editable.
344
+ cardHiddenCells?: string[]; // Sub-fields that only steer a row (e.g. the kind that decides which controls appear) and are not shown on phone cards; the desktop table still shows them.
345
+ fixedRows?: boolean; // `true` keeps the rows as they are: no add, remove, duplicate, move or paste-appended rows.
346
+ };
347
+ type EntityEditAction = RowAction & {
348
+ placement?: EntityEditActionPlacement;
349
+ };
350
+ type EntityListFacetConfig = {
351
+ display?: "select" | "chips" | "range"; // `"select"` (default): dropdown with checkboxes.
352
+ showCounts?: boolean; // Chips show how many rows each option matches (other facets and the search apply).
353
+ hideEmpty?: boolean; // Chips with zero matching rows are not shown.
354
+ extraOptions?: EntityListFacetExtraOption[];
355
+ };
356
+ type FeatureSchema = {
357
+ featureName: string;
358
+ entities: Readonly<Record<string, EntityDefinition>>;
359
+ screens: ScreenDefinition[];
360
+ navs?: NavDefinition[];
361
+ contentCollections?: QualifiedContentCollection[];
362
+ translations?: TranslationKeys;
363
+ workspaces?: WorkspaceSchema[];
364
+ searchAdapterMissing?: boolean;
365
+ queryLiveEntities?: Readonly<Record<string, string[]>>;
366
+ };
367
+ type FieldAffix =
368
+ | string
369
+ | {
370
+ field: string;
371
+ }
372
+ ;
373
+ type FieldFillAction = {
374
+ label: string; // i18n key (falls back to the literal) of the button label.
375
+ query: string; // Qualified query name, run with the current form values picked by `params`.
376
+ field: string; // Key in the query result written into the field; the value must be a string or a number, anything else counts as a failed fill.
377
+ params?: RowFieldExtractor; // Form fields sent as the query payload (none: an empty payload).
378
+ };
379
+ type FieldIconKey = "mail" | "lock" | "hash" | "search" | "user" | "phone" | "calendar" | "link" | "tag" | "building" | "globe" | "key" | "map-pin";
380
+ type FieldRenderer = PlatformComponent | string | FormatSpec;
381
+ type FormDerivationScreen = {
382
+ fields: Readonly<Record<string, FormFieldDefinition>>;
383
+ derive?: Readonly<Record<string, FormFieldDerivation>>;
384
+ };
385
+ type FormWidth = "3xl" | "4xl" | "full";
386
+ type Formality = "formal" | "informal";
387
+ type IconKey = NavIconKey;
388
+ type ListBulkAction = { // An action that runs a writeHandler row action once per checkbox-selected row.
389
+ id: string; // Stable id, kebab-case, unique among the screen's bulk actions.
390
+ rowAction: string; // Id of a writeHandler entry in the same screen's `rowActions`; it supplies handler, payload, label, style and `visible`.
391
+ confirm?: string; // i18n key of the one confirm dialog for the whole selection; gets `{count}` and `{action}`.
392
+ };
393
+ type ListEmptyStateSpec = { // Replacement for the default "no rows" box of a list.
394
+ title: string;
395
+ description?: string;
396
+ action?: RowAction;
397
+ };
398
+ type ListSortSpec = {
399
+ field: string;
400
+ dir: ListSortDir;
401
+ };
402
+ type OptionsQueryPayload = Readonly<Record<string, OptionsQueryPayloadValue>>;
403
+ type ParsedRefTarget = {
404
+ featureName: string;
405
+ entityName: string;
406
+ };
407
+ type PlatformComponent = {
408
+ react?: unknown;
409
+ native?: unknown;
410
+ };
411
+ type QualifiedContentCollection = ContentCollectionDefinition & {
412
+ navQn: string;
413
+ };
414
+ type RecordHeaderSubtitlePart =
415
+ | string
416
+ | {
417
+ field: string;
418
+ navigate?: MetricNavigate;
419
+ }
420
+ ;
421
+ type RelatedListEmptyStateSpec = ListEmptyStateSpec & { // The first `variants` entry whose `when` holds for the parent record replaces `title` and `description`; `action` stays.
422
+ variants?: RelatedListEmptyStateVariant[];
423
+ };
424
+ type RelatedListEmptyStateVariant = { // Empty-state text for one parent-record state, e.g. "still running" vs.
425
+ when: Exclude<FieldCondition, boolean>;
426
+ title: string;
427
+ description?: string;
428
+ };
429
+ type RelatedListRowTone = Exclude<FieldCondition, boolean> & { // Row tint rule: rows where the `FieldCondition` (`{ field, eq }`, `{ field, in }` …) holds get the tone.
430
+ tone: SelectOptionTone;
431
+ };
432
+ type RelatedListToolbarAction = (Extract<ToolbarAction, { kind: "navigate"; }> & { visible?: FieldCondition; params?: RowFieldExtractor; }) | (Extract<ToolbarAction, { kind: "writeHandler"; }> & { visible?: FieldCondition; params?: RowFieldExtractor; }) | (Extract<ToolbarAction, { kind: "drawer"; }> & { visible?: FieldCondition; params?: RowFieldExtractor; });
433
+ type RowActionClientAction = {
434
+ kind: "clientAction";
435
+ id: string;
436
+ label: string;
437
+ action: string; // Registered client handler name, `<feature>:<kebab>`, e.g. "auth-email-password:request-password-reset".
438
+ payload?: RowFieldExtractor; // Payload handed to the handler, picked from the row (or the shown record).
439
+ confirm?: string; // i18n key for the confirm dialog description.
440
+ confirmLabel?: string; // i18n key for the confirm button, default `label`.
441
+ visible?: FieldCondition; // Conditional visibility per row.
442
+ style?: "primary" | "secondary" | "danger"; // Visual style.
443
+ icon?: IconKey; // Overrides the id-based default icon, see `RowActionWriteHandler.icon`.
444
+ display?: RowActionDisplay; // Inline render form in the row-action column — see `RowActionDisplay`.
445
+ successMessage?: string; // i18n key of the toast shown after the handler reported `{ ok: true }` (default `kumiko.clientAction.success`).
446
+ };
447
+ type RowActionDisplay = "button" | "link" | "icon" | "responsive"; // How a row action renders inline in the row-action column.
448
+ type RowActionDrawer = {
449
+ kind: "drawer";
450
+ id: string;
451
+ label: string;
452
+ screen: string; // Short, unqualified id of an `actionForm` screen in the same feature.
453
+ params?: RowFieldExtractor; // Declarative prefill extracted from the clicked row.
454
+ visible?: FieldCondition; // Per-row conditional visibility.
455
+ style?: "primary" | "secondary" | "danger"; // Visual style only — unlike the writeHandler variants, "danger" does not force a confirm dialog here: the target form is the confirmation.
456
+ icon?: IconKey; // Overrides the id-based default icon (see ACTION_ICON_BY_ID in kumiko-renderer) — closed IconKey vocabulary into the ICONS registry (renderer/web), analogous to EditFieldSpec.icon.
457
+ display?: RowActionDisplay; // Inline render form in the row-action column — see `RowActionDisplay`.
458
+ title?: string; // Drawer heading (i18n key) instead of the action `label`.
459
+ subtitle?: string; // Line under the drawer heading (i18n key or a bare `{name}` template), resolved like `title`.
460
+ rowClick?: boolean; // Clicking the row opens this drawer with that row's `params`.
461
+ };
462
+ type RowActionMode = "adaptive" | "inline"; // Row-action column layout.
463
+ type RowActionNavigate = RowActionNavigateBase & ({ screen: string; entity?: never; } | { entity: string; screen?: never; }); // Exactly one of `screen` / `entity` — mutual exclusivity is type-enforced (boot validator still rejects both/neither for untyped schemas).
464
+ type RowFieldExtractor = // Declarative row-field extractor — JSON-safe (no function prop, survives GET /api/schema / buildAppSchema).
465
+ | {
466
+ pick: string[];
467
+ }
468
+ | {
469
+ map: Readonly<Record<string, string>>;
470
+ }
471
+ ;
472
+ type ScreenFallback = string | ScreenFallbackByReason; // Screen-level `fallback`: one screen for every reason, or one per reason.
473
+ type ScreenFallbackByReason = { [Reason in ScreenFallbackReason]?: string; }; // Per-reason fallback screens (same-feature short id or cross-feature QN).
474
+ type ScreenFallbackReason = "hidden" | "forbidden" | "notFound" | "error"; // Why a screen gate is not open.
475
+ type ScreenSlots = {
476
+ header?: PlatformComponent;
477
+ titleAction?: PlatformComponent; // entityEdit / actionForm: rendered on the right of the form title, same row.
478
+ beforeForm?: PlatformComponent;
479
+ afterForm?: PlatformComponent;
480
+ sidebar?: PlatformComponent;
481
+ footer?: PlatformComponent;
482
+ footerPrimary?: boolean; // entityEdit wizard: marks `footer` as the sticky-footer primary action (pinned `max-sm:fixed` group on mobile) instead of the non-sticky secondary bucket it falls into by default alongside Back/Cancel.
483
+ toolbar?: PlatformComponent;
484
+ };
485
+ type ScreenVisibility = DashboardPanelVisibility & {
486
+ idParam?: string; // Payload key that receives the screen's route id (projectionDetail only).
487
+ };
488
+ type SecretMintConfirmStep = { // Post-reveal proof-of-receipt step.
489
+ handler: string; // Write-handler QN dispatched when the confirm form is submitted.
490
+ fields: Readonly<Record<string, FormFieldDefinition>>;
491
+ layout: EditLayout;
492
+ carry?: string[]; // Mint success-payload fields merged into the confirm payload (e.g. a short-lived setup token).
493
+ submitLabel?: string;
494
+ doneMessage?: string; // i18n key for the banner shown after a successful confirm when the screen declares no `redirect`.
495
+ };
496
+ type SecretsEditScreenDefinition = {
497
+ id: string;
498
+ type: "secretsEdit";
499
+ nav?: ScreenNavSugar;
500
+ detailFor?: string;
501
+ description?: string;
502
+ agent?: AgentScreenHints;
503
+ dormant?: boolean; // Screen has no nav entry by design (opened via link or navved by the app); exempts it from the nav-area boot check.
504
+ visibleWhen?: ScreenVisibility; // UI-only gate, also on direct URL access; handlers still enforce access.
505
+ fallback?: ScreenFallback; // Same-feature short id or cross-feature QN `<feature>:screen:<id>` shown when `visibleWhen` is unmet (default: standard notice).
506
+ secretKeys: Readonly<Record<string, string>>; // field id -> qualified secret name (`<feature>:secret:<kebab>`).
507
+ fieldLabels: Readonly<Record<string, string>>; // field id -> i18n key for the label.
508
+ fieldHints?: Readonly<Record<string, string>>; // field id -> i18n key for the hint, only where the declaration carries one.
509
+ requiredFields?: string[]; // field ids whose declaration set `required: true`.
510
+ fieldAccess?: Readonly<Record<string, AccessRule>>; // field id -> write access derived from the key's `writeRoles`.
511
+ sections: SecretsEditSection[];
512
+ access?: AccessRule;
513
+ };
514
+ type SelectOptionTone = (typeof SELECT_OPTION_TONES)[number];
515
+ type TextFieldDef = PiiAndFieldState & {
516
+ type: "text";
517
+ description?: string; // Optional free-text semantics for the AI agent manifest — never rendered; the i18n label is what users see.
518
+ maxLength?: number;
519
+ minLength?: number; // Lower bound enforced by the generated write schema, so short input is rejected by validation instead of reaching the handler.
520
+ autoComplete?: string; // HTML `autocomplete` token for the rendered input (e.g. "one-time-code"), so browsers and password managers offer or suppress autofill correctly.
521
+ required?: boolean;
522
+ searchable?: boolean;
523
+ sortable?: boolean;
524
+ sortNulls?: SortNulls;
525
+ filterable?: boolean; // Allows screen filters on this field.
526
+ encrypted?: boolean;
527
+ sensitive?: boolean;
528
+ writeOnly?: boolean; // Write-only secret (API key): the value is never returned to clients.
529
+ readAsInstruction?: boolean; // A later run reads this field's value as an instruction — prompt, rule, template.
530
+ format?: "email" | "url" | "phone" | "password" | "month" | "iban"; // "password" is a pure render hint (masked input) — no storage semantics.
531
+ default?: string;
532
+ access?: FieldAccess;
533
+ multiline?: boolean | { rows?: number; }; // Multiline text causes DefaultInput to render a <textarea> instead of <input type="text">.
534
+ };
535
+ type TranslationValue = string | PluralForms;
536
+ type WorkspaceSchema = {
537
+ definition: WorkspaceDefinition;
538
+ navMembers: string[];
539
+ };
540
+ ```
541
+
542
+ Also exported here, documented in `api/framework-engine.md`: `AccessRule`, `ActionFormScreenDefinition`, `ConfigEditScreenDefinition`, `DashboardCustomPanel`, `DashboardI18nText`, `EditFieldSpec`, `EditRelatedListSection`, `EditSectionSpec`, `EntityDefinition`, `EntityEditScreenDefinition`, `EntityListScreenDefinition`, `FieldCondition`, `FieldDefinition`, `ListColumnSpec`, `ListFacetSpec`, `ListTableOptions`, `MetricNavigate`, `NavDefinition`, `NavIconKey`, `ProjectionDetailScreenDefinition`, `ProjectionListScreenDefinition`, `RowAction`, `RowActionWriteHandler`, `ScreenDefinition`, `SecretMintScreenDefinition`, `TargetRef`, `ToolbarAction`, `TreeAction`, `WorkspaceDefinition`, `handlerFieldLabelKey`, `handlerTitleKey`, `isFieldsEditSection`, `isOpenToAllGranted`, `normalizeEditField`, `normalizeListColumn`, `parseRefTarget`, `sectionFieldSpecs`