@cosmicdrift/kumiko-renderer 0.384.0 → 0.385.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 (97) hide show
  1. package/dist/app/action-form-body.d.ts +50 -0
  2. package/dist/app/action-form-body.js +275 -0
  3. package/dist/app/client-actions.js +2 -2
  4. package/dist/app/config-edit-body.d.ts +9 -0
  5. package/dist/app/config-edit-body.js +340 -0
  6. package/dist/app/entity-edit-prefill.d.ts +27 -0
  7. package/dist/app/entity-edit-prefill.js +377 -0
  8. package/dist/app/entity-edit-screen.d.ts +11 -0
  9. package/dist/app/entity-edit-screen.js +395 -0
  10. package/dist/app/entity-list-screen.d.ts +10 -0
  11. package/dist/app/entity-list-screen.js +547 -0
  12. package/dist/app/kumiko-screen.d.ts +1 -36
  13. package/dist/app/kumiko-screen.js +16 -2855
  14. package/dist/app/projection-detail-body.d.ts +12 -0
  15. package/dist/app/projection-detail-body.js +464 -0
  16. package/dist/app/projection-detail-shim.d.ts +1 -1
  17. package/dist/app/projection-detail-shim.js +5 -1
  18. package/dist/app/projection-list-body.d.ts +10 -0
  19. package/dist/app/projection-list-body.js +245 -0
  20. package/dist/app/projection-list-shim.d.ts +2 -1
  21. package/dist/app/projection-list-shim.js +4 -0
  22. package/dist/app/reference-facet-bridge.js +2 -2
  23. package/dist/app/row-actions.js +3 -4
  24. package/dist/app/screen-shared.d.ts +72 -0
  25. package/dist/app/screen-shared.js +325 -0
  26. package/dist/app/secret-mint-body.js +3 -3
  27. package/dist/components/edit-mounts.d.ts +33 -0
  28. package/dist/components/edit-mounts.js +53 -0
  29. package/dist/components/error-boundary.js +2 -2
  30. package/dist/components/reference-create-dialog.js +1 -1
  31. package/dist/components/related-list-section.js +2 -2
  32. package/dist/components/render-edit-helpers.d.ts +29 -0
  33. package/dist/components/render-edit-helpers.js +121 -0
  34. package/dist/components/render-edit.d.ts +1 -4
  35. package/dist/components/render-edit.js +41 -443
  36. package/dist/components/render-field.js +3 -4
  37. package/dist/components/render-list.js +2 -2
  38. package/dist/components/use-wizard-form-draft.d.ts +51 -0
  39. package/dist/components/use-wizard-form-draft.js +291 -0
  40. package/dist/dev-console.d.ts +3 -0
  41. package/dist/dev-console.js +15 -0
  42. package/dist/headless/contracts/primitives.js +14 -17
  43. package/dist/headless/format/index.js +3 -3
  44. package/dist/headless/view-model/edit.d.ts +1 -1
  45. package/dist/headless/view-model/edit.js +9 -16
  46. package/dist/headless/view-model/list.d.ts +1 -1
  47. package/dist/headless/view-model/list.js +3 -8
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.js +5 -5
  50. package/dist/live/error-mapping.js +3 -4
  51. package/dist/web/app/create-app.js +5 -8
  52. package/dist/web/app/dashboard-body.js +4 -599
  53. package/dist/web/app/dashboard-panels.d.ts +53 -0
  54. package/dist/web/app/dashboard-panels.js +373 -0
  55. package/dist/web/app/dashboard-shared.d.ts +90 -0
  56. package/dist/web/app/dashboard-shared.js +171 -0
  57. package/dist/web/app/dashboard-text.d.ts +6 -0
  58. package/dist/web/app/dashboard-text.js +65 -0
  59. package/dist/web/layout/nav-tree.js +3 -4
  60. package/dist/web/layout/target-resolver-stub.js +2 -2
  61. package/dist/web/primitives/banner.d.ts +4 -0
  62. package/dist/web/primitives/banner.js +28 -0
  63. package/dist/web/primitives/button.d.ts +7 -0
  64. package/dist/web/primitives/button.js +48 -0
  65. package/dist/web/primitives/card-styles.d.ts +8 -0
  66. package/dist/web/primitives/card-styles.js +18 -0
  67. package/dist/web/primitives/card.d.ts +4 -0
  68. package/dist/web/primitives/card.js +41 -0
  69. package/dist/web/primitives/contexts.d.ts +22 -0
  70. package/dist/web/primitives/contexts.js +50 -0
  71. package/dist/web/primitives/data-table-cell.d.ts +76 -0
  72. package/dist/web/primitives/data-table-cell.js +403 -0
  73. package/dist/web/primitives/data-table.d.ts +4 -0
  74. package/dist/web/primitives/data-table.js +279 -0
  75. package/dist/web/primitives/default-form.d.ts +4 -0
  76. package/dist/web/primitives/default-form.js +79 -0
  77. package/dist/web/primitives/facet-filter.d.ts +14 -0
  78. package/dist/web/primitives/facet-filter.js +39 -0
  79. package/dist/web/primitives/field.d.ts +63 -0
  80. package/dist/web/primitives/field.js +181 -0
  81. package/dist/web/primitives/form.d.ts +53 -0
  82. package/dist/web/primitives/form.js +252 -0
  83. package/dist/web/primitives/index.d.ts +7 -30
  84. package/dist/web/primitives/index.js +27 -2006
  85. package/dist/web/primitives/input.d.ts +4 -0
  86. package/dist/web/primitives/input.js +166 -0
  87. package/dist/web/primitives/layout-elements.d.ts +14 -0
  88. package/dist/web/primitives/layout-elements.js +194 -0
  89. package/dist/web/primitives/modal.js +1 -1
  90. package/dist/web/primitives/pager.d.ts +22 -0
  91. package/dist/web/primitives/pager.js +89 -0
  92. package/dist/web/primitives/row-actions.d.ts +13 -0
  93. package/dist/web/primitives/row-actions.js +178 -0
  94. package/dist/web/primitives/secret-reveal.d.ts +4 -0
  95. package/dist/web/primitives/secret-reveal.js +67 -0
  96. package/package.json +5 -5
  97. package/src/changes.json +6 -0
