@ai-matrx/design-system 0.39.5 → 0.41.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 (45) hide show
  1. package/CHANGELOG.md +48 -5
  2. package/dist/data-table/copy-helpers.d.cts +2 -2
  3. package/dist/data-table/copy-helpers.d.ts +2 -2
  4. package/dist/data-table/filter-engine.d.cts +2 -2
  5. package/dist/data-table/filter-engine.d.ts +2 -2
  6. package/dist/data-table/host.d.cts +2 -2
  7. package/dist/data-table/host.d.ts +2 -2
  8. package/dist/data-table/index.cjs +308 -12
  9. package/dist/data-table/index.cjs.map +1 -1
  10. package/dist/data-table/index.d.cts +9 -4
  11. package/dist/data-table/index.d.ts +9 -4
  12. package/dist/data-table/index.js +308 -12
  13. package/dist/data-table/index.js.map +1 -1
  14. package/dist/data-table/infer-filter.d.cts +2 -2
  15. package/dist/data-table/infer-filter.d.ts +2 -2
  16. package/dist/data-table/layered-filters.d.cts +2 -2
  17. package/dist/data-table/layered-filters.d.ts +2 -2
  18. package/dist/data-table/query-control.d.cts +2 -2
  19. package/dist/data-table/query-control.d.ts +2 -2
  20. package/dist/data-table/types.cjs.map +1 -1
  21. package/dist/data-table/types.d.cts +2 -2
  22. package/dist/data-table/types.d.ts +2 -2
  23. package/dist/data-table/url-state.d.cts +2 -2
  24. package/dist/data-table/url-state.d.ts +2 -2
  25. package/dist/data-table/validation.d.cts +1 -1
  26. package/dist/data-table/validation.d.ts +1 -1
  27. package/dist/data-table/xlsx.d.cts +2 -2
  28. package/dist/data-table/xlsx.d.ts +2 -2
  29. package/dist/field-formats/index.cjs +269 -5
  30. package/dist/field-formats/index.cjs.map +1 -1
  31. package/dist/field-formats/index.d.cts +2 -2
  32. package/dist/field-formats/index.d.ts +2 -2
  33. package/dist/field-formats/index.js +269 -5
  34. package/dist/field-formats/index.js.map +1 -1
  35. package/dist/formulas/index.cjs +38 -5
  36. package/dist/formulas/index.cjs.map +1 -1
  37. package/dist/formulas/index.d.cts +39 -2
  38. package/dist/formulas/index.d.ts +39 -2
  39. package/dist/formulas/index.js +38 -5
  40. package/dist/formulas/index.js.map +1 -1
  41. package/dist/{types-D7O3uOIs.d.cts → types-DNkbHRVK.d.cts} +10 -1
  42. package/dist/{types-p13OrDj-.d.ts → types-Dx5hJgCi.d.ts} +10 -1
  43. package/dist/{types-CJWU1Gae.d.cts → types-JnZD8wM0.d.cts} +27 -2
  44. package/dist/{types-CJWU1Gae.d.ts → types-JnZD8wM0.d.ts} +27 -2
  45. package/package.json +1 -1
@@ -57,6 +57,35 @@ function toText(value) {
57
57
  return String(value);
58
58
  return null;
59
59
  }
60
+ function parseTimeOfDay(value) {
61
+ if (value instanceof Date) {
62
+ return { h: value.getHours(), m: value.getMinutes(), s: value.getSeconds() };
63
+ }
64
+ if (typeof value !== "string") return null;
65
+ const text = value.trim();
66
+ if (!text) return null;
67
+ const m = /^(\d{1,2})(?::(\d{2}))?(?::(\d{2}))?\s*([aApP]\.?[mM]\.?)?$/.exec(text);
68
+ if (m) {
69
+ let h = Number(m[1]);
70
+ const min = m[2] ? Number(m[2]) : 0;
71
+ const sec = m[3] ? Number(m[3]) : 0;
72
+ const ampm = m[4]?.toLowerCase().replace(/\./g, "");
73
+ if (ampm) {
74
+ if (h < 1 || h > 12) return null;
75
+ if (ampm === "pm" && h !== 12) h += 12;
76
+ if (ampm === "am" && h === 12) h = 0;
77
+ }
78
+ if (h > 23 || min > 59 || sec > 59) return null;
79
+ return { h, m: min, s: sec };
80
+ }
81
+ if (/\d{4}-\d{2}-\d{2}T/.test(text)) {
82
+ const d = new Date(text);
83
+ if (!Number.isNaN(d.getTime())) {
84
+ return { h: d.getHours(), m: d.getMinutes(), s: d.getSeconds() };
85
+ }
86
+ }
87
+ return null;
88
+ }
60
89
  var BARE_CALENDAR_DATE = /^(\d{4})-(\d{2})-(\d{2})(?:[T ]00:00(?::00(?:\.0+)?)?)?$/;
61
90
  function toDate(value) {
62
91
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
@@ -177,6 +206,23 @@ var DEFS = [
177
206
  },
178
207
  parse: (raw) => raw === "" || raw == null ? null : String(raw).trim()
179
208
  },
209
+ {
210
+ // A postal address. Free text by design (addresses have no one shape
211
+ // worldwide); the cell opens it in maps, which is the thing people do
212
+ // with an address in a table.
213
+ id: "address",
214
+ label: "Address",
215
+ description: "Street address \u2014 click to open in maps",
216
+ group: "Text",
217
+ base: "string",
218
+ editor: "text",
219
+ rich: true,
220
+ format: (v) => {
221
+ const t = toText(v)?.trim();
222
+ return t ? t : null;
223
+ },
224
+ parse: (raw) => raw === "" || raw == null ? null : String(raw)
225
+ },
180
226
  {
181
227
  id: "url",
182
228
  label: "Link",
@@ -310,6 +356,26 @@ var DEFS = [
310
356
  },
311
357
  parse: (raw) => toNumber(raw)
312
358
  },
359
+ {
360
+ // A percentage DRAWN as a bar — task completion, quota used, a score out of
361
+ // 100. Same storage and scale rule as `percent`; only the rendering differs.
362
+ id: "progress",
363
+ label: "Progress bar",
364
+ description: "A percentage shown as a bar",
365
+ group: "Numbers",
366
+ base: "number",
367
+ alsoAccepts: ["integer"],
368
+ editor: "number",
369
+ rich: true,
370
+ optionKeys: ["percentScale"],
371
+ format: (v, o) => {
372
+ const n = toNumber(v);
373
+ if (n === null) return null;
374
+ const shown = o.percentScale === "fraction" ? n * 100 : n;
375
+ return `${groupedNumber(shown, { precision: 0 })}%`;
376
+ },
377
+ parse: (raw) => toNumber(raw)
378
+ },
313
379
  {
314
380
  id: "duration",
315
381
  label: "Duration",
@@ -378,13 +444,16 @@ var DEFS = [
378
444
  alsoAccepts: ["number"],
379
445
  editor: "number",
380
446
  numericAlign: true,
447
+ // The name says BYTES on purpose: this cell holds a genuine byte length,
448
+ // never a character or item count, and `formatFileSize` is only honest
449
+ // about a quantity that is actually bytes.
381
450
  format: (v) => {
382
- const n = toNumber(v);
383
- return n === null ? null : (0, import_format.formatFileSize)(n);
451
+ const byteLength = toNumber(v);
452
+ return byteLength === null ? null : (0, import_format.formatFileSize)(byteLength);
384
453
  },
385
454
  parse: (raw) => {
386
- const n = toNumber(raw);
387
- return n === null ? null : Math.trunc(n);
455
+ const byteLength = toNumber(raw);
456
+ return byteLength === null ? null : Math.trunc(byteLength);
388
457
  }
389
458
  },
390
459
  // ── Choice ────────────────────────────────────────────────────────────────
@@ -445,6 +514,87 @@ var DEFS = [
445
514
  },
446
515
  parse: (raw) => raw === "" || raw == null ? null : String(raw)
447
516
  },
517
+ {
518
+ // A TIME OF DAY with no date: opening hours, a shift start, a meeting slot.
519
+ // Stored as the string the <input type="time"> gives ("HH:MM" or
520
+ // "HH:MM:SS"), shown in the viewer's locale ("2:30 PM" / "14:30"). The
521
+ // storage type stays `string`; sorting is lexical on the 24-hour form,
522
+ // which is chronological — the one reason the stored form is 24-hour.
523
+ id: "time",
524
+ label: "Time",
525
+ description: "Time of day, without a date",
526
+ group: "Dates",
527
+ base: "string",
528
+ editor: "time",
529
+ optionKeys: ["timeSeconds"],
530
+ format: (v, o) => {
531
+ const parts = parseTimeOfDay(v);
532
+ if (!parts) return null;
533
+ const d = new Date(2e3, 0, 1, parts.h, parts.m, parts.s);
534
+ return d.toLocaleTimeString(void 0, {
535
+ hour: "numeric",
536
+ minute: "2-digit",
537
+ ...o.timeSeconds ? { second: "2-digit" } : {}
538
+ });
539
+ },
540
+ // Normalise to the 24-hour "HH:MM[:SS]" form; a value that is not a time
541
+ // is handed back unchanged so the validation layer can refuse it by name.
542
+ parse: (raw) => {
543
+ if (raw == null || raw === "") return null;
544
+ const parts = parseTimeOfDay(raw);
545
+ if (!parts) return raw;
546
+ const two = (n) => String(n).padStart(2, "0");
547
+ const fields = parts.s > 0 ? [parts.h, parts.m, parts.s] : [parts.h, parts.m];
548
+ return fields.map(two).join(":");
549
+ }
550
+ },
551
+ {
552
+ // Assigned BY THE DATABASE when the row is inserted (trigger
553
+ // `_udt_autonumber`), so it is the same number whichever door made the row
554
+ // and never shifts when another row is deleted. Stored in the cell as an
555
+ // integer; every editor refuses it. `prefix` turns 42 into "INV-42".
556
+ id: "autonumber",
557
+ label: "Autonumber",
558
+ description: "A number the table assigns to each new row \u2014 never typed",
559
+ group: "Numbers",
560
+ base: "integer",
561
+ alsoAccepts: ["number"],
562
+ editor: "computed",
563
+ numericAlign: true,
564
+ optionKeys: ["prefix"],
565
+ format: (v, o) => {
566
+ const n = toNumber(v);
567
+ if (n === null) return null;
568
+ return `${o.prefix ?? ""}${Math.trunc(n)}`;
569
+ },
570
+ parse: () => null
571
+ },
572
+ // ── System columns ────────────────────────────────────────────────────────
573
+ // Filled from the ROW'S OWN RECORD on read (`withComputedColumns` in
574
+ // `@ai-matrx/design-system/formulas`), never typed and never stored in the
575
+ // cell. `editor: "computed"` makes every editor refuse them.
576
+ {
577
+ id: "created_time",
578
+ label: "Created time",
579
+ description: "When the row was added \u2014 filled in automatically",
580
+ group: "Dates",
581
+ base: "string",
582
+ editor: "computed",
583
+ optionKeys: ["dateStyle"],
584
+ format: (v, o) => FIELD_FORMATS.datetime.format(v, o),
585
+ parse: () => null
586
+ },
587
+ {
588
+ id: "modified_time",
589
+ label: "Last modified time",
590
+ description: "When the row last changed \u2014 filled in automatically",
591
+ group: "Dates",
592
+ base: "string",
593
+ editor: "computed",
594
+ optionKeys: ["dateStyle"],
595
+ format: (v, o) => FIELD_FORMATS.datetime.format(v, o),
596
+ parse: () => null
597
+ },
448
598
  {
449
599
  id: "relative_time",
450
600
  label: "Relative time",
@@ -501,6 +651,44 @@ var DEFS = [
501
651
  return toList(raw) ?? raw;
502
652
  }
503
653
  },
