@cosmicdrift/kumiko-framework 0.399.0 → 0.401.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 (144) hide show
  1. package/AGENTS.md +4 -3
  2. package/agents/api/framework-engine.md +16 -8
  3. package/agents/api/framework-ui-types.md +10 -5
  4. package/agents/api/renderer-web.md +2 -1
  5. package/agents/api/testing-stack.md +1 -0
  6. package/agents/api/types.md +256 -80
  7. package/agents/api.md +3 -3
  8. package/agents/examples/derived-fields-sql.md +251 -0
  9. package/agents/examples/documents.md +161 -0
  10. package/agents/examples/inspection-checklist.md +807 -0
  11. package/agents/examples/relations.md +13 -3
  12. package/agents/examples.md +3 -0
  13. package/agents/features-index.md +2 -0
  14. package/agents/features.md +8 -0
  15. package/dist/api/entity-read-access.d.ts +2 -0
  16. package/dist/api/entity-read-access.js +46 -2
  17. package/dist/api/server-boot-checks.js +2 -2
  18. package/dist/api/server-routes.js +3 -1
  19. package/dist/config/index.d.ts +1 -1
  20. package/dist/db/derived-field-sql.d.ts +23 -0
  21. package/dist/db/derived-field-sql.js +115 -0
  22. package/dist/db/derived-related-sql.d.ts +31 -0
  23. package/dist/db/derived-related-sql.js +137 -0
  24. package/dist/db/driver-row-coercion.d.ts +1 -0
  25. package/dist/db/driver-row-coercion.js +13 -0
  26. package/dist/db/eagerload.d.ts +3 -1
  27. package/dist/db/eagerload.js +4 -6
  28. package/dist/db/event-store-executor-context.js +1 -1
  29. package/dist/db/event-store-executor-read-paging.js +1 -1
  30. package/dist/db/event-store-executor-read-search.d.ts +3 -0
  31. package/dist/db/event-store-executor-read-search.js +17 -9
  32. package/dist/db/event-store-executor-read-sort.d.ts +9 -2
  33. package/dist/db/event-store-executor-read-sort.js +27 -14
  34. package/dist/db/event-store-executor-read-where.d.ts +0 -5
  35. package/dist/db/event-store-executor-read-where.js +3 -9
  36. package/dist/db/event-store-executor-read.js +23 -4
  37. package/dist/db/event-store-executor-write-steps.d.ts +6 -1
  38. package/dist/db/event-store-executor-write-steps.js +20 -1
  39. package/dist/db/event-store-executor-write.js +8 -8
  40. package/dist/db/money.d.ts +4 -0
  41. package/dist/db/money.js +34 -29
  42. package/dist/db/parent-ref-clause.d.ts +1 -0
  43. package/dist/db/parent-ref-clause.js +18 -8
  44. package/dist/db/read-gate-clause.d.ts +7 -0
  45. package/dist/db/read-gate-clause.js +201 -0
  46. package/dist/db/read-ownership.d.ts +9 -0
  47. package/dist/db/read-ownership.js +12 -0
  48. package/dist/engine/boot-validator/boot-check.d.ts +3 -1
  49. package/dist/engine/boot-validator/boot-check.js +7 -11
  50. package/dist/engine/boot-validator/boot-context.d.ts +26 -0
  51. package/dist/engine/boot-validator/boot-context.js +57 -0
  52. package/dist/engine/boot-validator/boot-options.d.ts +38 -0
  53. package/dist/engine/boot-validator/boot-options.js +1 -0
  54. package/dist/engine/boot-validator/boot-phases-app.d.ts +5 -0
  55. package/dist/engine/boot-validator/boot-phases-app.js +107 -0
  56. package/dist/engine/boot-validator/boot-phases.d.ts +11 -0
  57. package/dist/engine/boot-validator/boot-phases.js +140 -0
  58. package/dist/engine/boot-validator/boot-report.d.ts +6 -0
  59. package/dist/engine/boot-validator/boot-report.js +47 -0
  60. package/dist/engine/boot-validator/boot-rule.d.ts +12 -0
  61. package/dist/engine/boot-validator/boot-rule.js +14 -0
  62. package/dist/engine/boot-validator/boot-warnings.d.ts +2 -0
  63. package/dist/engine/boot-validator/boot-warnings.js +20 -0
  64. package/dist/engine/boot-validator/derived-sql-fields.d.ts +4 -0
  65. package/dist/engine/boot-validator/derived-sql-fields.js +184 -0
  66. package/dist/engine/boot-validator/entity-handler.d.ts +3 -0
  67. package/dist/engine/boot-validator/entity-handler.js +6 -3
  68. package/dist/engine/boot-validator/entity-list-screens.js +3 -1
  69. package/dist/engine/boot-validator/field-conditions.d.ts +2 -0
  70. package/dist/engine/boot-validator/field-conditions.js +196 -0
  71. package/dist/engine/boot-validator/i18n-keys.d.ts +3 -2
  72. package/dist/engine/boot-validator/i18n-keys.js +18 -4
  73. package/dist/engine/boot-validator/index.d.ts +5 -34
  74. package/dist/engine/boot-validator/index.js +24 -204
  75. package/dist/engine/boot-validator/pii-retention.js +15 -9
  76. package/dist/engine/boot-validator/projection-list-screens.js +19 -1
  77. package/dist/engine/boot-validator/read-gate.d.ts +2 -0
  78. package/dist/engine/boot-validator/read-gate.js +59 -0
  79. package/dist/engine/boot-validator/screens-forms.js +19 -6
  80. package/dist/engine/boot-validator/screens-kind-entity-list.js +8 -6
  81. package/dist/engine/boot-validator/screens-lists.js +8 -0
  82. package/dist/engine/boot-validator/zod-shape.js +4 -2
  83. package/dist/engine/build-app-schema.js +11 -5
  84. package/dist/engine/embedded-derived.d.ts +8 -6
  85. package/dist/engine/embedded-derived.js +25 -7
  86. package/dist/engine/entity-handlers.d.ts +13 -6
  87. package/dist/engine/entity-handlers.js +17 -18
  88. package/dist/engine/entity-list-runtime-options.d.ts +2 -1
  89. package/dist/engine/entity-list-runtime-options.js +86 -5
  90. package/dist/engine/factories.d.ts +18 -6
  91. package/dist/engine/factories.js +21 -5
  92. package/dist/engine/field-access.d.ts +2 -0
  93. package/dist/engine/field-access.js +48 -1
  94. package/dist/engine/field-state.d.ts +12 -0
  95. package/dist/engine/field-state.js +67 -0
  96. package/dist/engine/handler-helpers.d.ts +3 -0
  97. package/dist/engine/handler-helpers.js +7 -1
  98. package/dist/engine/index.d.ts +3 -3
  99. package/dist/engine/index.js +3 -3
  100. package/dist/engine/ownership.d.ts +1 -1
  101. package/dist/engine/ownership.js +9 -5
  102. package/dist/engine/registry-state.d.ts +1 -0
  103. package/dist/engine/registry-state.js +1 -1
  104. package/dist/engine/schema-builder.js +3 -0
  105. package/dist/engine/strip-unreadable-related-lists.d.ts +4 -0
  106. package/dist/engine/strip-unreadable-related-lists.js +80 -0
  107. package/dist/engine/user-entity-reference.d.ts +2 -0
  108. package/dist/engine/user-entity-reference.js +7 -0
  109. package/dist/files/assert-file-refs-belong-to-tenant.js +26 -6
  110. package/dist/files/bind-pending-file-refs.d.ts +3 -2
  111. package/dist/files/bind-pending-file-refs.js +11 -12
  112. package/dist/files/bind-referenced-files.write.js +5 -5
  113. package/dist/files/create-file-ref-from-bytes.d.ts +2 -0
  114. package/dist/files/create-file-ref-from-bytes.js +2 -0
  115. package/dist/files/create-file-ref.d.ts +2 -0
  116. package/dist/files/create-file-ref.js +39 -22
  117. package/dist/files/dispose-removed-file-refs.js +5 -8
  118. package/dist/files/file-field-slots.d.ts +10 -0
  119. package/dist/files/file-field-slots.js +53 -0
  120. package/dist/files/file-ref-entity.d.ts +1 -1
  121. package/dist/files/file-ref-table.d.ts +1 -1
  122. package/dist/files/file-routes.js +12 -3
  123. package/dist/files/index.d.ts +3 -0
  124. package/dist/files/index.js +2 -0
  125. package/dist/files/load-save-file-refs.js +3 -4
  126. package/dist/files/rebind-file-ref.d.ts +41 -0
  127. package/dist/files/rebind-file-ref.js +170 -0
  128. package/dist/files/record-read-check.js +2 -30
  129. package/dist/files/record-write-check.d.ts +2 -0
  130. package/dist/files/record-write-check.js +18 -3
  131. package/dist/files/upload-field-resolution.d.ts +2 -1
  132. package/dist/files/upload-field-resolution.js +33 -13
  133. package/dist/i18n/required-surface-keys.d.ts +6 -0
  134. package/dist/i18n/required-surface-keys.js +61 -0
  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-boot-input.js +2 -0
  139. package/dist/schema-cli/validate-command.js +46 -12
  140. package/dist/schema-cli.js +1 -1
  141. package/dist/ui-types/index.d.ts +2 -1
  142. package/dist/ui-types/index.js +2 -1
  143. package/package.json +3 -3
  144. package/src/changes.json +68 -0