@@ -0,0 +1,245 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useState } from "react";
3
+ import { RenderList } from "../components/render-list.js";
4
+ import { useOptionalDispatcher } from "../context/dispatcher-context.js";
5
+ import { useIsEmbeddedScreen } from "../context/embedded-screen-context.js";
6
+ import { useUserRoles } from "../context/user-roles-context.js";
7
+ import { useListUrlState } from "../hooks/use-list-url-state.js";
8
+ import { useQuery } from "../hooks/use-query.js";
9
+ import { useOptionalTimeZone, useTranslation } from "../i18n.js";
10
+ import { usePrimitives, } from "../primitives.js";
11
+ import { screenFillsHeight } from "../screen-fills-height.js";
12
+ import { DrawerHost, useDrawerAction } from "./action-form-body.js";
13
+ import { useAppFeatures } from "./app-features-context.js";
14
+ import { useClientActionRuntime } from "./client-actions.js";
15
+ import { isListNarrowed, useListEmptyContent } from "./list-empty-content.js";
16
+ import { buildDateRangePayload, buildFilterFacets, buildFilterPayload, clampDateRange, mergeReferenceFacetOptions, readDateRange, resolveDateRangeFacets, resolveProjectionFacetSpecs, } from "./list-facets.js";
17
+ import { useNav } from "./nav.js";
18
+ import { synthesizeProjectionEntity, synthesizeProjectionScreen } from "./projection-list-shim.js";
19
+ import { QueryErrorBanner, QueryLoadingBanner } from "./query-state-banners.js";
20
+ import { ReferenceFacetBridges } from "./reference-facet-bridge.js";
21
+ import { useReturnHost } from "./return-to.js";
22
+ import { buildDefaultEditRowAction, buildProjectionRowActions, buildProjectionToolbarActions, findRowClickAction, runDrawerRowAction, runProjectionRowNavigate, } from "./row-actions.js";
23
+ import { findEditScreenFor } from "./screen-access.js";
24
+ import { buildListQueryPayload, pageSizeOptionsFor } from "./screen-shared.js";
25
+ // ---- projection-list ----
26
+ // Like entityList, but the list query comes DIRECTLY from `screen.query`
27
+ // (instead of being derived from an entity) — cross-feature capable.
28
+ // search/sort/pagination reuse the same URL-state + buildListQueryPayload
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
31
+ // declared here. Pages-mode pagination only — infinite-scroll accumulation
32
+ // isn't wired for projectionList.
33
+ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
34
+ const isEmbeddedScreen = useIsEmbeddedScreen();
35
+ const { Banner, Text } = usePrimitives();
36
+ const t = useTranslation();
37
+ const nav = useNav();
38
+ const host = useReturnHost();
39
+ const dispatcher = useOptionalDispatcher();
40
+ const clientActions = useClientActionRuntime();
41
+ const effectiveTranslate = translate ?? t;
42
+ const userRoles = useUserRoles();
43
+ const appFeatures = useAppFeatures();
44
+ const { drawerAction, drawerScreen, drawerInitialValues, openDrawer, closeDrawer } = useDrawerAction(schema);
45
+ const defaultEditScreen = useMemo(() => {
46
+ const detailFor = screen.detailFor;
47
+ if (detailFor === undefined)
48
+ return undefined;
49
+ return findEditScreenFor(detailFor, appFeatures, userRoles);
50
+ }, [appFeatures, screen.detailFor, userRoles]);
51
+ // searchable/sortable/paginated are derived at buildAppSchema time from the
52
+ // query handler's Zod schema (fw#2165) — not authored on the screen.
53
+ const searchable = screen.searchable ?? false;
54
+ const sortable = screen.sortable ?? false;
55
+ const paginated = screen.paginated ?? false;
56
+ const entity = useMemo(() => synthesizeProjectionEntity(screen.columns, sortable), [screen.columns, sortable]);
57
+ const listScreen = useMemo(() => synthesizeProjectionScreen(screen), [screen]);
58
+ const urlState = useListUrlState(screen.id);
59
+ // Gated on the derived capability, not just on whether URL-state happens to
60
+ // carry a value — a stale/hand-crafted URL (?…q=x on a non-searchable
61
+ // screen) must not smuggle a param the query's Zod schema doesn't accept.
62
+ const activeSearch = searchable ? urlState.q : "";
63
+ // Same predicate that makes a header clickable: a stale link onto a
64
+ // sortable:false/unknown column must not reach the server (it answers 400).
65
+ const urlSort = urlState.sort;
66
+ const urlSortField = urlSort === null ? undefined : entity.fields[urlSort.field];
67
+ const urlSortAllowed = urlSortField !== undefined && "sortable" in urlSortField && urlSortField.sortable === true;
68
+ const activeSort = sortable
69
+ ? ((urlSortAllowed ? urlSort : null) ?? screen.defaultSort ?? null)
70
+ : null;
71
+ const limit = urlState.pageSize ?? screen.pageSize ?? 50;
72
+ // Pages-mode only (see header comment) — an author-set pagination:
73
+ // "infinite" on a paginated projectionList is silently a no-op today.
74
+ const usePager = paginated && (screen.pagination ?? "pages") === "pages";
75
+ // Facets (fw#2224) — a projectionList has no entity, so screen.facets is
76
+ // the only field inventory; resolveProjectionFacetSpecs reshapes +
77
+ // translates its explicit labels into the same ResolvedFacetSpec the
78
+ // entityList adapter produces, so both feed the same
79
+ // buildFilterFacets/buildFilterPayload.
80
+ const facetSpecs = useMemo(() => resolveProjectionFacetSpecs(screen.facets, effectiveTranslate, schema.featureName), [screen.facets, effectiveTranslate, schema.featureName]);
81
+ const [projectionReferenceFacetOptions, setProjectionReferenceFacetOptions] = useState({});
82
+ const handleProjectionFacetOptions = useCallback((field, options) => setProjectionReferenceFacetOptions((prev) => prev[field] === options ? prev : { ...prev, [field]: options }), []);
83
+ const resolvedFacetSpecs = useMemo(() => mergeReferenceFacetOptions(facetSpecs, projectionReferenceFacetOptions), [facetSpecs, projectionReferenceFacetOptions]);
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
86
+ // become the top-level query params the facet declares, resolved from a
87
+ // calendar date to an instant in the viewer's zone.
88
+ const timeZone = useOptionalTimeZone();
89
+ const dateRangeSpecs = useMemo(() => resolveDateRangeFacets(screen.facets, effectiveTranslate), [screen.facets, effectiveTranslate]);
90
+ const dateRangePayload = useMemo(() => buildDateRangePayload(dateRangeSpecs, urlState.filters, timeZone), [dateRangeSpecs, urlState.filters, timeZone]);
91
+ const queryPayload = useMemo(() => {
92
+ const payload = buildListQueryPayload({
93
+ limit,
94
+ search: activeSearch,
95
+ sort: activeSort,
96
+ usePager,
97
+ page: urlState.page,
98
+ useInfinite: false,
99
+ cursor: undefined,
100
+ });
101
+ // Gated on screen.filter/screen.facets being declared (not just on
102
+ // urlState/filterPayload happening to carry a value) — same rule as
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).
105
+ if (screen.filter !== undefined) {
106
+ payload["filter"] = screen.filter;
107
+ }
108
+ if (screen.facets !== undefined && filterPayload.length > 0) {
109
+ payload["filters"] = filterPayload;
110
+ }
111
+ Object.assign(payload, dateRangePayload);
112
+ return payload;
113
+ }, [
114
+ limit,
115
+ activeSearch,
116
+ activeSort,
117
+ usePager,
118
+ urlState.page,
119
+ screen.filter,
120
+ screen.facets,
121
+ filterPayload,
122
+ dateRangePayload,
123
+ ]);
124
+ const rowsQuery = useQuery(screen.query, queryPayload, { live: true });
125
+ const filterFacets = useMemo(() => buildFilterFacets(resolvedFacetSpecs), [resolvedFacetSpecs]);
126
+ const dateRangeFacets = useMemo(() => dateRangeSpecs.map((spec) => ({
127
+ field: spec.field,
128
+ label: spec.label,
129
+ ...readDateRange(urlState.filters, spec.field),
130
+ })), [dateRangeSpecs, urlState.filters]);
131
+ const handleDateRangeChange = useCallback((field, bound, value) => {
132
+ urlState.setDateRange(field, clampDateRange(readDateRange(urlState.filters, field), bound, value));
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"]
136
+ // fallback — projectionList rows come from an arbitrary query projection
137
+ // with no guaranteed "id" field. The boot validator therefore enforces an
138
+ // explicit entityId for projectionList entity targets. Same helper
139
+ // relatedList's rowActions reuse (related-list-section.tsx) — a
140
+ // projectionDetail relatedList row has the identical "no guaranteed id"
141
+ // shape.
142
+ const runNavigate = useCallback((action, row) => runProjectionRowNavigate(nav, action, row, host), [nav, host]);
143
+ const defaultEditRowAction = useMemo(() => buildDefaultEditRowAction(defaultEditScreen), [defaultEditScreen]);
144
+ const rowActions = useMemo(() => buildProjectionRowActions({
145
+ rowActions: screen.rowActions,
146
+ translate: effectiveTranslate,
147
+ dispatcher,
148
+ nav,
149
+ refetch: rowsQuery.refetch,
150
+ openDrawer,
151
+ defaultEditRowAction,
152
+ host,
153
+ clientActions,
154
+ }), [
155
+ screen.rowActions,
156
+ effectiveTranslate,
157
+ dispatcher,
158
+ clientActions,
159
+ nav,
160
+ rowsQuery.refetch,
161
+ openDrawer,
162
+ defaultEditRowAction,
163
+ host,
164
+ ]);
165
+ const toolbarActions = useMemo(() => buildProjectionToolbarActions({
166
+ toolbarActions: screen.toolbarActions,
167
+ translate: effectiveTranslate,
168
+ dispatcher,
169
+ nav,
170
+ refetch: rowsQuery.refetch,
171
+ openDrawer,
172
+ host,
173
+ access: { appFeatures, userRoles },
174
+ }), [
175
+ screen.toolbarActions,
176
+ effectiveTranslate,
177
+ nav,
178
+ host,
179
+ dispatcher,
180
+ rowsQuery.refetch,
181
+ openDrawer,
182
+ appFeatures,
183
+ userRoles,
184
+ ]);
185
+ const emptyContent = useListEmptyContent(screen.emptyState, effectiveTranslate, {
186
+ nav,
187
+ host,
188
+ dispatcher,
189
+ openDrawer,
190
+ onWriteSuccess: rowsQuery.refetch,
191
+ });
192
+ if (rowsQuery.loading && rowsQuery.data === null) {
193
+ return _jsx(QueryLoadingBanner, {});
194
+ }
195
+ if (rowsQuery.error && rowsQuery.data === null) {
196
+ return (_jsx(QueryErrorBanner, { error: rowsQuery.error, translate: effectiveTranslate, onRetry: rowsQuery.refetch }));
197
+ }
198
+ // A failed refetch keeps the last good data on screen.
199
+ const refetchErrorBanner = rowsQuery.error ? (_jsx(QueryErrorBanner, { error: rowsQuery.error, translate: effectiveTranslate, onRetry: rowsQuery.refetch })) : null;
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.
203
+ if (rowsQuery.data !== null && !Array.isArray(rowsQuery.data.rows)) {
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
+ }
206
+ const rowClickAction = findRowClickAction(screen.rowActions);
207
+ const wrappedOnRowClick = rowClickAction
208
+ ? (row) => rowClickAction.kind === "drawer"
209
+ ? runDrawerRowAction(rowClickAction, openDrawer, row)
210
+ : runNavigate(rowClickAction, row)
211
+ : onRowClick !== undefined
212
+ ? (row) => onRowClick(row, listScreen.entity)
213
+ : undefined;
214
+ // Same pager-construction as EntityListBody: no Pager UI until the
215
+ // server-provided `total` arrives (guards pagination="pages" without
216
+ // totalCount support, see fw#2165 report on solon's leaseOverviewHandler).
217
+ const total = rowsQuery.data?.total;
218
+ const pager = usePager && total !== undefined
219
+ ? {
220
+ page: urlState.page,
221
+ limit,
222
+ total,
223
+ onPageChange: urlState.setPage,
224
+ pageSizeOptions: pageSizeOptionsFor(limit),
225
+ onPageSizeChange: urlState.setPageSize,
226
+ }
227
+ : undefined;
228
+ return (_jsxs(_Fragment, { children: [refetchErrorBanner, _jsx(ReferenceFacetBridges, { specs: facetSpecs, onOptions: handleProjectionFacetOptions }), _jsx(RenderList, { screen: listScreen, entity: entity, rows: rowsQuery.data?.rows ?? [], featureName: schema.featureName, searchable: searchable, narrowed: isListNarrowed(activeSearch, urlState.filters), ...(screen.description !== undefined && {
229
+ description: effectiveTranslate(screen.description),
230
+ }), ...(emptyContent !== undefined && { emptyContent }), searchValue: urlState.q, onSearchChange: urlState.setQ, sort: activeSort, onSortChange: urlState.setSort, ...(isEmbeddedScreen
231
+ ? { screenPadding: false, chromeless: true }
232
+ : { screenPadding: true }), ...(screenFillsHeight(listScreen) && { scrollBody: true }), ...(pager !== undefined && { pager }), ...(rowActions !== undefined && { rowActions }), ...(screen.rowActionMode !== undefined && { rowActionMode: screen.rowActionMode }), ...(toolbarActions !== undefined && { toolbarActions }), ...(translate !== undefined && { translate }), ...(wrappedOnRowClick !== undefined && { onRowClick: wrappedOnRowClick }), ...(filterFacets.length > 0 && {
233
+ filterFacets,
234
+ onFilterChange: urlState.setFilter,
235
+ }), ...(dateRangeFacets.length > 0 && {
236
+ dateRangeFacets,
237
+ onDateRangeChange: handleDateRangeChange,
238
+ }), ...((filterFacets.length > 0 || dateRangeFacets.length > 0) && {
239
+ filterValues: urlState.filters,
240
+ onFilterReset: urlState.clearFilters,
241
+ }) }), _jsx(DrawerHost, { schema: schema, drawerAction: drawerAction, drawerScreen: drawerScreen, ...(drawerInitialValues !== undefined && { drawerInitialValues }), userRoles: userRoles, ...(translate !== undefined && { translate }), onClose: closeDrawer, onSuccess: () => {
242
+ closeDrawer();
243
+ void rowsQuery.refetch();
244
+ } })] }));
245
+ }
@@ -1,4 +1,5 @@
1
1
  import type { EntityDefinition, EntityListScreenDefinition, ListColumnSpec, ProjectionListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ export declare const SYNTHESIZED_FIELD_PII_REASON = "synthesized display field, not stored entity data";
2
3
  /** Minimal EntityDefinition from the column list: every field is a text
3
4
  * field. `sortable` applies to all columns — buildAppSchema derives it from
4
5
  * the query's Zod schema (`screen.sortable`, fw#2165); the query itself has
@@ -6,7 +7,7 @@ import type { EntityDefinition, EntityListScreenDefinition, ListColumnSpec, Proj
6
7
  * `sortable: false` when the handler cannot order by it.
7
8
  * computeListViewModel only reads `fields[<col>].type` — text is enough,
8
9
  * presentation comes from the column renderer + explicit label. */
9
- export declare function synthesizeProjectionEntity(columns: readonly ListColumnSpec[], sortable: boolean): EntityDefinition;
10
+ export declare function synthesizeProjectionEntity(columns: readonly ListColumnSpec[], sortable: boolean): Pick<EntityDefinition, "fields">;
10
11
  /** Wandelt ein ProjectionListScreenDefinition in die EntityListScreen-Shape die
11
12
  * RenderList erwartet. `type` = "entityList" + Pseudo-Entity halten den Type-
12
13
  * Constraint; RenderList branched nicht auf `type` und liest `entity` nur für
@@ -15,6 +15,9 @@
15
15
  // query-native ListView-Komponente.
16
16
  import { normalizeListColumn } from "@cosmicdrift/kumiko-framework/ui-types";
17
17
  const PROJECTION_PSEUDO_ENTITY = "__projection__";
18
+ // Satisfies the text-field PII stance of the entity field type; these synthesized
19
+ // fields describe query columns and never map to stored entity data.
20
+ export const SYNTHESIZED_FIELD_PII_REASON = "synthesized display field, not stored entity data";
18
21
  /** Minimal EntityDefinition from the column list: every field is a text
19
22
  * field. `sortable` applies to all columns — buildAppSchema derives it from
20
23
  * the query's Zod schema (`screen.sortable`, fw#2165); the query itself has
@@ -28,6 +31,7 @@ export function synthesizeProjectionEntity(columns, sortable) {
28
31
  const normalized = normalizeListColumn(col);
29
32
  fields[normalized.field] = {
30
33
  type: "text",
34
+ allowPlaintext: SYNTHESIZED_FIELD_PII_REASON,
31
35
  sortable: sortable && normalized.sortable !== false,
32
36
  };
33
37
  }
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  // One bridge component per reference facet, mirroring ReferenceLookupBridge —
3
3
  // facet count varies per screen, so the hook can't be called in a loop.
4
4
  import { useEffect } from "react";
5
+ import { rendererWarn } from "../dev-console.js";
5
6
  import { REFERENCE_LIST_LOOKUP_LIMIT } from "../hooks/reference-limits.js";
6
7
  import { useReferenceLookup } from "../hooks/use-reference-lookup.js";
7
8
  function ReferenceFacetOptionsBridge({ field, refEntity, refFeature, labelField, onOptions, }) {
@@ -12,8 +13,7 @@ function ReferenceFacetOptionsBridge({ field, refEntity, refFeature, labelField,
12
13
  // The lookup is one capped list fetch; a full page means the facet may be
13
14
  // missing values the user wants to filter by.
14
15
  if (lookup.map.size >= REFERENCE_LIST_LOOKUP_LIMIT) {
15
- // biome-ignore lint/suspicious/noConsole: dev-warning, facet options are silently truncated
16
- console.warn(`[kumiko] reference facet "${field}" shows only the first ${REFERENCE_LIST_LOOKUP_LIMIT} ${refEntity} rows; further values cannot be filtered by`);
16
+ rendererWarn(`[kumiko] reference facet "${field}" shows only the first ${REFERENCE_LIST_LOOKUP_LIMIT} ${refEntity} rows; further values cannot be filtered by`);
17
17
  }
18
18
  onOptions(field, [...lookup.map.entries()].map(([value, label]) => ({ value, label })));
19
19
  }, [lookup.loading, lookup.map, field, refEntity, onOptions]);
@@ -1,5 +1,6 @@
1
1
  import { evalFieldCondition } from "@cosmicdrift/kumiko-framework/ui-types";
2
2
  import { resolveActionIcon } from "@cosmicdrift/kumiko-types/action-icon";
3
+ import { rendererError, rendererWarn } from "../dev-console.js";
3
4
  import { runClientAction } from "./client-actions.js";
4
5
  import { lastSegment } from "./qn.js";
5
6
  import { navigateWithReturnTo } from "./return-to.js";
@@ -57,8 +58,7 @@ export function navigateActionSearchParams(action, values) {
57
58
  }
58
59
  export async function refetchAfterWrite(refetch) {
59
60
  await refetch().catch((err) => {
60
- // biome-ignore lint/suspicious/noConsole: refetch must not poison the write-action error path
61
- console.error("kumiko-screen: refetch after write action failed", err);
61
+ rendererError("kumiko-screen: refetch after write action failed", err);
62
62
  });
63
63
  }
64
64
  // Navigate execution for query-driven rows (projectionList, and relatedList
@@ -118,8 +118,7 @@ function warnDrawerActionDropped(actionKind, actionId) {
118
118
  if (warnedDrawerActionIds.has(key))
119
119
  return;
120
120
  warnedDrawerActionIds.add(key);
121
- // biome-ignore lint/suspicious/noConsole: dev-warning for a setup error
122
- console.warn(`[kumiko] ${actionKind} "${actionId}" is kind:"drawer", but the host did not wire openDrawer (RelatedListSection: pass onOpenDrawer) — it will not render.`);
121
+ rendererWarn(`[kumiko] ${actionKind} "${actionId}" is kind:"drawer", but the host did not wire openDrawer (RelatedListSection: pass onOpenDrawer) — it will not render.`);
123
122
  }
124
123
  export function runDrawerRowAction(action, openDrawer, row) {
125
124
  openDrawer(action, action.params !== undefined ? evalRowExtractor(action.params, row.values) : undefined);
@@ -0,0 +1,72 @@
1
+ import type { ActionFormRedirect, ActionFormScreenDefinition, ActionFormSuccessLink, ScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import { type ReactNode } from "react";
3
+ import type { Dispatcher, Translate } from "../headless/index.ts";
4
+ import { type ListSort } from "../hooks/use-list-url-state.ts";
5
+ import type { ResolvedSuccessLink } from "./action-form-support.tsx";
6
+ import type { FeatureSchema } from "./feature-schema.ts";
7
+ import { type NavApi, type ScreenTarget } from "./nav.tsx";
8
+ /** Empty state for a screen QN that is not in the schema (feature disabled for
9
+ * the tenant, stale link, miswired screen). The QN only goes to the console. */
10
+ export declare function ScreenMissingState({ screenQn, homeScreenId, testId, }: {
11
+ readonly screenQn: string;
12
+ readonly homeScreenId?: string;
13
+ readonly testId?: string;
14
+ }): ReactNode;
15
+ export declare function ScreenAccessDeniedBanner({ screenQn, testId, }: {
16
+ readonly screenQn: string;
17
+ readonly testId: string;
18
+ }): ReactNode;
19
+ export declare function entityWriteCommand(featureName: string, entity: string, verb: "create" | "update" | "delete"): string;
20
+ export declare function findEntityListScreen(schema: FeatureSchema, entityName: string): ScreenDefinition | undefined;
21
+ export declare function RecordNotFoundState({ translate, onBack, }: {
22
+ readonly translate: Translate;
23
+ readonly onBack?: () => void;
24
+ }): ReactNode;
25
+ export declare function useNavigateToListAfter(schema: FeatureSchema, entityName: string): () => void;
26
+ export declare function pageSizeOptionsFor(currentLimit: number): readonly number[];
27
+ export declare function useNavigateToCreateFor(schema: FeatureSchema, entityName: string, createScreen?: string, createUnavailable?: boolean): (() => void) | undefined;
28
+ export declare function formatSummaryParams(prefill: Readonly<Record<string, unknown>> | undefined, locale: string): Record<string, string>;
29
+ export declare function resolveSuccessMessageParams(screen: ActionFormScreenDefinition, featureName: string, values: Readonly<Record<string, unknown>>, dispatcher: Dispatcher, locale: string): Promise<Record<string, string>>;
30
+ export declare function resolveActionFormSummary(summary: ActionFormScreenDefinition["summary"], prefill: Readonly<Record<string, unknown>> | undefined, translate: Translate, locale: string): {
31
+ readonly title: string;
32
+ readonly subtitle?: string;
33
+ } | undefined;
34
+ export declare function entityQueryCommand(featureName: string, entity: string, verb: "list"): string;
35
+ export type PagedRows = {
36
+ readonly rows: Readonly<Record<string, unknown>>[];
37
+ readonly nextCursor: string | null;
38
+ readonly total?: number;
39
+ };
40
+ export declare function buildListQueryPayload(state: {
41
+ readonly limit: number;
42
+ readonly search: string;
43
+ readonly sort: ListSort | null;
44
+ readonly usePager: boolean;
45
+ readonly page: number;
46
+ readonly useInfinite: boolean;
47
+ readonly cursor: string | undefined;
48
+ }): Record<string, unknown>;
49
+ type RedirectSpec = string | ActionFormRedirect | ActionFormSuccessLink;
50
+ export declare function redirectScreenTarget(redirect: RedirectSpec): string;
51
+ export declare function isEntityEditScreenOfEntity(entity: string | undefined, targetScreen: string, schema: FeatureSchema, appFeatures: readonly FeatureSchema[]): boolean;
52
+ export declare function isEntityEditScreenOfOtherEntity(entity: string, targetScreen: string, schema: FeatureSchema, appFeatures: readonly FeatureSchema[]): boolean;
53
+ export declare function returnToWinsOverRedirect(returnTarget: ScreenTarget | undefined, redirect: string | ActionFormRedirect, schema: FeatureSchema, appFeatures: readonly FeatureSchema[]): returnTarget is ScreenTarget;
54
+ export declare function resolveRedirectTarget(redirect: RedirectSpec, submittedData: unknown, schema: FeatureSchema, appFeatures: readonly FeatureSchema[], fallbackRecord?: Readonly<Record<string, unknown>>): ResolvedRedirectTarget;
55
+ type ResolvedRedirectTarget = {
56
+ readonly screenId: string;
57
+ readonly entityId: string | undefined;
58
+ readonly tab: string | undefined;
59
+ };
60
+ export declare function navigateToRedirectTarget(nav: NavApi, target: ResolvedRedirectTarget): void;
61
+ export declare function resolveSuccessLinks(links: readonly ActionFormSuccessLink[] | undefined, data: unknown, schema: FeatureSchema, appFeatures: readonly FeatureSchema[], nav: NavApi, translate: Translate): readonly ResolvedSuccessLink[];
62
+ export declare function navigateAfterRedirect(options: {
63
+ readonly nav: NavApi;
64
+ readonly schema: FeatureSchema;
65
+ readonly appFeatures: readonly FeatureSchema[];
66
+ readonly returnTarget: ScreenTarget | undefined;
67
+ readonly redirect: string | ActionFormRedirect;
68
+ readonly resultData: unknown;
69
+ readonly record: Readonly<Record<string, unknown>>;
70
+ }): void;
71
+ export declare function overlayUpdatedProjection(record: Readonly<Record<string, unknown>>, resultData: unknown): Readonly<Record<string, unknown>>;
72
+ export {};