@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,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
@@ -73,10 +72,10 @@ export type ExtensionSectionsProviderProps = {
73
72
  readonly value: ExtensionSectionsMap;
74
73
  };
75
74
  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`. */
75
+ /** Looks up the component for an extension-section name. Returns
76
+ * `undefined` when no provider is mounted or the name is not registered in
77
+ * the map — the caller (RenderEdit) then displays its placeholder banner.
78
+ * `name` is optional so callers can invoke the hook unconditionally
79
+ * (Rules-of-Hooks) without supplying a stub key such as `""`, analogous to
80
+ * `useColumnRenderer`. */
82
81
  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,7 +26,7 @@ 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
32
  // (validateNoWidgetRequiredField in boot-validator/screens.ts).
@@ -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
@@ -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;
@@ -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;