@cosmicdrift/kumiko-renderer 0.386.0 → 0.388.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 (222) hide show
  1. package/LICENSE +21 -57
  2. package/dist/app/action-form-body.d.ts +2 -2
  3. package/dist/app/action-form-body.js +12 -4
  4. package/dist/app/action-form-shim.d.ts +8 -8
  5. package/dist/app/action-form-shim.js +20 -22
  6. package/dist/app/action-form-support.d.ts +1 -0
  7. package/dist/app/action-form-support.js +1 -1
  8. package/dist/app/app-features-context.js +4 -4
  9. package/dist/app/column-renderers.d.ts +7 -9
  10. package/dist/app/column-renderers.js +15 -18
  11. package/dist/app/config-edit-body.js +7 -3
  12. package/dist/app/config-edit-shim.d.ts +7 -8
  13. package/dist/app/config-edit-shim.js +18 -20
  14. package/dist/app/custom-screens.d.ts +3 -3
  15. package/dist/app/custom-screens.js +8 -8
  16. package/dist/app/dashboard-body.d.ts +2 -2
  17. package/dist/app/dashboard-body.js +2 -2
  18. package/dist/app/entity-edit-prefill.d.ts +11 -0
  19. package/dist/app/entity-edit-prefill.js +18 -13
  20. package/dist/app/entity-edit-screen.js +4 -4
  21. package/dist/app/entity-list-screen.js +3 -3
  22. package/dist/app/extension-form-submit.d.ts +1 -1
  23. package/dist/app/extension-form-submit.js +14 -15
  24. package/dist/app/extension-sections.d.ts +29 -28
  25. package/dist/app/extension-sections.js +10 -10
  26. package/dist/app/feature-schema.js +11 -11
  27. package/dist/app/form-schema.js +2 -2
  28. package/dist/app/header-actions-bar.js +2 -2
  29. package/dist/app/kumiko-screen.js +8 -10
  30. package/dist/app/layout-fields.js +1 -1
  31. package/dist/app/list-facets.js +3 -3
  32. package/dist/app/nav.d.ts +10 -10
  33. package/dist/app/nav.js +13 -13
  34. package/dist/app/projection-detail-body.js +4 -4
  35. package/dist/app/projection-detail-shim.d.ts +6 -5
  36. package/dist/app/projection-detail-shim.js +23 -22
  37. package/dist/app/projection-list-body.js +10 -10
  38. package/dist/app/projection-list-shim.d.ts +5 -5
  39. package/dist/app/projection-list-shim.js +17 -18
  40. package/dist/app/qn.js +10 -12
  41. package/dist/app/qualify-screen-id.js +1 -1
  42. package/dist/app/screen-access.js +2 -2
  43. package/dist/app/screen-shared.js +1 -1
  44. package/dist/app/secret-mint-body.js +4 -4
  45. package/dist/app/write-failed-error.js +8 -7
  46. package/dist/components/edit-mounts.d.ts +2 -2
  47. package/dist/components/embedded-list-field.js +5 -5
  48. package/dist/components/grid-cell-for-field.d.ts +1 -1
  49. package/dist/components/reference-create-dialog.js +3 -3
  50. package/dist/components/related-list-section.d.ts +1 -1
  51. package/dist/components/related-list-section.js +15 -2
  52. package/dist/components/render-edit-action-button.d.ts +1 -3
  53. package/dist/components/render-edit-action-button.js +2 -2
  54. package/dist/components/render-edit-logic.js +1 -1
  55. package/dist/components/render-edit-types.d.ts +6 -6
  56. package/dist/components/render-edit.js +74 -73
  57. package/dist/components/render-field.d.ts +7 -7
  58. package/dist/components/render-field.js +62 -61
  59. package/dist/components/render-list.d.ts +42 -43
  60. package/dist/components/render-list.js +93 -84
  61. package/dist/components/use-wizard-form-draft.js +10 -10
  62. package/dist/context/dispatcher-context.js +5 -5
  63. package/dist/context/user-roles-context.js +2 -2
  64. package/dist/flow-diagram/render.js +9 -1
  65. package/dist/headless/apex/index.d.ts +1 -2
  66. package/dist/headless/apex/index.js +5 -6
  67. package/dist/headless/form/form-controller.js +14 -6
  68. package/dist/headless/form/types.d.ts +1 -0
  69. package/dist/headless/form/zod-bridge.js +2 -2
  70. package/dist/headless/format/index.js +89 -64
  71. package/dist/headless/format/money.js +1 -1
  72. package/dist/headless/index.js +2 -2
  73. package/dist/headless/view-model/edit.js +4 -2
  74. package/dist/headless/view-model/list.js +184 -150
  75. package/dist/headless/view-model/types.d.ts +6 -3
  76. package/dist/hooks/live-refetch-scheduler.js +4 -0
  77. package/dist/hooks/reference-limits.js +11 -12
  78. package/dist/hooks/use-list-url-state.d.ts +13 -13
  79. package/dist/hooks/use-list-url-state.js +21 -22
  80. package/dist/hooks/use-query.js +3 -3
  81. package/dist/hooks/use-reference-lookup.d.ts +9 -1
  82. package/dist/hooks/use-reference-lookup.js +32 -19
  83. package/dist/hooks/use-store.js +18 -22
  84. package/dist/hooks/use-stream-handler.js +4 -2
  85. package/dist/i18n-defaults.js +6 -8
  86. package/dist/i18n.d.ts +10 -10
  87. package/dist/i18n.js +25 -25
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.js +6 -4
  90. package/dist/live/dispatcher-live.js +2 -2
  91. package/dist/primitives.d.ts +244 -221
  92. package/dist/primitives.js +1 -1
  93. package/dist/sse/live-events.d.ts +5 -5
  94. package/dist/sse/live-events.js +17 -17
  95. package/dist/tokens.d.ts +8 -8
  96. package/dist/tokens.js +16 -17
  97. package/dist/web/app/browser-locale.d.ts +9 -9
  98. package/dist/web/app/browser-locale.js +19 -19
  99. package/dist/web/app/client-plugin.d.ts +44 -52
  100. package/dist/web/app/client-plugin.js +4 -5
  101. package/dist/web/app/create-app.d.ts +28 -31
  102. package/dist/web/app/create-app.js +29 -25
  103. package/dist/web/app/dashboard-body.js +17 -1
  104. package/dist/web/app/dashboard-panels.js +12 -2
  105. package/dist/web/app/document-lang-sync.js +1 -1
  106. package/dist/web/app/draft-storage.js +1 -1
  107. package/dist/web/app/nav-providers-context.d.ts +3 -3
  108. package/dist/web/app/nav-providers-context.js +3 -3
  109. package/dist/web/app/nav.d.ts +18 -17
  110. package/dist/web/app/nav.js +66 -64
  111. package/dist/web/app/rich-content-editor.js +1 -1
  112. package/dist/web/components/config-cascade.js +17 -18
  113. package/dist/web/components/config-source-badge.js +4 -4
  114. package/dist/web/index.d.ts +1 -1
  115. package/dist/web/index.js +1 -1
  116. package/dist/web/layout/app-layout.d.ts +9 -9
  117. package/dist/web/layout/app-layout.js +2 -2
  118. package/dist/web/layout/avatar.d.ts +4 -3
  119. package/dist/web/layout/avatar.js +15 -17
  120. package/dist/web/layout/default-app-shell.d.ts +22 -23
  121. package/dist/web/layout/editor-panel.js +3 -3
  122. package/dist/web/layout/nav-tree.js +86 -84
  123. package/dist/web/layout/profile-menu.d.ts +1 -1
  124. package/dist/web/layout/shell-header.js +2 -1
  125. package/dist/web/layout/sidebar-brand.d.ts +7 -7
  126. package/dist/web/layout/sidebar-user.d.ts +1 -1
  127. package/dist/web/layout/sidebar-user.js +4 -4
  128. package/dist/web/layout/sidebar.d.ts +8 -8
  129. package/dist/web/layout/sidebar.js +7 -7
  130. package/dist/web/layout/target-resolver-stub.d.ts +5 -5
  131. package/dist/web/layout/target-resolver-stub.js +20 -20
  132. package/dist/web/layout/target-url.d.ts +7 -7
  133. package/dist/web/layout/target-url.js +22 -22
  134. package/dist/web/layout/theme-toggle.js +4 -5
  135. package/dist/web/layout/uniform-bar-controls.d.ts +1 -0
  136. package/dist/web/layout/uniform-bar-controls.js +4 -0
  137. package/dist/web/layout/workspace-shell.d.ts +21 -21
  138. package/dist/web/layout/workspace-shell.js +29 -30
  139. package/dist/web/lib/cn.d.ts +4 -4
  140. package/dist/web/lib/cn.js +4 -4
  141. package/dist/web/lib/file-ref-download.js +4 -1
  142. package/dist/web/primitives/action-menu.d.ts +8 -8
  143. package/dist/web/primitives/action-menu.js +14 -13
  144. package/dist/web/primitives/calendar-popover.d.ts +1 -1
  145. package/dist/web/primitives/calendar-popover.js +10 -10
  146. package/dist/web/primitives/card-styles.js +1 -1
  147. package/dist/web/primitives/card.js +2 -2
  148. package/dist/web/primitives/code-input.d.ts +6 -0
  149. package/dist/web/primitives/code-input.js +31 -0
  150. package/dist/web/primitives/combobox.d.ts +12 -12
  151. package/dist/web/primitives/combobox.js +34 -35
  152. package/dist/web/primitives/data-table-cell.js +2 -2
  153. package/dist/web/primitives/data-table.js +6 -6
  154. package/dist/web/primitives/date-field.js +2 -2
  155. package/dist/web/primitives/date-input.js +4 -4
  156. package/dist/web/primitives/default-form.js +2 -2
  157. package/dist/web/primitives/dropdown-menu.js +14 -15
  158. package/dist/web/primitives/embedded-list-input.js +3 -3
  159. package/dist/web/primitives/facet-filter.js +2 -2
  160. package/dist/web/primitives/field.js +9 -8
  161. package/dist/web/primitives/file-thumbnail.d.ts +3 -0
  162. package/dist/web/primitives/file-thumbnail.js +5 -0
  163. package/dist/web/primitives/form.d.ts +1 -1
  164. package/dist/web/primitives/form.js +6 -6
  165. package/dist/web/primitives/input.js +8 -3
  166. package/dist/web/primitives/layout-elements.js +7 -5
  167. package/dist/web/primitives/layout.js +2 -2
  168. package/dist/web/primitives/located-timestamp-input.js +4 -4
  169. package/dist/web/primitives/money-input.js +13 -17
  170. package/dist/web/primitives/page-header.js +3 -1
  171. package/dist/web/primitives/pager.js +1 -1
  172. package/dist/web/primitives/row-actions.js +6 -7
  173. package/dist/web/primitives/secret-reveal.js +1 -1
  174. package/dist/web/primitives/timestamp-input.d.ts +9 -9
  175. package/dist/web/primitives/timestamp-input.js +21 -21
  176. package/dist/web/primitives/toast.js +12 -13
  177. package/dist/web/primitives/use-focus-on-mount.d.ts +2 -0
  178. package/dist/web/primitives/use-focus-on-mount.js +9 -0
  179. package/dist/web/sse/live-events.d.ts +6 -6
  180. package/dist/web/sse/live-events.js +3 -3
  181. package/dist/web/tokens.d.ts +11 -12
  182. package/dist/web/tokens.js +35 -37
  183. package/dist/web/version/update-checker.js +14 -13
  184. package/dist/web/widgets/ai-text-field.js +1 -1
  185. package/dist/web/widgets/bounded-load-cache.d.ts +1 -1
  186. package/dist/web/widgets/bounded-load-cache.js +10 -4
  187. package/dist/web/widgets/charts.d.ts +10 -10
  188. package/dist/web/widgets/charts.js +8 -8
  189. package/dist/web/widgets/choice-cards.d.ts +17 -0
  190. package/dist/web/widgets/choice-cards.js +12 -0
  191. package/dist/web/widgets/collapsible-section.d.ts +3 -3
  192. package/dist/web/widgets/collapsible-section.js +7 -7
  193. package/dist/web/widgets/detail-list.d.ts +5 -3
  194. package/dist/web/widgets/detail-list.js +5 -3
  195. package/dist/web/widgets/feed-list.d.ts +2 -2
  196. package/dist/web/widgets/feed-list.js +2 -2
  197. package/dist/web/widgets/form-fields.d.ts +14 -14
  198. package/dist/web/widgets/form-fields.js +14 -13
  199. package/dist/web/widgets/index.d.ts +1 -0
  200. package/dist/web/widgets/index.js +5 -4
  201. package/dist/web/widgets/infinity-list.js +3 -3
  202. package/dist/web/widgets/mode-switch.d.ts +2 -2
  203. package/dist/web/widgets/mode-switch.js +2 -2
  204. package/dist/web/widgets/pdf-preview.js +1 -1
  205. package/dist/web/widgets/plan-card.d.ts +2 -0
  206. package/dist/web/widgets/plan-card.js +1 -1
  207. package/dist/web/widgets/progress-list.d.ts +1 -2
  208. package/dist/web/widgets/progress-list.js +1 -2
  209. package/dist/web/widgets/query-table.d.ts +6 -6
  210. package/dist/web/widgets/result-panel.d.ts +16 -16
  211. package/dist/web/widgets/result-panel.js +10 -10
  212. package/dist/web/widgets/section-card.d.ts +3 -3
  213. package/dist/web/widgets/section-card.js +3 -3
  214. package/dist/web/widgets/stat.d.ts +4 -4
  215. package/dist/web/widgets/stat.js +6 -6
  216. package/dist/web/widgets/states.d.ts +4 -4
  217. package/dist/web/widgets/states.js +4 -4
  218. package/dist/web/widgets/upload-zone.js +13 -5
  219. package/dist/web/widgets/use-draft.d.ts +7 -7
  220. package/dist/web/widgets/use-draft.js +7 -7
  221. package/package.json +6 -6
  222. package/src/changes.json +49 -0
