@cosmicdrift/kumiko-renderer 0.327.0 → 0.328.1

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 (257) hide show
  1. package/dist/app/action-form-shim.d.ts +14 -0
  2. package/{src/app/action-form-shim.ts → dist/app/action-form-shim.js} +26 -44
  3. package/dist/app/app-features-context.d.ts +8 -0
  4. package/dist/app/app-features-context.js +16 -0
  5. package/dist/app/column-renderers.d.ts +30 -0
  6. package/dist/app/column-renderers.js +34 -0
  7. package/dist/app/config-edit-shim.d.ts +11 -0
  8. package/{src/app/config-edit-shim.ts → dist/app/config-edit-shim.js} +12 -25
  9. package/dist/app/content-editors.d.ts +38 -0
  10. package/dist/app/content-editors.js +26 -0
  11. package/dist/app/content-preview.d.ts +11 -0
  12. package/dist/app/content-preview.js +30 -0
  13. package/dist/app/custom-screens.d.ts +11 -0
  14. package/dist/app/custom-screens.js +20 -0
  15. package/dist/app/dashboard-body.d.ts +16 -0
  16. package/dist/app/dashboard-body.js +11 -0
  17. package/dist/app/extension-form-submit.d.ts +32 -0
  18. package/dist/app/extension-form-submit.js +73 -0
  19. package/dist/app/extension-sections.d.ts +72 -0
  20. package/dist/app/extension-sections.js +34 -0
  21. package/dist/app/feature-schema.d.ts +4 -0
  22. package/{src/app/feature-schema.ts → dist/app/feature-schema.js} +12 -22
  23. package/dist/app/form-schema.d.ts +4 -0
  24. package/dist/app/form-schema.js +85 -0
  25. package/dist/app/kumiko-screen.d.ts +43 -0
  26. package/dist/app/kumiko-screen.js +2206 -0
  27. package/dist/app/layout-fields.d.ts +3 -0
  28. package/dist/app/layout-fields.js +20 -0
  29. package/dist/app/list-facets.d.ts +48 -0
  30. package/dist/app/list-facets.js +185 -0
  31. package/dist/app/nav.d.ts +61 -0
  32. package/dist/app/nav.js +139 -0
  33. package/dist/app/projection-detail-shim.d.ts +8 -0
  34. package/dist/app/projection-detail-shim.js +71 -0
  35. package/dist/app/projection-list-shim.d.ts +13 -0
  36. package/{src/app/projection-list-shim.ts → dist/app/projection-list-shim.js} +26 -38
  37. package/dist/app/qn.d.ts +7 -0
  38. package/{src/app/qn.ts → dist/app/qn.js} +9 -11
  39. package/dist/app/qualify-screen-id.d.ts +2 -0
  40. package/{src/app/qualify-screen-id.ts → dist/app/qualify-screen-id.js} +5 -6
  41. package/dist/app/reference-facet-bridge.d.ts +10 -0
  42. package/dist/app/reference-facet-bridge.js +22 -0
  43. package/dist/app/return-to.d.ts +31 -0
  44. package/dist/app/return-to.js +188 -0
  45. package/dist/app/row-actions.d.ts +77 -0
  46. package/dist/app/row-actions.js +416 -0
  47. package/dist/app/screen-access.d.ts +8 -0
  48. package/dist/app/screen-access.js +48 -0
  49. package/dist/app/secret-mint-body.d.ts +10 -0
  50. package/dist/app/secret-mint-body.js +140 -0
  51. package/dist/app/secrets-edit-body.d.ts +8 -0
  52. package/dist/app/secrets-edit-body.js +76 -0
  53. package/dist/app/use-embedded-screen.d.ts +6 -0
  54. package/dist/app/use-embedded-screen.js +19 -0
  55. package/dist/app/variable-chips.d.ts +7 -0
  56. package/dist/app/variable-chips.js +16 -0
  57. package/dist/app/write-failed-error.d.ts +6 -0
  58. package/dist/app/write-failed-error.js +21 -0
  59. package/dist/components/embedded-list-field.d.ts +10 -0
  60. package/dist/components/embedded-list-field.js +259 -0
  61. package/dist/components/grid-cell-for-field.d.ts +25 -0
  62. package/dist/components/grid-cell-for-field.js +31 -0
  63. package/dist/components/multi-select-checkboxes.d.ts +14 -0
  64. package/dist/components/multi-select-checkboxes.js +36 -0
  65. package/dist/components/reference-create-dialog.d.ts +13 -0
  66. package/dist/components/reference-create-dialog.js +46 -0
  67. package/dist/components/related-list-section.d.ts +25 -0
  68. package/dist/components/related-list-section.js +273 -0
  69. package/dist/components/render-edit-action-button.d.ts +22 -0
  70. package/dist/components/render-edit-action-button.js +43 -0
  71. package/dist/components/render-edit-logic.d.ts +6 -0
  72. package/dist/components/render-edit-logic.js +63 -0
  73. package/dist/components/render-edit-types.d.ts +209 -0
  74. package/dist/components/render-edit-types.js +1 -0
  75. package/dist/components/render-edit.d.ts +6 -0
  76. package/dist/components/render-edit.js +1169 -0
  77. package/dist/components/render-field.d.ts +38 -0
  78. package/dist/components/render-field.js +544 -0
  79. package/dist/components/render-list.d.ts +117 -0
  80. package/dist/components/render-list.js +232 -0
  81. package/dist/components/write-form-section.d.ts +17 -0
  82. package/dist/components/write-form-section.js +104 -0
  83. package/dist/context/dispatcher-context.d.ts +10 -0
  84. package/{src/context/dispatcher-context.tsx → dist/context/dispatcher-context.js} +16 -29
  85. package/dist/context/draft-storage-context.d.ts +12 -0
  86. package/dist/context/draft-storage-context.js +18 -0
  87. package/dist/context/user-roles-context.d.ts +7 -0
  88. package/{src/context/user-roles-context.tsx → dist/context/user-roles-context.js} +7 -15
  89. package/dist/format-when.d.ts +1 -0
  90. package/{src/format-when.ts → dist/format-when.js} +7 -7
  91. package/dist/hooks/reference-limits.d.ts +3 -0
  92. package/{src/hooks/reference-limits.ts → dist/hooks/reference-limits.js} +0 -1
  93. package/dist/hooks/use-ai-text.d.ts +52 -0
  94. package/dist/hooks/use-ai-text.js +90 -0
  95. package/dist/hooks/use-disclosure.d.ts +7 -0
  96. package/dist/hooks/use-disclosure.js +8 -0
  97. package/dist/hooks/use-form.d.ts +7 -0
  98. package/dist/hooks/use-form.js +36 -0
  99. package/dist/hooks/use-list-url-state.d.ts +43 -0
  100. package/dist/hooks/use-list-url-state.js +131 -0
  101. package/dist/hooks/use-mutation.d.ts +9 -0
  102. package/dist/hooks/use-mutation.js +37 -0
  103. package/dist/hooks/use-query.d.ts +14 -0
  104. package/dist/hooks/use-query.js +118 -0
  105. package/dist/hooks/use-reference-lookup.d.ts +14 -0
  106. package/dist/hooks/use-reference-lookup.js +63 -0
  107. package/dist/hooks/use-store.d.ts +3 -0
  108. package/dist/hooks/use-store.js +38 -0
  109. package/dist/hooks/use-stream-handler.d.ts +14 -0
  110. package/dist/hooks/use-stream-handler.js +93 -0
  111. package/dist/i18n-defaults.d.ts +2 -0
  112. package/dist/i18n-defaults.js +201 -0
  113. package/dist/i18n.d.ts +64 -0
  114. package/dist/i18n.js +166 -0
  115. package/dist/index.d.ts +77 -0
  116. package/dist/index.js +55 -0
  117. package/dist/inside-drawer.d.ts +2 -0
  118. package/dist/inside-drawer.js +9 -0
  119. package/dist/page-header-slot.d.ts +2 -0
  120. package/dist/page-header-slot.js +9 -0
  121. package/dist/primitives.d.ts +1351 -0
  122. package/dist/primitives.js +73 -0
  123. package/dist/screen-fills-height.d.ts +3 -0
  124. package/dist/screen-fills-height.js +3 -0
  125. package/dist/share-links.d.ts +4 -0
  126. package/dist/share-links.js +11 -0
  127. package/dist/sort-by-accessor.d.ts +5 -0
  128. package/dist/sort-by-accessor.js +16 -0
  129. package/dist/sse/live-events.d.ts +23 -0
  130. package/dist/sse/live-events.js +28 -0
  131. package/dist/tokens.d.ts +83 -0
  132. package/dist/tokens.js +80 -0
  133. package/package.json +16 -9
  134. package/src/changes.json +52 -0
  135. package/src/__tests__/error-i18n-defaults.test.ts +0 -82
  136. package/src/__tests__/format-when.test.ts +0 -12
  137. package/src/__tests__/i18n.test.tsx +0 -283
  138. package/src/__tests__/merge-search-params-into-initial.test.ts +0 -525
  139. package/src/__tests__/qn.test.ts +0 -83
  140. package/src/__tests__/share-links.test.ts +0 -26
  141. package/src/__tests__/sort-by-accessor.test.ts +0 -48
  142. package/src/__tests__/stringify-nav-params.test.ts +0 -33
  143. package/src/__tests__/use-list-url-state.test.tsx +0 -250
  144. package/src/app/__tests__/action-form-field-labels.test.tsx +0 -92
  145. package/src/app/__tests__/action-form-shim.test.ts +0 -137
  146. package/src/app/__tests__/build-list-query-payload.test.ts +0 -107
  147. package/src/app/__tests__/config-edit-shim.test.ts +0 -57
  148. package/src/app/__tests__/content-editors.test.tsx +0 -128
  149. package/src/app/__tests__/content-preview.test.tsx +0 -184
  150. package/src/app/__tests__/entity-edit-create-search-params.test.tsx +0 -236
  151. package/src/app/__tests__/entity-edit-jsonb-embedded-submit.test.tsx +0 -239
  152. package/src/app/__tests__/entity-list-create-singleton.test.tsx +0 -128
  153. package/src/app/__tests__/entity-list-row-action-entity-target.test.tsx +0 -424
  154. package/src/app/__tests__/entity-list-row-action-icon.test.tsx +0 -248
  155. package/src/app/__tests__/entity-list-row-action-kumiko-actions-view.test.tsx +0 -162
  156. package/src/app/__tests__/entity-list-row-action-refetch.test.tsx +0 -401
  157. package/src/app/__tests__/entity-list-screen-padding.test.tsx +0 -111
  158. package/src/app/__tests__/feature-schema.test.ts +0 -40
  159. package/src/app/__tests__/form-schema.test.ts +0 -247
  160. package/src/app/__tests__/list-daterange-facet.test.ts +0 -165
  161. package/src/app/__tests__/list-filter-facets.test.tsx +0 -521
  162. package/src/app/__tests__/nav.test.ts +0 -198
  163. package/src/app/__tests__/projection-detail-actions.test.tsx +0 -719
  164. package/src/app/__tests__/projection-detail-reference-field.test.tsx +0 -165
  165. package/src/app/__tests__/projection-detail-shim.test.ts +0 -188
  166. package/src/app/__tests__/projection-detail-singleton.test.tsx +0 -191
  167. package/src/app/__tests__/projection-list-bad-shape.test.tsx +0 -152
  168. package/src/app/__tests__/projection-list-reference-column.test.tsx +0 -219
  169. package/src/app/__tests__/projection-list-search-sort.test.tsx +0 -212
  170. package/src/app/__tests__/qualified-names.test.ts +0 -37
  171. package/src/app/__tests__/return-to.test.ts +0 -396
  172. package/src/app/__tests__/row-actions-navigate-tab.test.ts +0 -223
  173. package/src/app/__tests__/row-default-edit-action.test.tsx +0 -324
  174. package/src/app/__tests__/screen-access-allows.test.ts +0 -34
  175. package/src/app/__tests__/secret-mint-body.test.tsx +0 -404
  176. package/src/app/__tests__/variable-chips.test.tsx +0 -70
  177. package/src/app/app-features-context.tsx +0 -25
  178. package/src/app/column-renderers.tsx +0 -68
  179. package/src/app/content-editors.tsx +0 -85
  180. package/src/app/content-preview.tsx +0 -67
  181. package/src/app/custom-screens.tsx +0 -29
  182. package/src/app/dashboard-body.tsx +0 -33
  183. package/src/app/extension-form-submit.tsx +0 -123
  184. package/src/app/extension-sections.tsx +0 -111
  185. package/src/app/form-schema.ts +0 -91
  186. package/src/app/kumiko-screen.tsx +0 -3614
  187. package/src/app/layout-fields.ts +0 -31
  188. package/src/app/list-facets.ts +0 -259
  189. package/src/app/nav.tsx +0 -258
  190. package/src/app/projection-detail-shim.ts +0 -85
  191. package/src/app/reference-facet-bridge.tsx +0 -58
  192. package/src/app/return-to.tsx +0 -234
  193. package/src/app/row-actions.ts +0 -639
  194. package/src/app/screen-access.ts +0 -63
  195. package/src/app/secret-mint-body.tsx +0 -259
  196. package/src/app/secrets-edit-body.tsx +0 -194
  197. package/src/app/use-embedded-screen.ts +0 -27
  198. package/src/app/variable-chips.tsx +0 -39
  199. package/src/app/write-failed-error.ts +0 -27
  200. package/src/components/__tests__/embedded-list-field.test.tsx +0 -531
  201. package/src/components/__tests__/multi-select-checkboxes.test.tsx +0 -162
  202. package/src/components/__tests__/related-list-section-empty-state.test.tsx +0 -143
  203. package/src/components/__tests__/related-list-section.test.tsx +0 -1669
  204. package/src/components/__tests__/render-edit-action-button.test.tsx +0 -205
  205. package/src/components/__tests__/render-edit-hidden-section.test.tsx +0 -128
  206. package/src/components/__tests__/render-edit-logic.test.ts +0 -314
  207. package/src/components/__tests__/render-edit-screen-description.test.tsx +0 -152
  208. package/src/components/__tests__/render-edit-submit-actions.test.tsx +0 -460
  209. package/src/components/__tests__/render-field-app-locale.test.tsx +0 -142
  210. package/src/components/__tests__/render-field-enum-option.test.tsx +0 -99
  211. package/src/components/__tests__/render-field-icon-derivation.test.tsx +0 -119
  212. package/src/components/__tests__/render-field-json-format.test.tsx +0 -164
  213. package/src/components/__tests__/render-field-money-embedded-list-consistency.test.tsx +0 -212
  214. package/src/components/__tests__/render-field-money-roundtrip.test.tsx +0 -217
  215. package/src/components/__tests__/render-field-number-grouping.test.tsx +0 -76
  216. package/src/components/__tests__/render-field-number-unit.test.tsx +0 -227
  217. package/src/components/__tests__/render-field-password-format.test.tsx +0 -101
  218. package/src/components/__tests__/render-field-select-display.test.tsx +0 -134
  219. package/src/components/__tests__/render-field-unit-format.test.tsx +0 -107
  220. package/src/components/__tests__/render-field-unsupported-types.test.tsx +0 -195
  221. package/src/components/__tests__/render-list-toolbar-icon-only.test.tsx +0 -146
  222. package/src/components/__tests__/write-form-section.test.tsx +0 -375
  223. package/src/components/embedded-list-field.tsx +0 -354
  224. package/src/components/grid-cell-for-field.tsx +0 -60
  225. package/src/components/multi-select-checkboxes.tsx +0 -93
  226. package/src/components/reference-create-dialog.tsx +0 -91
  227. package/src/components/related-list-section.tsx +0 -467
  228. package/src/components/render-edit-action-button.tsx +0 -106
  229. package/src/components/render-edit-logic.ts +0 -85
  230. package/src/components/render-edit-types.ts +0 -216
  231. package/src/components/render-edit.tsx +0 -1700
  232. package/src/components/render-field.tsx +0 -942
  233. package/src/components/render-list.tsx +0 -561
  234. package/src/components/write-form-section.tsx +0 -199
  235. package/src/context/draft-storage-context.tsx +0 -39
  236. package/src/hooks/__tests__/use-ai-text.test.tsx +0 -177
  237. package/src/hooks/__tests__/use-disclosure.test.tsx +0 -25
  238. package/src/hooks/__tests__/use-form.test.tsx +0 -65
  239. package/src/hooks/__tests__/use-mutation.test.tsx +0 -77
  240. package/src/hooks/__tests__/use-stream-handler.test.tsx +0 -107
  241. package/src/hooks/use-ai-text.ts +0 -172
  242. package/src/hooks/use-disclosure.ts +0 -20
  243. package/src/hooks/use-form.ts +0 -78
  244. package/src/hooks/use-list-url-state.ts +0 -171
  245. package/src/hooks/use-mutation.ts +0 -61
  246. package/src/hooks/use-query.ts +0 -132
  247. package/src/hooks/use-reference-lookup.ts +0 -76
  248. package/src/hooks/use-store.ts +0 -48
  249. package/src/hooks/use-stream-handler.ts +0 -134
  250. package/src/i18n-defaults.ts +0 -220
  251. package/src/i18n.tsx +0 -286
  252. package/src/index.ts +0 -272
  253. package/src/primitives.tsx +0 -1446
  254. package/src/share-links.ts +0 -12
  255. package/src/sort-by-accessor.ts +0 -20
  256. package/src/sse/live-events.tsx +0 -56
  257. package/src/tokens.tsx +0 -170
