@cosmicdrift/kumiko-renderer 0.385.0 → 0.387.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 (185) 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 +2 -2
  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/app-features-context.js +4 -4
  7. package/dist/app/column-renderers.d.ts +7 -9
  8. package/dist/app/column-renderers.js +15 -18
  9. package/dist/app/config-edit-body.js +1 -1
  10. package/dist/app/config-edit-shim.d.ts +7 -8
  11. package/dist/app/config-edit-shim.js +18 -20
  12. package/dist/app/custom-screens.d.ts +3 -3
  13. package/dist/app/custom-screens.js +8 -8
  14. package/dist/app/dashboard-body.d.ts +2 -2
  15. package/dist/app/dashboard-body.js +2 -2
  16. package/dist/app/entity-edit-prefill.js +9 -9
  17. package/dist/app/entity-edit-screen.js +4 -4
  18. package/dist/app/entity-list-screen.js +3 -3
  19. package/dist/app/extension-form-submit.d.ts +1 -1
  20. package/dist/app/extension-form-submit.js +14 -15
  21. package/dist/app/extension-sections.d.ts +24 -25
  22. package/dist/app/extension-sections.js +10 -10
  23. package/dist/app/feature-schema.js +11 -11
  24. package/dist/app/form-schema.js +1 -1
  25. package/dist/app/header-actions-bar.js +1 -1
  26. package/dist/app/kumiko-screen.js +8 -10
  27. package/dist/app/layout-fields.js +1 -1
  28. package/dist/app/list-facets.js +3 -3
  29. package/dist/app/nav.d.ts +10 -10
  30. package/dist/app/nav.js +13 -13
  31. package/dist/app/projection-detail-body.js +4 -4
  32. package/dist/app/projection-detail-shim.d.ts +6 -5
  33. package/dist/app/projection-detail-shim.js +23 -22
  34. package/dist/app/projection-list-body.js +10 -10
  35. package/dist/app/projection-list-shim.d.ts +5 -5
  36. package/dist/app/projection-list-shim.js +17 -18
  37. package/dist/app/qn.js +10 -12
  38. package/dist/app/qualify-screen-id.js +1 -1
  39. package/dist/app/screen-access.js +2 -2
  40. package/dist/app/screen-shared.js +1 -1
  41. package/dist/app/secret-mint-body.js +4 -4
  42. package/dist/app/write-failed-error.js +8 -7
  43. package/dist/components/embedded-list-field.js +4 -4
  44. package/dist/components/grid-cell-for-field.d.ts +1 -1
  45. package/dist/components/reference-create-dialog.js +3 -3
  46. package/dist/components/related-list-section.d.ts +1 -1
  47. package/dist/components/related-list-section.js +28 -5
  48. package/dist/components/render-edit-logic.js +1 -1
  49. package/dist/components/render-edit-types.d.ts +6 -6
  50. package/dist/components/render-edit.js +63 -65
  51. package/dist/components/render-field.d.ts +7 -7
  52. package/dist/components/render-field.js +57 -59
  53. package/dist/components/render-list.d.ts +43 -44
  54. package/dist/components/render-list.js +63 -67
  55. package/dist/components/use-wizard-form-draft.js +10 -10
  56. package/dist/context/dispatcher-context.js +5 -5
  57. package/dist/context/user-roles-context.js +2 -2
  58. package/dist/headless/apex/index.d.ts +1 -2
  59. package/dist/headless/apex/index.js +5 -6
  60. package/dist/headless/form/form-controller.js +6 -6
  61. package/dist/headless/form/zod-bridge.js +2 -2
  62. package/dist/headless/format/money.js +1 -1
  63. package/dist/headless/index.js +2 -2
  64. package/dist/headless/view-model/edit.js +2 -2
  65. package/dist/headless/view-model/list.js +3 -4
  66. package/dist/headless/view-model/types.d.ts +3 -3
  67. package/dist/hooks/reference-limits.js +11 -12
  68. package/dist/hooks/use-list-url-state.d.ts +13 -13
  69. package/dist/hooks/use-list-url-state.js +21 -22
  70. package/dist/hooks/use-query.js +3 -3
  71. package/dist/hooks/use-reference-lookup.js +16 -17
  72. package/dist/hooks/use-store.js +18 -22
  73. package/dist/hooks/use-stream-handler.js +4 -2
  74. package/dist/i18n-defaults.js +2 -8
  75. package/dist/i18n.d.ts +10 -10
  76. package/dist/i18n.js +25 -25
  77. package/dist/index.js +5 -4
  78. package/dist/live/dispatcher-live.js +2 -2
  79. package/dist/primitives.d.ts +225 -221
  80. package/dist/primitives.js +1 -1
  81. package/dist/sse/live-events.d.ts +5 -5
  82. package/dist/sse/live-events.js +17 -17
  83. package/dist/tokens.d.ts +8 -8
  84. package/dist/tokens.js +16 -17
  85. package/dist/web/app/browser-locale.d.ts +9 -9
  86. package/dist/web/app/browser-locale.js +19 -19
  87. package/dist/web/app/client-plugin.d.ts +44 -52
  88. package/dist/web/app/client-plugin.js +4 -5
  89. package/dist/web/app/create-app.d.ts +28 -31
  90. package/dist/web/app/create-app.js +21 -22
  91. package/dist/web/app/document-lang-sync.js +1 -1
  92. package/dist/web/app/draft-storage.js +1 -1
  93. package/dist/web/app/nav-providers-context.d.ts +3 -3
  94. package/dist/web/app/nav-providers-context.js +3 -3
  95. package/dist/web/app/nav.d.ts +18 -17
  96. package/dist/web/app/nav.js +66 -64
  97. package/dist/web/app/rich-content-editor.js +1 -1
  98. package/dist/web/components/config-cascade.js +17 -18
  99. package/dist/web/components/config-source-badge.js +4 -4
  100. package/dist/web/layout/app-layout.d.ts +9 -9
  101. package/dist/web/layout/app-layout.js +2 -2
  102. package/dist/web/layout/avatar.d.ts +4 -3
  103. package/dist/web/layout/avatar.js +15 -17
  104. package/dist/web/layout/default-app-shell.d.ts +22 -23
  105. package/dist/web/layout/editor-panel.js +3 -3
  106. package/dist/web/layout/nav-tree.js +86 -84
  107. package/dist/web/layout/profile-menu.d.ts +1 -1
  108. package/dist/web/layout/sidebar-brand.d.ts +7 -7
  109. package/dist/web/layout/sidebar-user.d.ts +1 -1
  110. package/dist/web/layout/sidebar-user.js +4 -4
  111. package/dist/web/layout/sidebar.d.ts +8 -8
  112. package/dist/web/layout/sidebar.js +7 -7
  113. package/dist/web/layout/target-resolver-stub.d.ts +5 -5
  114. package/dist/web/layout/target-resolver-stub.js +20 -20
  115. package/dist/web/layout/target-url.d.ts +7 -7
  116. package/dist/web/layout/target-url.js +22 -22
  117. package/dist/web/layout/theme-toggle.js +4 -5
  118. package/dist/web/layout/workspace-shell.d.ts +21 -21
  119. package/dist/web/layout/workspace-shell.js +29 -30
  120. package/dist/web/lib/cn.d.ts +4 -4
  121. package/dist/web/lib/cn.js +4 -4
  122. package/dist/web/primitives/action-menu.d.ts +8 -8
  123. package/dist/web/primitives/action-menu.js +14 -13
  124. package/dist/web/primitives/calendar-popover.d.ts +1 -1
  125. package/dist/web/primitives/calendar-popover.js +10 -10
  126. package/dist/web/primitives/card-styles.js +1 -1
  127. package/dist/web/primitives/card.js +2 -2
  128. package/dist/web/primitives/combobox.d.ts +12 -12
  129. package/dist/web/primitives/combobox.js +34 -35
  130. package/dist/web/primitives/data-table-cell.js +2 -2
  131. package/dist/web/primitives/data-table.js +2 -3
  132. package/dist/web/primitives/date-field.js +2 -2
  133. package/dist/web/primitives/date-input.js +4 -4
  134. package/dist/web/primitives/default-form.js +2 -2
  135. package/dist/web/primitives/dropdown-menu.js +14 -15
  136. package/dist/web/primitives/embedded-list-input.js +3 -3
  137. package/dist/web/primitives/facet-filter.js +1 -1
  138. package/dist/web/primitives/field.js +9 -8
  139. package/dist/web/primitives/form.d.ts +1 -1
  140. package/dist/web/primitives/form.js +6 -6
  141. package/dist/web/primitives/input.js +2 -2
  142. package/dist/web/primitives/layout-elements.js +1 -1
  143. package/dist/web/primitives/layout.js +2 -2
  144. package/dist/web/primitives/located-timestamp-input.js +4 -4
  145. package/dist/web/primitives/money-input.js +13 -17
  146. package/dist/web/primitives/pager.js +1 -1
  147. package/dist/web/primitives/row-actions.js +3 -4
  148. package/dist/web/primitives/secret-reveal.js +1 -1
  149. package/dist/web/primitives/timestamp-input.d.ts +9 -9
  150. package/dist/web/primitives/timestamp-input.js +21 -21
  151. package/dist/web/primitives/toast.js +12 -13
  152. package/dist/web/sse/live-events.d.ts +6 -6
  153. package/dist/web/sse/live-events.js +3 -3
  154. package/dist/web/tokens.d.ts +11 -12
  155. package/dist/web/tokens.js +35 -37
  156. package/dist/web/version/update-checker.js +14 -13
  157. package/dist/web/widgets/ai-text-field.js +1 -1
  158. package/dist/web/widgets/charts.d.ts +10 -10
  159. package/dist/web/widgets/charts.js +8 -8
  160. package/dist/web/widgets/collapsible-section.d.ts +3 -3
  161. package/dist/web/widgets/collapsible-section.js +6 -6
  162. package/dist/web/widgets/detail-list.d.ts +1 -3
  163. package/dist/web/widgets/detail-list.js +1 -3
  164. package/dist/web/widgets/feed-list.d.ts +2 -2
  165. package/dist/web/widgets/feed-list.js +2 -2
  166. package/dist/web/widgets/form-fields.d.ts +14 -14
  167. package/dist/web/widgets/form-fields.js +14 -13
  168. package/dist/web/widgets/index.js +4 -4
  169. package/dist/web/widgets/infinity-list.js +3 -3
  170. package/dist/web/widgets/mode-switch.d.ts +2 -2
  171. package/dist/web/widgets/mode-switch.js +2 -2
  172. package/dist/web/widgets/progress-list.d.ts +1 -2
  173. package/dist/web/widgets/progress-list.js +1 -2
  174. package/dist/web/widgets/query-table.d.ts +6 -6
  175. package/dist/web/widgets/result-panel.d.ts +16 -16
  176. package/dist/web/widgets/result-panel.js +10 -10
  177. package/dist/web/widgets/section-card.d.ts +3 -3
  178. package/dist/web/widgets/section-card.js +3 -3
  179. package/dist/web/widgets/stat.d.ts +4 -4
  180. package/dist/web/widgets/stat.js +6 -6
  181. package/dist/web/widgets/states.d.ts +4 -4
  182. package/dist/web/widgets/states.js +4 -4
  183. package/dist/web/widgets/use-draft.d.ts +7 -7
  184. package/dist/web/widgets/use-draft.js +7 -7
  185. package/package.json +6 -6