@@ -374,6 +374,7 @@ type EagerLoadEntityResolver = (entityName: string) => EntityDefinition | undefi
374
374
  type EagerLoadViewer = {
375
375
  user: SessionUser;
376
376
  parentVisibility: ParentVisibilityOption;
377
+ resolveTable?: (entityName: string) => ReturnType<typeof buildEntityTable>;
377
378
  };
378
379
  ```
379
380
 
@@ -896,6 +897,22 @@ type WorkflowTrigger = // Trigger configuration for a workflow.
896
897
  ;
897
898
  ```
898
899
 
900
+ ## packages/framework/src/engine/embedded-derived.ts
901
+
902
+ ```ts
903
+ type NumericDerivedOp = Exclude<EmbeddedDerivedCellDef["op"], "inRange">;
904
+ ```
905
+
906
+ ## packages/framework/src/engine/entity-handlers.ts
907
+
908
+ ```ts
909
+ type EntityWriteHandlerDef = WriteHandlerDef & { // The phase slots are always unset on entity handlers, so a consumer can spread one into its own handler and add typed phases without inheriting the slot types.
910
+ reserveBeforeTransaction?: undefined;
911
+ beforeTransaction?: undefined;
912
+ beforeTransactionWhenNested?: undefined;
913
+ };
914
+ ```
915
+
899
916
  ## packages/framework/src/engine/event-helpers.ts
900
917
 
901
918
  ```ts
@@ -981,6 +998,14 @@ type BuildManifestOptions = {
981
998
  };
982
999
  ```
983
1000
 
1001
+ ## packages/framework/src/engine/field-state.ts
1002
+
1003
+ ```ts
1004
+ type RequiredState = FieldStateConditions & {
1005
+ required?: boolean;
1006
+ };
1007
+ ```
1008
+
984
1009
  ## packages/framework/src/engine/handler-field-references.ts
985
1010
 
986
1011
  ```ts
@@ -1767,6 +1792,78 @@ type DataTableViewOptions = {
1767
1792
  };
1768
1793
  ```
1769
1794
 