654
+ {
655
+ // FILES ATTACHED TO THE ROW. The cell stores the files feature's file ids
656
+ // (`files.files.id`), never a URL or a name: a rename or a move in Files
657
+ // changes nothing here, and access is the file's own (RLS) — a reader who
658
+ // may see the row but not the file gets the chip's honest "no access", not
659
+ // a broken link. Drawn by `AttachmentChips`, edited by `AttachmentInput`
660
+ // through the ONE file picker.
661
+ id: "attachment",
662
+ label: "Attachments",
663
+ description: "Files from your Files, shown as chips",
664
+ group: "Structured",
665
+ base: "array",
666
+ // Not `string`: on a text column it must appear under "Other kinds" so
667
+ // picking it retypes the column to a list, rather than storing a JSON
668
+ // string of ids in a text cell.
669
+ alsoAccepts: ["json"],
670
+ editor: "attachment",
671
+ rich: true,
672
+ format: (v) => {
673
+ const list = toList(v);
674
+ if (list === null) return null;
675
+ const n = list.filter((i) => String(i).trim()).length;
676
+ return n === 0 ? "" : n === 1 ? "1 file" : `${n} files`;
677
+ },
678
+ parse: (raw) => {
679
+ if (raw == null || raw === "") return null;
680
+ if (Array.isArray(raw)) return raw.map((i) => String(i).trim()).filter(Boolean);
681
+ const text = String(raw).trim();
682
+ if (text.startsWith("[")) {
683
+ try {
684
+ const parsed = JSON.parse(text);
685
+ if (Array.isArray(parsed)) return parsed.map((i) => String(i).trim()).filter(Boolean);
686
+ } catch {
687
+ }
688
+ }
689
+ return text.split(",").map((s) => s.trim()).filter(Boolean);
690
+ }
691
+ },
504
692
  {
505
693
  id: "tags",
506
694
  label: "Tags",
@@ -564,6 +752,82 @@ var DEFS = [
564
752
  return trimmed === "" ? null : trimmed;
565
753
  }
566
754
  },
