@ai-matrx/records-ui 0.101.30 → 0.101.33

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/dist/index.d.cts CHANGED
@@ -584,8 +584,16 @@ interface ReferenceChipRef {
584
584
  * archived (chair ruling B3-21), so the chip still names it and draws the archived state.
585
585
  */
586
586
  archived?: boolean | undefined;
587
+ /** The record's (or its table's) icon — an emoji or short glyph — when the host knows one. */
588
+ icon?: string | null | undefined;
587
589
  }
588
590
  interface RecordsUiHost {
591
+ /**
592
+ * Draws an icon kept as a NAME ("Briefcase" — how the store keeps an icon a person picked from the
593
+ * icon list). A relation cell in an embedded table wears its record's icon; an emoji is drawn as
594
+ * text and a name only through this port. Unbound (or `null` back), the page glyph stands in.
595
+ */
596
+ renderIcon?: ((name: string) => ReactNode) | undefined;
589
597
  /**
590
598
  * TEMPORARY (one-grid merge, steps 5-8). Which table grid draws: `classic` — the default Grid
591
599
  * as it shipped in 0.86; `merged` — the same grid with the older /data grid's control layer on
@@ -2142,7 +2150,8 @@ declare const NO_COLUMNS_WHY: string;
2142
2150
  * ordered by the table's own sort exactly as before, and offers no Reorder.
2143
2151
  */
2144
2152
  interface GridViewOrder {
2145
- viewId: Uuid;
2153
+ /** The saved view's id; `null` for a view a host holds inline (it still carries its own `sorts`). */
2154
+ viewId: Uuid | null;
2146
2155
  /** The view's own order word (`definition.order`). */
2147
2156
  order: ViewOrder;
2148
2157
  /** The view's own sort (`definition.sorts`) — where a sorted view OPENS, over the table's. */
@@ -2324,6 +2333,11 @@ interface ViewReading {
2324
2333
  */
2325
2334
  type LabelLookup = ((field: Field$1, id: string) => string | null) & {
2326
2335
  archived?: (field: Field$1, id: string) => boolean;
2336
+ /** The linked record's own icon, and its table's icon for one that has none (same call as the words). */
2337
+ icons?: (field: Field$1, id: string) => {
2338
+ icon: string | null;
2339
+ tableIcon: string | null;
2340
+ } | undefined;
2327
2341
  };
2328
2342
  /**
2329
2343
  * HOW LONG A CELL MAY SAY "Loading…" BEFORE IT HAS TO SAY WHAT IT IS WAITING
@@ -3372,6 +3386,7 @@ declare function useChoiceNudge(enabled?: boolean): ChoiceNudge | null;
3372
3386
  interface EmbeddedLook {
3373
3387
  blankEmpty: boolean;
3374
3388
  choiceDot: boolean;
3389
+ plainRelation: boolean;
3375
3390
  }
3376
3391
 
3377
3392
  /** The envelope for one key, when the document carries one. */
package/dist/index.d.ts CHANGED
@@ -584,8 +584,16 @@ interface ReferenceChipRef {
584
584
  * archived (chair ruling B3-21), so the chip still names it and draws the archived state.
585
585
  */
586
586
  archived?: boolean | undefined;
587
+ /** The record's (or its table's) icon — an emoji or short glyph — when the host knows one. */
588
+ icon?: string | null | undefined;
587
589
  }
588
590
  interface RecordsUiHost {
591
+ /**
592
+ * Draws an icon kept as a NAME ("Briefcase" — how the store keeps an icon a person picked from the
593
+ * icon list). A relation cell in an embedded table wears its record's icon; an emoji is drawn as
594
+ * text and a name only through this port. Unbound (or `null` back), the page glyph stands in.
595
+ */
596
+ renderIcon?: ((name: string) => ReactNode) | undefined;
589
597
  /**
590
598
  * TEMPORARY (one-grid merge, steps 5-8). Which table grid draws: `classic` — the default Grid
591
599
  * as it shipped in 0.86; `merged` — the same grid with the older /data grid's control layer on
@@ -2142,7 +2150,8 @@ declare const NO_COLUMNS_WHY: string;
2142
2150
  * ordered by the table's own sort exactly as before, and offers no Reorder.
2143
2151
  */
2144
2152
  interface GridViewOrder {
2145
- viewId: Uuid;
2153
+ /** The saved view's id; `null` for a view a host holds inline (it still carries its own `sorts`). */
2154
+ viewId: Uuid | null;
2146
2155
  /** The view's own order word (`definition.order`). */
2147
2156
  order: ViewOrder;
2148
2157
  /** The view's own sort (`definition.sorts`) — where a sorted view OPENS, over the table's. */
@@ -2324,6 +2333,11 @@ interface ViewReading {
2324
2333
  */
2325
2334
  type LabelLookup = ((field: Field$1, id: string) => string | null) & {
2326
2335
  archived?: (field: Field$1, id: string) => boolean;
2336
+ /** The linked record's own icon, and its table's icon for one that has none (same call as the words). */
2337
+ icons?: (field: Field$1, id: string) => {
2338
+ icon: string | null;
2339
+ tableIcon: string | null;
2340
+ } | undefined;
2327
2341
  };
2328
2342
  /**
2329
2343
  * HOW LONG A CELL MAY SAY "Loading…" BEFORE IT HAS TO SAY WHAT IT IS WAITING
@@ -3372,6 +3386,7 @@ declare function useChoiceNudge(enabled?: boolean): ChoiceNudge | null;
3372
3386
  interface EmbeddedLook {
3373
3387
  blankEmpty: boolean;
3374
3388
  choiceDot: boolean;
3389
+ plainRelation: boolean;
3375
3390
  }
3376
3391
 
3377
3392
  /** The envelope for one key, when the document carries one. */
package/dist/index.js CHANGED
@@ -1844,10 +1844,10 @@ function shown(value) {
1844
1844
  if (typeof value === "number" || typeof value === "boolean") return String(value);
1845
1845
  return null;
1846
1846
  }
1847
- function tellApart(rows, nameOf) {
1847
+ function tellApart(rows, nameOf2) {
1848
1848
  const byName = /* @__PURE__ */ new Map();
1849
1849
  for (const row of rows) {
1850
- const name = nameOf(row.id).trim().toLowerCase();
1850
+ const name = nameOf2(row.id).trim().toLowerCase();
1851
1851
  byName.set(name, [...byName.get(name) ?? [], row]);
1852
1852
  }
1853
1853
  const out = /* @__PURE__ */ new Map();
@@ -2455,6 +2455,7 @@ function RecordLabelProvider({ children }) {
2455
2455
  const client = useRecordsClient3();
2456
2456
  const [names, setNames] = useState7({});
2457
2457
  const [archivedRefs, setArchivedRefs] = useState7({});
2458
+ const [iconsOf, setIconsOf] = useState7({});
2458
2459
  const [settled, setSettled] = useState7({});
2459
2460
  const [waitingLines, setWaitingLines] = useState7({});
2460
2461
  const asked3 = useRef3(/* @__PURE__ */ new Set());
@@ -2537,6 +2538,7 @@ function RecordLabelProvider({ children }) {
2537
2538
  void (async () => {
2538
2539
  const learned = {};
2539
2540
  const archivedNow = {};
2541
+ const iconsNow = {};
2540
2542
  await Promise.all(
2541
2543
  batch.map(async ({ field: field2, ids }) => {
2542
2544
  const wanted = [...ids];
@@ -2549,10 +2551,12 @@ function RecordLabelProvider({ children }) {
2549
2551
  learned[id] = !result.ok ? UNREAD_RECORD_LABEL : typeof words2 === "string" && words2 !== "" ? words2 : WITHHELD_RECORD_LABEL;
2550
2552
  }
2551
2553
  if (result.ok) for (const id of result.archived ?? []) archivedNow[`${field2.id}:${id}`] = true;
2554
+ if (result.ok) for (const [id, pair] of Object.entries(result.icons ?? {})) iconsNow[id] = pair;
2552
2555
  })
2553
2556
  );
2554
2557
  setNames((current) => ({ ...current, ...learned }));
2555
2558
  if (Object.keys(archivedNow).length > 0) setArchivedRefs((current) => ({ ...current, ...archivedNow }));
2559
+ if (Object.keys(iconsNow).length > 0) setIconsOf((current) => ({ ...current, ...iconsNow }));
2556
2560
  for (const { field: field2 } of batch) stopWaitClock(field2.id);
2557
2561
  })();
2558
2562
  }, [client, stopWaitClock]);
@@ -2600,9 +2604,10 @@ function RecordLabelProvider({ children }) {
2600
2604
  () => {
2601
2605
  const answer = ((field2, id) => lookupWords(field2, id));
2602
2606
  answer.archived = (field2, id) => archivedRefs[`${field2.id}:${id}`] === true;
2607
+ answer.icons = (_field, id) => iconsOf[id];
2603
2608
  return answer;
2604
2609
  },
2605
- [lookupWords, archivedRefs]
2610
+ [lookupWords, archivedRefs, iconsOf]
2606
2611
  );
2607
2612
  const waiting = useCallback2((field2) => waitingLines[field2.id] ?? null, [waitingLines]);
2608
2613
  const value = useMemo5(() => ({ lookup, want, waiting }), [lookup, want, waiting]);
@@ -5157,7 +5162,7 @@ import { Badge as Badge5, cn as cn17 } from "@ai-matrx/design-system";
5157
5162
 
5158
5163
  // src/embeddedLook.tsx
5159
5164
  import { createContext as createContext4, useContext as useContext4 } from "react";
5160
- var EMBEDDED_LOOK = { blankEmpty: true, choiceDot: true };
5165
+ var EMBEDDED_LOOK = { blankEmpty: true, choiceDot: true, plainRelation: true };
5161
5166
  var EmbeddedLookContext = createContext4(null);
5162
5167
  var EmbeddedLookProvider = EmbeddedLookContext.Provider;
5163
5168
  function useEmbeddedLook() {
@@ -5173,6 +5178,7 @@ var EMBEDDED_TABLE_CLASS = [
5173
5178
  "[&_thead_th]:border-border",
5174
5179
  "[&_thead_th:last-child]:border-r-0"
5175
5180
  ].join(" ");
5181
+ var EMBEDDED_PLAIN_FOOTER_CLASS = "[&_tfoot]:bg-transparent [&_tfoot]:border-t-0";
5176
5182
 
5177
5183
  // src/enumLabels.ts
5178
5184
  function exhaustiveValuesOf() {
@@ -5465,6 +5471,20 @@ function kindLabel(kind) {
5465
5471
 
5466
5472
  // src/ReferenceChips.tsx
5467
5473
  import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
5474
+ function glyphOf(icon) {
5475
+ const text = (icon ?? "").trim();
5476
+ return text !== "" && text.length <= 8 && /[^\u0000-\u007f]/.test(text) ? text : null;
5477
+ }
5478
+ function nameOf(icon) {
5479
+ const text = (icon ?? "").trim();
5480
+ return /^[A-Za-z][A-Za-z0-9_-]{1,40}$/.test(text) ? text : null;
5481
+ }
5482
+ function iconOf(pair) {
5483
+ const own3 = glyphOf(pair?.icon) ?? nameOf(pair?.icon);
5484
+ const table = glyphOf(pair?.tableIcon) ?? nameOf(pair?.tableIcon);
5485
+ const chosen = own3 ?? table;
5486
+ return chosen ? { icon: chosen } : {};
5487
+ }
5468
5488
  function referencesOf(field2, value, kind, labels) {
5469
5489
  if (kind === "entity_reference") {
5470
5490
  return entityRefsOf2(value).map((r) => ({ token: r.token, id: r.id, label: r.label }));
@@ -5476,15 +5496,45 @@ function referencesOf(field2, value, kind, labels) {
5476
5496
  id,
5477
5497
  label: labels?.(field2, id) ?? null,
5478
5498
  tableId,
5499
+ ...iconOf(labels?.icons?.(field2, id)),
5479
5500
  ...labels?.archived?.(field2, id) ? { archived: true } : {}
5480
5501
  }));
5481
5502
  }
5482
5503
  var ARCHIVED_REFERENCE_WORD = "Archived";
5483
5504
  var ARCHIVED_REFERENCE_TIP = "In the Archived view; restore it to use it again";
5505
+ function PageGlyph() {
5506
+ return /* @__PURE__ */ jsxs18("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", width: 14, height: 14, fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "data-records-page-glyph": "", children: [
5507
+ /* @__PURE__ */ jsx22("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
5508
+ /* @__PURE__ */ jsx22("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
5509
+ /* @__PURE__ */ jsx22("path", { d: "M10 13H8" }),
5510
+ /* @__PURE__ */ jsx22("path", { d: "M16 17H8" })
5511
+ ] });
5512
+ }
5484
5513
  function ReferenceChip({ reference, waiting }) {
5485
5514
  const host = useRecordsUi();
5486
5515
  const words2 = reference.label ?? (reference.token === "record" ? waiting ?? "Loading\u2026" : WITHHELD_ENTITY_LABEL);
5487
5516
  const drawn = host.renderReference ? host.renderReference({ ...reference, label: reference.label ?? words2 }) : null;
5517
+ const look = useEmbeddedLook();
5518
+ if (look?.plainRelation) {
5519
+ return /* @__PURE__ */ jsxs18(
5520
+ "span",
5521
+ {
5522
+ className: cn15(
5523
+ "inline-flex max-w-full min-w-0 items-center gap-1.5 text-sm text-foreground hover:underline [&_a]:text-inherit [&_a]:no-underline [&_a:hover]:underline",
5524
+ reference.archived && "opacity-70"
5525
+ ),
5526
+ "data-records-reference-chip": reference.token,
5527
+ "data-records-reference-plain": "",
5528
+ "data-records-reference-archived": reference.archived ? "" : void 0,
5529
+ title: reference.archived ? ARCHIVED_REFERENCE_TIP : words2,
5530
+ children: [
5531
+ /* @__PURE__ */ jsx22("span", { "aria-hidden": "true", "data-records-reference-icon": "", className: "inline-flex size-4 shrink-0 items-center justify-center text-muted-foreground", children: glyphOf(reference.icon) ? /* @__PURE__ */ jsx22("span", { className: "text-sm leading-none", children: reference.icon }) : reference.icon && host.renderIcon?.(reference.icon) || /* @__PURE__ */ jsx22(PageGlyph, {}) }),
5532
+ drawn ?? /* @__PURE__ */ jsx22("span", { className: "truncate", children: words2 }),
5533
+ reference.archived ? /* @__PURE__ */ jsx22("span", { className: "shrink-0 text-muted-foreground", children: ARCHIVED_REFERENCE_WORD }) : null
5534
+ ]
5535
+ }
5536
+ );
5537
+ }
5488
5538
  return /* @__PURE__ */ jsxs18(
5489
5539
  Badge4,
5490
5540
  {
@@ -6215,7 +6265,7 @@ function renderValue(field2, value, document2, labels, stillWaitingFor, look) {
6215
6265
  "data-records-choice-chip": "",
6216
6266
  ...chip.tinted ? { "data-records-choice-tint": "" } : {},
6217
6267
  children: [
6218
- look?.choiceDot ? /* @__PURE__ */ jsx26("span", { "aria-hidden": "true", "data-records-choice-dot": "", className: cn17("mr-1.5 inline-block size-1.5 shrink-0 rounded-full", chip.dot) }) : null,
6268
+ look?.choiceDot ? /* @__PURE__ */ jsx26("span", { "aria-hidden": "true", "data-matrx-pill-ink": "", "data-records-choice-dot": "", className: cn17("mr-1.5 inline-block size-1.5 shrink-0 rounded-full", chip.dot) }) : null,
6219
6269
  statusMark(field2, item, Array.isArray(value) ? void 0 : document2),
6220
6270
  scalarText(field2, item, labels, Array.isArray(value) ? void 0 : stillWaitingFor)
6221
6271
  ]
@@ -6370,7 +6420,7 @@ function RecordValue({
6370
6420
  const shape = shown4 && (shown4.id === "date" || shown4.id === "datetime") && isDateField(field2) ? dateFormatFor(field2, value) : void 0;
6371
6421
  const dated = shape ? { ...shape, ...shown4?.options ? { options: shown4.options } : {} } : void 0;
6372
6422
  const formatted = shown4 ? formatColumnValue({ format: dated ?? shown4 }, value) : null;
6373
- if (formatted) return /* @__PURE__ */ jsx26(FormattedValue, { value, format: dated ?? shown4 });
6423
+ if (formatted && !(embeddedLook?.blankEmpty && formatted.empty)) return /* @__PURE__ */ jsx26(FormattedValue, { value, format: dated ?? shown4 });
6374
6424
  return /* @__PURE__ */ jsx26(Fragment6, { children: renderValue(field2, value, document2, labels, stillWaitingFor, embeddedLook) });
6375
6425
  }
6376
6426
  var ONE_FIELD_ARRAYS = /* @__PURE__ */ new WeakMap();
@@ -16124,7 +16174,7 @@ function opensOnKey(event) {
16124
16174
  }
16125
16175
  return true;
16126
16176
  }
16127
- function withOpenRecord(columns, onOpen, nameOf, place = "end", hrefOf) {
16177
+ function withOpenRecord(columns, onOpen, nameOf2, place = "end", hrefOf) {
16128
16178
  if (!onOpen) return columns;
16129
16179
  return columns.map((column, index) => {
16130
16180
  const original = column.cell;
@@ -16134,8 +16184,8 @@ function withOpenRecord(columns, onOpen, nameOf, place = "end", hrefOf) {
16134
16184
  {
16135
16185
  type: "button",
16136
16186
  "data-records-open-record": row.id,
16137
- "aria-label": openRecordLabel(nameOf(row)),
16138
- title: `${openRecordLabel(nameOf(row))} (Space)`,
16187
+ "aria-label": openRecordLabel(nameOf2(row)),
16188
+ title: `${openRecordLabel(nameOf2(row))} (Space)`,
16139
16189
  className: "shrink-0 rounded px-1 text-xs leading-5 text-muted-foreground opacity-40 hover:bg-muted hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover/matrx-row:opacity-100",
16140
16190
  onClick: (event) => {
16141
16191
  event.stopPropagation();
@@ -22288,7 +22338,7 @@ function GridBody({
22288
22338
  [storePaged, query, queryTotal, classicInCard, records.data?.total]
22289
22339
  );
22290
22340
  const showAddLine = mayAddRow && rows.length > 0;
22291
- const showSummaryLine = rows.length > 0 && (!merged || Object.keys(chosenSummaries).length > 0);
22341
+ const showSummaryLine = rows.length > 0 && (embedded ? Object.keys(chosenSummaries).length > 0 : !merged || Object.keys(chosenSummaries).length > 0);
22292
22342
  const footerFace = JSON.stringify([
22293
22343
  showAddLine ? added.busy : null,
22294
22344
  showSummaryLine ? [chosenSummaries, summaries.values, summaries.rowCount, summaries.loading] : null
@@ -22471,7 +22521,7 @@ function GridBody({
22471
22521
  mode: phone ? "sheet" : toolbarFold2 >= 3 ? "menu" : "inline",
22472
22522
  pinned: /* @__PURE__ */ jsxs61(Fragment23, { children: [
22473
22523
  /* @__PURE__ */ jsx68(UndoRedoLive, { source: undoSource }),
22474
- !readOnly && rights.write ? /* @__PURE__ */ jsx68(
22524
+ !embedded && !readOnly && rights.write ? /* @__PURE__ */ jsx68(
22475
22525
  NewFromTemplateMenu,
22476
22526
  {
22477
22527
  tableId,
@@ -22483,7 +22533,8 @@ function GridBody({
22483
22533
  // NEW RECORD NEVER LEAVES THE ROW (live walk 2026-10-05: at 1440 px with the
22484
22534
  // side chat open it and "From template" both vanished behind the "…"). When the
22485
22535
  // row is short, "From template", undo and the rest ride the "…"; this stays.
22486
- ...!readOnly && rights.write ? {
22536
+ // EMBEDDED: the "+ New page" line under the table is the create action.
22537
+ ...!embedded && !readOnly && rights.write ? {
22487
22538
  primary: /* @__PURE__ */ jsx68(
22488
22539
  NewRecordControl,
22489
22540
  {
@@ -22548,7 +22599,7 @@ function GridBody({
22548
22599
  // 2026-09-28: with the Forms rail open "New record" read "New reco", faded
22549
22600
  // under the slot's edge). Pinned, it sticks to the row's right edge and what
22550
22601
  // scrolls passes under it — the merged grid's rule, not a second one.
22551
- ...!readOnly && rights.write ? {
22602
+ ...!embedded && !readOnly && rights.write ? {
22552
22603
  overflow: {
22553
22604
  mode: "inline",
22554
22605
  pinned: /* @__PURE__ */ jsx68(Button42, { size: "sm", className: "shrink-0", disabled: editingBusy, onClick: () => void addRecord(), children: editingBusy ? "Adding\u2026" : "New record" })
@@ -22799,7 +22850,8 @@ function GridBody({
22799
22850
  tableClassName: cn34(
22800
22851
  !look.wrap && "[&_tbody_td]:max-w-[28rem] [&_tbody_td]:overflow-hidden [&_tbody_td]:text-ellipsis [&_tbody_td]:whitespace-nowrap! [&_tbody_td_*]:whitespace-nowrap!",
22801
22852
  look.fit === "scroll" && "sm:[&>table]:w-max! sm:[&>table]:max-w-none! sm:[&>table]:min-w-full!",
22802
- embedded && EMBEDDED_TABLE_CLASS
22853
+ embedded && EMBEDDED_TABLE_CLASS,
22854
+ embedded && !showSummaryLine && EMBEDDED_PLAIN_FOOTER_CLASS
22803
22855
  ),
22804
22856
  footerRows,
22805
22857
  ...embedded ? { copy: false } : copyConfig ? { copy: copyConfig } : {},
@@ -31396,7 +31448,11 @@ function ViewSwitcher({
31396
31448
  onUseSort: (sort) => change({ sorts: [sort], order: "sorted" })
31397
31449
  } : {}
31398
31450
  }
31399
- } : {}
31451
+ } : (view.sorts?.length ?? 0) > 0 || local.sorts ? (
31452
+ /* A VIEW THE HOST HOLDS INLINE (Spaces) has no saved id but still has its sort: the
31453
+ grid opens in it, never in the table's own order. */
31454
+ { viewOrder: { viewId: null, order: null, sorts: local.sorts ?? view.sorts ?? [] } }
31455
+ ) : {}
31400
31456
  }
31401
31457
  ) : /* @__PURE__ */ jsx105(
31402
31458
  Board,
@@ -45796,22 +45852,22 @@ function RailFrame({
45796
45852
  return /* @__PURE__ */ jsx152("aside", { "data-rail-column": "", className: "w-[26rem] shrink-0 overflow-y-auto rounded-md border p-3", children });
45797
45853
  }
45798
45854
  function filterInWords(filter, fields) {
45799
- const nameOf = (key) => {
45855
+ const nameOf2 = (key) => {
45800
45856
  const field2 = fields.find((f) => f.key === key);
45801
45857
  return field2 ? fieldName(field2) : humanizeIdentifier17(key);
45802
45858
  };
45803
45859
  const parts = Object.keys(filter).map((key) => {
45804
45860
  const value = filter[key];
45805
- if (value === null || value === void 0) return `${nameOf(key)} is not filled in`;
45861
+ if (value === null || value === void 0) return `${nameOf2(key)} is not filled in`;
45806
45862
  if (typeof value === "object") {
45807
45863
  const from = (value.from ?? "").toString().trim();
45808
45864
  const to = (value.to ?? "").toString().trim();
45809
- if (from !== "" && to !== "") return `${nameOf(key)} from ${from} up to ${to}`;
45810
- if (from !== "") return `${nameOf(key)} from ${from} onwards`;
45811
- if (to !== "") return `${nameOf(key)} up to ${to}`;
45812
- return `${nameOf(key)} in a period nobody named`;
45865
+ if (from !== "" && to !== "") return `${nameOf2(key)} from ${from} up to ${to}`;
45866
+ if (from !== "") return `${nameOf2(key)} from ${from} onwards`;
45867
+ if (to !== "") return `${nameOf2(key)} up to ${to}`;
45868
+ return `${nameOf2(key)} in a period nobody named`;
45813
45869
  }
45814
- return `${nameOf(key)} is ${String(value)}`;
45870
+ return `${nameOf2(key)} is ${String(value)}`;
45815
45871
  });
45816
45872
  if (parts.length === 0) return "";
45817
45873
  if (parts.length === 1) return parts[0];