@asteby/metacore-runtime-react 18.19.0 → 18.21.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 18.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 53950ed: CollectionCell renders jsonb line-items from a declared sub-field schema when
8
+ the column carries one (`col.itemFields` / snake `col.item_fields`, kernel v3
9
+ `item_fields`). Headers use the schema's already-localized `label` verbatim (in
10
+ the declared order, no prettify/translate); `ref` columns resolve to the
11
+ backend-injected sibling label — the FK key without `_id` (`product_id` →
12
+ `product`), else `<key>_label` — showing the resolved name instead of the raw
13
+ uuid (`{ value, label }` → `label`, bare string → itself, missing → truncated
14
+ uuid fallback). The badge count noun stays locale-aware. When no schema is
15
+ present the generic dict/prettify behaviour is unchanged. `itemFields` is
16
+ threaded from the dynamic columns factory callsite.
17
+
18
+ ## 18.20.0
19
+
20
+ ### Minor Changes
21
+
22
+ - 6ec7baf: CollectionCell is now locale-aware: jsonb/array popover headers and the item
23
+ count noun render in the org's language. Resolution per key: host `t(rawKey)`
24
+ override → built-in es/en dictionary of common data/commerce keys (product_id,
25
+ quantity, price, total, name, sku, …) → snake→Title prettify fallback. Count
26
+ noun localizes (es: ítem/ítems). Locale + translator are threaded from the
27
+ dynamic columns factory; defaults to English when absent.
28
+
3
29
  ## 18.19.0
4
30
 
5
31
  ### Minor Changes
@@ -1,6 +1,37 @@
1
1
  import * as React from 'react';
2
- /** snake_case / dotted / kebab key Title Case (`product_id` → "Product ID"). */
3
- export declare function prettifyKey(key: string): string;
2
+ /** Host i18n translator (react-i18next `t`), as threaded into the columns factory. */
3
+ export type Translate = (key: string, options?: any) => string;
4
+ /**
5
+ * Declared schema for one column of a jsonb line-items array. Mirrors the
6
+ * kernel v3 `item_fields` entry the backend serves on the column metadata
7
+ * (`col.itemFields` / snake `col.item_fields`). When present it drives the
8
+ * popover mini-table: headers come from `label` (already LOCALIZED by the
9
+ * backend — never re-translated here) and `ref` columns resolve to the
10
+ * backend-injected sibling label instead of the raw uuid.
11
+ */
12
+ export interface ItemField {
13
+ /** jsonb key this column maps to (e.g. `product_id`, `quantity`). */
14
+ key: string;
15
+ /** Header text — ALREADY localized by the backend. Used verbatim. */
16
+ label: string;
17
+ /** Declarative cell type hint (informational; not branched on today). */
18
+ type?: string;
19
+ /** FK target model. When set, the cell renders the resolved sibling label. */
20
+ ref?: string;
21
+ }
22
+ /**
23
+ * Localized key label for a popover column header. Resolution order:
24
+ * (a) host i18n `t(rawKey)` if it resolves to something ≠ rawKey;
25
+ * (b) the built-in generic es/en data/commerce dictionary;
26
+ * (c) snake_case → Title Case prettify (`product_id` → "Product ID").
27
+ * `locale` defaults to 'en' when absent.
28
+ */
29
+ export declare function prettifyKey(key: string, locale?: string, t?: Translate): string;
30
+ /**
31
+ * Localized, pluralized count noun. Prefers the host i18n `t` (react-i18next
32
+ * count plural via `defaultValue`); otherwise the built-in es/en noun map.
33
+ */
34
+ export declare function countLabel(count: number, locale?: string, t?: Translate): string;
4
35
  /**
5
36
  * Render a single scalar (or near-scalar) value for compact display.
6
37
  * - uuid-like or very long (32+ char) strings → first 8 chars + "…"
@@ -13,10 +44,23 @@ export interface CollectionCellProps {
13
44
  value: unknown;
14
45
  /** Max items previewed inline for scalar arrays. */
15
46
  maxInline?: number;
47
+ /** Org/UI language tag (e.g. `es`, `en-US`). Defaults to `'en'`. */
48
+ locale?: string;
49
+ /** Host i18n translator; takes precedence over the built-in dictionary. */
50
+ t?: Translate;
51
+ /**
52
+ * Declared schema for the jsonb line-items columns (kernel v3 `item_fields`,
53
+ * read from `col.itemFields ?? col.item_fields` at the callsite). When
54
+ * present AND the value is an array of objects, the popover mini-table uses
55
+ * these (already-localized) headers in order and resolves `ref` columns to
56
+ * the backend-injected sibling label. Absent → the generic dict/prettify
57
+ * behaviour is unchanged.
58
+ */
59
+ itemFields?: ItemField[];
16
60
  }
17
61
  /**
18
62
  * Generic renderer for jsonb / array / object cell values. Brand-neutral,
19
- * compact, dark-mode friendly. Never throws on unexpected shapes.
63
+ * compact, dark-mode friendly, locale-aware. Never throws on unexpected shapes.
20
64
  */
21
- export declare function CollectionCell({ value, maxInline }: CollectionCellProps): React.JSX.Element;
65
+ export declare function CollectionCell({ value, maxInline, locale, t, itemFields, }: CollectionCellProps): React.JSX.Element;
22
66
  //# sourceMappingURL=collection-cell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"collection-cell.d.ts","sourceRoot":"","sources":["../src/collection-cell.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAoB9B,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAYnD;AAyID,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,OAAO,CAAA;IACd,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,SAAa,EAAE,EAAE,mBAAmB,qBA6E3E"}