@@ -0,0 +1,416 @@
1
+ import { evalFieldCondition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import { resolveActionIcon } from "@cosmicdrift/kumiko-types/action-icon";
3
+ import { lastSegment } from "./qn.js";
4
+ import { navigateWithReturnTo } from "./return-to.js";
5
+ import { dispatcherErrorText, WriteFailedError } from "./write-failed-error.js";
6
+ // entityId is explicit: the edit screen may live in another feature than
7
+ // the row source, where the same-feature fallback would miss it.
8
+ export function buildDefaultEditRowAction(editScreen, idColumn = "id") {
9
+ if (editScreen === undefined)
10
+ return undefined;
11
+ return {
12
+ kind: "navigate",
13
+ id: "edit",
14
+ label: "kumiko.actions.edit",
15
+ screen: lastSegment(editScreen.id),
16
+ entityId: idColumn,
17
+ };
18
+ }
19
+ export function evalRowExtractor(extractor, row) {
20
+ if ("pick" in extractor) {
21
+ return Object.fromEntries(extractor.pick.map((f) => [f, row[f]]));
22
+ }
23
+ return Object.fromEntries(Object.entries(extractor.map).map(([to, from]) => [to, row[from]]));
24
+ }
25
+ export function isWriteHandlerRowAction(action) {
26
+ return action.kind === "writeHandler" || action.kind === undefined;
27
+ }
28
+ function isPlainObject(value) {
29
+ if (typeof value !== "object" || value === null)
30
+ return false;
31
+ const proto = Object.getPrototypeOf(value);
32
+ return proto === Object.prototype || proto === null;
33
+ }
34
+ export function stringifyNavParams(params) {
35
+ const out = {};
36
+ for (const [k, v] of Object.entries(params)) {
37
+ // Structured field values such as money `{amount, currency}` must survive
38
+ // the URL round-trip as JSON; Date & other class instances stay String().
39
+ out[k] =
40
+ v === null || v === undefined
41
+ ? null
42
+ : Array.isArray(v) || isPlainObject(v)
43
+ ? JSON.stringify(v)
44
+ : String(v);
45
+ }
46
+ return out;
47
+ }
48
+ export function navigateActionSearchParams(action, values) {
49
+ const params = action.params !== undefined
50
+ ? stringifyNavParams(evalRowExtractor(action.params, values))
51
+ : undefined;
52
+ if (action.tab === undefined)
53
+ return params;
54
+ return { ...params, tab: action.tab };
55
+ }
56
+ export async function refetchAfterWrite(refetch) {
57
+ await refetch().catch((err) => {
58
+ // biome-ignore lint/suspicious/noConsole: refetch must not poison the write-action error path
59
+ console.error("kumiko-screen: refetch after write action failed", err);
60
+ });
61
+ }
62
+ // Navigate execution for query-driven rows (projectionList, and relatedList
63
+ // — both have no guaranteed "id" field, unlike entityList's rows which back
64
+ // a real entity). No same-entity row["id"] fallback (see EntityListBody's
65
+ // own runNavigate for that variant, which stays separate — entityList's
66
+ // fallback needs `screen.entity`, which neither projectionList nor
67
+ // relatedList has).
68
+ export function runProjectionRowNavigate(nav, action, row, host) {
69
+ if (action.entity !== undefined) {
70
+ const id = action.entityId !== undefined ? String(row.values[action.entityId] ?? "") : "";
71
+ // skip: no entityId column on this row — nothing to navigate to.
72
+ if (id === "")
73
+ return;
74
+ const params = navigateActionSearchParams(action, row.values);
75
+ navigateWithReturnTo(nav, { entity: action.entity, id }, host, params);
76
+ }
77
+ else if (action.screen !== undefined) {
78
+ const entityId = action.entityId !== undefined ? String(row.values[action.entityId] ?? "") : undefined;
79
+ const target = {
80
+ screenId: action.screen,
81
+ ...(entityId !== undefined && entityId !== "" && { entityId }),
82
+ };
83
+ const params = navigateActionSearchParams(action, row.values);
84
+ navigateWithReturnTo(nav, target, host, params);
85
+ }
86
+ // skip: neither entity nor screen set — the boot-validator rejects this
87
+ // shape (resolveRowActionNavigateTarget), so this only guards types.
88
+ }
89
+ function buildNavigateRowAction(action, translate, nav, host) {
90
+ const { visible } = action;
91
+ const actionIcon = resolveActionIcon(action.id, action.icon);
92
+ return {
93
+ id: action.id,
94
+ label: translate(action.label),
95
+ ...(action.style !== undefined && { style: action.style }),
96
+ confirmRequired: false,
97
+ ...(action.rowClick === true && { rowClick: true }),
98
+ ...(actionIcon !== undefined && { icon: actionIcon }),
99
+ onTrigger: (row) => runProjectionRowNavigate(nav, action, row, host),
100
+ ...(visible !== undefined && {
101
+ isVisible: (row) => evalFieldCondition(visible, row.values),
102
+ }),
103
+ };
104
+ }
105
+ // Dedupes the "openDrawer not wired" warning per action id, since the
106
+ // builders re-run inside a useMemo on every dep change.
107
+ const warnedDrawerActionIds = new Set();
108
+ /** @internal test-only */
109
+ export function resetDrawerActionWarningsForTests() {
110
+ warnedDrawerActionIds.clear();
111
+ }
112
+ function warnDrawerActionDropped(actionKind, actionId) {
113
+ const key = `${actionKind}:${actionId}`;
114
+ // skip: already warned for this id — suppresses the repeat, not the warning itself.
115
+ if (warnedDrawerActionIds.has(key))
116
+ return;
117
+ warnedDrawerActionIds.add(key);
118
+ // biome-ignore lint/suspicious/noConsole: dev-warning for a setup error
119
+ console.warn(`[kumiko] ${actionKind} "${actionId}" is kind:"drawer", but the host did not wire openDrawer (RelatedListSection: pass onOpenDrawer) — it will not render.`);
120
+ }
121
+ function buildDrawerRowAction(action, translate, openDrawer) {
122
+ const { visible, params } = action;
123
+ const actionIcon = resolveActionIcon(action.id, action.icon);
124
+ return {
125
+ id: action.id,
126
+ label: translate(action.label),
127
+ ...(action.style !== undefined && { style: action.style }),
128
+ confirmRequired: false,
129
+ ...(actionIcon !== undefined && { icon: actionIcon }),
130
+ onTrigger: (row) => {
131
+ openDrawer(action, params !== undefined ? evalRowExtractor(params, row.values) : undefined);
132
+ },
133
+ ...(visible !== undefined && {
134
+ isVisible: (row) => evalFieldCondition(visible, row.values),
135
+ }),
136
+ };
137
+ }
138
+ // Prepends defaultEditRowAction unless a declared action already claims id
139
+ // "edit" — declared wins.
140
+ function mergeDefaultEditAction(rowActions, defaultEditRowAction) {
141
+ const declaredHasEdit = rowActions?.some((a) => a.id === "edit") === true;
142
+ return defaultEditRowAction !== undefined && !declaredHasEdit
143
+ ? [defaultEditRowAction, ...(rowActions ?? [])]
144
+ : (rowActions ?? []);
145
+ }
146
+ function buildWriteHandlerRowAction(action, translate, refetch, dispatcher) {
147
+ const writeVisible = action.visible;
148
+ return {
149
+ id: action.id,
150
+ label: translate(action.label),
151
+ ...(action.style !== undefined && { style: action.style }),
152
+ icon: resolveActionIcon(action.id, action.icon),
153
+ ...(action.confirm !== undefined && { confirm: translate(action.confirm) }),
154
+ ...(action.confirmLabel !== undefined && {
155
+ confirmLabel: translate(action.confirmLabel),
156
+ }),
157
+ onTrigger: async (row) => {
158
+ const payload = action.payload !== undefined
159
+ ? evalRowExtractor(action.payload, row.values)
160
+ : { id: row.values["id"] };
161
+ const result = await dispatcher.write(action.handler, payload);
162
+ if (!result.isSuccess) {
163
+ throw new WriteFailedError(result.error, dispatcherErrorText(result.error, translate));
164
+ }
165
+ await refetchAfterWrite(refetch);
166
+ },
167
+ ...(writeVisible !== undefined && {
168
+ isVisible: (row) => evalFieldCondition(writeVisible, row.values),
169
+ }),
170
+ };
171
+ }
172
+ // Builds the DataTable-ready row-action set for a query-driven row source
173
+ // (projectionList, relatedList) — navigate dispatches through
174
+ // runProjectionRowNavigate, writeHandler dispatches through the shared
175
+ // Dispatcher and refetches `refetch` on success. Single implementation so
176
+ // projectionList's rowActions and a projectionDetail relatedList section's
177
+ // rowActions can't drift apart (fw editable-detail-screens).
178
+ export function buildProjectionRowActions(options) {
179
+ const { rowActions, translate, dispatcher, nav, refetch, openDrawer, defaultEditRowAction, host, } = options;
180
+ const effectiveActions = mergeDefaultEditAction(rowActions, defaultEditRowAction);
181
+ if (effectiveActions.length === 0)
182
+ return undefined;
183
+ const out = [];
184
+ for (const action of effectiveActions) {
185
+ if (action.kind === "navigate") {
186
+ out.push(buildNavigateRowAction(action, translate, nav, host));
187
+ continue;
188
+ }
189
+ if (action.kind === "drawer") {
190
+ if (openDrawer === undefined) {
191
+ warnDrawerActionDropped("rowAction", action.id);
192
+ continue;
193
+ }
194
+ out.push(buildDrawerRowAction(action, translate, openDrawer));
195
+ continue;
196
+ }
197
+ // writeHandler (default-kind) — a swallowed failure result must become a
198
+ // thrown error (fw prod-bug 2026-06-07), same as every other write path.
199
+ if (dispatcher === undefined)
200
+ continue;
201
+ out.push(buildWriteHandlerRowAction(action, translate, refetch, dispatcher));
202
+ }
203
+ return out.length > 0 ? out : undefined;
204
+ }
205
+ function buildNavigateToolbarAction(action, translate, nav, host, prefill, record) {
206
+ const actionIcon = resolveActionIcon(action.id);
207
+ const target = { screenId: action.screen };
208
+ return {
209
+ id: action.id,
210
+ label: translate(action.label),
211
+ ...(action.style !== undefined && { style: action.style }),
212
+ confirmRequired: false,
213
+ ...(actionIcon !== undefined && { icon: actionIcon }),
214
+ onTrigger: () => {
215
+ // A declared `params` extractor (evaluated against the relatedList's
216
+ // parent record) replaces the caller's implicit prefill (e.g. a
217
+ // relatedList's `{ [parentParam]: parentId }` or, with `parentFilter`
218
+ // set, `{ [parentFilter.field]: parentId }`) rather than merging
219
+ // with it — same "params present → drop the default" rule as
220
+ // rowActions.
221
+ const resolvedParams = action.params !== undefined && record !== undefined
222
+ ? evalRowExtractor(action.params, record)
223
+ : prefill;
224
+ const stringifiedParams = resolvedParams !== undefined ? stringifyNavParams(resolvedParams) : undefined;
225
+ const params = action.tab === undefined ? stringifiedParams : { ...stringifiedParams, tab: action.tab };
226
+ navigateWithReturnTo(nav, target, host, params);
227
+ },
228
+ };
229
+ }
230
+ function buildDrawerToolbarAction(action, translate, openDrawer) {
231
+ const actionIcon = resolveActionIcon(action.id);
232
+ return {
233
+ id: action.id,
234
+ label: translate(action.label),
235
+ ...(action.style !== undefined && { style: action.style }),
236
+ confirmRequired: false,
237
+ ...(actionIcon !== undefined && { icon: actionIcon }),
238
+ onTrigger: () => openDrawer(action),
239
+ };
240
+ }
241
+ function buildWriteHandlerToolbarAction(action, translate, refetch, dispatcher) {
242
+ const actionIcon = resolveActionIcon(action.id);
243
+ return {
244
+ id: action.id,
245
+ label: translate(action.label),
246
+ ...(action.style !== undefined && { style: action.style }),
247
+ ...(actionIcon !== undefined && { icon: actionIcon }),
248
+ ...(action.confirm !== undefined && { confirm: translate(action.confirm) }),
249
+ ...(action.confirmLabel !== undefined && {
250
+ confirmLabel: translate(action.confirmLabel),
251
+ }),
252
+ onTrigger: async () => {
253
+ const result = await dispatcher.write(action.handler, action.payload ?? {});
254
+ if (!result.isSuccess) {
255
+ throw new WriteFailedError(result.error, dispatcherErrorText(result.error, translate));
256
+ }
257
+ await refetchAfterWrite(refetch);
258
+ },
259
+ };
260
+ }
261
+ // Shared by entityList, projectionList, and relatedList toolbars so the
262
+ // three call sites can't drift apart.
263
+ export function buildProjectionToolbarActions(options) {
264
+ const { toolbarActions, translate, dispatcher, nav, refetch, openDrawer, navigatePrefill, record, host, } = options;
265
+ if (toolbarActions === undefined)
266
+ return undefined;
267
+ const out = [];
268
+ for (const action of toolbarActions) {
269
+ if (action.visible !== undefined &&
270
+ record !== undefined &&
271
+ !evalFieldCondition(action.visible, record)) {
272
+ continue;
273
+ }
274
+ if (action.kind === "navigate") {
275
+ out.push(buildNavigateToolbarAction(action, translate, nav, host, navigatePrefill, record));
276
+ continue;
277
+ }
278
+ if (action.kind === "drawer") {
279
+ if (openDrawer === undefined) {
280
+ warnDrawerActionDropped("toolbarAction", action.id);
281
+ continue;
282
+ }
283
+ out.push(buildDrawerToolbarAction(action, translate, openDrawer));
284
+ continue;
285
+ }
286
+ // writeHandler — skip without a dispatcher instead of crashing (same as rowActions).
287
+ if (dispatcher === undefined)
288
+ continue;
289
+ out.push(buildWriteHandlerToolbarAction(action, translate, refetch, dispatcher));
290
+ }
291
+ return out.length > 0 ? out : undefined;
292
+ }
293
+ function buildNavigateRecordAction(action, options) {
294
+ const { record, translate, nav, host, actionIcon, defaultScreenTargetEntityId, sameEntityScreenId, } = options;
295
+ if (action.entity !== undefined) {
296
+ const targetEntity = action.entity;
297
+ const id = action.entityId !== undefined ? String(record[action.entityId] ?? "") : "";
298
+ return {
299
+ id: action.id,
300
+ label: translate(action.label),
301
+ ...(action.style !== undefined && { style: action.style }),
302
+ confirmRequired: false,
303
+ ...(actionIcon !== undefined && { icon: actionIcon }),
304
+ onPress: () => {
305
+ // No entityId on record (id === "") → nothing to navigate to.
306
+ if (id !== "") {
307
+ const params = navigateActionSearchParams(action, record);
308
+ navigateWithReturnTo(nav, { entity: targetEntity, id }, host, params);
309
+ }
310
+ },
311
+ };
312
+ }
313
+ if (action.screen !== undefined) {
314
+ const explicit = action.entityId !== undefined ? String(record[action.entityId] ?? "") : undefined;
315
+ const fallback = sameEntityScreenId?.(action.screen) ?? defaultScreenTargetEntityId;
316
+ const navEntityId = explicit ?? fallback;
317
+ const targetScreen = action.screen;
318
+ return {
319
+ id: action.id,
320
+ label: translate(action.label),
321
+ ...(action.style !== undefined && { style: action.style }),
322
+ confirmRequired: false,
323
+ ...(actionIcon !== undefined && { icon: actionIcon }),
324
+ onPress: () => {
325
+ const target = {
326
+ screenId: targetScreen,
327
+ ...(navEntityId !== undefined && navEntityId !== "" && { entityId: navEntityId }),
328
+ };
329
+ const params = navigateActionSearchParams(action, record);
330
+ navigateWithReturnTo(nav, target, host, params);
331
+ },
332
+ };
333
+ }
334
+ return undefined;
335
+ }
336
+ function buildDrawerRecordAction(action, options) {
337
+ const { record, translate, actionIcon, openDrawer } = options;
338
+ return {
339
+ id: action.id,
340
+ label: translate(action.label),
341
+ ...(action.style !== undefined && { style: action.style }),
342
+ confirmRequired: false,
343
+ ...(actionIcon !== undefined && { icon: actionIcon }),
344
+ onPress: () => {
345
+ const initialValues = action.params !== undefined ? evalRowExtractor(action.params, record) : undefined;
346
+ openDrawer(action, initialValues);
347
+ },
348
+ };
349
+ }
350
+ function buildWriteHandlerRecordAction(action, options) {
351
+ const { record, translate, actionIcon, dispatcher, onWriteSuccess } = options;
352
+ return {
353
+ id: action.id,
354
+ label: translate(action.label),
355
+ ...(action.style !== undefined && { style: action.style }),
356
+ ...(actionIcon !== undefined && { icon: actionIcon }),
357
+ ...(action.confirm !== undefined && { confirm: translate(action.confirm) }),
358
+ ...(action.confirmLabel !== undefined && {
359
+ confirmLabel: translate(action.confirmLabel),
360
+ }),
361
+ onPress: async () => {
362
+ const payload = action.payload !== undefined
363
+ ? evalRowExtractor(action.payload, record)
364
+ : { id: record["id"] };
365
+ const result = await dispatcher.write(action.handler, payload);
366
+ if (!result.isSuccess) {
367
+ throw new WriteFailedError(result.error, dispatcherErrorText(result.error, translate));
368
+ }
369
+ await onWriteSuccess();
370
+ },
371
+ };
372
+ }
373
+ // Shared builder for a RowAction[] evaluated against one "record"
374
+ // context (not a specific list row) — a head card's `screen.actions`, an
375
+ // entityEdit's own `screen.actions`, and any section-level
376
+ // `actions`/`emptyState.action` are all this same shape: a RowAction
377
+ // resolved against the record currently being viewed/edited, independent of
378
+ // any table row.
379
+ export function buildRecordActions(options) {
380
+ const { actions, record, translate, nav, host, dispatcher, openDrawer, onWriteSuccess, defaultScreenTargetEntityId, sameEntityScreenId, } = options;
381
+ const out = [];
382
+ for (const action of actions) {
383
+ if (action.visible !== undefined && !evalFieldCondition(action.visible, record))
384
+ continue;
385
+ const actionIcon = resolveActionIcon(action.id, action.icon);
386
+ if (action.kind === "navigate") {
387
+ const built = buildNavigateRecordAction(action, {
388
+ record,
389
+ translate,
390
+ nav,
391
+ host,
392
+ actionIcon,
393
+ defaultScreenTargetEntityId,
394
+ sameEntityScreenId,
395
+ });
396
+ if (built !== undefined)
397
+ out.push(built);
398
+ continue;
399
+ }
400
+ if (action.kind === "drawer") {
401
+ out.push(buildDrawerRecordAction(action, { record, translate, actionIcon, openDrawer }));
402
+ continue;
403
+ }
404
+ // writeHandler — skip without a dispatcher instead of crashing.
405
+ if (dispatcher === undefined)
406
+ continue;
407
+ out.push(buildWriteHandlerRecordAction(action, {
408
+ record,
409
+ translate,
410
+ actionIcon,
411
+ dispatcher,
412
+ onWriteSuccess,
413
+ }));
414
+ }
415
+ return out.length > 0 ? out : undefined;
416
+ }
@@ -0,0 +1,8 @@
1
+ import type { EntityEditScreenDefinition, FeatureSchema } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import { isUiAccessGranted } from "@cosmicdrift/kumiko-framework/ui-types";
3
+ export { isUiAccessGranted as screenAccessAllows };
4
+ export declare function navigateTargetAllows(navigate: {
5
+ readonly screen?: string;
6
+ readonly entity?: string;
7
+ }, appFeatures: readonly FeatureSchema[], userRoles: readonly string[] | undefined): boolean;
8
+ export declare function findEditScreenFor(entity: string, appFeatures: readonly FeatureSchema[], userRoles: readonly string[] | undefined): EntityEditScreenDefinition | undefined;
@@ -0,0 +1,48 @@
1
+ import { isUiAccessGranted } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import { lastSegment } from "./qn.js";
3
+ // Shared UI-visibility predicate for the screen-render path (#1203 — nav
4
+ // filtering via filterByAccess in workspace-shell.tsx hid role-gated
5
+ // screens from the menu, but a direct URL/screenQn hit reached
6
+ // KumikoScreen unchecked). Public name kept as an alias of the shared
7
+ // predicate that headless/nav and workspace-shell use as well.
8
+ export { isUiAccessGranted as screenAccessAllows };
9
+ // A navigate target (metric click) declares no access rule of its own — the
10
+ // destination screen already does, so the jump is offered exactly when the
11
+ // user may open where it lands (solon#424). No second copy of the rule to
12
+ // drift from the screen's own.
13
+ export function navigateTargetAllows(navigate, appFeatures, userRoles) {
14
+ const { screen, entity } = navigate;
15
+ // Neither set — stays on the current screen (tab switch), already allowed.
16
+ if (screen === undefined && entity === undefined)
17
+ return true;
18
+ // KumikoScreen rendered outside createKumikoApp has no screen registry to
19
+ // resolve against; gating on an empty one would hide every jump, not the
20
+ // forbidden ones.
21
+ if (appFeatures.length === 0)
22
+ return true;
23
+ for (const feature of appFeatures) {
24
+ for (const candidate of feature.screens) {
25
+ const isTarget = entity !== undefined
26
+ ? candidate.detailFor === entity
27
+ : lastSegment(candidate.id) === screen;
28
+ // First match IS the destination: short ids are globally unique
29
+ // (validateScreenShortIdCollisions) and both the router and
30
+ // resolveTarget take the first hit.
31
+ if (isTarget)
32
+ return isUiAccessGranted(candidate.access, userRoles);
33
+ }
34
+ }
35
+ // Unresolvable target — the boot-validator doesn't check metric navigate
36
+ // targets, so this is a typo'd dead jump. Don't offer it.
37
+ return false;
38
+ }
39
+ // Searches all mounted features, and access is part of the predicate so a
40
+ // role-gated first match can't hide an accessible second one.
41
+ export function findEditScreenFor(entity, appFeatures, userRoles) {
42
+ for (const feature of appFeatures) {
43
+ const match = feature.screens.find((s) => s.type === "entityEdit" && s.entity === entity && isUiAccessGranted(s.access, userRoles));
44
+ if (match !== undefined)
45
+ return match;
46
+ }
47
+ return undefined;
48
+ }
@@ -0,0 +1,10 @@
1
+ import type { SecretMintScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import type { Translate } from "@cosmicdrift/kumiko-headless";
3
+ import { type ReactNode } from "react";
4
+ import type { FeatureSchema } from "./feature-schema.js";
5
+ export type SecretMintBodyProps = {
6
+ readonly schema: FeatureSchema;
7
+ readonly screen: SecretMintScreenDefinition;
8
+ readonly translate?: Translate;
9
+ };
10
+ export declare function SecretMintBody({ schema, screen, translate }: SecretMintBodyProps): ReactNode;
@@ -0,0 +1,140 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useRef, useState } from "react";
3
+ import { RenderEdit } from "../components/render-edit.js";
4
+ import { useTranslation } from "../i18n.js";
5
+ import { usePrimitives } from "../primitives.js";
6
+ import { synthesizeActionFormEntity, synthesizeActionFormScreen, synthesizeSecretMintConfirmScreen, } from "./action-form-shim.js";
7
+ import { buildInitialValues, literalCurrencyOverrides, mergeSearchParamsIntoInitial, } from "./kumiko-screen.js";
8
+ import { layoutFieldNames } from "./layout-fields.js";
9
+ import { useInitialValuesHandoff, useNav } from "./nav.js";
10
+ import { lastSegment } from "./qn.js";
11
+ import { navigateToReturnOr, useReturnTarget } from "./return-to.js";
12
+ function extractRevealValue(data, field) {
13
+ if (typeof data !== "object" || data === null)
14
+ return undefined;
15
+ if (!Object.hasOwn(data, field))
16
+ return undefined;
17
+ return data[field];
18
+ }
19
+ function extractCarriedValues(data, carry) {
20
+ const carried = {};
21
+ for (const field of carry) {
22
+ const value = extractRevealValue(data, field);
23
+ if (value === undefined)
24
+ continue;
25
+ carried[field] = value;
26
+ }
27
+ return carried;
28
+ }
29
+ function isBlank(value) {
30
+ if (value === undefined || value === null || value === "")
31
+ return true;
32
+ return Array.isArray(value) && value.length === 0;
33
+ }
34
+ // Mint form → one-time reveal → optional confirm → done. The revealed values
35
+ // live ONLY in this component's `revealed` state — never in the URL, a query
36
+ // cache, or nav — the write-handler's success payload is the only place the
37
+ // secret ever exists (fw#2548). onSubmit copies exclusively the fields
38
+ // declared in `screen.reveal.fields` out of that payload (a whitelist, never
39
+ // the payload as a whole) so an unrelated field (e.g. an internal "id") can
40
+ // never leak into the reveal. `screen.confirm` (fw#2838, e.g. TOTP enroll:
41
+ // scan the code, then enter one) works the same way for its own carried
42
+ // mint-payload fields — those live only in `carriedRef`, never in React state
43
+ // that gets rendered, never in the confirm form's own values.
44
+ export function SecretMintBody({ schema, screen, translate }) {
45
+ const nav = useNav();
46
+ const returnTarget = useReturnTarget(screen.id);
47
+ const { Card, Heading, Banner, Button, Text, Grid, GridCell, SecretReveal } = usePrimitives();
48
+ const t = useTranslation();
49
+ const effectiveTranslate = translate ?? t;
50
+ const synthEntity = useMemo(() => synthesizeActionFormEntity(screen.fields), [screen.fields]);
51
+ const synthScreen = useMemo(() => synthesizeActionFormScreen(screen), [screen]);
52
+ const handoffValues = useInitialValuesHandoff(screen.id);
53
+ const initial = useMemo(() => mergeSearchParamsIntoInitial(screen.fields, {
54
+ searchParams: nav.searchParams,
55
+ urlPrefillFields: screen.urlPrefillFields,
56
+ renderableFields: layoutFieldNames(synthScreen),
57
+ // A mint form has no entity either, so its money fields name their own
58
+ // currency source (fw#2839). Only the literal form is resolved here:
59
+ // `{ kind: "tenant" }` would need the config query this screen never
60
+ // runs, and a one-time secret mint has no money use case to justify it.
61
+ moneyCurrencyOverrides: literalCurrencyOverrides(screen.fields),
62
+ ...(handoffValues !== undefined && { handoffValues }),
63
+ }), [screen.fields, screen.urlPrefillFields, nav.searchParams, synthScreen, handoffValues]);
64
+ const [revealed, setRevealed] = useState(null);
65
+ const [done, setDone] = useState(false);
66
+ // Never rendered, never merged into `revealed` or the confirm form's
67
+ // initial values — read only from `buildPayload` at confirm-submit time.
68
+ const carriedRef = useRef({});
69
+ const confirm = screen.confirm;
70
+ const confirmEntity = useMemo(() => (confirm !== undefined ? synthesizeActionFormEntity(confirm.fields) : undefined), [confirm]);
71
+ const confirmScreen = useMemo(() => (confirm !== undefined ? synthesizeSecretMintConfirmScreen(screen, confirm) : undefined), [screen, confirm]);
72
+ const confirmInitial = useMemo(() => confirm !== undefined
73
+ ? buildInitialValues(confirm.fields, undefined, literalCurrencyOverrides(confirm.fields))
74
+ : undefined, [confirm]);
75
+ const handleSubmitted = useCallback((result) => {
76
+ if (!result.isSuccess)
77
+ return;
78
+ const values = {};
79
+ for (const revealField of screen.reveal.fields) {
80
+ const value = extractRevealValue(result.data, revealField.field);
81
+ if (value === undefined)
82
+ continue;
83
+ values[revealField.field] = value;
84
+ }
85
+ setRevealed(values);
86
+ carriedRef.current =
87
+ confirm?.carry !== undefined ? extractCarriedValues(result.data, confirm.carry) : {};
88
+ }, [screen.reveal.fields, confirm?.carry]);
89
+ const handleCancel = useMemo(() => {
90
+ const target = screen.cancelTarget ?? screen.redirect;
91
+ if (target === undefined || target === false)
92
+ return undefined;
93
+ return () => navigateToReturnOr(nav, returnTarget, () => nav.navigate({ screenId: lastSegment(target) }));
94
+ }, [nav, screen.redirect, screen.cancelTarget, returnTarget]);
95
+ // Ends the reveal phase for both paths (the bare acknowledge button, and a
96
+ // successful confirm submit): clears the secret and the carried values, then
97
+ // either navigates (screen.redirect) or shows a done-state — never falls
98
+ // back to re-rendering the mint form, which would let a stray click mint
99
+ // (and invalidate) the secret again.
100
+ const finishMint = useCallback(() => {
101
+ setRevealed(null);
102
+ carriedRef.current = {};
103
+ const redirect = screen.redirect;
104
+ if (redirect !== undefined) {
105
+ navigateToReturnOr(nav, returnTarget, () => nav.navigate({ screenId: lastSegment(redirect) }));
106
+ }
107
+ else {
108
+ setDone(true);
109
+ }
110
+ }, [nav, screen.redirect, returnTarget]);
111
+ const handleConfirmSubmitted = useCallback((result) => {
112
+ if (result.isSuccess)
113
+ finishMint();
114
+ }, [finishMint]);
115
+ if (done) {
116
+ return (_jsx(Card, { children: _jsx(Banner, { variant: "info", testId: "kumiko-screen-secret-mint-done", children: effectiveTranslate(confirm?.doneMessage ?? "kumiko.secretMint.done") }) }));
117
+ }
118
+ if (revealed !== null) {
119
+ const values = screen.reveal.fields.flatMap((revealField) => {
120
+ const raw = revealed[revealField.field];
121
+ if (isBlank(raw))
122
+ return [];
123
+ const display = revealField.display ?? "code";
124
+ const value = display === "list" && Array.isArray(raw)
125
+ ? raw.map((v) => String(v)).join("\n")
126
+ : String(raw);
127
+ return [
128
+ {
129
+ label: effectiveTranslate(revealField.label),
130
+ value,
131
+ copyable: revealField.copyable ?? true,
132
+ multiline: display === "list",
133
+ ...(display === "qr" && { qr: true }),
134
+ },
135
+ ];
136
+ });
137
+ return (_jsxs(Card, { testId: "kumiko-screen-secret-mint-card", children: [_jsx(Heading, { variant: "page", children: effectiveTranslate(screen.reveal.title ?? "kumiko.secretMint.title") }), _jsx(Banner, { variant: "warning", testId: "kumiko-screen-secret-mint-warning", children: effectiveTranslate(screen.reveal.warning ?? "kumiko.secretMint.warning") }), SecretReveal !== undefined ? (_jsx(SecretReveal, { values: values, copyLabel: effectiveTranslate("kumiko.secretMint.copy"), copiedLabel: effectiveTranslate("kumiko.secretMint.copied"), testId: "kumiko-screen-secret-mint-reveal" })) : (_jsx(Grid, { columns: 1, testId: "kumiko-screen-secret-mint-reveal", children: values.map((v) => (_jsxs(GridCell, { children: [_jsx(Text, { children: v.label }), _jsx(Text, { variant: "code", children: v.value })] }, v.label))) })), confirm !== undefined && confirmEntity !== undefined && confirmScreen !== undefined ? (_jsx(RenderEdit, { screen: confirmScreen, entity: confirmEntity, featureName: schema.featureName, initial: confirmInitial ?? {}, writeCommand: confirm.handler, payloadMode: "values", buildPayload: (snapshot) => ({ ...snapshot.values, ...carriedRef.current }), onSubmit: handleConfirmSubmitted, ...(handleCancel !== undefined && { onCancel: handleCancel }), ...(translate !== undefined && { translate }), ...(confirm.submitLabel !== undefined && { submitLabel: confirm.submitLabel }) })) : (_jsx(Button, { type: "button", variant: "primary", onClick: finishMint, testId: "kumiko-screen-secret-mint-confirm", children: effectiveTranslate(screen.reveal.confirmLabel ?? "kumiko.secretMint.confirm") }))] }));
138
+ }
139
+ return (_jsx(RenderEdit, { screen: synthScreen, entity: synthEntity, featureName: schema.featureName, initial: initial, extensionInitialValues: initial, writeCommand: screen.handler, payloadMode: "values", onSubmit: handleSubmitted, ...(handleCancel !== undefined && { onCancel: handleCancel }), ...(translate !== undefined && { translate }), ...(screen.submitLabel !== undefined && { submitLabel: screen.submitLabel }) }));
140
+ }
@@ -0,0 +1,8 @@
1
+ import type { SecretsEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import type { Translate } from "@cosmicdrift/kumiko-headless";
3
+ import { type ReactNode } from "react";
4
+ export type SecretsEditBodyProps = {
5
+ readonly screen: SecretsEditScreenDefinition;
6
+ readonly translate?: Translate;
7
+ };
8
+ export declare function SecretsEditBody({ screen, translate }: SecretsEditBodyProps): ReactNode;