@@ -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)
@@ -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);
@@ -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;
@@ -6,6 +6,7 @@ import { useClientActionRuntime } from "../app/client-actions.js";
6
6
  import { ErrorReference } from "../app/error-reference.js";
7
7
  import { buildFilterFacets, buildFilterPayload, mergeReferenceFacetOptions, resolveProjectionFacetSpecs, } from "../app/list-facets.js";
8
8
  import { useNav } from "../app/nav.js";
9
+ import { SYNTHESIZED_FIELD_PII_REASON } from "../app/projection-list-shim.js";
9
10
  import { ReferenceFacetBridges, } from "../app/reference-facet-bridge.js";
10
11
  import { navigateWithReturnTo, useReturnHost } from "../app/return-to.js";
11
12
  import { buildDefaultEditRowAction, buildProjectionRowActions, buildProjectionToolbarActions, buildRecordActions, findRowClickAction, runDrawerRowAction, runProjectionRowNavigate, } from "../app/row-actions.js";
@@ -21,6 +22,9 @@ import { usePrimitives, } from "../primitives.js";
21
22
  import { sortByAccessor } from "../sort-by-accessor.js";
22
23
  import { RenderEditActionButton } from "./render-edit-action-button.js";
23
24
  import { RenderList } from "./render-list.js";