1
+ {"version":3,"file":"collection-cell.d.ts","sourceRoot":"","sources":["../src/collection-cell.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAoB9B,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,MAAM,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAmGD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACvB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAWR;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACtB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAcR;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAYnD;AAiMD,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,OAAO,CAAA;IACd,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2EAA2E;IAC3E,CAAC,CAAC,EAAE,SAAS,CAAA;IACb;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;CAC3B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,SAAa,EACb,MAAM,EACN,CAAC,EACD,UAAU,GACb,EAAE,mBAAmB,qBAoGrB"}
@@ -3,11 +3,136 @@ import { List } from 'lucide-react';
3
3
  import { Badge, Popover, PopoverContent, PopoverTrigger, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, cn, } from '@asteby/metacore-ui';
4
4
  import { humanizeToken } from './dynamic-columns-helpers';
5
5
  const UUID_LIKE_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
- /** snake_case / dotted / kebab key → Title Case (`product_id` → "Product ID"). */
7
- export function prettifyKey(key) {
6
+ /**
7
+ * Resolves the backend-injected resolved sibling key for a ref item-field,
8
+ * mirroring `relationKeyFor` in dynamic-columns: the raw key with a trailing
9
+ * `_id` stripped (`product_id` → `product`), else `<key>_label`.
10
+ */
11
+ function siblingKeyFor(key) {
12
+ return key.endsWith('_id') ? key.slice(0, -3) : `${key}_label`;
13
+ }
14
+ /**
15
+ * Renders the cell value for one declared item-field of a jsonb row. For a
16
+ * `ref` field it prefers the backend-injected resolved sibling (the FK key
17
+ * without `_id`, else `<key>_label`): a `{ value, label }` object shows its
18
+ * `label`, a bare string shows itself; absent → the raw value via
19
+ * `formatScalar` (truncated uuid). Non-ref fields render `formatScalar(value)`.
20
+ */
21
+ function renderItemFieldValue(field, row) {
22
+ if (field.ref) {
23
+ const sibling = row[siblingKeyFor(field.key)];
24
+ if (sibling && typeof sibling === 'object' && !Array.isArray(sibling)) {
25
+ const label = sibling.label;
26
+ if (label !== undefined && label !== null && label !== '') {
27
+ return String(label);
28
+ }
29
+ }
30
+ else if (typeof sibling === 'string' && sibling !== '') {
31
+ return sibling;
32
+ }
33
+ }
34
+ return formatScalar(row[field.key]);
35
+ }
36
+ /** Normalize an org/UI language tag to a base language code (`es-MX` → `es`). */
37
+ function baseLang(locale) {
38
+ return (locale || 'en').toLowerCase().split('-')[0];
39
+ }
40
+ // Built-in generic data/commerce vocabulary. Localizes the common jsonb keys
41
+ // (line-item rows, config blobs) to the org language out of the box. This is
42
+ // intentionally GENERIC — no domain-specific narrative — and a host i18n bundle
43
+ // can override any key via the `t` resolver (which takes precedence). Keys not
44
+ // found here fall back to snake→Title prettify, so unknown shapes still read.
45
+ const KEY_DICTIONARY = {
46
+ es: {
47
+ product_id: 'Producto',
48
+ product: 'Producto',
49
+ quantity: 'Cantidad',
50
+ qty: 'Cantidad',
51
+ unit_cost: 'Costo unitario',
52
+ cost: 'Costo',
53
+ price: 'Precio',
54
+ total: 'Total',
55
+ subtotal: 'Subtotal',
56
+ amount: 'Importe',
57
+ name: 'Nombre',
58
+ sku: 'SKU',
59
+ code: 'Código',
60
+ date: 'Fecha',
61
+ notes: 'Notas',
62
+ reason: 'Motivo',
63
+ delta: 'Variación',
64
+ warehouse: 'Almacén',
65
+ description: 'Descripción',
66
+ id: 'ID',
67
+ },
68
+ en: {
69
+ product_id: 'Product',
70
+ product: 'Product',
71
+ quantity: 'Quantity',
72
+ qty: 'Quantity',
73
+ unit_cost: 'Unit Cost',
74
+ cost: 'Cost',
75
+ price: 'Price',
76
+ total: 'Total',
77
+ subtotal: 'Subtotal',
78
+ amount: 'Amount',
79
+ name: 'Name',
80
+ sku: 'SKU',
81
+ code: 'Code',
82
+ date: 'Date',
83
+ notes: 'Notes',
84
+ reason: 'Reason',
85
+ delta: 'Delta',
86
+ warehouse: 'Warehouse',
87
+ description: 'Description',
88
+ id: 'ID',
89
+ },
90
+ };
91
+ /** Localized count noun for the array-of-objects badge (`1 ítem` / `2 ítems`). */
92
+ const ITEM_NOUN = {
93
+ es: { one: 'ítem', other: 'ítems' },
94
+ en: { one: 'item', other: 'items' },
95
+ };
96
+ /**
97
+ * Localized key label for a popover column header. Resolution order:
98
+ * (a) host i18n `t(rawKey)` if it resolves to something ≠ rawKey;
99
+ * (b) the built-in generic es/en data/commerce dictionary;
100
+ * (c) snake_case → Title Case prettify (`product_id` → "Product ID").
101
+ * `locale` defaults to 'en' when absent.
102
+ */
103
+ export function prettifyKey(key, locale, t) {
104
+ if (t) {
105
+ const translated = t(key);
106
+ if (translated && translated !== key)
107
+ return translated;
108
+ }
109
+ const lang = baseLang(locale);
110
+ const dict = KEY_DICTIONARY[lang] ?? KEY_DICTIONARY.en;
111
+ const hit = dict[key.toLowerCase()];
112
+ if (hit)
113
+ return hit;
8
114
  const pretty = humanizeToken(key);
9
115
  return pretty || key;
10
116
  }
117
+ /**
118
+ * Localized, pluralized count noun. Prefers the host i18n `t` (react-i18next
119
+ * count plural via `defaultValue`); otherwise the built-in es/en noun map.
120
+ */
121
+ export function countLabel(count, locale, t) {
122
+ const lang = baseLang(locale);
123
+ const noun = ITEM_NOUN[lang] ?? ITEM_NOUN.en;
124
+ const fallback = `${count} ${count === 1 ? noun.one : noun.other}`;
125
+ if (t) {
126
+ const translated = t('runtime.collectionCell.itemCount', {
127
+ count,
128
+ defaultValue: fallback,
129
+ });
130
+ if (translated && translated !== 'runtime.collectionCell.itemCount') {
131
+ return translated;
132
+ }
133
+ }
134
+ return fallback;
135
+ }
11
136
  /**
12
137
  * Render a single scalar (or near-scalar) value for compact display.
13
138
  * - uuid-like or very long (32+ char) strings → first 8 chars + "…"
@@ -69,18 +194,26 @@ function unionKeys(rows) {
69
194
  return seen;
70
195
  }
71
196
  const PANEL_CLASS = 'w-auto max-w-[480px] max-h-[320px] overflow-auto p-0';
72
- function MiniTable({ rows }) {
197
+ function MiniTable({ rows, locale, t, itemFields, }) {
198
+ // Schema-driven path: a declared `item_fields` schema fixes the column
199
+ // order + headers (already localized by the backend, used VERBATIM) and
200
+ // resolves ref columns to the injected sibling label instead of the raw
201
+ // uuid. Sibling/raw keys not covered by the schema are dropped from the
202
+ // table (the schema is the source of truth for what to surface).
203
+ if (itemFields && itemFields.length > 0) {
204
+ return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: itemFields.map((field) => (_jsx(TableHead, { className: "text-xs whitespace-nowrap", children: field.label }, field.key))) }) }), _jsx(TableBody, { children: rows.map((row, i) => (_jsx(TableRow, { children: itemFields.map((field) => (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: renderItemFieldValue(field, row) }, field.key))) }, i))) })] }));
205
+ }
73
206
  const keys = unionKeys(rows);
74
207
  if (keys.length === 0) {
75
208
  return _jsx("div", { className: "p-3 text-xs text-muted-foreground", children: "-" });
76
209
  }
77
- return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: keys.map((key) => (_jsx(TableHead, { className: "text-xs whitespace-nowrap", children: prettifyKey(key) }, key))) }) }), _jsx(TableBody, { children: rows.map((row, i) => (_jsx(TableRow, { children: keys.map((key) => (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: formatScalar(row[key]) }, key))) }, i))) })] }));
210
+ return (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: keys.map((key) => (_jsx(TableHead, { className: "text-xs whitespace-nowrap", children: prettifyKey(key, locale, t) }, key))) }) }), _jsx(TableBody, { children: rows.map((row, i) => (_jsx(TableRow, { children: keys.map((key) => (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: formatScalar(row[key]) }, key))) }, i))) })] }));
78
211
  }
79
212
  function ScalarList({ values }) {
80
213
  return (_jsx("ul", { className: "p-3 space-y-1", children: values.map((v, i) => (_jsx("li", { className: "text-xs text-foreground", children: formatScalar(v) }, i))) }));
81
214
  }
82
- function PairList({ entries }) {
83
- return (_jsx("ul", { className: "p-3 space-y-1", children: entries.map(([key, v]) => (_jsxs("li", { className: "text-xs", children: [_jsxs("span", { className: "text-muted-foreground", children: [prettifyKey(key), ":"] }), ' ', _jsx("span", { className: "text-foreground", children: formatScalar(v) })] }, key))) }));
215
+ function PairList({ entries, locale, t, }) {
216
+ return (_jsx("ul", { className: "p-3 space-y-1", children: entries.map(([key, v]) => (_jsxs("li", { className: "text-xs", children: [_jsxs("span", { className: "text-muted-foreground", children: [prettifyKey(key, locale, t), ":"] }), ' ', _jsx("span", { className: "text-foreground", children: formatScalar(v) })] }, key))) }));
84
217
  }
85
218
  /** Compact badge trigger that opens a popover panel. */
86
219
  function PopoverShell({ label, title, children, icon = true, }) {
@@ -88,9 +221,9 @@ function PopoverShell({ label, title, children, icon = true, }) {
88
221
  }
89
222
  /**
90
223
  * Generic renderer for jsonb / array / object cell values. Brand-neutral,
91
- * compact, dark-mode friendly. Never throws on unexpected shapes.
224
+ * compact, dark-mode friendly, locale-aware. Never throws on unexpected shapes.
92
225
  */
93
- export function CollectionCell({ value, maxInline = 3 }) {
226
+ export function CollectionCell({ value, maxInline = 3, locale, t, itemFields, }) {
94
227
  const parsed = parseValue(value);
95
228
  // Empty-ish → muted dash.
96
229
  if (parsed === null ||
@@ -111,13 +244,23 @@ export function CollectionCell({ value, maxInline = 3 }) {
111
244
  if (allObjects) {
112
245
  const rows = parsed;
113
246
  const count = rows.length;
114
- const label = count === 1 ? '1 ítem' : `${count} ítems`;
115
- const title = rows
116
- .map((row) => Object.entries(row)
117
- .map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
118
- .join(', '))
119
- .join(' | ');
120
- return (_jsx(PopoverShell, { label: label, title: title, children: _jsx(MiniTable, { rows: rows }) }));
247
+ const label = countLabel(count, locale, t);
248
+ const hasSchema = !!(itemFields && itemFields.length > 0);
249
+ // The no-JS tooltip mirrors the rendered table: schema-driven
250
+ // labels + resolved ref values when a schema is present, else the
251
+ // generic prettify/scalar pairs.
252
+ const title = hasSchema
253
+ ? rows
254
+ .map((row) => itemFields
255
+ .map((field) => `${field.label}: ${renderItemFieldValue(field, row)}`)
256
+ .join(', '))
257
+ .join(' | ')
258
+ : rows
259
+ .map((row) => Object.entries(row)
260
+ .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
261
+ .join(', '))
262
+ .join(' | ');
263
+ return (_jsx(PopoverShell, { label: label, title: title, children: _jsx(MiniTable, { rows: rows, locale: locale, t: t, itemFields: itemFields }) }));
121
264
  }
122
265
  // Array of scalars (or mixed): preview first N joined, "+N" overflow.
123
266
  const preview = parsed.slice(0, maxInline).map(formatScalar).join(', ');
@@ -130,12 +273,12 @@ export function CollectionCell({ value, maxInline = 3 }) {
130
273
  const entries = Object.entries(parsed);
131
274
  const inline = entries
132
275
  .slice(0, maxInline)
133
- .map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
276
+ .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
134
277
  .join(', ');
135
278
  const overflow = entries.length - maxInline;
136
279
  const label = overflow > 0 ? `${inline} +${overflow}` : inline;
137
280
  const title = entries
138
- .map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
281
+ .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
139
282
  .join(', ');
140
- return (_jsx(PopoverShell, { label: label, title: title, icon: false, children: _jsx(PairList, { entries: entries }) }));
283
+ return (_jsx(PopoverShell, { label: label, title: title, icon: false, children: _jsx(PairList, { entries: entries, locale: locale, t: t }) }));
141
284
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAiC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AA8DD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAqKD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2DAA4D,CAAA;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6C5C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOtE,CAAA;AAsID;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAunBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
1
+ {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAiC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AA8DD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAqKD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2DAA4D,CAAA;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6C5C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOtE,CAAA;AAsID;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CA8nBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
@@ -736,7 +736,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
736
736
  }
737
737
  default: {
738
738
  if (typeof value === 'object' && value !== null) {
739
- return _jsx(CollectionCell, { value: value });
739
+ return (_jsx(CollectionCell, { value: value, locale: currentLanguage, t: t, itemFields: col.itemFields ?? col.item_fields }));
740
740
  }
741
741
  if (col.key === 'description' ||
742
742
  col.key === 'features' ||
package/dist/index.d.ts CHANGED
@@ -22,7 +22,7 @@ export * from './dynamic-icon';
22
22
  export type { ColumnFilterConfig, FilterOption as DynamicColumnFilterOption, GetDynamicColumns, DynamicIconComponent, } from './dynamic-columns-shim';
23
23
  export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, relationKeyFor, resolveRelationLabel, type DynamicColumnsHelpers, } from './dynamic-columns';
24
24
  export { humanizeToken } from './dynamic-columns-helpers';
25
- export { CollectionCell, formatScalar, prettifyKey, type CollectionCellProps, } from './collection-cell';
25
+ export { CollectionCell, formatScalar, prettifyKey, countLabel, type CollectionCellProps, type Translate as CollectionCellTranslate, } from './collection-cell';
26
26
  export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid';
27
27
  export { DynamicRecordDialog, ViewValue } from './dialogs/dynamic-record';
28
28
  export type { DynamicRecordDialogProps, FieldDef, FieldOption, GetImageUrl } from './dialogs/dynamic-record';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,SAAS,GACjB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,cAAc,EACd,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,KAAK,mBAAmB,GAC3B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzE,YAAY,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC5G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,0BAA0B,GAClC,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,GAC1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,gBAAgB,EAChB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,aAAa,EACb,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACR,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,UAAU,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,KAAK,iBAAiB,GACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,cAAc,EACd,cAAc,EACd,SAAS,EACT,UAAU,EACV,KAAK,mBAAmB,GAC3B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,UAAU,EACV,SAAS,EACT,WAAW,EACX,WAAW,EACX,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ export { useHotSwapReload, applyHotSwapReload, withVersionParam, clearFederation
26
26
  export * from './dynamic-icon';
27
27
  export { defaultGetDynamicColumns, makeDefaultGetDynamicColumns, relationKeyFor, resolveRelationLabel, } from './dynamic-columns';
28
28
  export { humanizeToken } from './dynamic-columns-helpers';
29
- export { CollectionCell, formatScalar, prettifyKey, } from './collection-cell';
29
+ export { CollectionCell, formatScalar, prettifyKey, countLabel, } from './collection-cell';
30
30
  export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid';
31
31
  export { DynamicRecordDialog, ViewValue } from './dialogs/dynamic-record';
32
32
  export { CreateRecordDialog } from './dialogs/create-record-dialog';
package/dist/types.d.ts CHANGED
@@ -137,6 +137,32 @@ export interface ColumnDefinition {
137
137
  * reference resolved through the OrgConfigProvider.
138
138
  */
139
139
  validation?: FieldValidation;
140
+ /**
141
+ * Declared schema for a jsonb line-items column (kernel v3 `item_fields`).
142
+ * Each entry describes one sub-field of the array's row objects: a `key`
143
+ * (the jsonb key), an already-LOCALIZED `label` (backend-translated), an
144
+ * optional `type` hint and an optional `ref` (FK target). When present the
145
+ * `CollectionCell` renders the popover mini-table with these headers in
146
+ * order and resolves `ref` columns to the backend-injected sibling label
147
+ * (the FK key without `_id`, else `<key>_label`) instead of the raw uuid.
148
+ * Tolerates the snake_case `item_fields` the kernel serves.
149
+ */
150
+ itemFields?: ColumnItemField[];
151
+ /** snake_case alias served by the kernel for `itemFields`. */
152
+ item_fields?: ColumnItemField[];
153
+ }
154
+ /**
155
+ * One declared sub-field of a jsonb line-items column (see
156
+ * `ColumnDefinition.itemFields`). `label` is already localized by the backend
157
+ * and consumed verbatim; a non-empty `ref` flags the column for resolved-label
158
+ * rendering against the injected sibling. Structurally compatible with the
159
+ * `ItemField` consumed by `collection-cell`.
160
+ */
161
+ export interface ColumnItemField {
162
+ key: string;
163
+ label: string;
164
+ type?: string;
165
+ ref?: string;
140
166
  }
141
167
  export interface ActionCondition {
142
168
  field: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "18.19.0",
3
+ "version": "18.21.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -6,14 +6,17 @@
6
6
  // - plain object → inline key: value pairs
7
7
  // - null / empty → "-"
8
8
  // - JSON-string value is defensively parsed
9
+ // - locale-aware: count noun + header keys render in the org language; the
10
+ // host `t` overrides; unknown keys fall back to snake→Title prettify.
9
11
  import { afterEach, describe, expect, it } from 'vitest'
10
- import { cleanup, render, screen } from '@testing-library/react'
12
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react'
11
13
 
12
14
  // Sin `globals: true` en vitest, RTL no auto-limpia entre tests.
13
15
  afterEach(cleanup)
14
16
 
15
17
  import {
16
18
  CollectionCell,
19
+ countLabel,
17
20
  formatScalar,
18
21
  prettifyKey,
19
22
  } from '../collection-cell'
@@ -41,14 +44,50 @@ describe('formatScalar', () => {
41
44
  })
42
45
 
43
46
  describe('prettifyKey', () => {
44
- it('snake_case Title Case with acronyms', () => {
45
- expect(prettifyKey('product_id')).toBe('Product ID')
46
- expect(prettifyKey('quantity')).toBe('Quantity')
47
+ it('localizes common data/commerce keys to Spanish', () => {
48
+ expect(prettifyKey('product_id', 'es')).toBe('Producto')
49
+ expect(prettifyKey('quantity', 'es')).toBe('Cantidad')
50
+ })
51
+
52
+ it('localizes common keys to English (default locale)', () => {
53
+ expect(prettifyKey('product_id')).toBe('Product')
54
+ expect(prettifyKey('product_id', 'en')).toBe('Product')
55
+ expect(prettifyKey('quantity', 'en')).toBe('Quantity')
56
+ })
57
+
58
+ it('accepts a regional tag and normalizes to base language', () => {
59
+ expect(prettifyKey('quantity', 'es-MX')).toBe('Cantidad')
60
+ })
61
+
62
+ it('prefers a host `t` translation over the built-in dictionary', () => {
63
+ const t = (k: string) => (k === 'quantity' ? 'Piezas' : k)
64
+ expect(prettifyKey('quantity', 'es', t)).toBe('Piezas')
65
+ })
66
+
67
+ it('falls back to snake→Title prettify for unknown keys', () => {
68
+ expect(prettifyKey('shelf_position', 'es')).toBe('Shelf Position')
69
+ expect(prettifyKey('warehouse_bin', 'en')).toBe('Warehouse Bin')
70
+ })
71
+ })
72
+
73
+ describe('countLabel', () => {
74
+ it('pluralizes the count noun per locale', () => {
75
+ expect(countLabel(1, 'es')).toBe('1 ítem')
76
+ expect(countLabel(2, 'es')).toBe('2 ítems')
77
+ expect(countLabel(1, 'en')).toBe('1 item')
78
+ expect(countLabel(3, 'en')).toBe('3 items')
79
+ expect(countLabel(1)).toBe('1 item') // default → en
80
+ })
81
+
82
+ it('prefers a host `t` count plural', () => {
83
+ const t = (_k: string, o?: any) =>
84
+ o?.count === 1 ? '1 renglón' : `${o?.count} renglones`
85
+ expect(countLabel(2, 'es', t)).toBe('2 renglones')
47
86
  })
48
87
  })
49
88
 
50
89
  describe('CollectionCell', () => {
51
- it('renders a count badge for an array of objects', () => {
90
+ it('renders an English count badge by default for an array of objects', () => {
52
91
  render(
53
92
  <CollectionCell
54
93
  value={[
@@ -57,32 +96,55 @@ describe('CollectionCell', () => {
57
96
  ]}
58
97
  />
59
98
  )
60
- // Count badge (plural).
61
- expect(screen.getByText('2 ítems')).toBeTruthy()
62
- // The trigger's title carries the formatted rows for the no-JS fallback.
63
- const badge = screen.getByText('2 ítems').closest('[title]')
99
+ // Default locale → English plural.
100
+ expect(screen.getByText('2 items')).toBeTruthy()
101
+ // The trigger's title carries the localized rows for the no-JS fallback.
102
+ const badge = screen.getByText('2 items').closest('[title]')
64
103
  expect(badge).toBeTruthy()
65
104
  const title = badge!.getAttribute('title') ?? ''
66
105
  expect(title).toContain('Quantity: 2')
67
106
  expect(title).toContain('Quantity: 5')
68
- expect(title).toContain('Product ID')
107
+ expect(title).toContain('Product:') // product_id → "Product" (en)
69
108
  })
70
109
 
71
- it('renders singular label for a single-item array', () => {
72
- render(<CollectionCell value={[{ sku: 'A1' }]} />)
110
+ it('renders Spanish count + headers when locale is es', () => {
111
+ render(
112
+ <CollectionCell
113
+ locale="es"
114
+ value={[
115
+ { product_id: 'abc', quantity: 2 },
116
+ { product_id: 'def', quantity: 5 },
117
+ ]}
118
+ />
119
+ )
120
+ expect(screen.getByText('2 ítems')).toBeTruthy()
121
+ const title =
122
+ screen.getByText('2 ítems').closest('[title]')!.getAttribute('title') ??
123
+ ''
124
+ expect(title).toContain('Producto:')
125
+ expect(title).toContain('Cantidad: 2')
126
+ })
127
+
128
+ it('renders the singular Spanish noun for a single-item array', () => {
129
+ render(<CollectionCell locale="es" value={[{ sku: 'A1' }]} />)
73
130
  expect(screen.getByText('1 ítem')).toBeTruthy()
74
131
  })
75
132
 
133
+ it('renders the singular English noun for a single-item array', () => {
134
+ render(<CollectionCell value={[{ sku: 'A1' }]} />)
135
+ expect(screen.getByText('1 item')).toBeTruthy()
136
+ })
137
+
76
138
  it('previews the first scalars with overflow for a scalar array', () => {
77
139
  render(<CollectionCell value={['a', 'b', 'c', 'd', 'e']} />)
78
140
  expect(screen.getByText('a, b, c +2')).toBeTruthy()
79
141
  })
80
142
 
81
- it('renders inline key: value pairs for a plain object', () => {
82
- render(<CollectionCell value={{ width: 10, height: 20 }} />)
83
- expect(
84
- screen.getByText(/Width: 10, Height: 20/)
85
- ).toBeTruthy()
143
+ it('renders inline key: value pairs (localized) for a plain object', () => {
144
+ render(
145
+ <CollectionCell locale="es" value={{ price: 10, quantity: 20 }} />
146
+ )
147
+ expect(screen.getByText(/Precio: 10, Cantidad: 20/)).toBeTruthy()
86
148
  })
87
149
 
88
150
  it('renders a dash for null / empty values', () => {
@@ -98,12 +160,13 @@ describe('CollectionCell', () => {
98
160
  it('parses a JSON-string value into a collection', () => {
99
161
  render(
100
162
  <CollectionCell
163
+ locale="es"
101
164
  value={'[{"product_id":"abc","quantity":1}]'}
102
165
  />
103
166
  )
104
167
  expect(screen.getByText('1 ítem')).toBeTruthy()
105
168
  const badge = screen.getByText('1 ítem').closest('[title]')
106
- expect(badge!.getAttribute('title')).toContain('Quantity: 1')
169
+ expect(badge!.getAttribute('title')).toContain('Cantidad: 1')
107
170
  })
108
171
 
109
172
  it('truncates an unparseable string instead of crashing', () => {
@@ -113,3 +176,156 @@ describe('CollectionCell', () => {
113
176
  expect(container.textContent).toContain('{not valid json')
114
177
  })
115
178
  })
179
+
180
+ describe('CollectionCell with itemFields schema', () => {
181
+ const itemFields = [
182
+ { key: 'product_id', label: 'Producto', ref: 'Product' },
183
+ { key: 'quantity', label: 'Cantidad' },
184
+ ]
185
+
186
+ // The popover mini-table mounts lazily — open it by clicking the count
187
+ // badge (Radix opens on pointerDown + click under happy-dom).
188
+ const openPopover = (badgeText: string) => {
189
+ const badge = screen.getByText(badgeText)
190
+ fireEvent.pointerDown(badge)
191
+ fireEvent.click(badge)
192
+ return badge
193
+ }
194
+
195
+ it('uses the schema labels verbatim as headers (no prettify/dict)', () => {
196
+ render(
197
+ <CollectionCell
198
+ locale="es"
199
+ itemFields={itemFields}
200
+ value={[
201
+ {
202
+ product_id: '550e8400-e29b-41d4-a716-446655440000',
203
+ product: { value: '550e8400-e29b-41d4-a716-446655440000', label: 'Llanta 195/65' },
204
+ quantity: 2,
205
+ },
206
+ ]}
207
+ />
208
+ )
209
+ openPopover('1 ítem')
210
+ // Headers come from the schema `label` verbatim.
211
+ expect(screen.getByRole('columnheader', { name: 'Producto' })).toBeTruthy()
212
+ expect(screen.getByRole('columnheader', { name: 'Cantidad' })).toBeTruthy()
213
+ })
214
+
215
+ it('resolves a ref field to the injected sibling label, not the uuid', () => {
216
+ render(
217
+ <CollectionCell
218
+ itemFields={itemFields}
219
+ value={[
220
+ {
221
+ product_id: '550e8400-e29b-41d4-a716-446655440000',
222
+ product: { value: '550e8400-e29b-41d4-a716-446655440000', label: 'Llanta 195/65' },
223
+ quantity: 2,
224
+ },
225
+ ]}
226
+ />
227
+ )
228
+ openPopover('1 item')
229
+ expect(screen.getByRole('cell', { name: 'Llanta 195/65' })).toBeTruthy()
230
+ // The raw uuid (truncated form) must NOT appear in any cell.
231
+ expect(screen.queryByText('550e8400…')).toBeNull()
232
+ })
233
+
234
+ it('resolves a ref field from a `<key>_label` sibling when key has no _id suffix', () => {
235
+ render(
236
+ <CollectionCell
237
+ itemFields={[{ key: 'product', label: 'Producto', ref: 'Product' }]}
238
+ value={[
239
+ {
240
+ product: '550e8400-e29b-41d4-a716-446655440000',
241
+ product_label: { value: '550e8400-e29b-41d4-a716-446655440000', label: 'Balanceo' },
242
+ },
243
+ ]}
244
+ />
245
+ )
246
+ openPopover('1 item')
247
+ expect(screen.getByRole('cell', { name: 'Balanceo' })).toBeTruthy()
248
+ })
249
+
250
+ it('accepts a bare string sibling for a ref field', () => {
251
+ render(
252
+ <CollectionCell
253
+ itemFields={[{ key: 'product_id', label: 'Producto', ref: 'Product' }]}
254
+ value={[{ product_id: 'x', product: 'Aceite 5W30' }]}
255
+ />
256
+ )
257
+ openPopover('1 item')
258
+ expect(screen.getByRole('cell', { name: 'Aceite 5W30' })).toBeTruthy()
259
+ })
260
+
261
+ it('falls back to a truncated uuid when the ref sibling is missing', () => {
262
+ render(
263
+ <CollectionCell
264
+ itemFields={[{ key: 'product_id', label: 'Producto', ref: 'Product' }]}
265
+ value={[{ product_id: '550e8400-e29b-41d4-a716-446655440000' }]}
266
+ />
267
+ )
268
+ openPopover('1 item')
269
+ expect(screen.getByRole('cell', { name: '550e8400…' })).toBeTruthy()
270
+ })
271
+
272
+ it('keeps the locale-aware count noun on the badge', () => {
273
+ render(
274
+ <CollectionCell
275
+ locale="es"
276
+ itemFields={itemFields}
277
+ value={[
278
+ { product_id: 'a', product: { value: 'a', label: 'A' }, quantity: 1 },
279
+ { product_id: 'b', product: { value: 'b', label: 'B' }, quantity: 2 },
280
+ ]}
281
+ />
282
+ )
283
+ expect(screen.getByText('2 ítems')).toBeTruthy()
284
+ })
285
+
286
+ it('renders non-ref fields via formatScalar under the schema header', () => {
287
+ render(
288
+ <CollectionCell
289
+ itemFields={itemFields}
290
+ value={[{ product_id: 'a', product: { value: 'a', label: 'A' }, quantity: 7 }]}
291
+ />
292
+ )
293
+ openPopover('1 item')
294
+ expect(screen.getByRole('cell', { name: '7' })).toBeTruthy()
295
+ })
296
+
297
+ it('mirrors the schema labels + resolved ref values in the badge title', () => {
298
+ render(
299
+ <CollectionCell
300
+ itemFields={itemFields}
301
+ value={[
302
+ {
303
+ product_id: '550e8400-e29b-41d4-a716-446655440000',
304
+ product: { value: 'x', label: 'Llanta 195/65' },
305
+ quantity: 2,
306
+ },
307
+ ]}
308
+ />
309
+ )
310
+ const title =
311
+ screen.getByText('1 item').closest('[title]')!.getAttribute('title') ?? ''
312
+ expect(title).toContain('Producto: Llanta 195/65')
313
+ expect(title).toContain('Cantidad: 2')
314
+ expect(title).not.toContain('550e8400')
315
+ })
316
+
317
+ it('is unchanged (generic prettify) when no itemFields are provided', () => {
318
+ render(
319
+ <CollectionCell
320
+ locale="es"
321
+ value={[{ product_id: 'abc', quantity: 2 }]}
322
+ />
323
+ )
324
+ // Generic dict path: the badge title carries the prettified headers and
325
+ // the raw (unresolved) values, exactly as before.
326
+ const title =
327
+ screen.getByText('1 ítem').closest('[title]')!.getAttribute('title') ?? ''
328
+ expect(title).toContain('Producto:')
329
+ expect(title).toContain('Cantidad: 2')
330
+ })
331
+ })
@@ -25,12 +25,173 @@ import { humanizeToken } from './dynamic-columns-helpers'
25
25
  const UUID_LIKE_RE =
26
26
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
27
27
 
28
- /** snake_case / dotted / kebab key Title Case (`product_id` → "Product ID"). */
29
- export function prettifyKey(key: string): string {
28
+ /** Host i18n translator (react-i18next `t`), as threaded into the columns factory. */
29
+ export type Translate = (key: string, options?: any) => string
30
+
31
+ /**
32
+ * Declared schema for one column of a jsonb line-items array. Mirrors the
33
+ * kernel v3 `item_fields` entry the backend serves on the column metadata
34
+ * (`col.itemFields` / snake `col.item_fields`). When present it drives the
35
+ * popover mini-table: headers come from `label` (already LOCALIZED by the
36
+ * backend — never re-translated here) and `ref` columns resolve to the
37
+ * backend-injected sibling label instead of the raw uuid.
38
+ */
39
+ export interface ItemField {
40
+ /** jsonb key this column maps to (e.g. `product_id`, `quantity`). */
41
+ key: string
42
+ /** Header text — ALREADY localized by the backend. Used verbatim. */
43
+ label: string
44
+ /** Declarative cell type hint (informational; not branched on today). */
45
+ type?: string
46
+ /** FK target model. When set, the cell renders the resolved sibling label. */
47
+ ref?: string
48
+ }
49
+
50
+ /**
51
+ * Resolves the backend-injected resolved sibling key for a ref item-field,
52
+ * mirroring `relationKeyFor` in dynamic-columns: the raw key with a trailing
53
+ * `_id` stripped (`product_id` → `product`), else `<key>_label`.
54
+ */
55
+ function siblingKeyFor(key: string): string {
56
+ return key.endsWith('_id') ? key.slice(0, -3) : `${key}_label`
57
+ }
58
+
59
+ /**
60
+ * Renders the cell value for one declared item-field of a jsonb row. For a
61
+ * `ref` field it prefers the backend-injected resolved sibling (the FK key
62
+ * without `_id`, else `<key>_label`): a `{ value, label }` object shows its
63
+ * `label`, a bare string shows itself; absent → the raw value via
64
+ * `formatScalar` (truncated uuid). Non-ref fields render `formatScalar(value)`.
65
+ */
66
+ function renderItemFieldValue(
67
+ field: ItemField,
68
+ row: Record<string, unknown>,
69
+ ): string {
70
+ if (field.ref) {
71
+ const sibling = row[siblingKeyFor(field.key)]
72
+ if (sibling && typeof sibling === 'object' && !Array.isArray(sibling)) {
73
+ const label = (sibling as Record<string, unknown>).label
74
+ if (label !== undefined && label !== null && label !== '') {
75
+ return String(label)
76
+ }
77
+ } else if (typeof sibling === 'string' && sibling !== '') {
78
+ return sibling
79
+ }
80
+ }
81
+ return formatScalar(row[field.key])
82
+ }
83
+
84
+ /** Normalize an org/UI language tag to a base language code (`es-MX` → `es`). */
85
+ function baseLang(locale?: string): string {
86
+ return (locale || 'en').toLowerCase().split('-')[0]
87
+ }
88
+
89
+ // Built-in generic data/commerce vocabulary. Localizes the common jsonb keys
90
+ // (line-item rows, config blobs) to the org language out of the box. This is
91
+ // intentionally GENERIC — no domain-specific narrative — and a host i18n bundle
92
+ // can override any key via the `t` resolver (which takes precedence). Keys not
93
+ // found here fall back to snake→Title prettify, so unknown shapes still read.
94
+ const KEY_DICTIONARY: Record<string, Record<string, string>> = {
95
+ es: {
96
+ product_id: 'Producto',
97
+ product: 'Producto',
98
+ quantity: 'Cantidad',
99
+ qty: 'Cantidad',
100
+ unit_cost: 'Costo unitario',
101
+ cost: 'Costo',
102
+ price: 'Precio',
103
+ total: 'Total',
104
+ subtotal: 'Subtotal',
105
+ amount: 'Importe',
106
+ name: 'Nombre',
107
+ sku: 'SKU',
108
+ code: 'Código',
109
+ date: 'Fecha',
110
+ notes: 'Notas',
111
+ reason: 'Motivo',
112
+ delta: 'Variación',
113
+ warehouse: 'Almacén',
114
+ description: 'Descripción',
115
+ id: 'ID',
116
+ },
117
+ en: {
118
+ product_id: 'Product',
119
+ product: 'Product',
120
+ quantity: 'Quantity',
121
+ qty: 'Quantity',
122
+ unit_cost: 'Unit Cost',
123
+ cost: 'Cost',
124
+ price: 'Price',
125
+ total: 'Total',
126
+ subtotal: 'Subtotal',
127
+ amount: 'Amount',
128
+ name: 'Name',
129
+ sku: 'SKU',
130
+ code: 'Code',
131
+ date: 'Date',
132
+ notes: 'Notes',
133
+ reason: 'Reason',
134
+ delta: 'Delta',
135
+ warehouse: 'Warehouse',
136
+ description: 'Description',
137
+ id: 'ID',
138
+ },
139
+ }
140
+
141
+ /** Localized count noun for the array-of-objects badge (`1 ítem` / `2 ítems`). */
142
+ const ITEM_NOUN: Record<string, { one: string; other: string }> = {
143
+ es: { one: 'ítem', other: 'ítems' },
144
+ en: { one: 'item', other: 'items' },
145
+ }
146
+
147
+ /**
148
+ * Localized key label for a popover column header. Resolution order:
149
+ * (a) host i18n `t(rawKey)` if it resolves to something ≠ rawKey;
150
+ * (b) the built-in generic es/en data/commerce dictionary;
151
+ * (c) snake_case → Title Case prettify (`product_id` → "Product ID").
152
+ * `locale` defaults to 'en' when absent.
153
+ */
154
+ export function prettifyKey(
155
+ key: string,
156
+ locale?: string,
157
+ t?: Translate,
158
+ ): string {
159
+ if (t) {
160
+ const translated = t(key)
161
+ if (translated && translated !== key) return translated
162
+ }
163
+ const lang = baseLang(locale)
164
+ const dict = KEY_DICTIONARY[lang] ?? KEY_DICTIONARY.en
165
+ const hit = dict[key.toLowerCase()]
166
+ if (hit) return hit
30
167
  const pretty = humanizeToken(key)
31
168
  return pretty || key
32
169
  }
33
170
 
171
+ /**
172
+ * Localized, pluralized count noun. Prefers the host i18n `t` (react-i18next
173
+ * count plural via `defaultValue`); otherwise the built-in es/en noun map.
174
+ */
175
+ export function countLabel(
176
+ count: number,
177
+ locale?: string,
178
+ t?: Translate,
179
+ ): string {
180
+ const lang = baseLang(locale)
181
+ const noun = ITEM_NOUN[lang] ?? ITEM_NOUN.en
182
+ const fallback = `${count} ${count === 1 ? noun.one : noun.other}`
183
+ if (t) {
184
+ const translated = t('runtime.collectionCell.itemCount', {
185
+ count,
186
+ defaultValue: fallback,
187
+ })
188
+ if (translated && translated !== 'runtime.collectionCell.itemCount') {
189
+ return translated
190
+ }
191
+ }
192
+ return fallback
193
+ }
194
+
34
195
  /**
35
196
  * Render a single scalar (or near-scalar) value for compact display.
36
197
  * - uuid-like or very long (32+ char) strings → first 8 chars + "…"
@@ -92,7 +253,55 @@ function unionKeys(rows: Record<string, unknown>[]): string[] {
92
253
 
93
254
  const PANEL_CLASS = 'w-auto max-w-[480px] max-h-[320px] overflow-auto p-0'
94
255
 
95
- function MiniTable({ rows }: { rows: Record<string, unknown>[] }) {
256
+ function MiniTable({
257
+ rows,
258
+ locale,
259
+ t,
260
+ itemFields,
261
+ }: {
262
+ rows: Record<string, unknown>[]
263
+ locale?: string
264
+ t?: Translate
265
+ itemFields?: ItemField[]
266
+ }) {
267
+ // Schema-driven path: a declared `item_fields` schema fixes the column
268
+ // order + headers (already localized by the backend, used VERBATIM) and
269
+ // resolves ref columns to the injected sibling label instead of the raw
270
+ // uuid. Sibling/raw keys not covered by the schema are dropped from the
271
+ // table (the schema is the source of truth for what to surface).
272
+ if (itemFields && itemFields.length > 0) {
273
+ return (
274
+ <Table>
275
+ <TableHeader>
276
+ <TableRow>
277
+ {itemFields.map((field) => (
278
+ <TableHead
279
+ key={field.key}
280
+ className="text-xs whitespace-nowrap"
281
+ >
282
+ {field.label}
283
+ </TableHead>
284
+ ))}
285
+ </TableRow>
286
+ </TableHeader>
287
+ <TableBody>
288
+ {rows.map((row, i) => (
289
+ <TableRow key={i}>
290
+ {itemFields.map((field) => (
291
+ <TableCell
292
+ key={field.key}
293
+ className="text-xs whitespace-nowrap"
294
+ >
295
+ {renderItemFieldValue(field, row)}
296
+ </TableCell>
297
+ ))}
298
+ </TableRow>
299
+ ))}
300
+ </TableBody>
301
+ </Table>
302
+ )
303
+ }
304
+
96
305
  const keys = unionKeys(rows)
97
306
  if (keys.length === 0) {
98
307
  return <div className="p-3 text-xs text-muted-foreground">-</div>
@@ -103,7 +312,7 @@ function MiniTable({ rows }: { rows: Record<string, unknown>[] }) {
103
312
  <TableRow>
104
313
  {keys.map((key) => (
105
314
  <TableHead key={key} className="text-xs whitespace-nowrap">
106
- {prettifyKey(key)}
315
+ {prettifyKey(key, locale, t)}
107
316
  </TableHead>
108
317
  ))}
109
318
  </TableRow>
@@ -138,13 +347,21 @@ function ScalarList({ values }: { values: unknown[] }) {
138
347
  )
139
348
  }
140
349
 
141
- function PairList({ entries }: { entries: [string, unknown][] }) {
350
+ function PairList({
351
+ entries,
352
+ locale,
353
+ t,
354
+ }: {
355
+ entries: [string, unknown][]
356
+ locale?: string
357
+ t?: Translate
358
+ }) {
142
359
  return (
143
360
  <ul className="p-3 space-y-1">
144
361
  {entries.map(([key, v]) => (
145
362
  <li key={key} className="text-xs">
146
363
  <span className="text-muted-foreground">
147
- {prettifyKey(key)}:
364
+ {prettifyKey(key, locale, t)}:
148
365
  </span>{' '}
149
366
  <span className="text-foreground">{formatScalar(v)}</span>
150
367
  </li>
@@ -191,13 +408,32 @@ export interface CollectionCellProps {
191
408
  value: unknown
192
409
  /** Max items previewed inline for scalar arrays. */
193
410
  maxInline?: number
411
+ /** Org/UI language tag (e.g. `es`, `en-US`). Defaults to `'en'`. */
412
+ locale?: string
413
+ /** Host i18n translator; takes precedence over the built-in dictionary. */
414
+ t?: Translate
415
+ /**
416
+ * Declared schema for the jsonb line-items columns (kernel v3 `item_fields`,
417
+ * read from `col.itemFields ?? col.item_fields` at the callsite). When
418
+ * present AND the value is an array of objects, the popover mini-table uses
419
+ * these (already-localized) headers in order and resolves `ref` columns to
420
+ * the backend-injected sibling label. Absent → the generic dict/prettify
421
+ * behaviour is unchanged.
422
+ */
423
+ itemFields?: ItemField[]
194
424
  }
195
425
 
196
426
  /**
197
427
  * Generic renderer for jsonb / array / object cell values. Brand-neutral,
198
- * compact, dark-mode friendly. Never throws on unexpected shapes.
428
+ * compact, dark-mode friendly, locale-aware. Never throws on unexpected shapes.
199
429
  */
200
- export function CollectionCell({ value, maxInline = 3 }: CollectionCellProps) {
430
+ export function CollectionCell({
431
+ value,
432
+ maxInline = 3,
433
+ locale,
434
+ t,
435
+ itemFields,
436
+ }: CollectionCellProps) {
201
437
  const parsed = parseValue(value)
202
438
 
203
439
  // Empty-ish → muted dash.
@@ -230,17 +466,40 @@ export function CollectionCell({ value, maxInline = 3 }: CollectionCellProps) {
230
466
  if (allObjects) {
231
467
  const rows = parsed as Record<string, unknown>[]
232
468
  const count = rows.length
233
- const label = count === 1 ? '1 ítem' : `${count} ítems`
234
- const title = rows
235
- .map((row) =>
236
- Object.entries(row)
237
- .map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
238
- .join(', ')
239
- )
240
- .join(' | ')
469
+ const label = countLabel(count, locale, t)
470
+ const hasSchema = !!(itemFields && itemFields.length > 0)
471
+ // The no-JS tooltip mirrors the rendered table: schema-driven
472
+ // labels + resolved ref values when a schema is present, else the
473
+ // generic prettify/scalar pairs.
474
+ const title = hasSchema
475
+ ? rows
476
+ .map((row) =>
477
+ itemFields!
478
+ .map(
479
+ (field) =>
480
+ `${field.label}: ${renderItemFieldValue(field, row)}`
481
+ )
482
+ .join(', ')
483
+ )
484
+ .join(' | ')
485
+ : rows
486
+ .map((row) =>
487
+ Object.entries(row)
488
+ .map(
489
+ ([k, v]) =>
490
+ `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`
491
+ )
492
+ .join(', ')
493
+ )
494
+ .join(' | ')
241
495
  return (
242
496
  <PopoverShell label={label} title={title}>
243
- <MiniTable rows={rows} />
497
+ <MiniTable
498
+ rows={rows}
499
+ locale={locale}
500
+ t={t}
501
+ itemFields={itemFields}
502
+ />
244
503
  </PopoverShell>
245
504
  )
246
505
  }
@@ -262,16 +521,16 @@ export function CollectionCell({ value, maxInline = 3 }: CollectionCellProps) {
262
521
  const entries = Object.entries(parsed)
263
522
  const inline = entries
264
523
  .slice(0, maxInline)
265
- .map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
524
+ .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
266
525
  .join(', ')
267
526
  const overflow = entries.length - maxInline
268
527
  const label = overflow > 0 ? `${inline} +${overflow}` : inline
269
528
  const title = entries
270
- .map(([k, v]) => `${prettifyKey(k)}: ${formatScalar(v)}`)
529
+ .map(([k, v]) => `${prettifyKey(k, locale, t)}: ${formatScalar(v)}`)
271
530
  .join(', ')
272
531
  return (
273
532
  <PopoverShell label={label} title={title} icon={false}>
274
- <PairList entries={entries} />
533
+ <PairList entries={entries} locale={locale} t={t} />
275
534
  </PopoverShell>
276
535
  )
277
536
  }
@@ -1174,7 +1174,14 @@ export function makeDefaultGetDynamicColumns(
1174
1174
 
1175
1175
  default: {
1176
1176
  if (typeof value === 'object' && value !== null) {
1177
- return <CollectionCell value={value} />
1177
+ return (
1178
+ <CollectionCell
1179
+ value={value}
1180
+ locale={currentLanguage}
1181
+ t={t}
1182
+ itemFields={col.itemFields ?? col.item_fields}
1183
+ />
1184
+ )
1178
1185
  }
1179
1186
  if (
1180
1187
  col.key === 'description' ||
package/src/index.ts CHANGED
@@ -105,7 +105,9 @@ export {
105
105
  CollectionCell,
106
106
  formatScalar,
107
107
  prettifyKey,
108
+ countLabel,
108
109
  type CollectionCellProps,
110
+ type Translate as CollectionCellTranslate,
109
111
  } from './collection-cell'
110
112
  export { NIL_UUID, isNilUuid, normalizeNilUuid } from './nil-uuid'
111
113
  export { DynamicRecordDialog, ViewValue } from './dialogs/dynamic-record'
package/src/types.ts CHANGED
@@ -170,6 +170,33 @@ export interface ColumnDefinition {
170
170
  * reference resolved through the OrgConfigProvider.
171
171
  */
172
172
  validation?: FieldValidation
173
+ /**
174
+ * Declared schema for a jsonb line-items column (kernel v3 `item_fields`).
175
+ * Each entry describes one sub-field of the array's row objects: a `key`
176
+ * (the jsonb key), an already-LOCALIZED `label` (backend-translated), an
177
+ * optional `type` hint and an optional `ref` (FK target). When present the
178
+ * `CollectionCell` renders the popover mini-table with these headers in
179
+ * order and resolves `ref` columns to the backend-injected sibling label
180
+ * (the FK key without `_id`, else `<key>_label`) instead of the raw uuid.
181
+ * Tolerates the snake_case `item_fields` the kernel serves.
182
+ */
183
+ itemFields?: ColumnItemField[]
184
+ /** snake_case alias served by the kernel for `itemFields`. */
185
+ item_fields?: ColumnItemField[]
186
+ }
187
+
188
+ /**
189
+ * One declared sub-field of a jsonb line-items column (see
190
+ * `ColumnDefinition.itemFields`). `label` is already localized by the backend
191
+ * and consumed verbatim; a non-empty `ref` flags the column for resolved-label
192
+ * rendering against the injected sibling. Structurally compatible with the
193
+ * `ItemField` consumed by `collection-cell`.
194
+ */
195
+ export interface ColumnItemField {
196
+ key: string
197
+ label: string
198
+ type?: string
199
+ ref?: string
173
200
  }
174
201
 
175
202
  export interface ActionCondition {