@@ -5,8 +5,8 @@ import { useMemo } from "react";
5
5
  import { rendererWarn } from "../dev-console.js";
6
6
  import { useQuery } from "../hooks/use-query.js";
7
7
  // `defaultCurrency` is entityEdit-only — it enables the `{amount, currency}`
8
- // write shape (fw#1923); config-edit's plain-number contract needs bare `0`.
9
- // `moneyCurrencyOverrides` (fw#2933) replaces `defaultCurrency` per field name
8
+ // write shape; config-edit's plain-number contract needs bare `0`.
9
+ // `moneyCurrencyOverrides` replaces `defaultCurrency` per field name
10
10
  // — used for a `money` field that declares `currency: { kind: "tenant" }`, so
11
11
  // its empty initial value carries the tenant's own currency instead of the
12
12
  // entity-wide default.
@@ -51,7 +51,7 @@ export function buildInitialValues(fields, defaultCurrency, moneyCurrencyOverrid
51
51
  return out;
52
52
  }
53
53
  // A `money` field opts a currently-empty value into the tenant-settings
54
- // bundle's per-tenant currency (fw#2933) via `currency: { kind: "tenant" }`
54
+ // bundle's per-tenant currency via `currency: { kind: "tenant" }`
55
55
  // on its FieldDefinition. Read structurally — renderer only depends on the