1795
+ ## packages/renderer/src/embedded-list-types.ts
1796
+
1797
+ ```ts
1798
+ type EmbeddedListCellType = "text" | "number" | "boolean" | "date" | "money" | "decimal" | "select" | "reference" | "timestamp" | "images" | "files"; // Cell type for one column of an embedded-list field.
1799
+ type EmbeddedListColumn = { // One column of an embedded-list table (one entry per key of the source EmbeddedFieldDef's `schema`).
1800
+ field: string;
1801
+ label: string;
1802
+ type: EmbeddedListCellType;
1803
+ required: boolean;
1804
+ derived: boolean; // Read-only, value supplied by the caller (already computed from sibling cells) — not directly user-editable.
1805
+ readOnly?: boolean; // Shown but not editable by the user; unlike `derived` it is not computed.
1806
+ hiddenInCard?: boolean; // Phone cards skip this column; the table still shows it.
1807
+ cardLimits?: {
1808
+ min: string;
1809
+ max: string;
1810
+ }; // Phone cards show the row's min/max (sub-field names) as a hint under this column's control.
1811
+ cardAttachedTo?: string; // Phone cards render this column unlabeled beside the named column's control.
1812
+ options?: string[]; // Only for `type: "select"`.
1813
+ optionLabels?: Readonly<Record<string, string>>;
1814
+ display?: "radio" | "dropdown"; // Only for `type: "select"`; `"radio"` renders segmented buttons, the default is a dropdown.
1815
+ referenceOptions?: { value: string; label: string; }[]; // Only for `type: "reference"` — pre-fetched by the caller, ONE query per column shared across every row (not one query per cell).
1816
+ referenceLoading?: boolean;
1817
+ optionTones?: Readonly<Partial<Record<string, SelectOptionTone>>>; // Only for `type: "select"`; tone per option (selected segment, derived badge).
1818
+ capture?: "user" | "environment"; // Only for `type: "images" | "files"`.
1819
+ maxCount?: number;
1820
+ accept?: string[];
1821
+ isHiddenInRow?: (row: Readonly<Record<string, unknown>>) => boolean; // Per-row state, evaluated by the caller (conditions are engine logic the primitive must not import).
1822
+ isRequiredInRow?: (row: Readonly<Record<string, unknown>>) => boolean;
1823
+ isDerivedInRow?: (row: Readonly<Record<string, unknown>>) => boolean; // Overrides `derived` per row: a conditional derived cell is editable on rows where it does not apply.
1824
+ };
1825
+ type EmbeddedListInputProps = { // Row-array + totals table for a `createEmbeddedListField` (invoice- positions-style) field — structurally too different from `Input`'s value/onChange union to fit there, so it's its own primitive, same as `DataTable`.
1826
+ id: string;
1827
+ columns: EmbeddedListColumn[];
1828
+ rows: Readonly<Record<string, unknown>>[];
1829
+ totals?: EmbeddedListTotal[];
1830
+ currency?: string; // Currency for money cells and the totals row — one value for the whole list (currency lives on the head aggregate, not the row).
1831
+ disabled?: boolean; // Whole-list read-only (e.g. a released invoice) — hides row-mutation affordances and disables every cell.
1832
+ fixedRows?: boolean; // Rows are fixed: no add, remove, duplicate or move; cells stay editable.
1833
+ minItems?: number;
1834
+ maxItems?: number;
1835
+ uploadTarget?: {
1836
+ entityType: string;
1837
+ fieldName: string;
1838
+ entityId?: string;
1839
+ }; // Upload target of file cells: `entityType`/`entityId`/`fieldName` of the list, the column name is the `subField`.
1840
+ listIssues?: FieldIssue[]; // Shown under the whole list (e.g. under the totals row).
1841
+ rowIssues?: Readonly<Record<number, FieldIssue[]>>; // Keyed by row index.
1842
+ cellIssues?: Readonly<Record<string, FieldIssue[]>>; // Keyed `${rowIndex}.${field}`.
1843
+ onCellChange: (rowIndex: number, field: string, value: unknown) => void;
1844
+ onAddRow: () => void;
1845
+ onRemoveRow: (rowIndex: number) => void;
1846
+ onDuplicateRow: (rowIndex: number) => void;
1847
+ onMoveRow: (fromIndex: number, toIndex: number) => void;
1848
+ onPasteCells?: (rowIndex: number, columnIndex: number, grid: (string[])[]) => void; // Tab/newline-delimited clipboard paste starting at (rowIndex, columnIndex) — parsed to a 2D string grid by the Web impl (paste events are a browser-only concept); undefined caller = no paste handling wired up.
1849
+ addLabel: string;
1850
+ actionsLabel: string; // Accessible name of the (otherwise headerless) row-actions column.
1851
+ removeLabel: string;
1852
+ duplicateLabel: string;
1853
+ moveUpLabel: string;
1854
+ moveDownLabel: string;
1855
+ emptyLabel: string;
1856
+ emptyCtaLabel: string;
1857
+ testId?: string;
1858
+ };
1859
+ type EmbeddedListTotal = { // One entry of an embedded-list totals row (e.g. an invoice's total amount, summed across all rows by the caller).
1860
+ field: string;
1861
+ label: string;
1862
+ value: number;
1863
+ money?: boolean; // Set for labelled multi-column totals, which have no single column to read the money type from.
1864
+ };
1865
+ ```
1866
+
1770
1867
  ## packages/renderer/src/headless/contracts/locale.ts
1771
1868
 
