@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
@@ -1,29 +1,30 @@
1
- // Shim für ProjectionDetail-Renderer (analog zu projection-list-shim.ts /
1
+ // Shim for the ProjectionDetail renderer (analogous to projection-list-shim.ts /
2
2
  // config-edit-shim.ts).
3
3
  //
4
- // RenderEdit verlangt ein `entity: EntityDefinition` + ein
5
- // `screen: EntityEditScreenDefinition`-Pair, nutzt davon aber nur
6
- // `entity.fields` (Feld-Typ für den Input-Renderer) und `screen.layout`
7
- // (welche Felder in welchen Sections). Ein projectionDetail-Screen ist
8
- // query-getrieben und hat KEINE Entity — die zwei Helper hier shapen den
9
- // Input ad-hoc um, damit RenderEdit reused werden kann.
4
+ // RenderEdit requires an `entity: EntityDefinition` +
5
+ // `screen: EntityEditScreenDefinition` pair, but only uses
6
+ // `entity.fields` (field type for the input renderer) and `screen.layout`
7
+ // (which fields belong in which sections). A projectionDetail screen is
8
+ // query-driven and has NO Entity — these two helpers shape the input ad hoc
9
+ // so RenderEdit can be reused.
10
10
  //
11
- // Der strukturelle Read-Only-Beweis liegt in synthesizeProjectionDetailScreen:
12
- // JEDES Feld wird hart auf readOnly:true gesetzt (nicht nur was der Author im
13
- // Layout gesetzt hat) — hasEditableSection() liest genau dieses Flag und
14
- // blendet den Save-Button aus, wenn kein Feld editierbar ist. Der Author kann
15
- // diese Garantie nicht versehentlich umgehen.
11
+ // The structural read-only guarantee is established in
12
+ // synthesizeProjectionDetailScreen: EVERY field is forced to readOnly:true
13
+ // (not just fields the author marked in the layout) — hasEditableSection()
14
+ // reads exactly this flag and hides the Save button when no field is
15
+ // editable. The author cannot accidentally bypass this guarantee.
16
16
  //
17
- // Selbe Schulden-Reservation wie bei den anderen Shims: greift RenderEdit
18
- // künftig auf weitere EntityDefinition-Felder (transitions, idType) zu, oder
19
- // cross-referenziert ein Boot-Validator die schema.entities-Map, brechen die
20
- // Type-Lies hier silent — dann ist Zeit für eine echte RenderProjectionDetail-
21
- // Komponente.
17
+ // Same debt reservation as in the other shims: if RenderEdit later accesses
18
+ // additional EntityDefinition fields (transitions, idType), or a boot
19
+ // validator cross-references the schema.entities map, these type lies will
20
+ // fail silently — then it is time for a real RenderProjectionDetail
21
+ // component.
22
22
  import { isFieldsEditSection, normalizeEditField, PROJECTION_DETAIL_ENTITY as PROJECTION_DETAIL_PSEUDO_ENTITY, sectionFieldSpecs, } from "@cosmicdrift/kumiko-framework/ui-types";
23
23
  import { SYNTHESIZED_FIELD_PII_REASON } from "./projection-list-shim.js";