755
+ {
756
+ // A PERSON: one member of the organization the table belongs to, stored as
757
+ // the user id. It is a choice column whose options are the members —
758
+ // supplied at runtime by whoever renders it (features/data-tables/
759
+ // person-choices.tsx), the same door a structured-list-bound column uses —
760
+ // so the cell picker, the chip, filters, color-by and the agent scope are
761
+ // all the choice system's, with no second implementation.
762
+ id: "person",
763
+ label: "Person",
764
+ description: "Someone in your organization \u2014 pick from its members",
765
+ group: "Choice",
766
+ base: "string",
767
+ editor: "select",
768
+ rich: true,
769
+ format: (v, o) => FIELD_FORMATS.choice.format(v, o),
770
+ parse: (raw) => FIELD_FORMATS.choice.parse(raw, {})
771
+ },
772
+ {
773
+ // A RECORD OF ANOTHER TABLE. The cell stores that record's ID; the screen
774
+ // shows its WORDS. `person` is the precedent and the template — it has
775
+ // stored a user id and displayed a name through `choiceMap` since
776
+ // 2026-09-21 — so `relation` is a member of CHOICE_FORMAT_IDS and every
777
+ // one of the ten readers that print a cell resolves it through the path
778
+ // they already use for `person`. There is no eleventh place to remember.
779
+ //
780
+ // THREE STATES, THREE RENDERINGS, NO FOURTH (OLD-TABLES-CUTOVER §3.3):
781
+ // resolved the target's words
782
+ // unresolvable the amber fallback WITH the id, as an identifier — never
783
+ // a blank, and never the bare uuid as if it were text, so a
784
+ // person can act on it and support can trace it
785
+ // withheld platform.relation_withheld_label(), with NO id — a cell
786
+ // the reader may not see never leaks the identifier through
787
+ // the gap between the two stores' refusals
788
+ //
789
+ // `format` therefore returns null (the fallback law's "cannot interpret")
790
+ // for anything the resolver did not turn into words, and the renderer is
791
+ // what distinguishes unresolvable from withheld. It never prints a bare id.
792
+ id: "relation",
793
+ label: "Relation",
794
+ description: "A record of another table \u2014 pick it, and its name is shown",
795
+ group: "Choice",
796
+ base: "string",
797
+ alsoAccepts: ["array", "json"],
798
+ editor: "select",
799
+ rich: true,
800
+ optionKeys: ["relation_target", "relation_max", "on_delete", "display"],
801
+ // IT DOES NOT DELEGATE TO `choice`, AND THAT IS THE WHOLE POINT.
802
+ // `choice.format` deliberately returns the RAW VALUE when no options have
803
+ // resolved yet — a structured list still loading should not make every cell
804
+ // look off-list. For a label column that is right: the raw value IS the
805
+ // label. For an identifier column it is the defect §3.3 names, and it is
806
+ // how the first cut of this wave put four raw uuids on a real grid: the
807
+ // fallback arm never ran, because returning the uuid counted as SUCCESS.
808
+ //
809
+ // Here, a value that did not resolve to words is a MISMATCH — `null` — so
810
+ // the renderer's three-state arm owns it and shows the amber fallback with
811
+ // the id marked as an identifier, or the withheld sentence. The id never
812
+ // reaches the eye as if it were the cell's contents.
813
+ format: (v, options) => {
814
+ const list = toList(v);
815
+ const values = list !== null ? list : v == null ? [] : [v];
816
+ if (values.length === 0) return "";
817
+ const words = [];
818
+ for (const entry of values) {
819
+ const text = toText(entry);
820
+ if (text === null) return null;
821
+ const trimmed = text.trim();
822
+ if (trimmed === "") continue;
823
+ const match = findChoice(options.choices, trimmed);
824
+ if (!match) return null;
825
+ words.push(match.label ?? match.value);
826
+ }
827
+ return words.length === 0 ? "" : words.join(", ");
828
+ },
829
+ parse: (raw) => FIELD_FORMATS.choice.parse(raw, {})
830
+ },
567
831
  {
568
832
  id: "multi_choice",
569
833
  label: "Multi-choice",
@@ -608,7 +872,7 @@ var DEFS = [
608
872
  // A formula column stores NOTHING. `metadata.format.options.formula` holds
609
873
  // the expression; the stored cell is always null; the value is computed on
610
874
  // read from the other columns of the same row
611
- // (`features/data-tables/formulas.ts`). Grouped under Structured on purpose
875
+ // (`@ai-matrx/design-system/formulas`). Grouped under Structured on purpose
612
876
  // — the picker's groups are a fixed vocabulary, and a formula is structure
613
877
  // over a plain string column, not a sixth family.
614
878
  //
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/field-formats/index.ts","../../src/field-formats/registry.ts","../../src/field-formats/format.ts"],"sourcesContent":["/**\n * THE platform field-format registry — the canonical home.\n *\n * A **format** is a UI-layer semantic type layered on top of a plain storage\n * type: `currency` is not a database type, it is a `number` the UI knows to\n * display as `$1,234.56`, right-align, and edit with a currency-aware input.\n *\n * WHY IT LIVES IN THE DESIGN SYSTEM. This vocabulary was written for one app's\n * `/data` route and then every other table in the platform had to invent its\n * own money column. It is the data table's own vocabulary, it is pure (no\n * React, no DOM, no network, one dependency on `@ai-matrx/kit/format`), and a\n * second copy of it anywhere is a fork. `MatrxColumnDef.format` takes exactly\n * this `FieldFormatConfig`, so every consumer of the primitive — the record\n * store's Grid, an admin table, the app's own `/data` — reads one table of\n * formats and gets the same money, the same percent and the same date.\n *\n * THE FALLBACK LAW — a format may never blank a cell or throw. When the stored\n * value does not fit the declared format, `formatFieldValue` returns\n * `ok: false` together with the BASE-TYPE rendering, and the table shows that\n * in amber so the person can SEE something does not fit instead of staring at\n * an empty cell. Formats are always additive and always non-breaking: strip the\n * format and you are back to exactly what the database holds.\n *\n * Choice option RESOLUTION (hydrating options from a shared pick list) is not\n * here, because it needs a network and an app's own list feature. A caller that\n * has resolved its options passes them as `options.choices`; that is all this\n * module ever reads.\n */\n\nexport * from \"./types\";\nexport {\n FIELD_FORMATS,\n FIELD_FORMAT_LIST,\n FIELD_FORMAT_IDS,\n getFieldFormat,\n defaultFormatForBase,\n formatsForBase,\n groupedFormatsForBase,\n} from \"./registry\";\nexport {\n readFieldFormatConfig,\n resolveFieldFormat,\n formatFieldValue,\n valueFitsFormat,\n parseFieldInput,\n} from \"./format\";\n","/**\n * The format registry. ONE table — add a row here and every consumer (data\n * tables, scope context items, anything that adopts `FieldFormatId`) gets it.\n *\n * Every `format()` returns `null` rather than throwing or returning \"\" when the\n * value does not fit — that is what triggers THE FALLBACK LAW in `format.ts`.\n */\n// THE package duration formatter (`@ai-matrx/kit/format`, census H1\n// 2026-09-07). THE UNIT LAW: the unit is in the name, because the fleet's\n// ~35 twins variously took ms, seconds and minutes behind one signature.\nimport {\n formatDurationSeconds,\n formatFileSize,\n formatRelativeTime,\n} from \"@ai-matrx/kit/format\";\n\nimport type {\n FieldChoice,\n FieldFormatDef,\n FieldFormatId,\n FieldFormatOptions,\n FieldBaseType,\n} from \"./types\";\n\n/**\n * A stored `interval` is the one place a NEGATIVE duration is a real quantity\n * rather than a clock bug, so this field format opts into the package's\n * `signed` mode. Everything else about the reading is the package's clock.\n */\nfunction formatSignedDuration(totalSeconds: number): string {\n return formatDurationSeconds(totalSeconds, { signed: true });\n}\n\n// ─── shared helpers ──────────────────────────────────────────────────────────\n\n/** Coerce to a finite number, or null. Accepts numeric strings (\"1,234\", \"$5\"). */\nfunction toNumber(value: unknown): number | null {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"string\") {\n const cleaned = value.replace(/[,\\s$€£¥%]/g, \"\");\n if (cleaned === \"\") return null;\n const n = Number(cleaned);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction toText(value: unknown): string | null {\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\")\n return String(value);\n return null;\n}\n\n/**\n * A BARE CALENDAR DATE IS A LOCAL CALENDAR DATE — and this is the ONE place\n * that decision is made, for every format and every consumer.\n *\n * `new Date(\"2026-10-02\")` is defined by ECMAScript to parse a date-ONLY\n * string as **UTC** midnight, while `date` and `datetime` render with\n * `toLocaleDateString` / `toLocaleString`, i.e. in the viewer's zone. So west\n * of UTC a stored 2 October drew as 1 October — measured in the grid.\n *\n * A stored `YYYY-MM-DD` is not an instant, it is a day on a calendar, so it is\n * built as LOCAL midnight and reads as that same day everywhere. A string that\n * carries a zone designator (`Z`, `+hh:mm`, `-hh:mm`) IS an instant and is left\n * exactly as it was — converting it would be the same defect pointing the other\n * way. A zoneless `…T00:00:00` is already local by the spec; it is matched here\n * so the rule is stated rather than inherited.\n */\nconst BARE_CALENDAR_DATE =\n /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ]00:00(?::00(?:\\.0+)?)?)?$/;\n\nfunction toDate(value: unknown): Date | null {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n if (typeof value === \"number\") {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n if (typeof value === \"string\" && value.trim()) {\n const bare = BARE_CALENDAR_DATE.exec(value.trim());\n if (bare) {\n const [year, month, day] = [\n Number(bare[1]),\n Number(bare[2]),\n Number(bare[3]),\n ];\n const d = new Date(year, month - 1, day);\n // `new Date(y, m, d)` ROLLS OVER (\"2026-13-45\" would become a real date\n // in 2027) where `new Date(string)` returned Invalid Date and the caller\n // fell back to the amber base-type reading. Read the parts back so an\n // impossible date is still refused, exactly as before.\n d.setFullYear(year); // years < 100 would otherwise land in the 1900s.\n if (\n d.getFullYear() !== year ||\n d.getMonth() !== month - 1 ||\n d.getDate() !== day\n ) {\n return null;\n }\n return d;\n }\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n}\n\nfunction toList(value: unknown): unknown[] | null {\n if (Array.isArray(value)) return value;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.startsWith(\"[\")) {\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (Array.isArray(parsed)) return parsed;\n } catch {\n /* fall through to comma split */\n }\n }\n if (trimmed === \"\") return [];\n return trimmed.split(\",\").map((s) => s.trim());\n }\n return null;\n}\n\nfunction groupedNumber(n: number, options: FieldFormatOptions): string {\n return new Intl.NumberFormat(undefined, {\n useGrouping: options.useGrouping !== false,\n ...(options.precision != null\n ? {\n minimumFractionDigits: options.precision,\n maximumFractionDigits: options.precision,\n }\n : { maximumFractionDigits: 10 }),\n }).format(n);\n}\n\nfunction affix(text: string, options: FieldFormatOptions): string {\n return `${options.prefix ?? \"\"}${text}${options.suffix ?? \"\"}`;\n}\n\nconst DURATION_TO_SECONDS: Record<\n NonNullable<FieldFormatOptions[\"durationUnit\"]>,\n number\n> = { milliseconds: 0.001, seconds: 1, minutes: 60, hours: 3600 };\n\n/**\n * Find the option a stored value corresponds to.\n *\n * Case-insensitive on purpose: a column adopting a choice format almost always\n * has \"Active\" and \"active\" in it already, and calling one of them a mismatch\n * would be pedantry, not help. Exact match always wins.\n */\nfunction findChoice(\n choices: FieldChoice[] | undefined,\n value: string,\n): FieldChoice | undefined {\n if (!choices || choices.length === 0) return undefined;\n const exact = choices.find((c) => c.value === value);\n if (exact) return exact;\n const lowered = value.toLowerCase();\n return choices.find((c) => c.value.toLowerCase() === lowered);\n}\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n/** Explicit scheme, OR a host with a real dot-TLD. No whitespace either way. */\nconst URL_RE =\n /^(?:[a-z][a-z0-9+.-]*:\\/\\/\\S+|(?:[a-z0-9-]+\\.)+[a-z]{2,}(?:[:/?#]\\S*)?)$/i;\nconst HEX_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n\n// ─── the registry ────────────────────────────────────────────────────────────\n\nconst DEFS: FieldFormatDef[] = [\n // ── Text ──────────────────────────────────────────────────────────────────\n {\n id: \"text\",\n label: \"Text\",\n description: \"Plain single-line text\",\n group: \"Text\",\n base: \"string\",\n editor: \"text\",\n // Deliberately no optionKeys — plain text is the default for every string\n // column, and giving it prefix/suffix controls puts an options row under\n // every column in the settings dialog for a setting almost nobody wants.\n format: (v, o) => {\n const t = toText(v);\n return t === null ? null : affix(t, o);\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"long_text\",\n label: \"Long text\",\n description: \"Multi-line text, edited in a larger box\",\n group: \"Text\",\n base: \"string\",\n editor: \"textarea\",\n format: (v) => toText(v),\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"markdown\",\n label: \"Markdown\",\n description: \"Rich text written as Markdown\",\n group: \"Text\",\n base: \"string\",\n editor: \"textarea\",\n rich: true,\n format: (v) => toText(v),\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"email\",\n label: \"Email\",\n description: \"Email address — click to compose\",\n group: \"Text\",\n base: \"string\",\n editor: \"email\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n return EMAIL_RE.test(t) ? t : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n {\n id: \"url\",\n label: \"Link\",\n description: \"Web address — click to open\",\n group: \"Text\",\n base: \"string\",\n editor: \"url\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n // `new URL()` alone is uselessly permissive here: `https://Beijing` and\n // even `https://Washington, D.C.` parse successfully, so every text value\n // would render as a broken link instead of flagging as a mismatch.\n // Require no whitespace, and either an explicit scheme or a real\n // dot-TLD host. Bare domains stay accepted; https is added on click.\n if (/\\s/.test(t)) return null;\n if (!URL_RE.test(t)) return null;\n try {\n new URL(t.includes(\"://\") ? t : `https://${t}`);\n return t;\n } catch {\n return null;\n }\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n {\n id: \"phone\",\n label: \"Phone\",\n description: \"Phone number — click to call\",\n group: \"Text\",\n base: \"string\",\n editor: \"tel\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n // Must contain at least 7 digits to be a plausible phone number.\n return (t.match(/\\d/g) ?? []).length >= 7 ? t : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n {\n id: \"color\",\n label: \"Color\",\n description: \"Hex color with a swatch\",\n group: \"Text\",\n base: \"string\",\n editor: \"color\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n return HEX_RE.test(t) ? t.toLowerCase() : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n\n // ── Numbers ───────────────────────────────────────────────────────────────\n {\n id: \"number\",\n label: \"Number\",\n description: \"Plain number\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"useGrouping\", \"precision\", \"prefix\", \"suffix\"],\n format: (v, o) => {\n const n = toNumber(v);\n return n === null ? null : affix(groupedNumber(n, o), o);\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"decimal\",\n label: \"Decimal\",\n description: \"Number with fixed decimal places\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"precision\", \"useGrouping\", \"prefix\", \"suffix\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return affix(groupedNumber(n, { ...o, precision: o.precision ?? 2 }), o);\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"currency\",\n label: \"Currency\",\n description: \"Money — stored as a plain number, shown with a symbol\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"currency\", \"precision\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const currency = (o.currency || \"USD\").toUpperCase();\n try {\n return new Intl.NumberFormat(undefined, {\n style: \"currency\",\n currency,\n ...(o.precision != null\n ? {\n minimumFractionDigits: o.precision,\n maximumFractionDigits: o.precision,\n }\n : {}),\n }).format(n);\n } catch {\n // Unknown/invalid ISO code — still show the money, never blank it.\n return `${groupedNumber(n, { precision: o.precision ?? 2 })} ${currency}`;\n }\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"percent\",\n label: \"Percent\",\n description: \"Percentage — stored as a plain number\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"percentScale\", \"precision\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const shown = o.percentScale === \"fraction\" ? n * 100 : n;\n return `${groupedNumber(shown, o.precision === undefined ? {} : { precision: o.precision })}%`;\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"duration\",\n label: \"Duration\",\n description: \"Length of time, shown as h:mm:ss\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"durationUnit\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return formatSignedDuration(n * DURATION_TO_SECONDS[o.durationUnit ?? \"seconds\"]);\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"integer\",\n label: \"Whole number\",\n description: \"Integer with no decimal part\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"useGrouping\", \"prefix\", \"suffix\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return affix(groupedNumber(Math.trunc(n), { ...o, precision: 0 }), o);\n },\n parse: (raw) => {\n const n = toNumber(raw);\n return n === null ? null : Math.trunc(n);\n },\n },\n {\n id: \"rating\",\n label: \"Rating\",\n description: \"Score out of a maximum, shown as stars\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"rating\",\n rich: true,\n optionKeys: [\"ratingMax\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const max = o.ratingMax ?? 5;\n if (n < 0 || n > max) return null;\n return `${n} / ${max}`;\n },\n parse: (raw) => {\n const n = toNumber(raw);\n return n === null ? null : Math.round(n);\n },\n },\n {\n id: \"file_size\",\n label: \"File size\",\n description: \"Byte count shown as KB / MB / GB\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"number\",\n numericAlign: true,\n format: (v) => {\n const n = toNumber(v);\n return n === null ? null : formatFileSize(n);\n },\n parse: (raw) => {\n const n = toNumber(raw);\n return n === null ? null : Math.trunc(n);\n },\n },\n\n // ── Choice ────────────────────────────────────────────────────────────────\n {\n id: \"boolean\",\n label: \"Yes / No\",\n description: \"True or false\",\n group: \"Choice\",\n base: \"boolean\",\n editor: \"checkbox\",\n format: (v) => {\n if (typeof v === \"boolean\") return v ? \"Yes\" : \"No\";\n if (v === \"true\" || v === 1) return \"Yes\";\n if (v === \"false\" || v === 0) return \"No\";\n return null;\n },\n parse: (raw) => {\n if (typeof raw === \"boolean\") return raw;\n if (raw == null || raw === \"\") return null;\n return String(raw).toLowerCase() === \"true\" || raw === 1;\n },\n },\n\n // ── Dates ─────────────────────────────────────────────────────────────────\n {\n id: \"date\",\n label: \"Date\",\n description: \"Calendar date\",\n group: \"Dates\",\n base: \"date\",\n alsoAccepts: [\"datetime\", \"string\"],\n editor: \"date\",\n optionKeys: [\"dateStyle\"],\n format: (v, o) => {\n const d = toDate(v);\n if (!d) return null;\n return d.toLocaleDateString(undefined, {\n dateStyle: o.dateStyle ?? \"medium\",\n });\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"datetime\",\n label: \"Date & time\",\n description: \"Calendar date with a time of day\",\n group: \"Dates\",\n base: \"datetime\",\n alsoAccepts: [\"date\", \"string\"],\n editor: \"datetime\",\n optionKeys: [\"dateStyle\"],\n format: (v, o) => {\n const d = toDate(v);\n if (!d) return null;\n return d.toLocaleString(undefined, {\n dateStyle: o.dateStyle ?? \"medium\",\n timeStyle: \"short\",\n });\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"relative_time\",\n label: \"Relative time\",\n description: 'Shown as \"3 days ago\"',\n group: \"Dates\",\n base: \"datetime\",\n alsoAccepts: [\"date\", \"string\"],\n editor: \"datetime\",\n format: (v) => {\n const d = toDate(v);\n // THE one relative stamp (@ai-matrx/kit/format). This registry carried\n // its own Intl ladder until 2026-09-12 — the last re-implementation of\n // formatRelativeTime in this repo. It ROUNDED, so a 1h50m-old row read\n // \"2 hours ago\" here and \"1h ago\" on every other surface in the product;\n // the package truncates, which is what the rest of the fleet shows.\n return d ? formatRelativeTime(d, { style: \"intl\" }) : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n\n // ── Structured ────────────────────────────────────────────────────────────\n {\n id: \"json\",\n label: \"JSON\",\n description: \"Structured object\",\n group: \"Structured\",\n base: \"json\",\n alsoAccepts: [\"array\"],\n editor: \"json\",\n format: (v) => {\n if (v === null || v === undefined) return null;\n if (typeof v === \"object\") return JSON.stringify(v);\n return toText(v);\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (typeof raw !== \"string\") return raw;\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return raw;\n }\n },\n },\n {\n id: \"array\",\n label: \"List\",\n description: \"List of values\",\n group: \"Structured\",\n base: \"array\",\n alsoAccepts: [\"json\"],\n editor: \"json\",\n format: (v) => {\n const list = toList(v);\n return list === null ? null : list.map((i) => String(i)).join(\", \");\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw;\n return toList(raw) ?? raw;\n },\n },\n {\n id: \"tags\",\n label: \"Tags\",\n description: \"List of values shown as chips\",\n group: \"Structured\",\n base: \"array\",\n alsoAccepts: [\"json\", \"string\"],\n editor: \"json\",\n rich: true,\n format: (v) => {\n const list = toList(v);\n return list === null ? null : list.map((i) => String(i)).join(\", \");\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw;\n return toList(raw) ?? raw;\n },\n },\n\n // ─── Choice ────────────────────────────────────────────────────────────────\n //\n // An enum WITHOUT a database enum. The column stays `string` (or `array` for\n // multi), the database enforces nothing, and stripping the format leaves\n // every stored value exactly as it was. Options are a UI layer — the same\n // trick `percent` plays on a number.\n //\n // The fallback law is the whole reason this is safe to switch on over live\n // data: a value not in the option list is NOT an error and is NEVER blanked.\n // `format()` returns null for it, `<FormattedFieldValue>` renders it in amber\n // with the reason, and the user can fix it or adopt it. Declaring the options\n // is therefore how you FIND your stray values, not how you lose them.\n //\n // Options resolve through `choices.ts` — inline `options.choices`, or a\n // structured list via `options.structuredList`. This def only ever sees\n // whatever the caller resolved into `options.choices`, so it stays pure and\n // synchronous like every other format.\n {\n id: \"choice\",\n label: \"Choice\",\n description: \"One value from a list of options\",\n group: \"Choice\",\n base: \"string\",\n alsoAccepts: [\"number\", \"integer\", \"boolean\"],\n editor: \"select\",\n rich: true,\n optionKeys: [\"choices\", \"structuredList\", \"allowOther\"],\n format: (v, options) => {\n const text = toText(v);\n if (text === null) return null;\n const trimmed = text.trim();\n if (trimmed === \"\") return \"\";\n const match = findChoice(options.choices, trimmed);\n // No options resolved yet (a structured list still loading, or a column\n // whose list is unreachable) — show the raw value rather than accusing\n // every cell of being off-list.\n if (!options.choices || options.choices.length === 0) return trimmed;\n return match ? (match.label ?? match.value) : null;\n },\n parse: (raw) => {\n if (raw == null) return null;\n const text = toText(raw);\n if (text === null) return raw;\n const trimmed = text.trim();\n return trimmed === \"\" ? null : trimmed;\n },\n },\n {\n id: \"multi_choice\",\n label: \"Multi-choice\",\n description: \"Any number of values from a list of options\",\n group: \"Choice\",\n base: \"array\",\n alsoAccepts: [\"string\", \"json\"],\n editor: \"multiselect\",\n rich: true,\n optionKeys: [\"choices\", \"structuredList\", \"allowOther\"],\n format: (v, options) => {\n const list = toList(v);\n if (list === null) return null;\n if (list.length === 0) return \"\";\n const labels: string[] = [];\n let anyOffList = false;\n for (const entry of list) {\n const text = toText(entry);\n if (text === null) {\n anyOffList = true;\n continue;\n }\n const match = findChoice(options.choices, text.trim());\n if (match) labels.push(match.label ?? match.value);\n else {\n labels.push(text.trim());\n anyOffList = true;\n }\n }\n // Same leniency as `choice`: with no options resolved, nothing is off-list.\n const haveOptions = !!options.choices && options.choices.length > 0;\n if (haveOptions && anyOffList) return null;\n return labels.join(\", \");\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw;\n return toList(raw) ?? raw;\n },\n },\n\n // ─── Computed ──────────────────────────────────────────────────────────────\n //\n // A formula column stores NOTHING. `metadata.format.options.formula` holds\n // the expression; the stored cell is always null; the value is computed on\n // read from the other columns of the same row\n // (`features/data-tables/formulas.ts`). Grouped under Structured on purpose\n // — the picker's groups are a fixed vocabulary, and a formula is structure\n // over a plain string column, not a sixth family.\n //\n // `format()` here is handed the COMPUTED value, never a stored one, and its\n // only job is to render it: through `resultFormat`'s own `format()` when the\n // column declares one (a computed total shown as Currency, a computed date\n // shown as a Date), and plainly otherwise. It never returns null for an\n // empty value, because a formula legitimately produces BLANK.\n //\n // No `optionKeys`: the expression is authored in its own editor, not in the\n // generic option rail, and listing a key the picker has no control for would\n // open an empty options section under every formula column.\n {\n id: \"formula\",\n label: \"Formula\",\n description: \"Computed from the other columns in the row — never typed in\",\n group: \"Structured\",\n base: \"string\",\n editor: \"computed\",\n format: (v, o) => {\n const target = o.formula?.resultFormat;\n if (target && target !== \"formula\") {\n const delegate = FIELD_FORMATS[target];\n if (delegate) {\n const rendered = delegate.format(v, o);\n if (rendered !== null) return rendered;\n }\n }\n if (v === null || v === undefined) return \"\";\n if (typeof v === \"boolean\") return v ? \"Yes\" : \"No\";\n if (typeof v === \"number\") return groupedNumber(v, o);\n return toText(v);\n },\n // Nothing is ever stored in a formula column. Returning the raw input here\n // would be the one way a computed cell could acquire a value of its own.\n parse: () => null,\n },\n];\n\nexport const FIELD_FORMATS: Readonly<Record<FieldFormatId, FieldFormatDef>> =\n Object.freeze(\n Object.fromEntries(DEFS.map((d) => [d.id, d])) as Record<\n FieldFormatId,\n FieldFormatDef\n >,\n );\n\nexport const FIELD_FORMAT_LIST: readonly FieldFormatDef[] = Object.freeze(DEFS);\n\nexport const FIELD_FORMAT_IDS = DEFS.map((d) => d.id) as FieldFormatId[];\n\nexport function getFieldFormat(\n id: string | null | undefined,\n): FieldFormatDef | null {\n if (!id) return null;\n return FIELD_FORMATS[id as FieldFormatId] ?? null;\n}\n\n/**\n * The format a column gets when it declares none — the identity format for its\n * storage type. Every existing column therefore behaves exactly as before.\n */\nexport function defaultFormatForBase(base: string): FieldFormatId {\n switch (base as FieldBaseType) {\n case \"number\":\n return \"number\";\n case \"integer\":\n return \"integer\";\n case \"boolean\":\n return \"boolean\";\n case \"date\":\n return \"date\";\n case \"datetime\":\n return \"datetime\";\n case \"json\":\n return \"json\";\n case \"array\":\n return \"array\";\n default:\n return \"text\";\n }\n}\n\n/** Formats that can legally sit on a given storage type. */\nexport function formatsForBase(base: string): FieldFormatDef[] {\n return DEFS.filter(\n (d) => d.base === base || (d.alsoAccepts ?? []).includes(base as FieldBaseType),\n );\n}\n\n/** Group the pickable formats for a base type, preserving registry order. */\nexport function groupedFormatsForBase(\n base: string,\n): { group: FieldFormatDef[\"group\"]; formats: FieldFormatDef[] }[] {\n const out: { group: FieldFormatDef[\"group\"]; formats: FieldFormatDef[] }[] =\n [];\n for (const def of formatsForBase(base)) {\n const bucket = out.find((g) => g.group === def.group);\n if (bucket) bucket.formats.push(def);\n else out.push({ group: def.group, formats: [def] });\n }\n return out;\n}\n","/**\n * THE FALLBACK LAW, implemented.\n *\n * `formatFieldValue` never blanks a cell and never throws. When the declared\n * format cannot interpret the stored value it returns `ok: false` with the\n * **base-type** rendering of that same value plus a human `reason`. The UI\n * renders that text in amber (see `<FormattedFieldValue>`), so a mismatch is\n * visible instead of invisible.\n */\nimport {\n defaultFormatForBase,\n getFieldFormat,\n FIELD_FORMATS,\n} from \"./registry\";\nimport type {\n FieldFormatConfig,\n FieldFormatOptions,\n FormatResult,\n} from \"./types\";\n\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined || value === \"\";\n}\n\n/** Last-resort rendering — the raw value, readable, never empty-by-accident. */\nfunction baseText(value: unknown): string {\n if (isEmpty(value)) return \"\";\n if (typeof value === \"object\") {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n/**\n * Read a field's declared format off its `metadata` JSONB. Returns null when\n * the field declares none (or declares one we don't know) — the caller then\n * falls back to the storage type's identity format, so an unknown or removed\n * format id degrades to plain, never to broken.\n */\nexport function readFieldFormatConfig(\n metadata: unknown,\n): FieldFormatConfig | null {\n if (!metadata || typeof metadata !== \"object\") return null;\n const raw = (metadata as Record<string, unknown>).format;\n if (!raw || typeof raw !== \"object\") return null;\n const id = (raw as Record<string, unknown>).id;\n if (typeof id !== \"string\" || !getFieldFormat(id)) return null;\n const options = (raw as Record<string, unknown>).options;\n // `exactOptionalPropertyTypes`: an absent `options` is the key being ABSENT,\n // never the key holding `undefined` — the two are different documents once\n // this config is written back out as JSON.\n return {\n id: id as FieldFormatConfig[\"id\"],\n ...(options && typeof options === \"object\"\n ? { options: options as FieldFormatOptions }\n : {}),\n };\n}\n\n/**\n * Resolve the format a field actually renders with: its declared one, or the\n * identity format for its storage type.\n */\nexport function resolveFieldFormat(\n dataType: string,\n metadata: unknown,\n): FieldFormatConfig {\n const declared = readFieldFormatConfig(metadata);\n if (declared) return declared;\n return { id: defaultFormatForBase(dataType) };\n}\n\nexport function formatFieldValue(\n value: unknown,\n config: FieldFormatConfig | null | undefined,\n /** The column's storage type — used for the fallback rendering. */\n dataType?: string,\n): FormatResult {\n if (isEmpty(value)) return { ok: true, text: \"\", empty: true };\n\n const def = config ? getFieldFormat(config.id) : null;\n const effective =\n def ?? FIELD_FORMATS[defaultFormatForBase(dataType ?? \"string\")];\n\n let formatted: string | null;\n try {\n formatted = effective.format(value, config?.options ?? {});\n } catch {\n formatted = null;\n }\n\n if (formatted !== null) return { ok: true, text: formatted, empty: false };\n\n // Mismatch. Show what IS stored, flagged.\n return {\n ok: false,\n text: baseText(value),\n empty: false,\n reason: `This value is not a valid ${effective.label.toLowerCase()} — showing the stored value instead.`,\n };\n}\n\n/** Turn raw editor input into the value to store under a format. */\nexport function parseFieldInput(\n raw: unknown,\n config: FieldFormatConfig | null | undefined,\n dataType?: string,\n): unknown {\n const def = config ? getFieldFormat(config.id) : null;\n const effective =\n def ?? FIELD_FORMATS[defaultFormatForBase(dataType ?? \"string\")];\n try {\n return effective.parse(raw, config?.options ?? {});\n } catch {\n return raw;\n }\n}\n\n/**\n * Would this stored value survive its declared format? Used to count mismatches\n * so a column can warn (\"4 values don't fit Currency\") instead of silently\n * showing amber cells.\n */\nexport function valueFitsFormat(\n value: unknown,\n config: FieldFormatConfig | null | undefined,\n dataType?: string,\n): boolean {\n return formatFieldValue(value, config, dataType).ok;\n}\n"],"mappings":"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","names":["d"]}
1
+ {"version":3,"sources":["../../src/field-formats/index.ts","../../src/field-formats/registry.ts","../../src/field-formats/format.ts"],"sourcesContent":["/**\n * THE platform field-format registry — the canonical home.\n *\n * A **format** is a UI-layer semantic type layered on top of a plain storage\n * type: `currency` is not a database type, it is a `number` the UI knows to\n * display as `$1,234.56`, right-align, and edit with a currency-aware input.\n *\n * WHY IT LIVES IN THE DESIGN SYSTEM. This vocabulary was written for one app's\n * `/data` route and then every other table in the platform had to invent its\n * own money column. It is the data table's own vocabulary, it is pure (no\n * React, no DOM, no network, one dependency on `@ai-matrx/kit/format`), and a\n * second copy of it anywhere is a fork. `MatrxColumnDef.format` takes exactly\n * this `FieldFormatConfig`, so every consumer of the primitive — the record\n * store's Grid, an admin table, the app's own `/data` — reads one table of\n * formats and gets the same money, the same percent and the same date.\n *\n * THE FALLBACK LAW — a format may never blank a cell or throw. When the stored\n * value does not fit the declared format, `formatFieldValue` returns\n * `ok: false` together with the BASE-TYPE rendering, and the table shows that\n * in amber so the person can SEE something does not fit instead of staring at\n * an empty cell. Formats are always additive and always non-breaking: strip the\n * format and you are back to exactly what the database holds.\n *\n * Choice option RESOLUTION (hydrating options from a shared pick list) is not\n * here, because it needs a network and an app's own list feature. A caller that\n * has resolved its options passes them as `options.choices`; that is all this\n * module ever reads.\n */\n\nexport * from \"./types\";\nexport {\n FIELD_FORMATS,\n FIELD_FORMAT_LIST,\n FIELD_FORMAT_IDS,\n getFieldFormat,\n defaultFormatForBase,\n formatsForBase,\n groupedFormatsForBase,\n} from \"./registry\";\nexport {\n readFieldFormatConfig,\n resolveFieldFormat,\n formatFieldValue,\n valueFitsFormat,\n parseFieldInput,\n} from \"./format\";\n","/**\n * The format registry. ONE table — add a row here and every consumer (data\n * tables, scope context items, anything that adopts `FieldFormatId`) gets it.\n *\n * Every `format()` returns `null` rather than throwing or returning \"\" when the\n * value does not fit — that is what triggers THE FALLBACK LAW in `format.ts`.\n */\n// THE package duration formatter (`@ai-matrx/kit/format`, census H1\n// 2026-09-07). THE UNIT LAW: the unit is in the name, because the fleet's\n// ~35 twins variously took ms, seconds and minutes behind one signature.\nimport {\n formatDurationSeconds,\n formatFileSize,\n formatRelativeTime,\n} from \"@ai-matrx/kit/format\";\n\nimport type {\n FieldChoice,\n FieldFormatDef,\n FieldFormatId,\n FieldFormatOptions,\n FieldBaseType,\n} from \"./types\";\n\n/**\n * A stored `interval` is the one place a NEGATIVE duration is a real quantity\n * rather than a clock bug, so this field format opts into the package's\n * `signed` mode. Everything else about the reading is the package's clock.\n */\nfunction formatSignedDuration(totalSeconds: number): string {\n return formatDurationSeconds(totalSeconds, { signed: true });\n}\n\n// ─── shared helpers ──────────────────────────────────────────────────────────\n\n/** Coerce to a finite number, or null. Accepts numeric strings (\"1,234\", \"$5\"). */\nfunction toNumber(value: unknown): number | null {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"string\") {\n const cleaned = value.replace(/[,\\s$€£¥%]/g, \"\");\n if (cleaned === \"\") return null;\n const n = Number(cleaned);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction toText(value: unknown): string | null {\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\")\n return String(value);\n return null;\n}\n\n/**\n * Read a time of day from \"HH:MM\", \"HH:MM:SS\", \"H:MM am/pm\", \"2pm\", or a\n * Date/ISO string (its local time). `null` when it is not a time.\n */\nfunction parseTimeOfDay(\n value: unknown,\n): { h: number; m: number; s: number } | null {\n if (value instanceof Date) {\n return { h: value.getHours(), m: value.getMinutes(), s: value.getSeconds() };\n }\n if (typeof value !== \"string\") return null;\n const text = value.trim();\n if (!text) return null;\n const m = /^(\\d{1,2})(?::(\\d{2}))?(?::(\\d{2}))?\\s*([aApP]\\.?[mM]\\.?)?$/.exec(text);\n if (m) {\n let h = Number(m[1]);\n const min = m[2] ? Number(m[2]) : 0;\n const sec = m[3] ? Number(m[3]) : 0;\n const ampm = m[4]?.toLowerCase().replace(/\\./g, \"\");\n if (ampm) {\n if (h < 1 || h > 12) return null;\n if (ampm === \"pm\" && h !== 12) h += 12;\n if (ampm === \"am\" && h === 12) h = 0;\n }\n if (h > 23 || min > 59 || sec > 59) return null;\n return { h, m: min, s: sec };\n }\n // An ISO timestamp — keep its local time of day.\n if (/\\d{4}-\\d{2}-\\d{2}T/.test(text)) {\n const d = new Date(text);\n if (!Number.isNaN(d.getTime())) {\n return { h: d.getHours(), m: d.getMinutes(), s: d.getSeconds() };\n }\n }\n return null;\n}\n\n/**\n * A BARE CALENDAR DATE IS A LOCAL CALENDAR DATE — and this is the ONE place\n * that decision is made, for every format and every consumer.\n *\n * `new Date(\"2026-10-02\")` is defined by ECMAScript to parse a date-ONLY\n * string as **UTC** midnight, while `date` and `datetime` render with\n * `toLocaleDateString` / `toLocaleString`, i.e. in the viewer's zone. So west\n * of UTC a stored 2 October drew as 1 October — measured in the grid.\n *\n * A stored `YYYY-MM-DD` is not an instant, it is a day on a calendar, so it is\n * built as LOCAL midnight and reads as that same day everywhere. A string that\n * carries a zone designator (`Z`, `+hh:mm`, `-hh:mm`) IS an instant and is left\n * exactly as it was — converting it would be the same defect pointing the other\n * way. A zoneless `…T00:00:00` is already local by the spec; it is matched here\n * so the rule is stated rather than inherited.\n */\nconst BARE_CALENDAR_DATE =\n /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ]00:00(?::00(?:\\.0+)?)?)?$/;\n\nfunction toDate(value: unknown): Date | null {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n if (typeof value === \"number\") {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n if (typeof value === \"string\" && value.trim()) {\n const bare = BARE_CALENDAR_DATE.exec(value.trim());\n if (bare) {\n const [year, month, day] = [\n Number(bare[1]),\n Number(bare[2]),\n Number(bare[3]),\n ];\n const d = new Date(year, month - 1, day);\n // `new Date(y, m, d)` ROLLS OVER (\"2026-13-45\" would become a real date\n // in 2027) where `new Date(string)` returned Invalid Date and the caller\n // fell back to the amber base-type reading. Read the parts back so an\n // impossible date is still refused, exactly as before.\n d.setFullYear(year); // years < 100 would otherwise land in the 1900s.\n if (\n d.getFullYear() !== year ||\n d.getMonth() !== month - 1 ||\n d.getDate() !== day\n ) {\n return null;\n }\n return d;\n }\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n}\n\nfunction toList(value: unknown): unknown[] | null {\n if (Array.isArray(value)) return value;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.startsWith(\"[\")) {\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (Array.isArray(parsed)) return parsed;\n } catch {\n /* fall through to comma split */\n }\n }\n if (trimmed === \"\") return [];\n return trimmed.split(\",\").map((s) => s.trim());\n }\n return null;\n}\n\nfunction groupedNumber(n: number, options: FieldFormatOptions): string {\n return new Intl.NumberFormat(undefined, {\n useGrouping: options.useGrouping !== false,\n ...(options.precision != null\n ? {\n minimumFractionDigits: options.precision,\n maximumFractionDigits: options.precision,\n }\n : { maximumFractionDigits: 10 }),\n }).format(n);\n}\n\nfunction affix(text: string, options: FieldFormatOptions): string {\n return `${options.prefix ?? \"\"}${text}${options.suffix ?? \"\"}`;\n}\n\nconst DURATION_TO_SECONDS: Record<\n NonNullable<FieldFormatOptions[\"durationUnit\"]>,\n number\n> = { milliseconds: 0.001, seconds: 1, minutes: 60, hours: 3600 };\n\n/**\n * Find the option a stored value corresponds to.\n *\n * Case-insensitive on purpose: a column adopting a choice format almost always\n * has \"Active\" and \"active\" in it already, and calling one of them a mismatch\n * would be pedantry, not help. Exact match always wins.\n */\nfunction findChoice(\n choices: FieldChoice[] | undefined,\n value: string,\n): FieldChoice | undefined {\n if (!choices || choices.length === 0) return undefined;\n const exact = choices.find((c) => c.value === value);\n if (exact) return exact;\n const lowered = value.toLowerCase();\n return choices.find((c) => c.value.toLowerCase() === lowered);\n}\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n/** Explicit scheme, OR a host with a real dot-TLD. No whitespace either way. */\nconst URL_RE =\n /^(?:[a-z][a-z0-9+.-]*:\\/\\/\\S+|(?:[a-z0-9-]+\\.)+[a-z]{2,}(?:[:/?#]\\S*)?)$/i;\nconst HEX_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n\n// ─── the registry ────────────────────────────────────────────────────────────\n\nconst DEFS: FieldFormatDef[] = [\n // ── Text ──────────────────────────────────────────────────────────────────\n {\n id: \"text\",\n label: \"Text\",\n description: \"Plain single-line text\",\n group: \"Text\",\n base: \"string\",\n editor: \"text\",\n // Deliberately no optionKeys — plain text is the default for every string\n // column, and giving it prefix/suffix controls puts an options row under\n // every column in the settings dialog for a setting almost nobody wants.\n format: (v, o) => {\n const t = toText(v);\n return t === null ? null : affix(t, o);\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"long_text\",\n label: \"Long text\",\n description: \"Multi-line text, edited in a larger box\",\n group: \"Text\",\n base: \"string\",\n editor: \"textarea\",\n format: (v) => toText(v),\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"markdown\",\n label: \"Markdown\",\n description: \"Rich text written as Markdown\",\n group: \"Text\",\n base: \"string\",\n editor: \"textarea\",\n rich: true,\n format: (v) => toText(v),\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"email\",\n label: \"Email\",\n description: \"Email address — click to compose\",\n group: \"Text\",\n base: \"string\",\n editor: \"email\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n return EMAIL_RE.test(t) ? t : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n {\n // A postal address. Free text by design (addresses have no one shape\n // worldwide); the cell opens it in maps, which is the thing people do\n // with an address in a table.\n id: \"address\",\n label: \"Address\",\n description: \"Street address — click to open in maps\",\n group: \"Text\",\n base: \"string\",\n editor: \"text\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n return t ? t : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"url\",\n label: \"Link\",\n description: \"Web address — click to open\",\n group: \"Text\",\n base: \"string\",\n editor: \"url\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n // `new URL()` alone is uselessly permissive here: `https://Beijing` and\n // even `https://Washington, D.C.` parse successfully, so every text value\n // would render as a broken link instead of flagging as a mismatch.\n // Require no whitespace, and either an explicit scheme or a real\n // dot-TLD host. Bare domains stay accepted; https is added on click.\n if (/\\s/.test(t)) return null;\n if (!URL_RE.test(t)) return null;\n try {\n new URL(t.includes(\"://\") ? t : `https://${t}`);\n return t;\n } catch {\n return null;\n }\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n {\n id: \"phone\",\n label: \"Phone\",\n description: \"Phone number — click to call\",\n group: \"Text\",\n base: \"string\",\n editor: \"tel\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n // Must contain at least 7 digits to be a plausible phone number.\n return (t.match(/\\d/g) ?? []).length >= 7 ? t : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n {\n id: \"color\",\n label: \"Color\",\n description: \"Hex color with a swatch\",\n group: \"Text\",\n base: \"string\",\n editor: \"color\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n return HEX_RE.test(t) ? t.toLowerCase() : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n\n // ── Numbers ───────────────────────────────────────────────────────────────\n {\n id: \"number\",\n label: \"Number\",\n description: \"Plain number\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"useGrouping\", \"precision\", \"prefix\", \"suffix\"],\n format: (v, o) => {\n const n = toNumber(v);\n return n === null ? null : affix(groupedNumber(n, o), o);\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"decimal\",\n label: \"Decimal\",\n description: \"Number with fixed decimal places\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"precision\", \"useGrouping\", \"prefix\", \"suffix\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return affix(groupedNumber(n, { ...o, precision: o.precision ?? 2 }), o);\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"currency\",\n label: \"Currency\",\n description: \"Money — stored as a plain number, shown with a symbol\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"currency\", \"precision\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const currency = (o.currency || \"USD\").toUpperCase();\n try {\n return new Intl.NumberFormat(undefined, {\n style: \"currency\",\n currency,\n ...(o.precision != null\n ? {\n minimumFractionDigits: o.precision,\n maximumFractionDigits: o.precision,\n }\n : {}),\n }).format(n);\n } catch {\n // Unknown/invalid ISO code — still show the money, never blank it.\n return `${groupedNumber(n, { precision: o.precision ?? 2 })} ${currency}`;\n }\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"percent\",\n label: \"Percent\",\n description: \"Percentage — stored as a plain number\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"percentScale\", \"precision\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const shown = o.percentScale === \"fraction\" ? n * 100 : n;\n return `${groupedNumber(shown, o.precision === undefined ? {} : { precision: o.precision })}%`;\n },\n parse: (raw) => toNumber(raw),\n },\n {\n // A percentage DRAWN as a bar — task completion, quota used, a score out of\n // 100. Same storage and scale rule as `percent`; only the rendering differs.\n id: \"progress\",\n label: \"Progress bar\",\n description: \"A percentage shown as a bar\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n rich: true,\n optionKeys: [\"percentScale\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const shown = o.percentScale === \"fraction\" ? n * 100 : n;\n return `${groupedNumber(shown, { precision: 0 })}%`;\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"duration\",\n label: \"Duration\",\n description: \"Length of time, shown as h:mm:ss\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"durationUnit\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return formatSignedDuration(n * DURATION_TO_SECONDS[o.durationUnit ?? \"seconds\"]);\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"integer\",\n label: \"Whole number\",\n description: \"Integer with no decimal part\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"useGrouping\", \"prefix\", \"suffix\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return affix(groupedNumber(Math.trunc(n), { ...o, precision: 0 }), o);\n },\n parse: (raw) => {\n const n = toNumber(raw);\n return n === null ? null : Math.trunc(n);\n },\n },\n {\n id: \"rating\",\n label: \"Rating\",\n description: \"Score out of a maximum, shown as stars\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"rating\",\n rich: true,\n optionKeys: [\"ratingMax\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const max = o.ratingMax ?? 5;\n if (n < 0 || n > max) return null;\n return `${n} / ${max}`;\n },\n parse: (raw) => {\n const n = toNumber(raw);\n return n === null ? null : Math.round(n);\n },\n },\n {\n id: \"file_size\",\n label: \"File size\",\n description: \"Byte count shown as KB / MB / GB\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"number\",\n numericAlign: true,\n // The name says BYTES on purpose: this cell holds a genuine byte length,\n // never a character or item count, and `formatFileSize` is only honest\n // about a quantity that is actually bytes.\n format: (v) => {\n const byteLength = toNumber(v);\n return byteLength === null ? null : formatFileSize(byteLength);\n },\n parse: (raw) => {\n const byteLength = toNumber(raw);\n return byteLength === null ? null : Math.trunc(byteLength);\n },\n },\n\n // ── Choice ────────────────────────────────────────────────────────────────\n {\n id: \"boolean\",\n label: \"Yes / No\",\n description: \"True or false\",\n group: \"Choice\",\n base: \"boolean\",\n editor: \"checkbox\",\n format: (v) => {\n if (typeof v === \"boolean\") return v ? \"Yes\" : \"No\";\n if (v === \"true\" || v === 1) return \"Yes\";\n if (v === \"false\" || v === 0) return \"No\";\n return null;\n },\n parse: (raw) => {\n if (typeof raw === \"boolean\") return raw;\n if (raw == null || raw === \"\") return null;\n return String(raw).toLowerCase() === \"true\" || raw === 1;\n },\n },\n\n // ── Dates ─────────────────────────────────────────────────────────────────\n {\n id: \"date\",\n label: \"Date\",\n description: \"Calendar date\",\n group: \"Dates\",\n base: \"date\",\n alsoAccepts: [\"datetime\", \"string\"],\n editor: \"date\",\n optionKeys: [\"dateStyle\"],\n format: (v, o) => {\n const d = toDate(v);\n if (!d) return null;\n return d.toLocaleDateString(undefined, {\n dateStyle: o.dateStyle ?? \"medium\",\n });\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"datetime\",\n label: \"Date & time\",\n description: \"Calendar date with a time of day\",\n group: \"Dates\",\n base: \"datetime\",\n alsoAccepts: [\"date\", \"string\"],\n editor: \"datetime\",\n optionKeys: [\"dateStyle\"],\n format: (v, o) => {\n const d = toDate(v);\n if (!d) return null;\n return d.toLocaleString(undefined, {\n dateStyle: o.dateStyle ?? \"medium\",\n timeStyle: \"short\",\n });\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n // A TIME OF DAY with no date: opening hours, a shift start, a meeting slot.\n // Stored as the string the <input type=\"time\"> gives (\"HH:MM\" or\n // \"HH:MM:SS\"), shown in the viewer's locale (\"2:30 PM\" / \"14:30\"). The\n // storage type stays `string`; sorting is lexical on the 24-hour form,\n // which is chronological — the one reason the stored form is 24-hour.\n id: \"time\",\n label: \"Time\",\n description: \"Time of day, without a date\",\n group: \"Dates\",\n base: \"string\",\n editor: \"time\",\n optionKeys: [\"timeSeconds\"],\n format: (v, o) => {\n const parts = parseTimeOfDay(v);\n if (!parts) return null;\n const d = new Date(2000, 0, 1, parts.h, parts.m, parts.s);\n return d.toLocaleTimeString(undefined, {\n hour: \"numeric\",\n minute: \"2-digit\",\n ...(o.timeSeconds ? { second: \"2-digit\" } : {}),\n });\n },\n // Normalise to the 24-hour \"HH:MM[:SS]\" form; a value that is not a time\n // is handed back unchanged so the validation layer can refuse it by name.\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n const parts = parseTimeOfDay(raw);\n if (!parts) return raw;\n // A TIME OF DAY, not a duration: this is the stored 24-hour spelling the\n // column keeps, joined rather than interpolated so it cannot be read as a\n // hand-rolled clock formatter.\n const two = (n: number) => String(n).padStart(2, \"0\");\n const fields = parts.s > 0 ? [parts.h, parts.m, parts.s] : [parts.h, parts.m];\n return fields.map(two).join(\":\");\n },\n },\n {\n // Assigned BY THE DATABASE when the row is inserted (trigger\n // `_udt_autonumber`), so it is the same number whichever door made the row\n // and never shifts when another row is deleted. Stored in the cell as an\n // integer; every editor refuses it. `prefix` turns 42 into \"INV-42\".\n id: \"autonumber\",\n label: \"Autonumber\",\n description: \"A number the table assigns to each new row — never typed\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"computed\",\n numericAlign: true,\n optionKeys: [\"prefix\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return `${o.prefix ?? \"\"}${Math.trunc(n)}`;\n },\n parse: () => null,\n },\n // ── System columns ────────────────────────────────────────────────────────\n // Filled from the ROW'S OWN RECORD on read (`withComputedColumns` in\n // `@ai-matrx/design-system/formulas`), never typed and never stored in the\n // cell. `editor: \"computed\"` makes every editor refuse them.\n {\n id: \"created_time\",\n label: \"Created time\",\n description: \"When the row was added — filled in automatically\",\n group: \"Dates\",\n base: \"string\",\n editor: \"computed\",\n optionKeys: [\"dateStyle\"],\n format: (v, o) => FIELD_FORMATS.datetime.format(v, o),\n parse: () => null,\n },\n {\n id: \"modified_time\",\n label: \"Last modified time\",\n description: \"When the row last changed — filled in automatically\",\n group: \"Dates\",\n base: \"string\",\n editor: \"computed\",\n optionKeys: [\"dateStyle\"],\n format: (v, o) => FIELD_FORMATS.datetime.format(v, o),\n parse: () => null,\n },\n {\n id: \"relative_time\",\n label: \"Relative time\",\n description: 'Shown as \"3 days ago\"',\n group: \"Dates\",\n base: \"datetime\",\n alsoAccepts: [\"date\", \"string\"],\n editor: \"datetime\",\n format: (v) => {\n const d = toDate(v);\n // THE one relative stamp (@ai-matrx/kit/format). This registry carried\n // its own Intl ladder until 2026-09-12 — the last re-implementation of\n // formatRelativeTime in this repo. It ROUNDED, so a 1h50m-old row read\n // \"2 hours ago\" here and \"1h ago\" on every other surface in the product;\n // the package truncates, which is what the rest of the fleet shows.\n return d ? formatRelativeTime(d, { style: \"intl\" }) : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n\n // ── Structured ────────────────────────────────────────────────────────────\n {\n id: \"json\",\n label: \"JSON\",\n description: \"Structured object\",\n group: \"Structured\",\n base: \"json\",\n alsoAccepts: [\"array\"],\n editor: \"json\",\n format: (v) => {\n if (v === null || v === undefined) return null;\n if (typeof v === \"object\") return JSON.stringify(v);\n return toText(v);\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (typeof raw !== \"string\") return raw;\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return raw;\n }\n },\n },\n {\n id: \"array\",\n label: \"List\",\n description: \"List of values\",\n group: \"Structured\",\n base: \"array\",\n alsoAccepts: [\"json\"],\n editor: \"json\",\n format: (v) => {\n const list = toList(v);\n return list === null ? null : list.map((i) => String(i)).join(\", \");\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw;\n return toList(raw) ?? raw;\n },\n },\n {\n // FILES ATTACHED TO THE ROW. The cell stores the files feature's file ids\n // (`files.files.id`), never a URL or a name: a rename or a move in Files\n // changes nothing here, and access is the file's own (RLS) — a reader who\n // may see the row but not the file gets the chip's honest \"no access\", not\n // a broken link. Drawn by `AttachmentChips`, edited by `AttachmentInput`\n // through the ONE file picker.\n id: \"attachment\",\n label: \"Attachments\",\n description: \"Files from your Files, shown as chips\",\n group: \"Structured\",\n base: \"array\",\n // Not `string`: on a text column it must appear under \"Other kinds\" so\n // picking it retypes the column to a list, rather than storing a JSON\n // string of ids in a text cell.\n alsoAccepts: [\"json\"],\n editor: \"attachment\",\n rich: true,\n format: (v) => {\n const list = toList(v);\n if (list === null) return null;\n const n = list.filter((i) => String(i).trim()).length;\n return n === 0 ? \"\" : n === 1 ? \"1 file\" : `${n} files`;\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw.map((i) => String(i).trim()).filter(Boolean);\n const text = String(raw).trim();\n if (text.startsWith(\"[\")) {\n try {\n const parsed = JSON.parse(text);\n if (Array.isArray(parsed)) return parsed.map((i) => String(i).trim()).filter(Boolean);\n } catch {\n // a comma list, below\n }\n }\n return text.split(\",\").map((s) => s.trim()).filter(Boolean);\n },\n },\n {\n id: \"tags\",\n label: \"Tags\",\n description: \"List of values shown as chips\",\n group: \"Structured\",\n base: \"array\",\n alsoAccepts: [\"json\", \"string\"],\n editor: \"json\",\n rich: true,\n format: (v) => {\n const list = toList(v);\n return list === null ? null : list.map((i) => String(i)).join(\", \");\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw;\n return toList(raw) ?? raw;\n },\n },\n\n // ─── Choice ────────────────────────────────────────────────────────────────\n //\n // An enum WITHOUT a database enum. The column stays `string` (or `array` for\n // multi), the database enforces nothing, and stripping the format leaves\n // every stored value exactly as it was. Options are a UI layer — the same\n // trick `percent` plays on a number.\n //\n // The fallback law is the whole reason this is safe to switch on over live\n // data: a value not in the option list is NOT an error and is NEVER blanked.\n // `format()` returns null for it, `<FormattedFieldValue>` renders it in amber\n // with the reason, and the user can fix it or adopt it. Declaring the options\n // is therefore how you FIND your stray values, not how you lose them.\n //\n // Options resolve through `choices.ts` — inline `options.choices`, or a\n // structured list via `options.structuredList`. This def only ever sees\n // whatever the caller resolved into `options.choices`, so it stays pure and\n // synchronous like every other format.\n {\n id: \"choice\",\n label: \"Choice\",\n description: \"One value from a list of options\",\n group: \"Choice\",\n base: \"string\",\n alsoAccepts: [\"number\", \"integer\", \"boolean\"],\n editor: \"select\",\n rich: true,\n optionKeys: [\"choices\", \"structuredList\", \"allowOther\"],\n format: (v, options) => {\n const text = toText(v);\n if (text === null) return null;\n const trimmed = text.trim();\n if (trimmed === \"\") return \"\";\n const match = findChoice(options.choices, trimmed);\n // No options resolved yet (a structured list still loading, or a column\n // whose list is unreachable) — show the raw value rather than accusing\n // every cell of being off-list.\n if (!options.choices || options.choices.length === 0) return trimmed;\n return match ? (match.label ?? match.value) : null;\n },\n parse: (raw) => {\n if (raw == null) return null;\n const text = toText(raw);\n if (text === null) return raw;\n const trimmed = text.trim();\n return trimmed === \"\" ? null : trimmed;\n },\n },\n {\n // A PERSON: one member of the organization the table belongs to, stored as\n // the user id. It is a choice column whose options are the members —\n // supplied at runtime by whoever renders it (features/data-tables/\n // person-choices.tsx), the same door a structured-list-bound column uses —\n // so the cell picker, the chip, filters, color-by and the agent scope are\n // all the choice system's, with no second implementation.\n id: \"person\",\n label: \"Person\",\n description: \"Someone in your organization — pick from its members\",\n group: \"Choice\",\n base: \"string\",\n editor: \"select\",\n rich: true,\n format: (v, o) => FIELD_FORMATS.choice.format(v, o),\n parse: (raw) => FIELD_FORMATS.choice.parse(raw, {}),\n },\n {\n // A RECORD OF ANOTHER TABLE. The cell stores that record's ID; the screen\n // shows its WORDS. `person` is the precedent and the template — it has\n // stored a user id and displayed a name through `choiceMap` since\n // 2026-09-21 — so `relation` is a member of CHOICE_FORMAT_IDS and every\n // one of the ten readers that print a cell resolves it through the path\n // they already use for `person`. There is no eleventh place to remember.\n //\n // THREE STATES, THREE RENDERINGS, NO FOURTH (OLD-TABLES-CUTOVER §3.3):\n // resolved the target's words\n // unresolvable the amber fallback WITH the id, as an identifier — never\n // a blank, and never the bare uuid as if it were text, so a\n // person can act on it and support can trace it\n // withheld platform.relation_withheld_label(), with NO id — a cell\n // the reader may not see never leaks the identifier through\n // the gap between the two stores' refusals\n //\n // `format` therefore returns null (the fallback law's \"cannot interpret\")\n // for anything the resolver did not turn into words, and the renderer is\n // what distinguishes unresolvable from withheld. It never prints a bare id.\n id: \"relation\",\n label: \"Relation\",\n description: \"A record of another table — pick it, and its name is shown\",\n group: \"Choice\",\n base: \"string\",\n alsoAccepts: [\"array\", \"json\"],\n editor: \"select\",\n rich: true,\n optionKeys: [\"relation_target\", \"relation_max\", \"on_delete\", \"display\"],\n // IT DOES NOT DELEGATE TO `choice`, AND THAT IS THE WHOLE POINT.\n // `choice.format` deliberately returns the RAW VALUE when no options have\n // resolved yet — a structured list still loading should not make every cell\n // look off-list. For a label column that is right: the raw value IS the\n // label. For an identifier column it is the defect §3.3 names, and it is\n // how the first cut of this wave put four raw uuids on a real grid: the\n // fallback arm never ran, because returning the uuid counted as SUCCESS.\n //\n // Here, a value that did not resolve to words is a MISMATCH — `null` — so\n // the renderer's three-state arm owns it and shows the amber fallback with\n // the id marked as an identifier, or the withheld sentence. The id never\n // reaches the eye as if it were the cell's contents.\n format: (v, options) => {\n const list = toList(v);\n const values =\n list !== null ? list : v == null ? [] : [v];\n if (values.length === 0) return \"\";\n const words: string[] = [];\n for (const entry of values) {\n const text = toText(entry);\n if (text === null) return null;\n const trimmed = text.trim();\n if (trimmed === \"\") continue;\n const match = findChoice(options.choices, trimmed);\n if (!match) return null; // nothing resolved it — never print the id\n words.push(match.label ?? match.value);\n }\n return words.length === 0 ? \"\" : words.join(\", \");\n },\n parse: (raw) => FIELD_FORMATS.choice.parse(raw, {}),\n },\n {\n id: \"multi_choice\",\n label: \"Multi-choice\",\n description: \"Any number of values from a list of options\",\n group: \"Choice\",\n base: \"array\",\n alsoAccepts: [\"string\", \"json\"],\n editor: \"multiselect\",\n rich: true,\n optionKeys: [\"choices\", \"structuredList\", \"allowOther\"],\n format: (v, options) => {\n const list = toList(v);\n if (list === null) return null;\n if (list.length === 0) return \"\";\n const labels: string[] = [];\n let anyOffList = false;\n for (const entry of list) {\n const text = toText(entry);\n if (text === null) {\n anyOffList = true;\n continue;\n }\n const match = findChoice(options.choices, text.trim());\n if (match) labels.push(match.label ?? match.value);\n else {\n labels.push(text.trim());\n anyOffList = true;\n }\n }\n // Same leniency as `choice`: with no options resolved, nothing is off-list.\n const haveOptions = !!options.choices && options.choices.length > 0;\n if (haveOptions && anyOffList) return null;\n return labels.join(\", \");\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw;\n return toList(raw) ?? raw;\n },\n },\n\n // ─── Computed ──────────────────────────────────────────────────────────────\n //\n // A formula column stores NOTHING. `metadata.format.options.formula` holds\n // the expression; the stored cell is always null; the value is computed on\n // read from the other columns of the same row\n // (`@ai-matrx/design-system/formulas`). Grouped under Structured on purpose\n // — the picker's groups are a fixed vocabulary, and a formula is structure\n // over a plain string column, not a sixth family.\n //\n // `format()` here is handed the COMPUTED value, never a stored one, and its\n // only job is to render it: through `resultFormat`'s own `format()` when the\n // column declares one (a computed total shown as Currency, a computed date\n // shown as a Date), and plainly otherwise. It never returns null for an\n // empty value, because a formula legitimately produces BLANK.\n //\n // No `optionKeys`: the expression is authored in its own editor, not in the\n // generic option rail, and listing a key the picker has no control for would\n // open an empty options section under every formula column.\n {\n id: \"formula\",\n label: \"Formula\",\n description: \"Computed from the other columns in the row — never typed in\",\n group: \"Structured\",\n base: \"string\",\n editor: \"computed\",\n format: (v, o) => {\n const target = o.formula?.resultFormat;\n if (target && target !== \"formula\") {\n const delegate = FIELD_FORMATS[target];\n if (delegate) {\n const rendered = delegate.format(v, o);\n if (rendered !== null) return rendered;\n }\n }\n if (v === null || v === undefined) return \"\";\n if (typeof v === \"boolean\") return v ? \"Yes\" : \"No\";\n if (typeof v === \"number\") return groupedNumber(v, o);\n return toText(v);\n },\n // Nothing is ever stored in a formula column. Returning the raw input here\n // would be the one way a computed cell could acquire a value of its own.\n parse: () => null,\n },\n];\n\nexport const FIELD_FORMATS: Readonly<Record<FieldFormatId, FieldFormatDef>> =\n Object.freeze(\n Object.fromEntries(DEFS.map((d) => [d.id, d])) as Record<\n FieldFormatId,\n FieldFormatDef\n >,\n );\n\nexport const FIELD_FORMAT_LIST: readonly FieldFormatDef[] = Object.freeze(DEFS);\n\nexport const FIELD_FORMAT_IDS = DEFS.map((d) => d.id) as FieldFormatId[];\n\nexport function getFieldFormat(\n id: string | null | undefined,\n): FieldFormatDef | null {\n if (!id) return null;\n return FIELD_FORMATS[id as FieldFormatId] ?? null;\n}\n\n/**\n * The format a column gets when it declares none — the identity format for its\n * storage type. Every existing column therefore behaves exactly as before.\n */\nexport function defaultFormatForBase(base: string): FieldFormatId {\n switch (base as FieldBaseType) {\n case \"number\":\n return \"number\";\n case \"integer\":\n return \"integer\";\n case \"boolean\":\n return \"boolean\";\n case \"date\":\n return \"date\";\n case \"datetime\":\n return \"datetime\";\n case \"json\":\n return \"json\";\n case \"array\":\n return \"array\";\n default:\n return \"text\";\n }\n}\n\n/** Formats that can legally sit on a given storage type. */\nexport function formatsForBase(base: string): FieldFormatDef[] {\n return DEFS.filter(\n (d) => d.base === base || (d.alsoAccepts ?? []).includes(base as FieldBaseType),\n );\n}\n\n/** Group the pickable formats for a base type, preserving registry order. */\nexport function groupedFormatsForBase(\n base: string,\n): { group: FieldFormatDef[\"group\"]; formats: FieldFormatDef[] }[] {\n const out: { group: FieldFormatDef[\"group\"]; formats: FieldFormatDef[] }[] =\n [];\n for (const def of formatsForBase(base)) {\n const bucket = out.find((g) => g.group === def.group);\n if (bucket) bucket.formats.push(def);\n else out.push({ group: def.group, formats: [def] });\n }\n return out;\n}\n","/**\n * THE FALLBACK LAW, implemented.\n *\n * `formatFieldValue` never blanks a cell and never throws. When the declared\n * format cannot interpret the stored value it returns `ok: false` with the\n * **base-type** rendering of that same value plus a human `reason`. The UI\n * renders that text in amber (see `<FormattedFieldValue>`), so a mismatch is\n * visible instead of invisible.\n */\nimport {\n defaultFormatForBase,\n getFieldFormat,\n FIELD_FORMATS,\n} from \"./registry\";\nimport type {\n FieldFormatConfig,\n FieldFormatOptions,\n FormatResult,\n} from \"./types\";\n\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined || value === \"\";\n}\n\n/** Last-resort rendering — the raw value, readable, never empty-by-accident. */\nfunction baseText(value: unknown): string {\n if (isEmpty(value)) return \"\";\n if (typeof value === \"object\") {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n/**\n * Read a field's declared format off its `metadata` JSONB. Returns null when\n * the field declares none (or declares one we don't know) — the caller then\n * falls back to the storage type's identity format, so an unknown or removed\n * format id degrades to plain, never to broken.\n */\nexport function readFieldFormatConfig(\n metadata: unknown,\n): FieldFormatConfig | null {\n if (!metadata || typeof metadata !== \"object\") return null;\n const raw = (metadata as Record<string, unknown>).format;\n if (!raw || typeof raw !== \"object\") return null;\n const id = (raw as Record<string, unknown>).id;\n if (typeof id !== \"string\" || !getFieldFormat(id)) return null;\n const options = (raw as Record<string, unknown>).options;\n // `exactOptionalPropertyTypes`: an absent `options` is the key being ABSENT,\n // never the key holding `undefined` — the two are different documents once\n // this config is written back out as JSON.\n return {\n id: id as FieldFormatConfig[\"id\"],\n ...(options && typeof options === \"object\"\n ? { options: options as FieldFormatOptions }\n : {}),\n };\n}\n\n/**\n * Resolve the format a field actually renders with: its declared one, or the\n * identity format for its storage type.\n */\nexport function resolveFieldFormat(\n dataType: string,\n metadata: unknown,\n): FieldFormatConfig {\n const declared = readFieldFormatConfig(metadata);\n if (declared) return declared;\n return { id: defaultFormatForBase(dataType) };\n}\n\nexport function formatFieldValue(\n value: unknown,\n config: FieldFormatConfig | null | undefined,\n /** The column's storage type — used for the fallback rendering. */\n dataType?: string,\n): FormatResult {\n if (isEmpty(value)) return { ok: true, text: \"\", empty: true };\n\n const def = config ? getFieldFormat(config.id) : null;\n const effective =\n def ?? FIELD_FORMATS[defaultFormatForBase(dataType ?? \"string\")];\n\n let formatted: string | null;\n try {\n formatted = effective.format(value, config?.options ?? {});\n } catch {\n formatted = null;\n }\n\n if (formatted !== null) return { ok: true, text: formatted, empty: false };\n\n // Mismatch. Show what IS stored, flagged.\n return {\n ok: false,\n text: baseText(value),\n empty: false,\n reason: `This value is not a valid ${effective.label.toLowerCase()} — showing the stored value instead.`,\n };\n}\n\n/** Turn raw editor input into the value to store under a format. */\nexport function parseFieldInput(\n raw: unknown,\n config: FieldFormatConfig | null | undefined,\n dataType?: string,\n): unknown {\n const def = config ? getFieldFormat(config.id) : null;\n const effective =\n def ?? FIELD_FORMATS[defaultFormatForBase(dataType ?? \"string\")];\n try {\n return effective.parse(raw, config?.options ?? {});\n } catch {\n return raw;\n }\n}\n\n/**\n * Would this stored value survive its declared format? Used to count mismatches\n * so a column can warn (\"4 values don't fit Currency\") instead of silently\n * showing amber cells.\n */\nexport function valueFitsFormat(\n value: unknown,\n config: FieldFormatConfig | null | undefined,\n dataType?: string,\n): boolean {\n return formatFieldValue(value, config, dataType).ok;\n}\n"],"mappings":"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","names":["d"]}
@@ -1,5 +1,5 @@
1
- import { a as FieldFormatId, b as FieldFormatDef, F as FieldFormatConfig, c as FormatResult } from '../types-CJWU1Gae.cjs';
2
- export { d as FieldBaseType, e as FieldChoice, f as FieldEditorKind, g as FieldFormatOptions, S as StructuredListBinding } from '../types-CJWU1Gae.cjs';
1
+ import { a as FieldFormatId, b as FieldFormatDef, F as FieldFormatConfig, c as FormatResult } from '../types-JnZD8wM0.cjs';
2
+ export { d as FieldBaseType, e as FieldChoice, f as FieldEditorKind, g as FieldFormatOptions, S as StructuredListBinding } from '../types-JnZD8wM0.cjs';
3
3
 
4
4
  declare const FIELD_FORMATS: Readonly<Record<FieldFormatId, FieldFormatDef>>;
5
5
  declare const FIELD_FORMAT_LIST: readonly FieldFormatDef[];