1772
1869
  ```ts
@@ -1986,6 +2083,7 @@ type EditFieldViewModel = {
1986
2083
  payload: OptionsQueryPayload;
1987
2084
  }; // Set for `type: "select"` / `"multiSelect"` when optionsAvailabilityQuery is set: the renderer loads `{ rows: { value, disabled?, hint?
1988
2085
  display?: "checkboxes" | "dropdown" | "radio"; // Set for `type: "multiSelect"` when MultiSelectFieldDef.display is "checkboxes" — the renderer shows a checkbox grid with a select-all toggle instead of the default combobox dropdown.
2086
+ optionTones?: Readonly<Partial<Record<string, SelectOptionTone>>>; // `type: "select"` only: tone per option, shown on the selected radio segment.
1989
2087
  columns?: 1 | 2 | 3 | 4; // Set for `type: "multiSelect"` when MultiSelectFieldDef.columns is defined — column count for the checkbox grid at the widest breakpoint.
1990
2088
  maxRows?: number; // Set for `type: "multiSelect"` when MultiSelectFieldDef.maxRows is defined — grid rows visible before the checkbox grid becomes scrollable.
1991
2089
  multiline?: boolean | { rows?: number; }; // For `type: "text"`, set when TextFieldDef.multiline is true — the renderer then renders a textarea instead of a single-line input.
@@ -2021,9 +2119,14 @@ type EditFieldViewModel = {
2021
2119
  embeddedListCells?: EmbeddedListCellViewModel[]; // Only set for an embedded-LIST field (`multiple: true`) — the per-cell metadata the renderer needs to draw each column/cell.
2022
2120
  embeddedListMinItems?: number; // Minimum row count for the embedded list.
2023
2121
  embeddedListMaxItems?: number; // Maximum row count for the embedded list.
2024
- embeddedListDerived?: Readonly<Record<string, { op: "multiply" | "sum" | "subtract" | "percent"; from: string[]; }>>; // Sub-field name → how to compute its cell from other sub-fields of the same row.
2122
+ embeddedListDerived?: Readonly<Record<string, EmbeddedDerivedCellDef>>; // Sub-field name → how to compute its cell from other sub-fields of the same row.
2025
2123
  embeddedListTotals?: (string | { label: string; from: string[]; })[]; // Footer totals: a sub-field name, or a labelled sum of several.
2026
2124
  embeddedListFixedRows?: boolean; // No add, remove, duplicate or move; cells stay editable.
2125
+ embeddedListUploadTarget?: {
2126
+ entityType: string;
2127
+ fieldName: string;
2128
+ entityId?: string;
2129
+ }; // Where photos of a row's file cells are uploaded: the entity and the top-level list field; the cell name travels as `subField`.
2027
2130
  embeddedListCurrency?: string; // Currency for the whole embedded list's money cells and totals row — currency lives on the head aggregate (EntityDefinition.defaultCurrency), not per row.
2028
2131
  currency?: string; // Only set for a plain (non-list, non-embedded) `type: "money"` field — same source as embeddedListCurrency (EntityDefinition.defaultCurrency, falls back to "EUR").
2029
2132
  unit?: string | { field: string; }; // Only set for `type: "number"` when NumberFieldDef.unit is set — a static suffix string, or a sibling-field reference the renderer resolves against the live row values.
@@ -2031,12 +2134,19 @@ type EditFieldViewModel = {
2031
2134
  type EmbeddedListCellViewModel = {
2032
2135
  field: string;
2033
2136
  label: string;
2034
- type: "text" | "number" | "boolean" | "date" | "money" | "decimal" | "select" | "reference" | "timestamp";
2137
+ type: "text" | "number" | "boolean" | "date" | "money" | "decimal" | "select" | "reference" | "timestamp" | "images" | "files";
2035
2138
  required: boolean;
2139
+ visibleWhen?: Exclude<FieldCondition, boolean>; // Per-row state: hidden / required for a row where the condition holds.
2140
+ requiredWhen?: Exclude<FieldCondition, boolean>;
2036
2141
  options?: string[]; // Only for `type: "select"`.
2142
+ optionTones?: Readonly<Partial<Record<string, SelectOptionTone>>>; // Only for `type: "select"`; tone of each option's badge or selected segment.
2143
+ capture?: "user" | "environment"; // Only for `type: "images" | "files"`.
2144
+ maxCount?: number;
2145
+ accept?: string[];
2037
2146
  optionLabels?: Readonly<Record<string, string>>;
2038
2147
  display?: "radio" | "dropdown"; // Only for `type: "select"`; `"radio"` renders segmented buttons instead of a dropdown.
2039
2148
  readOnly?: boolean; // Cell is shown but not editable (`EditFieldSpec.embeddedList.readOnlyCells`).
2149
+ hiddenInCard?: boolean; // Not shown on phone cards (`EditFieldSpec.embeddedList.cardHiddenCells`).
2040
2150
  refEntity?: string; // Only for `type: "reference"`.
2041
2151
  refFeature?: string;
2042
2152
  refLabelField?: string;
@@ -2222,55 +2332,6 @@ type DrawerProps = { // Side panel for hosting an existing self-contained form/w
2222
2332
  children: ReactNode;
2223
2333
  testId?: string;
2224
2334
  };
2225
- type EmbeddedListCellType = "text" | "number" | "boolean" | "date" | "money" | "decimal" | "select" | "reference" | "timestamp"; // Cell type for one column of an embedded-list field.
2226
- type EmbeddedListColumn = { // One column of an embedded-list table (one entry per key of the source EmbeddedFieldDef's `schema`).
2227
- field: string;
2228
- label: string;
2229
- type: EmbeddedListCellType;
2230
- required: boolean;
2231
- derived: boolean; // Read-only, value supplied by the caller (already computed from sibling cells) — not directly user-editable.
2232
- readOnly?: boolean; // Shown but not editable by the user; unlike `derived` it is not computed.
2233
- options?: string[]; // Only for `type: "select"`.
2234
- optionLabels?: Readonly<Record<string, string>>;
2235
- display?: "radio" | "dropdown"; // Only for `type: "select"`; `"radio"` renders segmented buttons, the default is a dropdown.
2236
- referenceOptions?: { value: string; label: string; }[]; // Only for `type: "reference"` — pre-fetched by the caller, ONE query per column shared across every row (not one query per cell).
2237
- referenceLoading?: boolean;
2238
- };
2239
- type EmbeddedListInputProps = { // Row-array + totals table for a `createEmbeddedListField` (invoice- positions-style) field — structurally too different from `Input`'s value/onChange union to fit there, so it's its own primitive, same as `DataTable`.
2240
- id: string;
2241
- columns: EmbeddedListColumn[];
2242
- rows: Readonly<Record<string, unknown>>[];
2243
- totals?: EmbeddedListTotal[];
2244
- currency?: string; // Currency for money cells and the totals row — one value for the whole list (currency lives on the head aggregate, not the row).
2245
- disabled?: boolean; // Whole-list read-only (e.g. a released invoice) — hides row-mutation affordances and disables every cell.
2246
- fixedRows?: boolean; // Rows are fixed: no add, remove, duplicate or move; cells stay editable.
2247
- minItems?: number;
2248
- maxItems?: number;
2249
- listIssues?: FieldIssue[]; // Shown under the whole list (e.g. under the totals row).
2250
- rowIssues?: Readonly<Record<number, FieldIssue[]>>; // Keyed by row index.
2251
- cellIssues?: Readonly<Record<string, FieldIssue[]>>; // Keyed `${rowIndex}.${field}`.
2252
- onCellChange: (rowIndex: number, field: string, value: unknown) => void;
2253
- onAddRow: () => void;
2254
- onRemoveRow: (rowIndex: number) => void;
2255
- onDuplicateRow: (rowIndex: number) => void;
2256
- onMoveRow: (fromIndex: number, toIndex: number) => void;
2257
- onPasteCells?: (rowIndex: number, columnIndex: number, grid: (string[])[]) => void; // Tab/newline-delimited clipboard paste starting at (rowIndex, columnIndex) — parsed to a 2D string grid by the Web impl (paste events are a browser-only concept); undefined caller = no paste handling wired up.
2258
- addLabel: string;
2259
- actionsLabel: string; // Accessible name of the (otherwise headerless) row-actions column.
2260
- removeLabel: string;
2261
- duplicateLabel: string;
2262
- moveUpLabel: string;
2263
- moveDownLabel: string;
2264
- emptyLabel: string;
2265
- emptyCtaLabel: string;
2266
- testId?: string;
2267
- };
2268
- type EmbeddedListTotal = { // One entry of an embedded-list totals row (e.g. an invoice's total amount, summed across all rows by the caller).
2269
- field: string;
2270
- label: string;
2271
- value: number;
2272
- money?: boolean; // Set for labelled multi-column totals, which have no single column to read the money type from.
2273
- };
2274
2335
  type FieldCellWidth = "text" | "number" | "money" | "date" | "timestamp" | "select" | "full" | "toggle"; // Width class of a field cell inside a `flow` grid: sized by what the field holds.
2275
2336
  type FileValueListProps = { // Read-only list of stored files (FileRef ids): name, download link and, for PDFs, an inline preview.
2276
2337
  fileIds: string[];
@@ -2500,6 +2561,7 @@ type FileUploadFormDataInput = {
2500
2561
  entityType?: string | undefined;
2501
2562
  entityId?: string | undefined;
2502
2563
  fieldName?: string | undefined;
2564
+ subField?: string | undefined; // File sub-field of the embedded `fieldName`.
2503
2565
  actionForm?: string | undefined; // Screen QN of an actionForm: the upload is pending for that form's field, not for an entity.
2504
2566
  };
2505
2567
  ```
@@ -3097,6 +3159,30 @@ type RegisterEntityCrudOptions = {
3097
3159
  };
3098
3160
  ```
3099
3161
 
3162
+ ## packages/types/src/entity-row-visibility.ts
3163
+
3164
+ ```ts
3165
+ type ParentRefDef = {
3166
+ entityTypeField: string; // Field holding the name of the host entity this row hangs off.
3167
+ entityIdField: string; // Field holding the host row's id.
3168
+ allowedTypes?: string[]; // Narrow which registered entities may act as host.
3169
+ };
3170
+ type ReadGateDef = {
3171
+ anyLink?: {
3172
+ entity: string;
3173
+ field: string;
3174
+ }; // Row readable only if at least one live row of `entity` with `field` = row.id is readable through that entity's own parentRef gate (host visibility).
3175
+ allLinks?: {
3176
+ entity: string;
3177
+ field: string;
3178
+ }; // Like `anyLink`, but every live link row must be readable, and there must be at least one.
3179
+ fileOwner?: {
3180
+ field: string;
3181
+ }; // Row readable only if the file_ref in `field` is live and readable through its owner: owner row readable (ownership, parentRef, handler gate) and the field read access on the file_ref's fieldName passes.
3182
+ linkExemptRoles?: string[]; // Roles that skip `anyLink` / `allLinks` (they also see unlinked rows).
3183
+ };
3184
+ ```
3185
+
3100
3186
  ## packages/types/src/entity-table-meta-types.ts
3101
3187
 
3102
3188
  ```ts
@@ -3155,12 +3241,21 @@ type ListRuntimeOptions = {
3155
3241
  referenceSearch?: {
3156
3242
  fields: ReadonlyArray<{ fieldName: string; targetEntityName: string; labelField: string; }>;
3157
3243
  resolveEntity: (entityName: string) => EntityDefinition | undefined;
3244
+ resolveTable?: (entityName: string) => SchemaTable;
3158
3245
  };
3159
3246
  referenceSort?: {
3160
3247
  fields: ReadonlyArray<{ fieldName: string; targetEntityName: string; labelField: string; }>;
3161
3248
  resolveEntity: (entityName: string) => EntityDefinition | undefined;
3249
+ resolveTable?: (entityName: string) => SchemaTable;
3162
3250
  };
3163
3251
  parentVisibility?: ParentVisibilityOption;
3252
+ derivedSql?: {
3253
+ resolveEntity: (entityName: string) => EntityDefinition | undefined;
3254
+ resolveTable: (entityName: string) => SchemaTable;
3255
+ canReadEntity: (entityName: string) => boolean;
3256
+ today: string;
3257
+ now: string;
3258
+ };
3164
3259
  };
3165
3260
  type LockGuardOption = {
3166
3261
  unsafeSkipLockGuard?: boolean;
@@ -3171,6 +3266,8 @@ type ParentVisibilityGateOption = {
3171
3266
  };
3172
3267
  type ParentVisibilityOption = {
3173
3268
  entities: ReadonlyMap<string, EntityDefinition>;
3269
+ canReadEntity?: (entityName: string) => boolean;
3270
+ resolveTable?: (entityName: string) => SchemaTable;
3174
3271
  };
3175
3272
  type PreSaveRunner = (changes: Record<string, unknown>, previous: Readonly<Record<string, unknown>>, isNew: boolean) => Promise<Record<string, unknown>>;
3176
3273
  type StreamTenantOption = {
@@ -3291,21 +3388,103 @@ type UiHints = {
3291
3388
  };
3292
3389
  ```
3293
3390
 
3391
+ ## packages/types/src/field-state-conditions.ts
3392
+
3393
+ ```ts
3394
+ type EmbeddedFilesSubFieldProps = { // Value is an array of FileRef ids; `file_refs.fieldName` stays the top-level embedded field name.
3395
+ maxSize?: string;
3396
+ accept?: string[];
3397
+ maxCount?: number;
3398
+ };
3399
+ type EmbeddedImagesSubFieldProps = EmbeddedFilesSubFieldProps & {
3400
+ capture?: "environment" | "user";
3401
+ };
3402
+ type FieldStateConditions = {
3403
+ visibleWhen?: Exclude<FieldCondition, boolean>; // Field is shown only while the condition holds on the record values.
3404
+ requiredWhen?: Exclude<FieldCondition, boolean>; // Field is required while the condition holds (and the field is visible).
3405
+ };
3406
+ ```
3407
+
3294
3408
  ## packages/types/src/fields-derived.ts
3295
3409
 
3296
3410
  ```ts
3297
- type DerivedFieldDef = {
3411
+ type DerivedCondition =
3412
+ | {
3413
+ field: string;
3414
+ op: "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in";
3415
+ value: DerivedConditionValue;
3416
+ }
3417
+ | {
3418
+ activeOn: {
3419
+ from: string;
3420
+ to: string;
3421
+ };
3422
+ }
3423
+ ;
3424
+ type DerivedConditionValue =
3425
+ | string
3426
+ | number
3427
+ | boolean
3428
+ | null
3429
+ | (string | number)[]
3430
+ | {
3431
+ today: true;
3432
+ }
3433
+ | {
3434
+ now: true;
3435
+ }
3436
+ ;
3437
+ type DerivedFieldDef = JsDerivedFieldDef | SqlDerivedFieldDef;
3438
+ type DerivedFieldsMap = Readonly<Record<string, DerivedFieldDef>>;
3439
+ type DerivedRelatedMeasure =
3440
+ | {
3441
+ kind: "count";
3442
+ }
3443
+ | {
3444
+ kind: "countDistinct";
3445
+ field: string;
3446
+ }
3447
+ | {
3448
+ kind: "first";
3449
+ field: string;
3450
+ orderBy: string;
3451
+ direction: "asc" | "desc";
3452
+ label?: boolean; // Only for a reference field with labelField: yields the referenced row's label.
3453
+ }
3454
+ ;
3455
+ type DerivedSqlSpec =
3456
+ | {
3457
+ kind: "coalesce";
3458
+ fields: string[];
3459
+ }
3460
+ | {
3461
+ kind: "related";
3462
+ entity: string; // Same convention as a reference field's `entity` ("feature:entity").
3463
+ on: {
3464
+ field: string;
3465
+ outer?: string;
3466
+ }; // related.<field> = outer.<outer ??
3467
+ measure: DerivedRelatedMeasure;
3468
+ where?: DerivedCondition[]; // AND-ed.
3469
+ }
3470
+ ;
3471
+ type DerivedValueType = "text" | "number" | "decimal" | "boolean" | "date" | "timestamp" | "money"; // Display type a derived value formats as — drives the column's renderer choice in the view-model, parallel to FieldDefinition["type"].
3472
+ type JsDerivedFieldDef = {
3298
3473
  valueType: DerivedValueType;
3299
3474
  derive: (row: Readonly<Record<string, unknown>>, ctx: DeriveContext) => unknown; // Pure function of the stored row + clock.
3300
3475
  };
3301
- type DerivedFieldsMap = Readonly<Record<string, DerivedFieldDef>>;
3302
- type DerivedValueType = "text" | "number" | "decimal" | "boolean" | "date" | "timestamp"; // Display type a derived value formats as — drives the column's renderer choice in the view-model, parallel to FieldDefinition["type"].
3476
+ type SqlDerivedFieldDef = {
3477
+ valueType: DerivedValueType;
3478
+ sortable?: boolean;
3479
+ sql: DerivedSqlSpec;
3480
+ derive?: undefined;
3481
+ };
3303
3482
  ```
3304
3483
 
3305
3484
  ## packages/types/src/fields.ts
3306
3485
 
3307
3486
  ```ts
3308
- type BigIntFieldDef = ResolvedPiiFlags & { // 64-bit integer field for audit counters, byte sizes, event IDs, and other values that can exceed 2^31 (~2.1 billion).
3487
+ type BigIntFieldDef = PiiAndFieldState & { // 64-bit integer field for audit counters, byte sizes, event IDs, and other values that can exceed 2^31 (~2.1 billion).
3309
3488
  type: "bigInt";
3310
3489
  description?: string;
3311
3490
  required?: boolean;
@@ -3316,7 +3495,7 @@ type BigIntFieldDef = ResolvedPiiFlags & { // 64-bit integer field for audit cou
3316
3495
  default?: number;
3317
3496
  access?: FieldAccess;
3318
3497
  };
3319
- type BooleanFieldDef = {
3498
+ type BooleanFieldDef = FieldStateConditions & {
3320
3499
  type: "boolean";
3321
3500
  description?: string;
3322
3501
  required?: boolean;
@@ -3327,7 +3506,7 @@ type BooleanFieldDef = {
3327
3506
  default?: boolean;
3328
3507
  access?: FieldAccess;
3329
3508
  };
3330
- type DateFieldDef = ResolvedPiiFlags & {
3509
+ type DateFieldDef = PiiAndFieldState & {
3331
3510
  type: "date";
3332
3511
  description?: string;
3333
3512
  required?: boolean;
@@ -3340,7 +3519,7 @@ type DateFieldDef = ResolvedPiiFlags & {
3340
3519
  max?: string;
3341
3520
  locale?: string; // Format/locale override for display and input parsing (for example, "de-DE").
3342
3521
  };
3343
- type DecimalFieldDef = ResolvedPiiFlags & { // Exact decimal — Postgres `numeric(precision, scale)`.
3522
+ type DecimalFieldDef = PiiAndFieldState & { // Exact decimal — Postgres `numeric(precision, scale)`.
3344
3523
  type: "decimal";
3345
3524
  description?: string;
3346
3525
  precision: number;
@@ -3353,7 +3532,7 @@ type DecimalFieldDef = ResolvedPiiFlags & { // Exact decimal — Postgres `numer
3353
3532
  default?: number;
3354
3533
  access?: FieldAccess;
3355
3534
  };
3356
- type EmbeddedFieldDef = ResolvedPiiFlags & {
3535
+ type EmbeddedFieldDef = PiiAndFieldState & {
3357
3536
  type: "embedded";
3358
3537
  description?: string;
3359
3538
  required?: boolean;
@@ -3367,12 +3546,12 @@ type EmbeddedFieldDef = ResolvedPiiFlags & {
3367
3546
  totals?: EmbeddedTotalDef[]; // Renderer metadata: footer totals.
3368
3547
  totalsMatch?: Readonly<Record<string, string>>; // Maps an embedded-list subfield name to a sibling top-level money field name — the sum of that subfield across all rows must equal the sibling field's amount.
3369
3548
  };
3370
- type EmbeddedSubFieldBase = {
3549
+ type EmbeddedSubFieldBase = FieldStateConditions & {
3371
3550
  required?: boolean;
3372
3551
  searchable?: boolean;
3373
3552
  access?: FieldAccess;
3374
3553
  };
3375
- type EmbeddedSubFieldDef = (EmbeddedSubFieldBase & { type: "text" | "number" | "boolean" | "date" | "money" | "timestamp"; }) | (EmbeddedSubFieldBase & { type: "decimal"; scale: number; }) | (EmbeddedSubFieldBase & { type: "select"; options: string[]; display?: "radio" | "dropdown"; }) | (EmbeddedSubFieldBase & { type: "reference"; entity: string; labelField?: string; optionsQuery?: string; }); // `money` is a signed integer amount in minor units (cents); which currency it means is defined by the owning aggregate (a head-level field or tenant config), not per row.
3554
+ type EmbeddedSubFieldDef = (EmbeddedSubFieldBase & { type: "text" | "number" | "boolean" | "date" | "money" | "timestamp"; }) | (EmbeddedSubFieldBase & { type: "decimal"; scale: number; }) | (EmbeddedSubFieldBase & { type: "select"; options: string[]; display?: "radio" | "dropdown"; optionTones?: { [option: string]: SelectOptionTone; }; }) | (EmbeddedSubFieldBase & { type: "reference"; entity: string; labelField?: string; optionsQuery?: string; }) | (EmbeddedSubFieldBase & EmbeddedImagesSubFieldProps …; // `money` is a signed integer amount in minor units (cents); which currency it means is defined by the owning aggregate (a head-level field or tenant config), not per row.
3376
3555
  type EmbeddedTotalDef =
3377
3556
  | string
3378
3557
  | {
@@ -3400,7 +3579,7 @@ type FieldAccess = {
3400
3579
  write?: OwnershipMap | string[];
3401
3580
  };
3402
3581
  type FieldsMap = Readonly<Record<string, EntityFieldDefinition>>;
3403
- type FileFieldDef = ResolvedPiiFlags & {
3582
+ type FileFieldDef = PiiAndFieldState & {
3404
3583
  type: "file";
3405
3584
  description?: string;
3406
3585
  required?: boolean;
@@ -3409,7 +3588,7 @@ type FileFieldDef = ResolvedPiiFlags & {
3409
3588
  accept?: string[];
3410
3589
  access?: FieldAccess;
3411
3590
  };
3412
- type FilesFieldDef = ResolvedPiiFlags & {
3591
+ type FilesFieldDef = PiiAndFieldState & {
3413
3592
  type: "files";
3414
3593
  description?: string;
3415
3594
  maxSize?: string;
@@ -3421,7 +3600,7 @@ type FilesFieldDef = ResolvedPiiFlags & {
3421
3600
  };
3422
3601
  type Findability = "exact" | "fuzzy" | "none" | "secret";
3423
3602
  type FormFieldDefinition = Exclude<FieldDefinition, MoneyFieldDef> | (MoneyFieldDef & { currency: MoneyCurrencySource; }); // FieldDefinition as an entity-less inline form screen (actionForm, secretMint) may declare it: a `money` field there has no entity to borrow `defaultCurrency` from, so it must name its own currency source, or the form seeds a bare `0` that the handler's zod schema then rejects .
3424
- type ImageFieldDef = ResolvedPiiFlags & {
3603
+ type ImageFieldDef = PiiAndFieldState & {
3425
3604
  type: "image";
3426
3605
  description?: string;
3427
3606
  required?: boolean;
@@ -3432,7 +3611,7 @@ type ImageFieldDef = ResolvedPiiFlags & {
3432
3611
  capture?: "environment" | "user"; // Forwarded to the file input's `capture` attribute so a phone opens the camera instead of the file picker.
3433
3612
  access?: FieldAccess;
3434
3613
  };
3435
- type ImagesFieldDef = ResolvedPiiFlags & {
3614
+ type ImagesFieldDef = PiiAndFieldState & {
3436
3615
  type: "images";
3437
3616
  description?: string;
3438
3617
  maxSize?: string;
@@ -3445,7 +3624,7 @@ type ImagesFieldDef = ResolvedPiiFlags & {
3445
3624
  access?: FieldAccess;
3446
3625
  };
3447
3626
  type JsonbEmptyValue = "object" | "array";
3448
- type LocatedTimestampFieldDef = ResolvedPiiFlags & {
3627
+ type LocatedTimestampFieldDef = PiiAndFieldState & {
3449
3628
  type: "locatedTimestamp";
3450
3629
  description?: string;
3451
3630
  required?: boolean;
@@ -3458,7 +3637,7 @@ type LocatedTimestampFieldDef = ResolvedPiiFlags & {
3458
3637
  max?: string;
3459
3638
  locale?: string; // Format/locale override for display and input parsing.
3460
3639
  };
3461
- type LongTextFieldDef = ResolvedPiiFlags & { // Long-form text content — source code, markdown, blog posts, email templates, and anything that can be megabytes large.
3640
+ type LongTextFieldDef = PiiAndFieldState & { // Long-form text content — source code, markdown, blog posts, email templates, and anything that can be megabytes large.
3462
3641
  type: "longText";
3463
3642
  description?: string;
3464
3643
  maxLength?: number; // Optional soft cap.
@@ -3480,7 +3659,7 @@ type MoneyCurrencySource = // Where a money field's currency comes from.
3480
3659
  code: string;
3481
3660
  }
3482
3661
  ;
3483
- type MoneyFieldDef = {
3662
+ type MoneyFieldDef = FieldStateConditions & {
3484
3663
  type: "money";
3485
3664
  description?: string;
3486
3665
  required?: boolean;
@@ -3491,7 +3670,7 @@ type MoneyFieldDef = {
3491
3670
  access?: FieldAccess;
3492
3671
  currency?: MoneyCurrencySource; // Declares where this field's currency comes from for a currently-empty value in an entityEdit form.
3493
3672
  };
3494
- type MultiSelectFieldDef<TOptions extends string[] = string[]> = ResolvedPiiFlags & {
3673
+ type MultiSelectFieldDef<TOptions extends string[] = string[]> = PiiAndFieldState & {
3495
3674
  type: "multiSelect";
3496
3675
  description?: string;
3497
3676
  options: TOptions;
@@ -3511,7 +3690,7 @@ type NoPersonalAnnotation = {
3511
3690
  reason?: never;
3512
3691
  anonymize?: never;
3513
3692
  };
3514
- type NumberFieldDef = ResolvedPiiFlags & { // Storage: `integer` flag decides the Postgres column type, not just Zod validation.
3693
+ type NumberFieldDef = PiiAndFieldState & { // Storage: `integer` flag decides the Postgres column type, not just Zod validation.
3515
3694
  type: "number";
3516
3695
  description?: string;
3517
3696
  required?: boolean;
@@ -3535,11 +3714,6 @@ type OptionsQueryPayloadValue = // One `optionsQueryPayload` entry: a literal, o
3535
3714
  field: string;
3536
3715
  }
3537
3716
  ;
3538
- type ParentRefDef = {
3539
- entityTypeField: string; // Field holding the name of the host entity this row hangs off.
3540
- entityIdField: string; // Field holding the host row's id.
3541
- allowedTypes?: string[]; // Narrow which registered entities may act as host.
3542
- };
3543
3717
  type PersonalAnnotations =
3544
3718
  | {
3545
3719
  personal: PersonalSubject;
@@ -3593,7 +3767,8 @@ type PersonalSubject =
3593
3767
  }
3594
3768
  | "tenant"
3595
3769
  ;
3596
- type ReferenceFieldDef = ResolvedPiiFlags & {
3770
+ type PiiAndFieldState = ResolvedPiiFlags & FieldStateConditions;
3771
+ type ReferenceFieldDef = PiiAndFieldState & {
3597
3772
  type: "reference";
3598
3773
  description?: string;
3599
3774
  entity: string;
@@ -3633,7 +3808,7 @@ type SelectConditionalOptions<TOption extends string = string> = { // Options of
3633
3808
  options: TOption[];
3634
3809
  when: Exclude<FieldCondition, boolean>;
3635
3810
  };
3636
- type SelectFieldDef<TOptions extends string[] = string[]> = ResolvedPiiFlags & {
3811
+ type SelectFieldDef<TOptions extends string[] = string[]> = PiiAndFieldState & {
3637
3812
  type: "select";
3638
3813
  description?: string;
3639
3814
  options: TOptions;
@@ -3652,7 +3827,7 @@ type SelectFieldDef<TOptions extends string[] = string[]> = ResolvedPiiFlags & {
3652
3827
  conditionalOptions?: SelectConditionalOptions<TOptions[number]>[]; // Options that are only available while `when` holds for the row values; options in no rule are always available.
3653
3828
  };
3654
3829
  type SortNulls = "first" | "last"; // Where rows without a value sort in a list, in both directions.
3655
- type TimestampFieldDef = ResolvedPiiFlags & {
3830
+ type TimestampFieldDef = PiiAndFieldState & {
3656
3831
  type: "timestamp";
3657
3832
  description?: string;
3658
3833
  required?: boolean;
@@ -3667,7 +3842,7 @@ type TimestampFieldDef = ResolvedPiiFlags & {
3667
3842
  locale?: string; // Format/locale override for display and input parsing.
3668
3843
  };
3669
3844
  type TransitionMap = Readonly<Record<string, string[]>>;
3670
- type TzFieldDef = ResolvedPiiFlags & {
3845
+ type TzFieldDef = PiiAndFieldState & {
3671
3846
  type: "tz";
3672
3847
  description?: string;
3673
3848
  required?: boolean;
@@ -4060,6 +4235,7 @@ type HookMap = {
4060
4235
  type HookOwner = {
4061
4236
  featureName?: string;
4062
4237
  };
4238
+ type LifecycleResult = SaveContext | DeleteContext;
4063
4239
  type OwnedFn<TFn> = HookOwner & {
4064
4240
  fn: TFn;
4065
4241
  };
@@ -4565,7 +4741,7 @@ type ScreenFilterOp = "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in";
4565
4741
  ## packages/types/src/screen-expandable-row.ts
4566
4742
 
4567
4743
  ```ts
4568
- type EntityListExpandableRow = Omit<EditRelatedListSection, "id" | "countField" | "table" | "bulkActions"> & { // Expandable area of an `entityList` row: a relatedList of the row's record.
4744
+ type EntityListExpandableRow = Omit<EditRelatedListSection, "id" | "countField" | "count" | "table" | "bulkActions"> & { // Expandable area of an `entityList` row: a relatedList of the row's record.
4569
4745
  table?: {
4570
4746
  grouping?: ExpandableRowGrouping;
4571
4747
  };
package/agents/api.md CHANGED
@@ -8,13 +8,13 @@ Format: one line per function or constant, object and options types as property
8
8
 
9
9
  | Import path | File | Exports |
10
10
  | --- | --- | --- |
11
- | `@cosmicdrift/kumiko-framework/engine` | `api/framework-engine.md` | 409 |
11
+ | `@cosmicdrift/kumiko-framework/engine` | `api/framework-engine.md` | 412 |
12
12
  | `@cosmicdrift/kumiko-framework/errors` | `api/framework-errors.md` | 25 |
13
- | `@cosmicdrift/kumiko-framework/ui-types` | `api/framework-ui-types.md` | 149 |
13
+ | `@cosmicdrift/kumiko-framework/ui-types` | `api/framework-ui-types.md` | 154 |
14
14
  | `@cosmicdrift/kumiko-framework/db` | `api/framework-db.md` | 150 |
15
15
  | `@cosmicdrift/kumiko-framework/event-store` | `api/framework-event-store.md` | 38 |
16
16
  | `@cosmicdrift/kumiko-framework/seeding` | `api/framework-seeding.md` | 6 |
17
17
  | `@cosmicdrift/kumiko-renderer/web` | `api/renderer-web.md` | 298 |
18
- | `@cosmicdrift/kumiko-testing/stack` | `api/testing-stack.md` | 21 |
18
+ | `@cosmicdrift/kumiko-testing/stack` | `api/testing-stack.md` | 22 |
19
19
  | `@cosmicdrift/kumiko-testing/helpers` | `api/testing-helpers.md` | 71 |
20
20
  | `@cosmicdrift/kumiko-dev-server/testing` | `api/dev-server-testing.md` | 5 |