24
- /** Minimale EntityDefinition aus den Layout-Feldern: jedes Feld ein Text-
25
- * Feld — computeEditViewModel liest nur `fields[<f>].type`, Text reicht für
26
- * eine reine Anzeige (kein Select/Number-spezifisches Rendering nötig). */
24
+ /** Minimal EntityDefinition built from the layout fields: every field is a text
25
+ * field — computeEditViewModel only reads `fields[<f>].type`, and text is
26
+ * sufficient for display-only rendering (no Select/Number-specific rendering
27
+ * is needed). */
27
28
  export function synthesizeProjectionDetailEntity(layout) {
28
29
  const fields = {};
29
30
  for (const section of layout.sections) {
@@ -39,8 +40,8 @@ export function synthesizeProjectionDetailEntity(layout) {
39
40
  }
40
41
  return { fields };
41
42
  }
42
- /** Wandelt ein ProjectionDetailScreenDefinition in die EntityEditScreen-Shape
43
- * die RenderEdit erwartet — mit jedem Feld hart auf readOnly:true erzwungen. */
43
+ /** Converts a ProjectionDetailScreenDefinition to the EntityEditScreen shape
44
+ * expected by RenderEdit, with every field forcibly set to readOnly:true. */
44
45
  export function synthesizeProjectionDetailScreen(screen) {
45
46
  const forceReadOnly = (spec) => ({ ...normalizeEditField(spec), readOnly: true });
46
47
  const sections = screen.layout.sections.map((section) => {
@@ -27,7 +27,7 @@ import { buildListQueryPayload, pageSizeOptionsFor } from "./screen-shared.js";
27
27
  // (instead of being derived from an entity) — cross-feature capable.
28
28
  // search/sort/pagination reuse the same URL-state + buildListQueryPayload
29
29
  // wiring as EntityListBody; which of them are actually offered is derived
30
- // from the query handler's Zod schema at buildAppSchema time (fw#2165), not
30
+ // from the query handler's Zod schema at buildAppSchema time, not
31
31
  // declared here. Pages-mode pagination only — infinite-scroll accumulation
32
32
  // isn't wired for projectionList.
33
33
  export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
@@ -49,7 +49,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
49
49
  return findEditScreenFor(detailFor, appFeatures, userRoles);
50
50
  }, [appFeatures, screen.detailFor, userRoles]);
51
51
  // searchable/sortable/paginated are derived at buildAppSchema time from the
52
- // query handler's Zod schema (fw#2165) — not authored on the screen.
52
+ // query handler's Zod schema — not authored on the screen.
53
53
  const searchable = screen.searchable ?? false;
54
54
  const sortable = screen.sortable ?? false;
55
55
  const paginated = screen.paginated ?? false;
@@ -72,7 +72,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
72
72
  // Pages-mode only (see header comment) — an author-set pagination:
73
73
  // "infinite" on a paginated projectionList is silently a no-op today.
74
74
  const usePager = paginated && (screen.pagination ?? "pages") === "pages";
75
- // Facets (fw#2224) — a projectionList has no entity, so screen.facets is
75
+ // Facets — a projectionList has no entity, so screen.facets is
76
76
  // the only field inventory; resolveProjectionFacetSpecs reshapes +
77
77
  // translates its explicit labels into the same ResolvedFacetSpec the
78
78
  // entityList adapter produces, so both feed the same
@@ -82,7 +82,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
82
82
  const handleProjectionFacetOptions = useCallback((field, options) => setProjectionReferenceFacetOptions((prev) => prev[field] === options ? prev : { ...prev, [field]: options }), []);
83
83
  const resolvedFacetSpecs = useMemo(() => mergeReferenceFacetOptions(facetSpecs, projectionReferenceFacetOptions), [facetSpecs, projectionReferenceFacetOptions]);
84
84
  const filterPayload = useMemo(() => buildFilterPayload(urlState.filters, (field) => facetSpecs.find((spec) => spec.field === field)?.type), [urlState.filters, facetSpecs]);
85
- // dateRange facets (fw#3104) bypass `filters` entirely — their two bounds
85
+ // dateRange facets bypass `filters` entirely — their two bounds
86
86
  // become the top-level query params the facet declares, resolved from a
87
87
  // calendar date to an instant in the viewer's zone.
88
88
  const timeZone = useOptionalTimeZone();
@@ -101,7 +101,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
101
101
  // Gated on screen.filter/screen.facets being declared (not just on
102
102
  // urlState/filterPayload happening to carry a value) — same rule as
103
103
  // activeSearch/activeSort above: a stale URL must not smuggle a param
104
- // the bound query's Zod schema doesn't accept (fw#2165 pattern).
104
+ // the bound query's Zod schema doesn't accept.
105
105
  if (screen.filter !== undefined) {
106
106
  payload["filter"] = screen.filter;
107
107
  }
@@ -131,8 +131,8 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
131
131
  const handleDateRangeChange = useCallback((field, bound, value) => {
132
132
  urlState.setDateRange(field, clampDateRange(readDateRange(urlState.filters, field), bound, value));
133
133
  }, [urlState.setDateRange, urlState.filters]);
134
- // Entity-Targets (fw#2228) — see EntityListBody.runNavigate for why the
135
- // resolution happens in the NavApi impl. Unlike there: NO row["id"]
134
+ // Entity targets: see EntityListBody.runNavigate for why the resolution happens
135
+ // in the NavApi impl. Unlike there: NO row["id"]
136
136
  // fallback — projectionList rows come from an arbitrary query projection
137
137
  // with no guaranteed "id" field. The boot validator therefore enforces an
138
138
  // explicit entityId for projectionList entity targets. Same helper
@@ -198,8 +198,8 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
198
198
  // A failed refetch keeps the last good data on screen.
199
199
  const refetchErrorBanner = rowsQuery.error ? (_jsx(QueryErrorBanner, { error: rowsQuery.error, translate: effectiveTranslate, onRetry: rowsQuery.refetch })) : null;
200
200
  // rowsQuery.data is typed as PagedRows but the query handler is free to
201
- // return anything at runtime (fw#2216: a bare array silently rendered an
202
- // empty table instead of surfacing the mismatch), so the shape is checked.
201
+ // return anything at runtime (a bare array silently rendered an empty
202
+ // table instead of surfacing the mismatch), so the shape is checked.
203
203
  if (rowsQuery.data !== null && !Array.isArray(rowsQuery.data.rows)) {
204
204
  return (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-screen-projection-list-bad-shape", children: ["Screen ", _jsx(Text, { variant: "code", children: screen.id }), " query", " ", _jsx(Text, { variant: "code", children: screen.query }), " did not return a PagedRows envelope; a projectionList query must return ", _jsx(Text, { variant: "code", children: "{ rows, nextCursor }" }), "."] }));
205
205
  }
@@ -213,7 +213,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
213
213
  : undefined;
214
214
  // Same pager-construction as EntityListBody: no Pager UI until the
215
215
  // server-provided `total` arrives (guards pagination="pages" without
216
- // totalCount support, see fw#2165 report on solon's leaseOverviewHandler).
216
+ // totalCount support).
217
217
  const total = rowsQuery.data?.total;
218
218
  const pager = usePager && total !== undefined
219
219
  ? {
@@ -2,14 +2,14 @@ import type { EntityDefinition, EntityListScreenDefinition, ListColumnSpec, Proj
2
2
  export declare const SYNTHESIZED_FIELD_PII_REASON = "synthesized display field, not stored entity data";
3
3
  /** Minimal EntityDefinition from the column list: every field is a text
4
4
  * field. `sortable` applies to all columns — buildAppSchema derives it from
5
- * the query's Zod schema (`screen.sortable`, fw#2165); the query itself has
5
+ * the query's Zod schema (`screen.sortable`); the query itself has
6
6
  * no per-field server-sort guarantee, so a column opts out with
7
7
  * `sortable: false` when the handler cannot order by it.
8
8
  * computeListViewModel only reads `fields[<col>].type` — text is enough,
9
9
  * presentation comes from the column renderer + explicit label. */
10
10
  export declare function synthesizeProjectionEntity(columns: readonly ListColumnSpec[], sortable: boolean): Pick<EntityDefinition, "fields">;
11
- /** Wandelt ein ProjectionListScreenDefinition in die EntityListScreen-Shape die
12
- * RenderList erwartet. `type` = "entityList" + Pseudo-Entity halten den Type-
13
- * Constraint; RenderList branched nicht auf `type` und liest `entity` nur für
14
- * Fehlermeldungen. Die List-Felder werden 1:1 durchgereicht. */
11
+ /** Converts a ProjectionListScreenDefinition to the EntityListScreen shape
12
+ * expected by RenderList. `type` = "entityList" plus a pseudo-entity satisfy
13
+ * the type constraint; RenderList does not branch on `type` and reads `entity`
14
+ * only for error messages. The list fields are passed through unchanged. */
15
15
  export declare function synthesizeProjectionScreen(screen: ProjectionListScreenDefinition): EntityListScreenDefinition;
@@ -1,18 +1,17 @@
1
- // Shim für ProjectionList-Renderer (analog action-form-shim.ts / config-edit-shim.ts).
1
+ // Shim for the ProjectionList renderer (analogous to action-form-shim.ts / config-edit-shim.ts).
2
2
  //
3
- // RenderList + computeListViewModel verlangen ein `entity: EntityDefinition` +
4
- // ein `screen: EntityListScreenDefinition`-Pair, nutzen davon aber nur
5
- // `entity.fields` (Spalten-Typ/Label) und die List-Felder des Screens
6
- // (columns/rowActions/…). Ein projectionList-Screen ist query-getrieben und hat
7
- // KEINE Entity — die zwei Helper hier shapen den Input ad-hoc um, damit die
8
- // bestehende List-Maschinerie reused werden kann. Die Query selbst wird NICHT
9
- // hierüber aufgelöst (der Body nimmt `screen.query` direkt).
3
+ // RenderList + computeListViewModel require an `entity: EntityDefinition` +
4
+ // `screen: EntityListScreenDefinition` pair, but only use `entity.fields`
5
+ // (column type/label) and the screen's list fields (columns/rowActions/…). A
6
+ // projectionList screen is query-driven and has NO entity, so these two helpers
7
+ // reshape the input ad hoc to reuse the existing list machinery. The query is
8
+ // NOT resolved here (the body reads `screen.query` directly).
10
9
  //
11
- // Selbe Schulden-Reservation wie bei den anderen Shims: greift die List-
12
- // Maschinerie künftig auf weitere EntityDefinition-Felder (transitions, idType,
13
- // derivedFields) oder cross-referenziert ein Boot-Validator die schema.entities-
14
- // Map, brechen die Type-Lies hier silent — dann ist Zeit für eine echte
15
- // query-native ListView-Komponente.
10
+ // This preserves the same debt reservation as the other shims: if the list
11
+ // machinery later accesses additional EntityDefinition fields (transitions,
12
+ // idType, derivedFields), or a boot validator cross-references the
13
+ // schema.entities map, these type lies will fail silently. At that point, a
14
+ // query-native ListView component is needed.
16
15
  import { normalizeListColumn } from "@cosmicdrift/kumiko-framework/ui-types";
17
16
  const PROJECTION_PSEUDO_ENTITY = "__projection__";
18
17
  // Satisfies the text-field PII stance of the entity field type; these synthesized
@@ -20,7 +19,7 @@ const PROJECTION_PSEUDO_ENTITY = "__projection__";
20
19
  export const SYNTHESIZED_FIELD_PII_REASON = "synthesized display field, not stored entity data";
21
20
  /** Minimal EntityDefinition from the column list: every field is a text
22
21
  * field. `sortable` applies to all columns — buildAppSchema derives it from
23
- * the query's Zod schema (`screen.sortable`, fw#2165); the query itself has
22
+ * the query's Zod schema (`screen.sortable`); the query itself has
24
23
  * no per-field server-sort guarantee, so a column opts out with
25
24
  * `sortable: false` when the handler cannot order by it.
26
25
  * computeListViewModel only reads `fields[<col>].type` — text is enough,
@@ -37,10 +36,10 @@ export function synthesizeProjectionEntity(columns, sortable) {
37
36
  }
38
37
  return { fields };
39
38
  }
40
- /** Wandelt ein ProjectionListScreenDefinition in die EntityListScreen-Shape die
41
- * RenderList erwartet. `type` = "entityList" + Pseudo-Entity halten den Type-
42
- * Constraint; RenderList branched nicht auf `type` und liest `entity` nur für
43
- * Fehlermeldungen. Die List-Felder werden 1:1 durchgereicht. */
39
+ /** Converts a ProjectionListScreenDefinition to the EntityListScreen shape
40
+ * expected by RenderList. `type` = "entityList" plus a pseudo-entity satisfy
41
+ * the type constraint; RenderList does not branch on `type` and reads `entity`
42
+ * only for error messages. The list fields are passed through unchanged. */
44
43
  export function synthesizeProjectionScreen(screen) {
45
44
  return {
46
45
  id: screen.id,
package/dist/app/qn.js CHANGED
@@ -1,21 +1,19 @@
1
- // Qualified-Name-Helper. Kumiko's Registry stempelt beim Boot den
2
- // Feature-Prefix auf jede Screen/Nav/Workspace-id ein:
1
+ // Qualified-name helper. At boot, Kumiko's Registry adds the feature prefix to every Screen/Nav/Workspace ID:
3
2
  //
4
3
  // r.screen({ id: "task-list", ... })
5
4
  // → registry-stored.id === "tasks:screen:task-list"
6
- // → schema.screens (ans Browser) hat id === "tasks:screen:task-list"
5
+ // → schema.screens (sent to the browser) has id === "tasks:screen:task-list"
7
6
  //
8
- // Der Renderer arbeitet aber mit Short-Form ids — formatPath schreibt
9
- // sie 1:1 in die URL, parsePath liest sie 1:1 raus. Beim Übergang von
10
- // Schema (QN-Form) → nav.navigate (Short-Form) muss der Prefix weg,
11
- // sonst landet die URL doppelt-qualifiziert
12
- // ("/tasks:screen:task-list" + Re-Lookup → "tasks:screen:tasks:screen:
7
+ // The renderer uses short-form IDs: formatPath writes them to the URL unchanged,
8
+ // and parsePath reads them back unchanged. When converting from the schema (QN form)
9
+ // to nav.navigate (short form), the prefix must be removed; otherwise the URL becomes
10
+ // doubly qualified
11
+ // ("/tasks:screen:task-list" + re-lookup → "tasks:screen:tasks:screen:
13
12
  // task-list" → 404).
14
13
  //
15
- // `lastSegment` ist die Inverse von qualifyScreenId/Nav/Workspace —
16
- // nimmt den letzten ":"-getrennten Teil. Robust gegen Strings ohne
17
- // ":" (returnt sie unverändert, damit App-Author-Code mit Short-Form-
18
- // ids in eigenen Stellen weiter passt).
14
+ // `lastSegment` is the inverse of qualifyScreenId/Nav/Workspace: it takes the final
15
+ // ":"-separated part. Strings without ":" are returned unchanged so app-author code
16
+ // using short-form IDs continues to work.
19
17
  export function lastSegment(qn) {
20
18
  const idx = qn.lastIndexOf(":");
21
19
  return idx < 0 ? qn : qn.slice(idx + 1);
@@ -7,7 +7,7 @@ export function qualifyScreenId(featureName, screenId) {
7
7
  // Inverse of qualifyScreenId. A short id ("task-list") has no feature
8
8
  // segment and returns undefined — callers need that to tell "this redirect
9
9
  // names a feature" apart from "this redirect is feature-less, resolve by
10
- // short id alone" (fw#2485).
10
+ // short id alone".
11
11
  export function featureNameFromQualifiedScreenId(id) {
12
12
  const parts = id.split(":");
13
13
  return parts.length === 3 && parts[1] === "screen" ? parts[0] : undefined;
@@ -1,7 +1,7 @@
1
1
  import { isUiAccessGranted } from "@cosmicdrift/kumiko-framework/ui-types";
2
2
  import { findDetailForScreen } from "./nav.js";
3
3
  import { lastSegment } from "./qn.js";
4
- // Shared UI-visibility predicate for the screen-render path (#1203 — nav
4
+ // Shared UI-visibility predicate for the screen-render path — nav
5
5
  // filtering via filterByAccess in workspace-shell.tsx hid role-gated
6
6
  // screens from the menu, but a direct URL/screenQn hit reached
7
7
  // KumikoScreen unchecked). Public name kept as an alias of the shared
@@ -9,7 +9,7 @@ import { lastSegment } from "./qn.js";
9
9
  export { isUiAccessGranted as screenAccessAllows };
10
10
  // A navigate target (metric click) declares no access rule of its own — the
11
11
  // destination screen already does, so the jump is offered exactly when the
12
- // user may open where it lands (solon#424). No second copy of the rule to
12
+ // user may open where it lands. No second copy of the rule to
13
13
  // drift from the screen's own.
14
14
  export function navigateTargetAllows(navigate, appFeatures, userRoles) {
15
15
  const { screen, entity } = navigate;
@@ -166,7 +166,7 @@ export function entityQueryCommand(featureName, entity, verb) {
166
166
  // search/sort/sortDirection/limit + offset/totalCount for pager mode OR
167
167
  // cursor for infinite scroll. Shared by EntityListBody and
168
168
  // ProjectionListBody so the two branches can't drift on this shape
169
- // (fw#2165) — entity-only additions (screen.filter, faceted filters) are
169
+ // — entity-only additions (screen.filter, faceted filters) are
170
170
  // layered on top by the caller instead of living in here.
171
171
  export function buildListQueryPayload(state) {
172
172
  const payload = { limit: state.limit };
@@ -40,10 +40,10 @@ function isBlank(value) {
40
40
  // Mint form → one-time reveal → optional confirm → done. The revealed values
41
41
  // live ONLY in this component's `revealed` state — never in the URL, a query
42
42
  // cache, or nav — the write-handler's success payload is the only place the
43
- // secret ever exists (fw#2548). onSubmit copies exclusively the fields
43
+ // secret ever exists. onSubmit copies exclusively the fields
44
44
  // declared in `screen.reveal.fields` out of that payload (a whitelist, never
45
45
  // the payload as a whole) so an unrelated field (e.g. an internal "id") can
46
- // never leak into the reveal. `screen.confirm` (fw#2838, e.g. TOTP enroll:
46
+ // never leak into the reveal. `screen.confirm` (e.g. TOTP enroll:
47
47
  // scan the code, then enter one) works the same way for its own carried
48
48
  // mint-payload fields — those live only in `carriedRef`, never in React state
49
49
  // that gets rendered, never in the confirm form's own values.
@@ -63,7 +63,7 @@ export function SecretMintBody({ schema, screen, translate }) {
63
63
  urlPrefillFields: screen.urlPrefillFields,
64
64
  renderableFields: layoutFieldNames(synthScreen),
65
65
  // A mint form has no entity either, so its money fields name their own
66
- // currency source (fw#2839, fw#2933).
66
+ // currency source.
67
67
  ...(mintCurrencyOverrides !== undefined && {
68
68
  moneyCurrencyOverrides: mintCurrencyOverrides,
69
69
  }),
@@ -188,7 +188,7 @@ export function SecretMintBody({ schema, screen, translate }) {
188
188
  // submit state) across phases.
189
189
  if (confirm !== undefined && confirmEntity !== undefined && confirmScreen !== undefined) {
190
190
  // Hold until a tenant-declared currency lands, else the form would seed (and
191
- // on a fast click submit) the fallback currency (fw#2933).
191
+ // on a fast click submit) the fallback currency.
192
192
  if (confirmCurrencyLoading) {
193
193
  return _jsx(QueryLoadingBanner, {});
194
194
  }
@@ -1,8 +1,8 @@
1
- // dispatcher.write wirft bei Server-Fehlern NICHT — es returnt
2
- // { isSuccess: false, error }. Action-Wiring das das Result verwirft,
3
- // macht Fehler unsichtbar ("Klick tut nichts"-Prod-Bug 2026-06-07).
4
- // Diese Klasse trägt den strukturierten DispatcherError dahin, wo eine
5
- // UI ihn anzeigen kann (Toast mit docsUrl).
1
+ // dispatcher.write does NOT throw on server errors; it returns
2
+ // { isSuccess: false, error }. Action wiring that discards the result
3
+ // makes failures invisible ("click does nothing" in production).
4
+ // This class carries the structured DispatcherError to a layer that can
5
+ // display it in the UI, such as a toast with docsUrl.
6
6
  export class WriteFailedError extends Error {
7
7
  dispatcherError;
8
8
  constructor(error, message) {
@@ -11,8 +11,9 @@ export class WriteFailedError extends Error {
11
11
  this.dispatcherError = error;
12
12
  }
13
13
  }
14
- // i18nKey gewinnt wenn das Bundle ihn kennt (translate returnt den Key
15
- // unverändert wenn nicht — Renderer-Convention), sonst message, zuletzt code.
14
+ // i18nKey takes precedence when the bundle knows it (translate returns
15
+ // the key unchanged when it does not, per the renderer convention),
16
+ // followed by message and then code.
16
17
  export function dispatcherErrorText(error, translate) {
17
18
  const translated = translate(error.i18nKey, error.i18nParams);
18
19
  if (translated !== error.i18nKey)
@@ -10,7 +10,7 @@ export declare function ExtensionSectionMount({ section, entityName: hostEntityN
10
10
  readonly values?: Readonly<Record<string, unknown>>;
11
11
  readonly patch?: (partial: Readonly<Record<string, unknown>>) => void;
12
12
  readonly validate?: () => boolean;
13
- readonly markSaved?: () => void;
13
+ readonly markSaved?: (fields?: readonly string[]) => void;
14
14
  readonly hideTitle?: boolean;
15
15
  /** section.actions, already resolved into buttons by the caller. */
16
16
  readonly actions?: ReactNode;
@@ -24,7 +24,7 @@ export declare function EditSlotMount({ slot, slotName, screenId, entityName, en
24
24
  readonly entityName: string;
25
25
  readonly entityId: string | null;
26
26
  readonly values: Readonly<Record<string, unknown>>;
27
- readonly markSaved?: () => void;
27
+ readonly markSaved?: (fields?: readonly string[]) => void;
28
28
  readonly hasUnsavedChanges?: boolean;
29
29
  readonly wizardStep?: {
30
30
  readonly index: number;
@@ -71,11 +71,11 @@ function coerceCellValue(column, text, currency) {
71
71
  // (renderCellControl's MoneyInput, which scales by the same
72
72
  // currencyDecimals) — a hardcoded ×100 here diverged for zero-/three-
73
73
  // decimal currencies (JPY, BHD, ...), landing a pasted value 100x off
74
- // from the same value typed by hand (kumiko-framework#1972).
74
+ // from the same value typed by hand.
75
75
  // parseLocaleAwareDecimal handles both "1.234,56" (de) and
76
76
  // "1,234.56" (en-US) thousands/decimal conventions — a naive
77
- // `replace(",", ".")` mangled DE-locale pastes with thousands
78
- // separators into NaN (kumiko-framework#1838).
77
+ // `replace(",", ".")` mangled German-locale pastes with thousands
78
+ // separators into NaN.
79
79
  if (text.trim() === "")
80
80
  return undefined;
81
81
  const n = parseLocaleAwareDecimal(text);
@@ -169,7 +169,7 @@ function EmbeddedListFieldInner({ field, id, onChange, allIssues, featureName, }
169
169
  };
170
170
  }
171
171
  return {
172
- field: entry.from.join("+"),
172
+ field: `${entry.from.join("+")}:${entry.label}`,
173
173
  label: t(entry.label),
174
174
  value: entry.from.reduce((sum, name) => sum + sumEmbeddedListColumn(rows, name), 0),
175
175
  money: entry.from.every((name) => cells.find((c) => c.field === name)?.type === "money"),
@@ -259,7 +259,7 @@ function EmbeddedListFieldInner({ field, id, onChange, allIssues, featureName, }
259
259
  const coerced = coerceCellValue(column, text, field.embeddedListCurrency ?? "EUR");
260
260
  // Also covers an unparseable money paste (e.g. mangled thousands
261
261
  // separators) — the cell must stay unchanged instead of silently
262
- // clearing to undefined (kumiko-framework#1838).
262
+ // clearing to undefined.
263
263
  const isUnmatchedOrUnparseable = (column.type === "select" || column.type === "reference" || column.type === "money") &&
264
264
  coerced === undefined &&
265
265
  text.trim() !== "";
@@ -7,7 +7,7 @@ export type GridCellForFieldProps = {
7
7
  readonly issues: readonly FieldIssue[] | undefined;
8
8
  readonly onChange: (value: unknown) => void;
9
9
  readonly GridCell: ReturnType<typeof usePrimitives>["GridCell"];
10
- /** Tier 2.7e-3: passed through so Reference fields can build the correct
10
+ /** Passed through so Reference fields can build the correct
11
11
  * lookup query QN (`<feature>:query:<refEntity>:list`). */
12
12
  readonly featureName: string;
13
13
  readonly labelAppendix?: ReactNode;
@@ -8,7 +8,7 @@ import { usePrimitives } from "../primitives.js";
8
8
  import { RenderEdit } from "./render-edit.js";
9
9
  // Hosts a target entity's create-form inside a bare Modal so a reference
10
10
  // field can create a missing record without leaving the current form
11
- // (kumiko-framework#1681) — same create wiring as EntityEditCreateBody
11
+ // — same create wiring as EntityEditCreateBody
12
12
  // (kumiko-screen.tsx), but reports the new id back via a callback instead
13
13
  // of navigating to the entity's list screen.
14
14
  function entityWriteCommand(featureName, entity) {
@@ -17,7 +17,7 @@ function entityWriteCommand(featureName, entity) {
17
17
  // The create handler's success payload is `{ kind: "save", id, ... }`
18
18
  // (see event-store-executor-write.ts) but RenderEdit's onSubmit only
19
19
  // types it as `unknown` — narrow defensively instead of casting through it.
20
- // `field` is author-declared (ActionFormRedirect.idFrom, fw#2670), never
20
+ // `field` is author-declared (ActionFormRedirect.idFrom), never
21
21
  // user input; the own-property check keeps a prototype key like
22
22
  // "constructor" from resolving to anything.
23
23
  export function extractIdField(data, field) {
@@ -36,7 +36,7 @@ export function ReferenceCreateDialog({ open, onClose, onCreated, featureName, s
36
36
  const t = useTranslation();
37
37
  const entityDefaultCurrency = entity.defaultCurrency ?? "EUR";
38
38
  const tenantCurrencyFieldNames = useMemo(() => (open ? tenantCurrencyMoneyFieldNames(entity.fields) : []), [open, entity.fields]);
39
- // Same tenant-currency resolution as the entityEdit create screen (fw#2933),
39
+ // Same tenant-currency resolution as the entityEdit create screen,
40
40
  // so a `currency: { kind: "tenant" }` field seeds identically via this dialog.
41
41
  const { overrides: moneyCurrencyOverrides, loading: currencyLoading } = useMoneyCurrencyOverrides(entity.fields, tenantCurrencyFieldNames, entityDefaultCurrency);
42
42
  const initial = useMemo(() => buildInitialValues(entity.fields, entityDefaultCurrency, moneyCurrencyOverrides), [entity.fields, entityDefaultCurrency, moneyCurrencyOverrides]);
@@ -13,7 +13,7 @@ export declare function RelatedListSection({ section, parentId, record, featureN
13
13
  readonly hideTitle?: boolean;
14
14
  /** Under a fixed-height screen the tab panel chain stretches so the count footer sits at the bottom. */
15
15
  readonly grow?: boolean;
16
- /** Opens a drawer-kind rowAction (fw#2710). Supplied by the parent
16
+ /** Opens a drawer-kind rowAction. Supplied by the parent
17
17
  * (ProjectionDetailBody), which owns schema + the actual Drawer render —
18
18
  * this component only ever invokes the callback. */
19
19
  readonly onOpenDrawer?: (action: RowActionDrawer, initialValues: Readonly<Record<string, unknown>> | undefined) => void;
@@ -22,8 +22,21 @@ import { usePrimitives, } from "../primitives.js";
22
22
  import { sortByAccessor } from "../sort-by-accessor.js";
23
23
  import { RenderEditActionButton } from "./render-edit-action-button.js";
24
24
  import { RenderList } from "./render-list.js";
25
+ // Keyed on the type, not on key presence: most factories omit `sortable`
26
+ // from the definition, so `"sortable" in field` would drop the column flag.
27
+ const NON_SORTABLE_FIELD_TYPES = new Set([
28
+ "longText",
29
+ "multiSelect",
30
+ "embedded",
31
+ "jsonb",
32
+ "tz",
33
+ "file",
34
+ "image",
35
+ "files",
36
+ "images",
37
+ ]);
25
38
  function isSortableCapable(field) {
26
- return "sortable" in field;
39
+ return !NON_SORTABLE_FIELD_TYPES.has(field.type);
27
40
  }
28
41
  const RELATED_LIST_PSEUDO_ENTITY = "__related-list__";
29
42
  // Minimal EntityDefinition from the section's own columns — same shape as
@@ -338,7 +351,7 @@ export function RelatedListSection({ section, parentId, record, featureName, tra
338
351
  // A truncated fetch means `sortedRows` is a sort of a partial set, not of
339
352
  // the full related-row set — the client-side sort above (or even plain
340
353
  // unsorted display) would silently claim "these are the top N" when they
341
- // are really just "the first N in the server's own order" (fw#2722 review).
354
+ // are really just "the first N in the server's own order".
342
355
  // `nextCursor` is exactly how the paged envelope marks that: non-null means
343
356
  // more rows exist server-side beyond what was fetched.
344
357
  // A handler that omits `nextCursor` (or reports a `total` the fetched rows
@@ -10,15 +10,13 @@ export declare function RenderEditActionConfirmDialog({ action, open, onOpenChan
10
10
  readonly onConfirm: () => void | Promise<void>;
11
11
  readonly Dialog: ReturnType<typeof usePrimitives>["Dialog"];
12
12
  }): ReactNode;
13
- export declare function RenderEditActionButton({ action, iconOnly, hideIcon, compactLabel, Button, Dialog, onError, }: {
13
+ export declare function RenderEditActionButton({ action, iconOnly, hideIcon, Button, Dialog, onError, }: {
14
14
  readonly action: RenderEditAction;
15
15
  /** Group-level collapse (see `shouldRenderActionsIconOnly`) — only takes
16
16
  * effect when this action actually resolved an icon. */
17
17
  readonly iconOnly?: boolean;
18
18
  /** Record-header actions are text-only; the icon still shows when collapsed to icon-only. */
19
19
  readonly hideIcon?: boolean;
20
- /** Short labelled button for a phone header, where a bare icon would hide what the action does. */
21
- readonly compactLabel?: boolean;
22
20
  readonly Button: ReturnType<typeof usePrimitives>["Button"];
23
21
  readonly Dialog: ReturnType<typeof usePrimitives>["Dialog"];
24
22
  readonly onError: (notice: ErrorNotice | null) => void;
@@ -15,7 +15,7 @@ export function RenderEditActionConfirmDialog({ action, open, onOpenChange, onCo
15
15
  // render-list.tsx's ToolbarActionView (each RenderEditAction is
16
16
  // independently bound by the caller, there is no shared trigger pipeline
17
17
  // to hook into like the built-in onDelete/onSubmit paths have).
18
- export function RenderEditActionButton({ action, iconOnly = false, hideIcon = false, compactLabel = false, Button, Dialog, onError, }) {
18
+ export function RenderEditActionButton({ action, iconOnly = false, hideIcon = false, Button, Dialog, onError, }) {
19
19
  const [busy, setBusy] = useState(false);
20
20
  const [confirmOpen, setConfirmOpen] = useState(false);
21
21
  const trigger = async () => {
@@ -33,7 +33,7 @@ export function RenderEditActionButton({ action, iconOnly = false, hideIcon = fa
33
33
  };
34
34
  const variant = action.style ?? "secondary";
35
35
  const showIconOnly = iconOnly && action.icon !== undefined;
36
- return (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", variant: variant, loading: busy, ...(action.icon !== undefined && (!hideIcon || showIconOnly) && { icon: action.icon }), ...(compactLabel && !showIconOnly && { size: "sm" }), ...(showIconOnly && {
36
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", variant: variant, loading: busy, ...(action.icon !== undefined && (!hideIcon || showIconOnly) && { icon: action.icon }), ...(showIconOnly && {
37
37
  size: "icon",
38
38
  ariaLabel: action.label,
39
39
  title: action.label,
@@ -1,6 +1,6 @@
1
1
  // Hides the Save button when no field is editable and no extension section
2
2
  // contributes to the composed form submit. Extensions that persist via their
3
- // own dispatcher writes must not opt in (fw#2359).
3
+ // own dispatcher writes must not opt in.
4
4
  // Explicit-positive per kind (653/1), not `s.kind !== "fields"` — a future
5
5
  // third EditSectionViewModel member would otherwise default to "editable"
6
6
  // without anyone deciding that on purpose.
@@ -56,7 +56,7 @@ export type RenderEditProps<TValues extends FormValues, TCtx = unknown> = {
56
56
  /** Fires when the form gains or loses unsaved input (field changes or a
57
57
  * dirty extension section). Lets a host such as a drawer guard closing. */
58
58
  readonly onDirtyChange?: (dirty: boolean) => void;
59
- /** Copy-link action (issue #912) — only set in update mode (create mode
59
+ /** Copy-link action — only set in update mode (create mode
60
60
  * has no entity id yet, hence no permalink). The callback is already fully
61
61
  * bound (URL building + clipboard happen outside, in
62
62
  * `@cosmicdrift/kumiko-renderer/web`'s RoutedScreen — this platform-neutral
@@ -74,7 +74,7 @@ export type RenderEditProps<TValues extends FormValues, TCtx = unknown> = {
74
74
  * header menu (drawer, card form, shell without a header slot) they fall
75
75
  * back to buttons next to `actions`. */
76
76
  readonly menuActions?: readonly RenderEditAction[];
77
- /** Opens a relatedList section row's drawer-kind action (fw#2710).
77
+ /** Opens a relatedList section row's drawer-kind action.
78
78
  * RenderEdit has no `schema` to resolve the target actionForm itself —
79
79
  * the caller (ProjectionDetailBody, which does have schema) supplies the
80
80
  * opener and owns the actual Drawer state/rendering. Only projectionDetail
@@ -117,7 +117,7 @@ export type RenderEditProps<TValues extends FormValues, TCtx = unknown> = {
117
117
  /** Edit-mode "changed" markers also for screens without an entity id
118
118
  * (configEdit loads its values from a query, not a record). */
119
119
  readonly markChangedFields?: boolean;
120
- /** Controlled mode (issue #1887): fires on every values-snapshot change
120
+ /** Controlled mode: fires on every values-snapshot change
121
121
  * (typing, `patch(...)` from outside) with the current values. `changes`
122
122
  * is the delta against the initial values — same semantics as
123
123
  * `payloadMode: "changes"` — so a caller never overwrites unseen fields.
@@ -134,7 +134,7 @@ export type RenderEditProps<TValues extends FormValues, TCtx = unknown> = {
134
134
  * happens, so a caller patching dependent fields from inside `onChange`
135
135
  * never has to guard against `controls` being undefined. */
136
136
  readonly onChange?: (state: RenderEditChangeState<TValues>) => void;
137
- /** Controlled mode (issue #1887): called once after mount, hands the
137
+ /** Controlled mode: called once after mount, hands the
138
138
  * caller `patch`/`validate`/`getValues` bound to this RenderEdit
139
139
  * instance — addressable from outside without a remount. `patch` merges
140
140
  * only the given keys (existing `controller.setValues` semantics),
@@ -153,7 +153,7 @@ export type RenderEditProps<TValues extends FormValues, TCtx = unknown> = {
153
153
  * validation scope (the underlying `useForm` controller is mount-lived);
154
154
  * rendering and `controls.validate()` do stay reactive to later changes. */
155
155
  readonly fields?: readonly string[];
156
- /** Locked state (issue #1896): every rendered field and the submit button
156
+ /** Locked state: every rendered field and the submit button
157
157
  * go visibly inactive, no write possible. For cases where input becomes
158
158
  * moot — e.g. Solon's editor pointing at an existing record instead of
159
159
  * creating a new one. Extension sections are out of scope: RenderEdit has
@@ -177,7 +177,7 @@ export type RenderEditProps<TValues extends FormValues, TCtx = unknown> = {
177
177
  /** "form" (default) — every field renders as its Input widget, disabled
178
178
  * when `field.readOnly`, unchanged behavior. "text" renders a
179
179
  * `field.readOnly` field as plain text instead of a disabled Input
180
- * (ProjectionDetailBody's read view, fw#2245) — editable fields are
180
+ * (ProjectionDetailBody's read view) — editable fields are
181
181
  * unaffected either way, so this only changes forms that already have
182
182
  * readOnly fields. */
183
183
  readonly valueDisplay?: "form" | "text";