56
56
  // client-safe FieldDefinition subset, not the concrete MoneyFieldDef/
57
57
  // MoneyCurrencySource types, same idiom as the other field-shape narrowings
@@ -68,11 +68,11 @@ export function tenantCurrencyMoneyFieldNames(fields) {
68
68
  }
69
69
  // Resolves what a tenant-declared money field's currency should be, from the
70
70
  // same `config:query:values` query ConfigEditBody already uses. Never falls
71
- // back to "EUR" while the query is genuinely still in flight (fw#2933) — a
71
+ // back to "EUR" while the query is genuinely still in flight — a
72
72
  // screen with no tenant-declared money field, or a query that errored or
73
73
  // returned no value for the key, resolves immediately instead of blocking.
74
- function resolveTenantCurrency(fieldNames, query, fallback) {
75
- if (fieldNames.length === 0)
74
+ function resolveTenantCurrency(needed, query, fallback) {
75
+ if (!needed)
76
76
  return { status: "not-needed" };
77
77
  if (query.error) {
78
78
  return fallback === undefined
@@ -88,8 +88,14 @@ function resolveTenantCurrency(fieldNames, query, fallback) {
88
88
  ? { status: "unresolved" }
89
89
  : { status: "ready", currency: fallback };
90
90
  }
91
- // A money field naming a fixed ISO code (`currency: { kind: "literal", code }`,
92
- // fw#2839) resolves with no query at all. Read structurally, same idiom as
91
+ // `needed = false` skips the config query entirely; an errored query or a missing value
92
+ // resolves to `fallback` (or `unresolved` without one).
93
+ export function useTenantCurrency(needed, fallback) {
94
+ const query = useQuery("config:query:values", { keys: [TENANT_CURRENCY_CONFIG_KEY] }, { enabled: needed });
95
+ return resolveTenantCurrency(needed, query, fallback);
96
+ }
97
+ // A money field naming a fixed ISO code (`currency: { kind: "literal", code }`)
98
+ // resolves with no query at all. Read structurally, same idiom as
93
99
  // tenantCurrencyMoneyFieldNames above.
94
100
  export function literalCurrencyOverrides(fields) {
95
101
  const out = {};
@@ -105,9 +111,9 @@ export function literalCurrencyOverrides(fields) {
105
111
  return out;
106
112
  }
107
113
  // Per-field currency for the money fields that declare their own source: a
108
- // literal code resolves synchronously (fw#2839), a `{ kind: "tenant" }` field
114
+ // literal code resolves synchronously, a `{ kind: "tenant" }` field
109
115
  // needs the tenant-settings config query and makes the caller hold the form
110
- // until it lands (fw#2933) — seeding the fallback meanwhile would render, and
116
+ // until it lands — seeding the fallback meanwhile would render, and
111
117
  // on a fast click submit, the wrong currency. Callers pass the tenant field
112
118
  // names themselves because an edit form only needs the fetch for fields the
113
119
  // record has no stored value for.
@@ -115,8 +121,7 @@ export function useMoneyCurrencyOverrides(fields, tenantFieldNames,
115
121
  // `undefined` = no safe last resort (entity-less forms): an unresolvable
116
122
  // tenant currency is reported via `unresolved` instead of guessing a code.
117
123
  fallback) {
118
- const query = useQuery("config:query:values", { keys: [TENANT_CURRENCY_CONFIG_KEY] }, { enabled: tenantFieldNames.length > 0 });
119
- const resolution = resolveTenantCurrency(tenantFieldNames, query, fallback);
124
+ const resolution = useTenantCurrency(tenantFieldNames.length > 0, fallback);
120
125
  const resolvedTenantCurrency = resolution.status === "ready" ? resolution.currency : undefined;
121
126
  const overrides = useMemo(() => {
122
127
  const out = { ...literalCurrencyOverrides(fields) };
@@ -164,7 +169,7 @@ function warnPrefillParam(message) {
164
169
  rendererWarn(`[kumiko] ${message}`);
165
170
  }
166
171
  // An actionForm has no entity and thus no defaultCurrency, so a bare-number
167
- // prefill would stay a number the handler's zod schema rejects (fw#2763).
172
+ // prefill would stay a number the handler's zod schema rejects.
168
173
  function coerceMoneyValue(raw, fieldName, defaultCurrency) {
169
174
  if (raw === null || raw === undefined)
170
175
  return undefined;
@@ -52,7 +52,7 @@ function EntityEditCreateBody({ schema, screen, entity, translate, onSaved, }) {
52
52
  // returnTo would fight that.
53
53
  const returnTarget = screen.singleton === true ? undefined : returnTargetParam;
54
54
  const entityDefaultCurrency = entity.defaultCurrency ?? "EUR";
55
- // A money field declaring `currency: { kind: "tenant" }` (fw#2933) resolves
55
+ // A money field declaring `currency: { kind: "tenant" }` resolves
56
56
  // its empty-value currency from the tenant-settings config key instead of
57
57
  // entityDefaultCurrency — `enabled` keeps the query a no-op for entities
58
58
  // without any such field.
@@ -117,7 +117,7 @@ function EntityEditCreateBody({ schema, screen, entity, translate, onSaved, }) {
117
117
  }
118
118
  }, [nav, screen.redirect, schema, appFeatures, navigateToList, onSaved, returnTarget]);
119
119
  // Never seed a tenant-declared money field with entityDefaultCurrency while
120
- // its real tenant currency is still in flight (fw#2933) — wait instead.
120
+ // its real tenant currency is still in flight — wait instead.
121
121
  if (currencyLoading) {
122
122
  return _jsx(QueryLoadingBanner, {});
123
123
  }
@@ -172,7 +172,7 @@ function EntityEditUpdateForm({ schema, screen, entity, entityId, record, onRelo
172
172
  // error and the user reloads.
173
173
  const recordVersion = versionOf(record);
174
174
  const entityDefaultCurrency = entity.defaultCurrency ?? "EUR";
175
- // A money field declaring `currency: { kind: "tenant" }` (fw#2933) resolves
175
+ // A money field declaring `currency: { kind: "tenant" }` resolves
176
176
  // its empty-value currency from the tenant-settings config key instead of
177
177
  // entityDefaultCurrency — `enabled` keeps the query a no-op for entities
178
178
  // without any such field. A record's own stored value (below) always wins
@@ -333,7 +333,7 @@ function EntityEditUpdateForm({ schema, screen, entity, entityId, record, onRelo
333
333
  return undefined;
334
334
  }, [dispatcher, deleteCommand, entityId, navigateToList, onDeleted, returnTarget, nav]);
335
335
  // Never seed a tenant-declared money field with entityDefaultCurrency while
336
- // its real tenant currency is still in flight (fw#2933) — wait instead. A
336
+ // its real tenant currency is still in flight — wait instead. A
337
337
  // stored (non-empty) value is unaffected — it never reaches this branch's
338
338
  // fallback because `initial` above already prefers `record[name]`.
339
339
  if (currencyLoading) {
@@ -298,7 +298,7 @@ function EntityListBody({ schema, screen, entity, translate, onRowClick, }) {
298
298
  // drift apart.
299
299
  const runNavigate = useCallback((action, row) => {
300
300
  if (action.entity !== undefined) {
301
- // Entity targets (fw#2228) only resolve in the NavApi implementation
301
+ // Entity targets only resolve in the NavApi implementation
302
302
  // against ALL features (resolveTarget in renderer/web/nav.tsx); this
303
303
  // package only knows `schema` (the one calling feature) and cannot look up
304
304
  // detailFor across features itself.
@@ -499,8 +499,8 @@ function EntityListBody({ schema, screen, entity, translate, onRowClick, }) {
499
499
  // the entity has searchable fields (dead toolbar slot otherwise — the
500
500
  // server search index has nothing to filter). Gated on
501
501
  // schema.searchAdapterMissing !== true regardless of source (explicit or
502
- // auto) — a search bar the server can't serve would 422 at request-time
503
- // (#2032); matches the boot-time check in api/server.ts (#2051).
502
+ // auto) — a search bar the server can't serve would 422 at request-time;
503
+ // matches the boot-time check in api/server.ts.
504
504
  const searchable = schema.searchAdapterMissing !== true &&
505
505
  (screen.searchable ??
506
506
  Object.values(entity.fields).some((f) => "searchable" in f && f.searchable === true));
@@ -3,7 +3,7 @@ export type ExtensionSubmitContext = {
3
3
  };
4
4
  export type ExtensionSubmitResult = {
5
5
  readonly isSuccess: boolean;
6
- /** i18n-Key für die Fehlermeldung, die die Form als Banner zeigt. */
6
+ /** i18n key for the error message that the form displays as a banner. */
7
7
  readonly errorKey?: string;
8
8
  };
9
9
  export type ExtensionFormSubmitHandler = (ctx: ExtensionSubmitContext) => Promise<ExtensionSubmitResult>;
@@ -1,14 +1,13 @@
1
1
  // @runtime client
2
- // Extension-Form-Submit-Registry — erlaubt einer Extension-Section (z.B.
3
- // Custom-Fields), beim Submit des umgebenden entityEdit-Forms mitzuschreiben,
4
- // statt einen eigenen Save-Button zu führen ("composed form, ein Save",
5
- // Bug-Bash 3 #1). Die Section meldet (a) einen Submit-Handler und (b) ihren
6
- // dirty-State an die Form; die Form aktiviert ihren Save-Button auch wenn nur
7
- // eine Section dirty ist und ruft nach dem Entity-Write alle Section-Handler
8
- // mit der entityId.
2
+ // Extension-Form-Submit-Registry — lets an extension section (e.g.
3
+ // Custom-Fields) write during submission of the surrounding entityEdit form
4
+ // instead of maintaining its own Save button ("composed form, one save").
5
+ // The section reports (a) a submit handler and (b) its dirty state to the form;
6
+ // the form enables its Save button even when only one section is dirty and,
7
+ // after the entity write, calls all section handlers with the entityId.
9
8
  //
10
- // Ohne umgebende Form (Context === null) fällt die Section auf ihren eigenen
11
- // Save-Button zurück (Standalone-Backward-Compat).
9
+ // Without a surrounding form (Context === null), the section falls back to its
10
+ // own Save button (Standalone-Backward-Compat).
12
11
  import { createContext, useContext, useEffect, useId, useMemo, useRef } from "react";
13
12
  const ExtensionFormRegistryContext = createContext(null);
14
13
  export const ExtensionFormRegistryProvider = ExtensionFormRegistryContext.Provider;
@@ -48,15 +47,15 @@ export function useExtensionFormHost(onStatusChange) {
48
47
  }, []);
49
48
  return { registry, runAll };
50
49
  }
51
- // Section-Seite: meldet Handler + dirty an die umgebende Form. Returnt true
52
- // wenn eine composed-Form vorhanden ist (Section blendet dann ihren eigenen
53
- // Save-Button aus); false = standalone (eigener Save-Button bleibt).
50
+ // Section side: reports the handler and dirty state to the surrounding form.
51
+ // Returns true when a composed form is present, so the section hides its own
52
+ // Save button; false means standalone, so its own Save button remains.
54
53
  export function useExtensionFormSubmit(opts) {
55
54
  const registry = useContext(ExtensionFormRegistryContext);
56
55
  const key = useId();
57
- // Handler über ref, damit das Re-Registrieren NICHT an jeder Handler-
58
- // Identity hängt (nur an dirty); der Handler liest immer den frischen
59
- // Closure-State (pending) zur Aufruf-Zeit.
56
+ // The ref prevents re-registration whenever the handler identity changes
57
+ // (only dirty triggers it); the handler always reads the latest closure state
58
+ // (pending) when invoked.
60
59
  const handlerRef = useRef(opts.onSubmit);
61
60
  handlerRef.current = opts.onSubmit;
62
61
  useEffect(() => {
@@ -1,31 +1,30 @@
1
1
  import type { PlatformComponent } from "@cosmicdrift/kumiko-framework/ui-types";
2
2
  import { type ComponentType, type ReactNode } from "react";
3
- /** Extrahiert den `__component`-Namen aus einer PlatformComponent. Liest
4
- * react- und native-Branch (die App-Author registriert beide unter dem
5
- * gleichen Namen); returnt den ersten gefundenen string. Pure narrowing
6
- * via `in` — kein cast, kein assertion. */
3
+ /** Extracts the `__component` name from a PlatformComponent. Reads the
4
+ * react and native branches (the app author registers both under the same
5
+ * name); returns the first string found. Pure narrowing via `in` — no cast
6
+ * or assertion. */
7
7
  export declare function extensionSectionName(component: PlatformComponent): string | undefined;
8
8
  export type ExtensionSectionProps = {
9
9
  readonly entityName: string;
10
10
  readonly entityId: string | null;
11
- /** Bereits gespeicherte Extension-Werte der Entity (aus der geladenen
12
- * detail-row durchgereicht). `undefined` im Create-Mode oder wenn der
13
- * Host-Screen keine Werte liefert. Erlaubt der Section, den Bestand
14
- * beim Edit anzuzeigen statt write-only zu sein. */
11
+ /** Previously saved extension values for the entity (passed through from
12
+ * the loaded detail row). `undefined` in create mode or when the host
13
+ * screen provides no values. Allows the section to display existing
14
+ * values while editing instead of being write-only. */
15
15
  readonly initialValues?: Readonly<Record<string, unknown>>;
16
- /** Nur im List-Header-Slot gesetzt (entityId ist dort null): die screen.id
17
- * der Liste, damit ein Header-Control den per-Screen URL-Filter-State
18
- * (useListUrlState) ansprechen kann — z.B. ein Tag-Filter der die Liste auf
19
- * eine id-Menge narrowed. In entityEdit-Sections undefined. */
16
+ /** Set only in the list-header slot (`entityId` is null): the list's
17
+ * `screen.id`, so a header control can access the per-screen URL filter
18
+ * state (`useListUrlState`) — for example, a tag filter that narrows the
19
+ * list to a set of IDs. `undefined` in entityEdit sections. */
20
20
  readonly screenId?: string;
21
- /** Nur im Dashboard-`custom`-Panel gesetzt: der aktuell gewählte Wert des
22
- * Screen-Filters (siehe DashboardFilterDefinition), gemerged wie bei jeder
23
- * anderen Panel-Query. In allen anderen Mount-Orten undefined. Dashboard-
24
- * Panels haben keine Entity — entityName/entityId tragen dort die
25
- * screen.id bzw. null, siehe CustomPanelBody in dashboard-body.tsx. */
21
+ /** Set only in a Dashboard-`custom` panel: the currently selected value
22
+ * of the screen filter (see DashboardFilterDefinition), merged as with any
23
+ * other panel query. `undefined` in all other mount locations. Dashboard
24
+ * panels have no entity — `entityName`/`entityId` contain the `screen.id`
25
+ * and `null`, respectively; see CustomPanelBody in dashboard-body.tsx. */
26
26
  readonly filterParams?: Readonly<Record<string, unknown>>;
27
- /** Current form values of the host RenderEdit (controlled mode,
28
- * issue #1887/#1888) — same snapshot reference as
27
+ /** Current form values of the host RenderEdit (controlled mode) — same snapshot reference as
29
28
  * `RenderEditChangeState.values`. Lets the section read the host
30
29
  * form's live state (e.g. for a review step). Only set in
31
30
  * entityEdit sections; undefined in list-header and dashboard
@@ -44,10 +43,12 @@ export type ExtensionSectionProps = {
44
43
  * `snapshot.errors` on the field instead of a collective message.
45
44
  * Undefined outside entityEdit sections. */
46
45
  readonly validate?: () => boolean;
47
- /** Marks the current host form values as saved after the section wrote
48
- * them itself, so a following navigation does not ask to discard.
46
+ /** Marks host form values as saved after the section wrote them itself, so
47
+ * a following navigation does not ask to discard. Pass the field names the
48
+ * section persisted: other host fields keep their unsaved state. Without
49
+ * `fields`, every current host value counts as saved.
49
50
  * Undefined outside entityEdit sections and the entityEdit footer slot. */
50
- readonly markSaved?: () => void;
51
+ readonly markSaved?: (fields?: readonly string[]) => void;
51
52
  /** Whether the host form has unsaved changes. Only set in the entityEdit
52
53
  * footer slot (`screen.slots.footer`); undefined in all other mounts. */
53
54
  readonly hasUnsavedChanges?: boolean;
@@ -73,10 +74,10 @@ export type ExtensionSectionsProviderProps = {
73
74
  readonly value: ExtensionSectionsMap;
74
75
  };
75
76
  export declare function ExtensionSectionsProvider({ children, value, }: ExtensionSectionsProviderProps): ReactNode;
76
- /** Schaut die Component für einen extension-section-Namen nach. Returnt
77
- * undefined wenn weder Provider gemounted noch der Name in der Map
78
- * registriert ist — der Caller (RenderEdit) zeigt dann seinen
79
- * Placeholder-Banner. `name` ist optional damit Caller den Hook
80
- * unkonditional aufrufen können (Rules-of-Hooks), ohne einen Stub-Key
81
- * wie `""` reichen zu müssen — analog `useColumnRenderer`. */
77
+ /** Looks up the component for an extension-section name. Returns
78
+ * `undefined` when no provider is mounted or the name is not registered in
79
+ * the map — the caller (RenderEdit) then displays its placeholder banner.
80
+ * `name` is optional so callers can invoke the hook unconditionally
81
+ * (Rules-of-Hooks) without supplying a stub key such as `""`, analogous to
82
+ * `useColumnRenderer`. */
82
83
  export declare function useExtensionSectionComponent(name?: string): ExtensionSectionComponent | undefined;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext } from "react";
3
- /** Extrahiert den `__component`-Namen aus einer PlatformComponent. Liest
4
- * react- und native-Branch (die App-Author registriert beide unter dem
5
- * gleichen Namen); returnt den ersten gefundenen string. Pure narrowing
6
- * via `in` — kein cast, kein assertion. */
3
+ /** Extracts the `__component` name from a PlatformComponent. Reads the
4
+ * react and native branches (the app author registers both under the same
5
+ * name); returns the first string found. Pure narrowing via `in` — no cast
6
+ * or assertion. */
7
7
  export function extensionSectionName(component) {
8
8
  for (const branch of [component.react, component.native]) {
9
9
  if (branch !== null && typeof branch === "object" && "__component" in branch) {
@@ -18,12 +18,12 @@ const ExtensionSectionsContext = createContext(undefined);
18
18
  export function ExtensionSectionsProvider({ children, value, }) {
19
19
  return (_jsx(ExtensionSectionsContext.Provider, { value: value, children: children }));
20
20
  }
21
- /** Schaut die Component für einen extension-section-Namen nach. Returnt
22
- * undefined wenn weder Provider gemounted noch der Name in der Map
23
- * registriert ist — der Caller (RenderEdit) zeigt dann seinen
24
- * Placeholder-Banner. `name` ist optional damit Caller den Hook
25
- * unkonditional aufrufen können (Rules-of-Hooks), ohne einen Stub-Key
26
- * wie `""` reichen zu müssen — analog `useColumnRenderer`. */
21
+ /** Looks up the component for an extension-section name. Returns
22
+ * `undefined` when no provider is mounted or the name is not registered in
23
+ * the map — the caller (RenderEdit) then displays its placeholder banner.
24
+ * `name` is optional so callers can invoke the hook unconditionally
25
+ * (Rules-of-Hooks) without supplying a stub key such as `""`, analogous to
26
+ * `useColumnRenderer`. */
27
27
  export function useExtensionSectionComponent(name) {
28
28
  const map = useContext(ExtensionSectionsContext);
29
29
  if (name === undefined || name === "")
@@ -4,10 +4,10 @@
4
4
  // file that exports a schema can be imported from the browser bundle
5
5
  // without dragging in Node-only framework internals.
6
6
  //
7
- // Types wohnen in `framework/ui-types/app-schema.ts` damit der Server
8
- // (buildAppSchema, dev-server) sie produzieren kann ohne renderer als
9
- // Dependency zu ziehen. Hier nur Re-Export + Runtime-Helpers
10
- // (toAppSchema, isAppSchema) die Client-Code zur Laufzeit braucht.
7
+ // Types live in `framework/ui-types/app-schema.ts` so the server
8
+ // (buildAppSchema, dev-server) can produce them without pulling in
9
+ // renderer as a dependency. Only re-exports and runtime helpers
10
+ // (toAppSchema, isAppSchema) needed by client code at runtime belong here.
11
11
  //
12
12
  // Typical layout on the feature author's side:
13
13
  //
@@ -28,10 +28,10 @@
28
28
  // r.screen(editScreen);
29
29
  // ...
30
30
  // });
31
- // Normalisiert FeatureSchema → AppSchema. Idempotent für AppSchema.
32
- // Hebt eine Feature-lokal deklarierte `workspaces`-Liste (Legacy) auf
33
- // App-Ebene hoch, damit alle Layouts mit der neuen Form arbeiten können
34
- // ohne dass alte clientSchemas migriert werden müssen.
31
+ // Normalizes FeatureSchema → AppSchema and is idempotent for AppSchema.
32
+ // Promotes a feature-local `workspaces` list (legacy) to the app level
33
+ // so all layouts can use the new form without requiring old clientSchemas
34
+ // to be migrated.
35
35
  export function toAppSchema(input) {
36
36
  if ("features" in input)
37
37
  return input;
@@ -42,9 +42,9 @@ export function toAppSchema(input) {
42
42
  ...(workspaces !== undefined && { workspaces }),
43
43
  };
44
44
  }
45
- // TypeGuard — Caller die schon zur Laufzeit unterscheiden müssen
46
- // (selten; meist reicht toAppSchema). Nicht via `"features" in x` inline
47
- // machen — narrow'd TS dann auf den join-Typ statt die echte Differenz.
45
+ // Type guard for callers that need to distinguish the types at runtime
46
+ // (rare; toAppSchema is usually sufficient). Do not use `"features" in x`
47
+ // inline: TS then narrows to the join type instead of the actual distinction.
48
48
  export function isAppSchema(input) {
49
49
  return "features" in input;
50
50
  }
@@ -26,10 +26,10 @@ function isRequiredValueMissing(spec, field, record, unchangedFrom) {
26
26
  if (!isRequired)
27
27
  return false;
28
28
  // Embedded LIST fields get their own EmbeddedListField grid widget
29
- // (#1838) — they're fillable, so NO_WIDGET_FIELD_TYPES's "embedded"
29
+ // — they're fillable, so NO_WIDGET_FIELD_TYPES's "embedded"
30
30
  // entry must not exempt them. A statically-`required: true` field
31
31
  // with no bound widget is also caught at boot
32
- // (validateNoWidgetRequiredField in boot-validator/screens.ts).
32
+ // (validateNoWidgetRequiredField in boot-validator/screens-forms.ts).
33
33
  if (NO_WIDGET_FIELD_TYPES.includes(field.type) && !isEmbeddedListField(field))
34
34
  return false;
35
35
  if (isPresent(record[spec.field]))
@@ -3,7 +3,7 @@ import { RenderEditActionButton } from "../components/render-edit-action-button.
3
3
  import { useActionMenuItems } from "../components/use-action-menu-items.js";
4
4
  import { useTranslation } from "../i18n.js";
5
5
  import { compactHeaderPrimary, orderPlainHeaderActions, primaryHeaderAction, } from "./header-actions.js";
6
- // Header actions bar (A7): <=2 actions render as plain buttons; >2 with an
6
+ // Header actions bar: <=2 actions render as plain buttons; >2 with an
7
7
  // `ActionOverflowMenu` primitive collapse to the primary button plus a menu
8
8
  // for the rest — same rule as RowActionsCell for table rows. Owns confirm
9
9
  // state for menu items: RenderEditActionButton already owns a per-button
@@ -18,7 +18,7 @@ export function HeaderActionsBar({ actions, Button, Dialog, ActionOverflowMenu,
18
18
  if (compact && PageHeader !== undefined) {
19
19
  const compactPrimary = compactHeaderPrimary(actions, preferredPrimaryId);
20
20
  const overflowActions = actions.filter((a) => a !== compactPrimary?.action);
21
- return (_jsxs(_Fragment, { children: [compactPrimary !== undefined && (_jsx(RenderEditActionButton, { action: compactPrimary.action, Button: Button, Dialog: Dialog, iconOnly: compactPrimary.iconOnly, compactLabel: true, onError: onError }, compactPrimary.action.id)), overflowActions.length > 0 && (_jsx(PageHeader, { overflowItems: overflowActions.map(toMenuItem) })), pendingActionDialog] }));
21
+ return (_jsxs(_Fragment, { children: [compactPrimary !== undefined && (_jsx(RenderEditActionButton, { action: compactPrimary.action, Button: Button, Dialog: Dialog, iconOnly: compactPrimary.iconOnly, onError: onError }, compactPrimary.action.id)), overflowActions.length > 0 && (_jsx(PageHeader, { overflowItems: overflowActions.map(toMenuItem) })), pendingActionDialog] }));
22
22
  }
23
23
  const maxPlainButtons = collapseAfterPrimary ? 1 : 2;
24
24
  if (actions.length <= maxPlainButtons || ActionOverflowMenu === undefined) {
@@ -130,9 +130,9 @@ function renderScreenBody({ schema, screen, translate, entityId, onRowClick, onC
130
130
  return _jsx(CustomScreenBody, { screenId: screen.id, featureName: schema.featureName });
131
131
  }
132
132
  }
133
- // Injection-Body für dashboard-Screens: die Panel-Implementierung (StatCard,
134
- // Charts) ist plattform-spezifisch und kommt aus dem DashboardBody-Context
135
- // (renderer/web registriert die Web-Variante in createKumikoApp).
133
+ // The dashboard panel implementation (StatCard, charts) is platform-specific and
134
+ // comes from the DashboardBody context; renderer/web registers the web
135
+ // implementation in createKumikoApp.
136
136
  function DashboardScreenBody({ featureName, screen, translate, }) {
137
137
  const { Banner, Text } = usePrimitives();
138
138
  const Body = useDashboardBody();
@@ -141,13 +141,11 @@ function DashboardScreenBody({ featureName, screen, translate, }) {
141
141
  }
142
142
  return (_jsx(Body, { featureName: featureName, screen: screen, ...(translate !== undefined && { translate }) }));
143
143
  }
144
- // Lookup-Body für custom-screens: schaut die Component aus dem
145
- // CustomScreens-Context (gefüttert von clientFeatures.components in
146
- // createKumikoApp). Wenn weder Provider gemounted noch screenId
147
- // registriert ist, fällt es auf einen error-Banner zurück statt leer zu
148
- // rendern — der Screen ist per URL erreichbar sobald das Server-Feature
149
- // gemountet ist, unabhängig davon ob ein Client-Plugin existiert
150
- // (kumiko-framework#2025).
144
+ // Looks up the component from the CustomScreens context, populated by
145
+ // clientFeatures.components in createKumikoApp. If no provider is mounted or
146
+ // the screenId is unregistered, an error banner is rendered instead of an
147
+ // empty screen. The screen remains reachable by URL when the server feature is
148
+ // mounted, even without a client plugin.
151
149
  function CustomScreenBody({ screenId, featureName, }) {
152
150
  const { Banner, Text } = usePrimitives();
153
151
  const Component = useCustomScreenComponent(screenId);
@@ -3,7 +3,7 @@ import { isFieldsEditSection, normalizeEditField, sectionFieldSpecs, } from "@co
3
3
  // (and relatedList) sections skipped. Both this and `layoutFieldNames` key
4
4
  // off "rendered by the layout" for the same reason: a field the user never
5
5
  // sees gets no chance to review/correct a value nor to fix a presence error
6
- // (search-param merge, #1708; presence schema in form-schema.ts).
6
+ // (search-param merge; presence schema in form-schema.ts).
7
7
  export function layoutEditFields(screen) {
8
8
  const specs = [];
9
9
  for (const section of screen.layout.sections) {
@@ -94,7 +94,7 @@ export function normalizeDefaultFilters(defaults) {
94
94
  // hand-crafted URL param for an undeclared field never reaches the
95
95
  // server). Deliberately NOT gated on the field being a *facet* — entityList
96
96
  // passes through any field present in entity.fields, matching its
97
- // pre-fw#2224 behavior; only the boolean-coercion branch cares about type.
97
+ // earlier behavior; only the boolean-coercion branch cares about type.
98
98
  function isFacetI18nKey(label) {
99
99
  return !/\s/.test(label) && (label.includes(".") || label.includes(":"));
100
100
  }
@@ -119,8 +119,8 @@ export function buildFilterPayload(urlFilters, typeOf) {
119
119
  return out;
120
120
  }
121
121
  // projectionList/relatedList adapter — neither has an entity/i18n convention
122
- // to derive labels from, so ListFacetSpec carries every label explicitly
123
- // (fw#2224). Labels may be raw display strings or i18n keys; run them
122
+ // to derive labels from, so ListFacetSpec carries every label explicitly.
123
+ // Labels may be raw display strings or i18n keys; run them
124
124
  // through translate like entity facets (passthrough when the key is missing).
125
125
  // `featureName` resolves a reference facet's `entity` target the same way
126
126
  // ListColumnSpec.refEntity does (same-feature short name, or "feature:entity").
package/dist/app/nav.d.ts CHANGED
@@ -40,16 +40,16 @@ export type NavApi = {
40
40
  * platform-specific Link-Komponenten (Web: `<a href>`; Native
41
41
  * typically doesn't need this). */
42
42
  readonly hrefFor: (target: NavTarget, options?: NavigateOptions) => string;
43
- /** Lese-Snapshot der aktuellen Search-Params (Browser: ?key=value-
44
- * Pairs nach dem Pfad). Native-Impls die kein URL-Konzept haben
45
- * liefern ein leeres Object. Wert ist ein Plain-Record (kein Map)
46
- * damit React-Subscribers shallow-compare können. */
43
+ /** Snapshot of the current search params (browser: `?key=value` pairs
44
+ * after the path). Native implementations without a URL concept return
45
+ * an empty object. The value is a plain record, not a Map, so React
46
+ * subscribers can compare it shallowly. */
47
47
  readonly searchParams: Readonly<Record<string, string>>;
48
- /** Mergt Updates in die aktuellen Search-Params. Wert `null` löscht
49
- * den Key. Ändert NICHT den Pfad. Plattform-Impls nutzen
50
- * history.replaceState (kein Push) — Sort/Filter-Toggles sollen
51
- * nicht die Back-Navigation fluten. Native-Impls können no-op'en
52
- * wenn das Konzept nicht existiert. */
48
+ /** Merges updates into the current search params. A `null` value deletes
49
+ * the key. The path is unchanged. Platform implementations use
50
+ * `history.replaceState` rather than a push so sort/filter toggles do not
51
+ * flood back navigation. Native implementations may no-op when the
52
+ * concept does not exist. */
53
53
  readonly setSearchParams: (updates: Readonly<Record<string, string | null>>) => void;
54
54
  };
55
55
  export declare function parsePath(pathname: string, hasWorkspaces?: boolean): NavRoute | undefined;
@@ -60,7 +60,7 @@ export declare function findDetailForScreen(features: readonly FeatureSchema[],
60
60
  readonly screen: FeatureSchema["screens"][number];
61
61
  } | undefined;
62
62
  /** Non-throwing existence check for findDetailForScreen — lets a caller pick
63
- * a different default (fw#2164's row-click fallback) instead of resolving. */
63
+ * a different default (row-click fallback) instead of resolving. */
64
64
  export declare function hasDetailScreen(features: readonly FeatureSchema[], entity: string): boolean;
65
65
  export declare function resolveTarget(features: readonly FeatureSchema[], target: NavTarget): ScreenTarget;
66
66
  export type NavProviderProps = {
package/dist/app/nav.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useEffect, useState } from "react";
3
3
  import { lastSegment } from "./qn.js";
4
- // Pfad-Format:
5
- // ohne workspaces: `/task-list`, `/task-edit/abc-123`, `/` → undefined
6
- // mit workspaces: `/admin/task-list`, `/admin/task-edit/abc`,
7
- // `/admin` → workspace-only (no screen yet),
8
- // `/` → undefined.
9
- // Alles nach den ersten 2/3 Segmenten wird ignoriert — kumikos
10
- // Navigation-Grammatik nested nicht weiter. Nested-Routes wäre eine
11
- // Spec-Änderung, nicht ein URL-Shape-Unfall.
4
+ // Path format:
5
+ // without workspaces: `/task-list`, `/task-edit/abc-123`, `/` → undefined
6
+ // with workspaces: `/admin/task-list`, `/admin/task-edit/abc`,
7
+ // `/admin` → workspace-only (no screen yet),
8
+ // `/` → undefined.
9
+ // Everything after the first 2/3 segments is ignored because Kumiko's
10
+ // navigation grammar does not support further nesting. Nested routes would
11
+ // be a spec change, not an accidental URL shape.
12
12
  function decodeSegments(segments) {
13
13
  try {
14
14
  return segments.map((segment) => decodeURIComponent(segment));
@@ -57,7 +57,7 @@ export function formatPath(target) {
57
57
  return `/${segments.map((segment) => encodeURIComponent(segment)).join("/")}`;
58
58
  }
59
59
  // Shared entity→detailFor-screen lookup — resolveTarget's ObjectTarget
60
- // branch and hasDetailScreen (create-app's row-click default, fw#2164) both
60
+ // branch and hasDetailScreen (create-app's row-click default) both
61
61
  // need "is there a detail screen for this entity", one to resolve it, the
62
62
  // other to just check before falling back to a different default.
63
63
  export function findDetailForScreen(features, entity) {
@@ -70,7 +70,7 @@ export function findDetailForScreen(features, entity) {
70
70
  return undefined;
71
71
  }
72
72
  /** Non-throwing existence check for findDetailForScreen — lets a caller pick
73
- * a different default (fw#2164's row-click fallback) instead of resolving. */
73
+ * a different default (row-click fallback) instead of resolving. */
74
74
  export function hasDetailScreen(features, entity) {
75
75
  return findDetailForScreen(features, entity) !== undefined;
76
76
  }
@@ -93,9 +93,9 @@ export function resolveTarget(features, target) {
93
93
  }
94
94
  throw new Error(`resolveTarget: no detail screen for entity "${target.entity}". Add detailFor: "${target.entity}" to the screen that shows it.`);
95
95
  }
96
- // Context + Hook. Default ist `undefined` damit fehlender Provider
97
- // laut kracht statt ein silent-no-op NavApi mit toten navigate()
98
- // Aufrufen anzubieten.
96
+ // Context and hook. The default is `undefined` so a missing provider fails
97
+ // loudly instead of exposing a silent no-op NavApi with dead `navigate()`
98
+ // calls.
99
99
  const NavContext = createContext(undefined);
100
100
  const InitialValuesHandoffContext = createContext(undefined);
101
101
  export function NavProvider({ children, value }) {
@@ -188,11 +188,11 @@ export function ProjectionDetailBody({ schema, screen, translate, entityId, }) {
188
188
  await refetchAfterWrite(detailQuery.refetch);
189
189
  setReloadNonce((n) => n + 1);
190
190
  }, [detailQuery.refetch]);
191
- // Header actions render directly via RenderEditActionButton (fw#2713,
192
- // headerActions moved out of RenderEdit's footer) — this component owns
191
+ // Header actions render directly via RenderEditActionButton
192
+ // headerActions moved out of RenderEdit's footer — this component owns
193
193
  // the failure-surfacing state RenderEdit used to own for them.
194
194
  const [actionError, setActionError] = useState(null);
195
- // Default edit action (fw#2166): resolved cross-feature over ALL mounted
195
+ // Default edit action: resolved cross-feature over ALL mounted
196
196
  // features, not just this feature's own schema — detailFor itself is
197
197
  // resolved cross-feature by the boot-validator (detail-screens.ts), and
198
198
  // the motivating case is a projectionDetail whose query belongs to one
@@ -383,7 +383,7 @@ export function ProjectionDetailBody({ schema, screen, translate, entityId, }) {
383
383
  }),
384
384
  }) }));
385
385
  }
386
- // Grouped into the head Card alongside title/status/metrics (fw#2713):
386
+ // Grouped into the head Card alongside title/status/metrics:
387
387
  // these are actions on the record the head shows, not on whichever tab is
388
388
  // open, so they must stay in place across tab switches instead of
389
389
  // trailing the active tab's content in the card footer.
@@ -1,8 +1,9 @@
1
1
  import type { EditLayout, EntityDefinition, EntityEditScreenDefinition, ProjectionDetailScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
- /** Minimale EntityDefinition aus den Layout-Feldern: jedes Feld ein Text-
3
- * Feld — computeEditViewModel liest nur `fields[<f>].type`, Text reicht für
4
- * eine reine Anzeige (kein Select/Number-spezifisches Rendering nötig). */
2
+ /** Minimal EntityDefinition built from the layout fields: every field is a text
3
+ * field — computeEditViewModel only reads `fields[<f>].type`, and text is
4
+ * sufficient for display-only rendering (no Select/Number-specific rendering
5
+ * is needed). */
5
6
  export declare function synthesizeProjectionDetailEntity(layout: EditLayout): Pick<EntityDefinition, "fields">;
6
- /** Wandelt ein ProjectionDetailScreenDefinition in die EntityEditScreen-Shape
7
- * die RenderEdit erwartet — mit jedem Feld hart auf readOnly:true erzwungen. */
7
+ /** Converts a ProjectionDetailScreenDefinition to the EntityEditScreen shape
8
+ * expected by RenderEdit, with every field forcibly set to readOnly:true. */
8
9
  export declare function synthesizeProjectionDetailScreen(screen: ProjectionDetailScreenDefinition): EntityEditScreenDefinition;