25
+ function isSortableCapable(field) {
26
+ return "sortable" in field;
27
+ }
24
28
  const RELATED_LIST_PSEUDO_ENTITY = "__related-list__";
25
29
  // Minimal EntityDefinition from the section's own columns — same shape as
26
30
  // projection-list-shim's synthesizeProjectionEntity, but sortable is read
@@ -36,11 +40,30 @@ function synthesizeRelatedListEntity(columns, sourceEntity) {
36
40
  const sortable = normalized.sortable === true;
37
41
  const sourceField = sourceEntity?.fields[normalized.field];
38
42
  fields[normalized.field] =
39
- sourceField !== undefined
40
- ? { ...sourceField, sortable }
41
- : { type: normalized.valueType ?? "text", sortable };
43
+ sourceField === undefined
44
+ ? valueTypeField(normalized.valueType, sortable)
45
+ : isSortableCapable(sourceField)
46
+ ? Object.assign({}, sourceField, { sortable })
47
+ : sourceField;
48
+ }
49
+ return {
50
+ fields,
51
+ ...(sourceEntity?.derivedFields !== undefined && { derivedFields: sourceEntity.derivedFields }),
52
+ };
53
+ }
54
+ function valueTypeField(valueType, sortable) {
55
+ switch (valueType) {
56
+ case "number":
57
+ // decimal renders through the same number formatter; digits come from the column spec, not scale
58
+ case "decimal":
59
+ return { type: "number", sortable };
60
+ case "bigInt":
61
+ return { type: "bigInt", sortable };
62
+ case "money":
63
+ return { type: "money", sortable };
64
+ case undefined:
65
+ return { type: "text", allowPlaintext: SYNTHESIZED_FIELD_PII_REASON, sortable };
42
66
  }
43
- return { ...sourceEntity, fields };
44
67
  }
45
68
  // Mirrors row-actions.ts's own warnDrawerActionDropped (module-private
46
69
  // there, so not reusable directly) — a drawer-kind emptyState.action with no
@@ -315,7 +338,7 @@ export function RelatedListSection({ section, parentId, record, featureName, tra
315
338
  // A truncated fetch means `sortedRows` is a sort of a partial set, not of
316
339
  // the full related-row set — the client-side sort above (or even plain
317
340
  // unsorted display) would silently claim "these are the top N" when they
318
- // are really just "the first N in the server's own order" (fw#2722 review).
341
+ // are really just "the first N in the server's own order".
319
342
  // `nextCursor` is exactly how the paged envelope marks that: non-null means
320
343
  // more rows exist server-side beyond what was fetched.
321
344
  // A handler that omits `nextCursor` (or reports a `total` the fetched rows
@@ -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";