@ai-matrx/records-ui 0.63.0 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,8 +31,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var src_exports = {};
33
33
  __export(src_exports, {
34
+ ABSENCE_WORD_LABEL: () => ABSENCE_WORD_LABEL,
35
+ ABSENCE_WORD_VALUES: () => ABSENCE_WORD_VALUES,
34
36
  ACTION_KINDS: () => ACTION_KINDS,
35
37
  ActionInbox: () => ActionInbox,
38
+ BOOKING_PAGE_STATE_LABEL: () => BOOKING_PAGE_STATE_LABEL,
39
+ BOOKING_PAGE_STATE_VALUES: () => BOOKING_PAGE_STATE_VALUES,
36
40
  BookingBuilder: () => BookingBuilder,
37
41
  BookingSlots: () => BookingSlots,
38
42
  BuildOrAsk: () => BuildOrAsk,
@@ -40,7 +44,10 @@ __export(src_exports, {
40
44
  CHART_KINDS: () => CHART_KINDS,
41
45
  CHART_KIND_LABEL: () => CHART_KIND_LABEL,
42
46
  CHART_NEEDS: () => CHART_NEEDS,
47
+ CHECKLIST_REQUIREMENT_KINDS: () => CHECKLIST_REQUIREMENT_KINDS,
48
+ CHECKLIST_REQUIREMENT_LABEL: () => CHECKLIST_REQUIREMENT_LABEL,
43
49
  CONDITION_OPS: () => CONDITION_OPS,
50
+ CONTEXT_POLICY_LABEL: () => CONTEXT_POLICY_LABEL,
44
51
  CaptureRun: () => CaptureRun,
45
52
  CaptureSheet: () => CaptureSheet,
46
53
  ChartBlock: () => ChartBlock,
@@ -56,17 +63,25 @@ __export(src_exports, {
56
63
  CustomFieldsSection: () => CustomFieldsSection,
57
64
  DEFAULT_FIELDS: () => DEFAULT_FIELDS,
58
65
  DEFAULT_VIEW_NAME: () => DEFAULT_VIEW_NAME,
66
+ DUE_STATE_LABEL: () => DUE_STATE_LABEL,
59
67
  DashboardCanvas: () => DashboardCanvas,
60
68
  DigestScheduler: () => DigestScheduler,
61
69
  DocRender: () => DocRender,
62
70
  DocTemplate: () => DocTemplate,
63
71
  EMPTY_PRESENTATION: () => EMPTY_PRESENTATION,
72
+ ENGINEER_TEXT: () => ENGINEER_TEXT,
64
73
  EmbedFrame: () => EmbedFrame,
65
74
  EnrichBadge: () => EnrichBadge,
66
75
  EnrichPanel: () => EnrichPanel,
67
76
  ExportMenu: () => ExportMenu,
77
+ FIELD_RULE_KIND_LABEL: () => FIELD_RULE_KIND_LABEL,
78
+ FIELD_RULE_KIND_VALUES: () => FIELD_RULE_KIND_VALUES,
79
+ FIELD_SENSITIVITY_LABEL: () => FIELD_SENSITIVITY_LABEL,
68
80
  FIELD_TYPE_CHOICES: () => FIELD_TYPE_CHOICES,
69
81
  FIELD_TYPE_GROUPS: () => FIELD_TYPE_GROUPS,
82
+ FORMULA_OP_JOINS: () => FORMULA_OP_JOINS,
83
+ FORMULA_OP_LABEL: () => FORMULA_OP_LABEL,
84
+ FORMULA_OP_VALUES: () => FORMULA_OP_VALUES,
70
85
  FORM_FLOWS: () => FORM_FLOWS,
71
86
  FROZEN_COLUMN_WIDTH: () => FROZEN_COLUMN_WIDTH,
72
87
  FieldControl: () => FieldControl,
@@ -114,6 +129,8 @@ __export(src_exports, {
114
129
  PAGE_VIEW_LABEL: () => PAGE_VIEW_LABEL,
115
130
  PARITY_LABEL: () => PARITY_LABEL,
116
131
  PARITY_MADE_OF: () => PARITY_MADE_OF,
132
+ PROPOSED_CHANGE_ACT_LABEL: () => PROPOSED_CHANGE_ACT_LABEL,
133
+ PROPOSED_CHANGE_ACT_VALUES: () => PROPOSED_CHANGE_ACT_VALUES,
117
134
  Peek: () => Peek,
118
135
  PersonPicker: () => PersonPicker,
119
136
  PipelineBoard: () => PipelineBoard,
@@ -124,6 +141,8 @@ __export(src_exports, {
124
141
  ProposalRow: () => ProposalRow,
125
142
  ProvenanceBadge: () => ProvenanceBadge,
126
143
  PublicViewPage: () => PublicViewPage,
144
+ ROLLUP_AGG_LABEL: () => ROLLUP_AGG_LABEL,
145
+ ROLLUP_AGG_VALUES: () => ROLLUP_AGG_VALUES,
127
146
  RecordChat: () => RecordChat,
128
147
  RecordChip: () => RecordChip,
129
148
  RecordForm: () => RecordForm,
@@ -139,8 +158,13 @@ __export(src_exports, {
139
158
  SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS: () => SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
140
159
  SERIES_COLORS: () => SERIES_COLORS,
141
160
  SOMEBODY: () => SOMEBODY,
161
+ STAGE_RULE_ON_FAIL_LABEL: () => STAGE_RULE_ON_FAIL_LABEL,
142
162
  STORE_ANSWERS_THESE: () => STORE_ANSWERS_THESE,
143
163
  STORE_DECIDES_REASON: () => STORE_DECIDES_REASON,
164
+ SUBSCRIPTION_CADENCES: () => SUBSCRIPTION_CADENCES,
165
+ SUBSCRIPTION_CADENCE_LABEL: () => SUBSCRIPTION_CADENCE_LABEL,
166
+ SUBSCRIPTION_CHANNEL_LABEL: () => SUBSCRIPTION_CHANNEL_LABEL,
167
+ SUBSCRIPTION_CHANNEL_VALUES: () => SUBSCRIPTION_CHANNEL_VALUES,
144
168
  ShareControl: () => ShareControl,
145
169
  SignBlock: () => SignBlock,
146
170
  StageRulesSection: () => StageRulesSection,
@@ -158,6 +182,9 @@ __export(src_exports, {
158
182
  ViewBar: () => ViewBar,
159
183
  ViewSwitcher: () => ViewSwitcher,
160
184
  WITHHELD_RECORD_LABEL: () => WITHHELD_RECORD_LABEL,
185
+ WORK_DUE_STATES: () => WORK_DUE_STATES,
186
+ WORK_INBOX_KINDS: () => WORK_INBOX_KINDS,
187
+ WORK_INBOX_KIND_LABEL: () => WORK_INBOX_KIND_LABEL,
161
188
  WhoChangedSource: () => WhoChangedSource,
162
189
  actorBadge: () => actorBadge,
163
190
  actorWords: () => actorWords,
@@ -166,6 +193,7 @@ __export(src_exports, {
166
193
  blockFromSpec: () => blockFromSpec,
167
194
  bodyForReading: () => bodyForReading,
168
195
  bodyFromKeys: () => bodyFromKeys,
196
+ cameFromLine: () => cameFromLine,
169
197
  cellState: () => cellState,
170
198
  chooseSurface: () => chooseSurface,
171
199
  colorFromTheValue: () => colorFromTheValue,
@@ -197,6 +225,7 @@ __export(src_exports, {
197
225
  hintIsMachineIdentity: () => hintIsMachineIdentity,
198
226
  humanize: () => humanize,
199
227
  idsOf: () => idsOf,
228
+ isEngineerText: () => isEngineerText,
200
229
  isId: () => isId,
201
230
  isMachineIdentity: () => isMachineIdentity,
202
231
  isPlainFieldType: () => isPlainFieldType,
@@ -347,12 +376,12 @@ var NO_RIGHTS = tableRights(NOT_ANSWERED_YET);
347
376
  function tableRightsAt(level) {
348
377
  return tableRights(whatYouMayDo(level, true));
349
378
  }
350
- var NO_SAVED_VIEWS_REASON = "No saved-view port is bound, so there is nothing here to subscribe to. A subscription points at a `platform.saved_view`, which the record store has no door onto \u2014 bind `savedViews` on <RecordsUiProvider> with your host's own list and the picker appears.";
351
- var NO_MEMBERS_REASON = "No membership port is bound, so this question cannot offer anybody. A person field points at the kernel Person Table, and who is in this organization is the platform's answer rather than the record store's \u2014 bind `members` on <RecordsUiProvider> with your host's own roster and the picker appears.";
352
- var NO_SHARE_REASON = "No share dialog is bound, so this screen offers no Share button rather than one that opens nothing. Bind `share` on <RecordsUiProvider> with the app's own sharing dialog \u2014 AI Matrx has exactly one (features/sharing), and this package deliberately does not ship a second.";
353
- var NO_OPEN_RECORDS_REASON = "These numbers do not click through yet. Every one of them is a count of real records, and where this app puts its grid is the host's routing rather than this package's \u2014 bind `openRecords` on <RecordsUiProvider> and a bar, a slice or a row opens the records it counted.";
354
- var NO_REASK_REASON = "Asking for a change in words needs an agent, and this package deliberately reaches one through the host rather than calling a model itself. Bind `onReask` on <RecordsUiProvider> \u2014 it drives `dashboard_propose` on the server's `records` tool with this dashboard's id \u2014 and this box works.";
355
- var NO_UPLOAD_REASON = "No file store is bound, so this question cannot take a file. Bind `upload` on <RecordsUiProvider> with your host's file service and the chooser appears.";
379
+ var NO_SAVED_VIEWS_REASON = "There are no saved views here yet, so there is nothing to be told about. Save a view of this table first and it will be offered here.";
380
+ var NO_MEMBERS_REASON = "This app cannot offer the people in your organization on this screen yet, so there is nobody to pick. Type the person's name into a text field for now, and tell us \u2014 it is on our list, not yours.";
381
+ var NO_SHARE_REASON = "Sharing is not available on this screen, so there is no Share button here rather than one that opens nothing. Share this table from the tables list instead.";
382
+ var NO_OPEN_RECORDS_REASON = "These numbers do not click through yet. Every one of them is a count of real records \u2014 open the records view and group it by the same field to see them. It is on our list, not yours.";
383
+ var NO_REASK_REASON = "Changing this dashboard by describing it is not switched on here yet, so this box is off rather than one that goes nowhere. Add and remove the blocks by hand above. It is on our list, not yours.";
384
+ var NO_UPLOAD_REASON = "This screen cannot take a file yet, so there is no chooser here rather than one that loses what you pick. Put a link to the file in a text field for now. It is on our list, not yours.";
356
385
  var HostContext = (0, import_react.createContext)(null);
357
386
  function RecordsUiProvider({ value, children }) {
358
387
  const bound = (0, import_react.useMemo)(() => value, [value]);
@@ -427,6 +456,10 @@ var TITLE = {
427
456
  not_supported: "That cannot be done here",
428
457
  store_limit: "This has got as big as it can",
429
458
  store_not_served: "This organization does not keep its data here",
459
+ unreachable: "We could not reach your data",
460
+ needs_approval: "This is waiting on someone's approval",
461
+ stale_read: "Your view of this is a moment behind",
462
+ timed_out: "That took too long to answer",
430
463
  internal: "Something went wrong at our end"
431
464
  };
432
465
  var REMEDY = {
@@ -441,8 +474,35 @@ var REMEDY = {
441
474
  not_supported: "There is another way to do this \u2014 ask, and we will point you at it.",
442
475
  store_limit: "Split it up, or ask us to raise the ceiling for your organization.",
443
476
  store_not_served: "An administrator of this organization turns the record store on from the data settings screen.",
477
+ // 🚨 NOTHING WAS WRITTEN AND NOTHING WAS REFUSED (lane TAILS-6). The call
478
+ // never arrived, so "try again" is the honest instruction rather than a
479
+ // consolation — and saying nothing changed is the half a person actually
480
+ // worries about.
481
+ unreachable: "Nothing was changed. Check your connection and open this again.",
482
+ needs_approval: "Nothing was changed yet. The people who can approve it have been asked.",
483
+ stale_read: "Open this again in a moment and it will be up to date.",
484
+ timed_out: "Nothing was changed. Try a smaller page, or try again in a moment.",
444
485
  internal: "Nothing you did caused this. Try again, and tell us if it keeps happening."
445
486
  };
487
+ var ENGINEER_TEXT = [
488
+ /@ai-matrx\//,
489
+ /\bSQLSTATE\b/i,
490
+ /\bPostgREST\b/i,
491
+ /\bIndexedDB\b/i,
492
+ // `<RecordsUiProvider>` — a component, on a screen.
493
+ /<\/?[A-Z][A-Za-z0-9]*\s*\/?>/,
494
+ // `savedViews`, `openRecords`, `onReask` — an identifier, however it is
495
+ // quoted. English does not write camelCase; code does.
496
+ /\b[a-z][a-z0-9]*[A-Z][A-Za-z0-9]*\b/,
497
+ // A backticked token is a thing you type into an editor, not a thing you do.
498
+ /`[A-Za-z_][A-Za-z0-9_.]*`/,
499
+ // "bind X on Y", "pass X", "import X" — an instruction to whoever maintains
500
+ // the app, arriving on the screen of somebody who cannot act on it.
501
+ /\bbind\s+[`'"A-Za-z]/i
502
+ ];
503
+ function isEngineerText(text) {
504
+ return ENGINEER_TEXT.some((shape) => shape.test(text));
505
+ }
446
506
  function withoutMachineClauses(sentence2) {
447
507
  const dropped = [];
448
508
  const footnotes = [];
@@ -480,11 +540,17 @@ function refusalForAPerson(error) {
480
540
  const message = plainSentence(String(error.message ?? ""));
481
541
  const hint = plainSentence(String(error.hint ?? ""));
482
542
  const title = TITLE[code] ?? "The store refused this";
483
- const remedy = hint.kept ?? REMEDY[code] ?? REMEDY["internal"];
484
- const said = (message.kept ?? "").trim();
543
+ const hintIsForEngineers = isEngineerText(String(error.hint ?? ""));
544
+ const remedy = (hintIsForEngineers ? null : hint.kept) ?? REMEDY[code] ?? REMEDY["internal"];
545
+ const messageIsForEngineers = isEngineerText(String(error.message ?? ""));
546
+ const said = (messageIsForEngineers ? "" : message.kept ?? "").trim();
485
547
  const forEngineers = [
486
548
  error.sqlstate ? `SQLSTATE ${error.sqlstate}.` : null,
487
- error.hint && hint.kept !== error.hint ? `Hint: ${error.hint}` : null,
549
+ // The client's own note about a class it did not recognise, or a call that
550
+ // never reached the store. It is rendered out of sight, never as a sentence.
551
+ error.diagnostic ? error.diagnostic : null,
552
+ messageIsForEngineers ? `Message: ${error.message}` : null,
553
+ error.hint && (hintIsForEngineers || hint.kept !== error.hint) ? `Hint: ${error.hint}` : null,
488
554
  message.dropped.length > 0 || hint.dropped.length > 0 ? `Dropped from the person's view: ${[...message.dropped, ...hint.dropped].join(" | ")}` : null
489
555
  ].filter(Boolean).join(" ");
490
556
  return {
@@ -826,50 +892,9 @@ var PARITY_LABEL = {
826
892
  var PARITY_MADE_OF = Object.fromEntries(
827
893
  import_records3.PARITY_FIELD_TYPES.map((p) => [p.parity_type, p.made_of])
828
894
  );
895
+ var KERNEL = { file: KERNEL_FILE_TABLE, person: KERNEL_PERSON_TABLE };
829
896
  function editorKindFor(field) {
830
- const format = (field.format ?? "").toLowerCase();
831
- const config = field.config ?? {};
832
- switch (field.type) {
833
- // LIMITS-FIX 2026-09-21: the tick box is a BEHAVIOUR of its own, so its editor is
834
- // decided by the behaviour alone — no format, no unit, no target to read. A record
835
- // that never answered holds no key at all, and `CheckboxControl` draws that third
836
- // state rather than pretending it is an unticked box.
837
- case "boolean":
838
- return "checkbox";
839
- case "text":
840
- if (format === "email") return "email";
841
- if (format === "phone") return "phone";
842
- if (format === "url") return "url";
843
- if (format === "json" || config["json"] === true) return "json";
844
- if (format === "long" || config["multiline"] === true) return "long_text";
845
- return "text";
846
- case "range": {
847
- const kind = String(config["kind"] ?? "number");
848
- if (kind === "date" || kind === "datetime") return "datetime";
849
- if (format === "currency") return "currency";
850
- if (format === "percent" || field.unit === "%") return "percent";
851
- return "number";
852
- }
853
- case "list":
854
- return field.multi ? "multi_select" : "select";
855
- case "relation":
856
- return relationEditor(field);
857
- case "formula": {
858
- const via = config["via"];
859
- if (via && config["agg"]) return "rollup";
860
- if (via) return "lookup";
861
- return "formula";
862
- }
863
- default:
864
- return "text";
865
- }
866
- }
867
- function relationEditor(field) {
868
- const parity = String(field.config?.["parity"] ?? "");
869
- if (parity === "attachment" || parity === "member") return parity;
870
- if (field.relation_target === KERNEL_FILE_TABLE) return "attachment";
871
- if (field.relation_target === KERNEL_PERSON_TABLE) return "member";
872
- return "relation";
897
+ return (0, import_records3.fieldKindFor)(field, KERNEL);
873
898
  }
874
899
  function fieldTypeLabel(field) {
875
900
  const kind = editorKindFor(field);
@@ -1765,18 +1790,137 @@ function useLabelWait(field) {
1765
1790
  return field ? waiting(field) : null;
1766
1791
  }
1767
1792
 
1768
- // src/values.tsx
1769
- var import_jsx_runtime9 = require("react/jsx-runtime");
1770
- function envelopeFor(document2, key) {
1771
- const block = document2?._values;
1772
- return block?.[key];
1793
+ // src/enumLabels.ts
1794
+ function exhaustiveValuesOf() {
1795
+ return function(values) {
1796
+ return values;
1797
+ };
1773
1798
  }
1774
- var ABSENCE_WORDS = {
1799
+ var WORK_DUE_STATES = exhaustiveValuesOf()([
1800
+ "overdue",
1801
+ "due_today",
1802
+ "scheduled",
1803
+ "undated",
1804
+ "finished"
1805
+ ]);
1806
+ var DUE_STATE_LABEL = {
1807
+ overdue: "Overdue",
1808
+ due_today: "Today",
1809
+ scheduled: "Scheduled",
1810
+ undated: "No date",
1811
+ finished: "Done"
1812
+ };
1813
+ var WORK_INBOX_KINDS = exhaustiveValuesOf()(["assignment", "approval", "proposal"]);
1814
+ var WORK_INBOX_KIND_LABEL = {
1815
+ assignment: "Yours",
1816
+ approval: "Approve",
1817
+ proposal: "Agent"
1818
+ };
1819
+ var CHECKLIST_REQUIREMENT_KINDS = exhaustiveValuesOf()([
1820
+ "none",
1821
+ "note",
1822
+ "answer",
1823
+ "record_field",
1824
+ "form",
1825
+ "document"
1826
+ ]);
1827
+ var CHECKLIST_REQUIREMENT_LABEL = {
1828
+ none: "",
1829
+ note: "Say what you did",
1830
+ answer: "Fill this in",
1831
+ record_field: "This has to be filled in on the record",
1832
+ form: "This step is a form",
1833
+ document: "This step is a document"
1834
+ };
1835
+ var FIELD_SENSITIVITY_LABEL = {
1836
+ public: "Anyone who can open the record",
1837
+ internal: "People in this organization",
1838
+ confidential: "People who have been given the record",
1839
+ restricted: "Owners and administrators only"
1840
+ };
1841
+ var CONTEXT_POLICY_LABEL = {
1842
+ include: "Yes, an assistant can read it",
1843
+ summarize: "Only a summary of it",
1844
+ on_request: "Only when somebody asks for it",
1845
+ exclude: "No, keep it away from assistants"
1846
+ };
1847
+ var STAGE_RULE_ON_FAIL_LABEL = {
1848
+ refuse: "Turn it away",
1849
+ require_approval: "Ask for approval"
1850
+ };
1851
+ var SUBSCRIPTION_CADENCES = exhaustiveValuesOf()(["instant", "hourly", "daily", "weekly"]);
1852
+ var SUBSCRIPTION_CADENCE_LABEL = {
1853
+ instant: "as it happens",
1854
+ hourly: "hourly summary",
1855
+ daily: "daily summary",
1856
+ weekly: "weekly summary"
1857
+ };
1858
+ var SUBSCRIPTION_CHANNEL_VALUES = ["in_app", "email", "sms"];
1859
+ var SUBSCRIPTION_CHANNEL_LABEL = {
1860
+ in_app: "in the app",
1861
+ email: "by email \u2014 only if an address is on the account",
1862
+ sms: "by text \u2014 only if a number is on the account"
1863
+ };
1864
+ var ROLLUP_AGG_VALUES = ["count", "sum", "avg", "min", "max"];
1865
+ var ROLLUP_AGG_LABEL = {
1866
+ count: "How many there are",
1867
+ sum: "The total of",
1868
+ avg: "The average of",
1869
+ min: "The smallest",
1870
+ max: "The largest"
1871
+ };
1872
+ var FORMULA_OP_VALUES = ["concat", "add", "sub", "mul", "div"];
1873
+ var FORMULA_OP_LABEL = {
1874
+ concat: "Join them together",
1875
+ add: "Add them up",
1876
+ sub: "Take the second from the first",
1877
+ mul: "Multiply them",
1878
+ div: "Divide the first by the second"
1879
+ };
1880
+ var FORMULA_OP_JOINS = {
1881
+ concat: true,
1882
+ add: true,
1883
+ sub: false,
1884
+ mul: true,
1885
+ div: false
1886
+ };
1887
+ var FIELD_RULE_KIND_VALUES = ["min", "max", "length", "pattern", "equals_field", "differs_from_field"];
1888
+ var FIELD_RULE_KIND_LABEL = {
1889
+ min: "At least",
1890
+ max: "At most",
1891
+ length: "No longer than",
1892
+ pattern: "Looks like",
1893
+ equals_field: "Same as the column",
1894
+ differs_from_field: "Different from the column"
1895
+ };
1896
+ var PROPOSED_CHANGE_ACT_VALUES = ["add", "update", "remove"];
1897
+ var PROPOSED_CHANGE_ACT_LABEL = {
1898
+ add: "Add",
1899
+ update: "Update",
1900
+ remove: "Remove"
1901
+ };
1902
+ var BOOKING_PAGE_STATE_VALUES = ["draft", "open", "closed", "full"];
1903
+ var BOOKING_PAGE_STATE_LABEL = {
1904
+ draft: "Not open yet \u2014 nobody can book until you open it.",
1905
+ open: "Open. Anyone with the link can book a time.",
1906
+ closed: "Closed. The link still works and says you are not taking bookings.",
1907
+ full: "Every time you offered is taken."
1908
+ };
1909
+ var ABSENCE_WORD_VALUES = ["never asked", "none", "refused", "conflicting"];
1910
+ var ABSENCE_WORD_LABEL = {
1775
1911
  "never asked": "never asked",
1776
1912
  none: "none",
1777
1913
  refused: "refused",
1778
1914
  conflicting: "conflicting"
1779
1915
  };
1916
+
1917
+ // src/values.tsx
1918
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1919
+ function envelopeFor(document2, key) {
1920
+ const block = document2?._values;
1921
+ return block?.[key];
1922
+ }
1923
+ var ABSENCE_WORDS = ABSENCE_WORD_LABEL;
1780
1924
  function renderValue(field, value, document2, labels, stillWaitingFor) {
1781
1925
  const envelope = envelopeFor(document2, field.key);
1782
1926
  if (envelope?.absent) {
@@ -2600,7 +2744,7 @@ var import_react17 = require("react");
2600
2744
  var import_react18 = require("@ai-matrx/records/react");
2601
2745
  var import_design_system8 = require("@ai-matrx/design-system");
2602
2746
  var import_jsx_runtime11 = require("react/jsx-runtime");
2603
- var NO_ENRICH_REASON = "No enrich port is bound, so this panel does not offer to fill a column it cannot fill. The value has to be written by the SERVER with actor `agent` on behalf of you \u2014 a browser that stamped itself `agent` would be a browser claiming to be one \u2014 so bind `enrich` on <RecordsUiProvider> with a call to the server's own `records` tool (action `enrich_propose`). Everything else on this panel works without it.";
2747
+ var NO_ENRICH_REASON = "Having an agent fill this column is not switched on here yet, so this panel does not offer it rather than offering something that cannot happen. Everything else on this panel works. It is on our list, not yours.";
2604
2748
  function cellState(cell) {
2605
2749
  if (!cell || !cell.agent_owned) return "none";
2606
2750
  if (cell.pinned) return "yours";
@@ -3199,113 +3343,11 @@ function choiceOptionsOf(options) {
3199
3343
  }
3200
3344
  return { byWord, labels };
3201
3345
  }
3202
- var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
3203
- var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
3204
3346
  function valueFromPastedText(field, raw, options) {
3205
- const label = fieldName(field);
3206
- const text = raw.trim();
3207
- const kind = editorKindFor(field);
3208
- if (text === "" && kind !== "checkbox") return { value: null };
3209
- switch (kind) {
3210
- case "checkbox": {
3211
- const word = text.toLowerCase();
3212
- if (TRUE_WORDS.has(word)) return { value: true };
3213
- if (FALSE_WORDS.has(word)) return { value: word === "" ? null : false };
3214
- return {
3215
- refusal: `${label} is a tick box, so it takes yes or no \u2014 \u201C${raw}\u201D is neither. Paste TRUE/FALSE, yes/no or 1/0.`
3216
- };
3217
- }
3218
- case "number":
3219
- case "currency":
3220
- case "percent": {
3221
- const negative = /^\(.*\)$/.test(text);
3222
- const bare = text.replace(/^\(|\)$/g, "").replace(/[\s,]/g, "").replace(/^[^\d.\-+]+/, "").replace(/[^\d.\-+eE]+$/, "");
3223
- const n = Number(bare);
3224
- if (bare === "" || Number.isNaN(n)) {
3225
- return {
3226
- refusal: `${label} holds a number, and \u201C${raw}\u201D is not one. Paste digits \u2014 a currency symbol, a percent sign, commas and brackets for a negative are all fine.`
3227
- };
3228
- }
3229
- return { value: negative ? -n : n };
3230
- }
3231
- case "datetime": {
3232
- const wantsTime = String(field.config?.["kind"] ?? "date") === "datetime";
3233
- const plainDay = /^\s*(\d{4})-(\d{2})-(\d{2})\s*$/.exec(raw);
3234
- if (!wantsTime && plainDay) return { value: `${plainDay[1]}-${plainDay[2]}-${plainDay[3]}` };
3235
- const at = Date.parse(text);
3236
- if (Number.isNaN(at)) {
3237
- return {
3238
- refusal: `${label} holds a date, and \u201C${raw}\u201D is not one this can read. Paste it as 2026-09-21, or as your spreadsheet's own date format.`
3239
- };
3240
- }
3241
- const parsed = new Date(at);
3242
- if (wantsTime) return { value: parsed.toISOString() };
3243
- const y = parsed.getFullYear();
3244
- const m = String(parsed.getMonth() + 1).padStart(2, "0");
3245
- const d = String(parsed.getDate()).padStart(2, "0");
3246
- return { value: `${y}-${m}-${d}` };
3247
- }
3248
- case "json": {
3249
- try {
3250
- return { value: JSON.parse(text) };
3251
- } catch {
3252
- return {
3253
- refusal: `${label} holds a document, and \u201C${raw}\u201D is not valid JSON. Paste the whole object, braces and all.`
3254
- };
3255
- }
3256
- }
3257
- // A LIST, A PERSON, A FILE OR ANOTHER RECORD IS AN IDENTITY, NOT A WORD.
3258
- // The store keeps the id of the option or the record, and a word off a
3259
- // clipboard is not that id — writing the word would put a string where an
3260
- // id belongs and every filter, colour and rollup built on that column would
3261
- // stop agreeing with it. So the cell is left alone and the person is told
3262
- // which control does hold it.
3263
- case "select":
3264
- case "multi_select": {
3265
- if (!options) {
3266
- return {
3267
- refusal: `${label} is a list and its options have not been read yet, so nothing can be matched to \u201C${raw}\u201D. Reopen the paste once the column's choices have loaded.`
3268
- };
3269
- }
3270
- const wanted = kind === "multi_select" ? text.split(/[,;]/).map((w) => w.trim()).filter((w) => w.length > 0) : [text];
3271
- if (wanted.length === 0) return { value: kind === "multi_select" ? [] : null };
3272
- const matched = [];
3273
- const unmatched = [];
3274
- for (const word of wanted) {
3275
- const hit = options.byWord.get(word.toLowerCase());
3276
- if (hit === void 0) unmatched.push(word);
3277
- else matched.push(hit);
3278
- }
3279
- if (unmatched.length > 0) {
3280
- const offered = options.labels.length > 0 ? options.labels.join(", ") : "it has none yet";
3281
- return {
3282
- refusal: `${label} does not have ${unmatched.map((w) => `\u201C${w}\u201D`).join(" or ")} among its options. It offers: ${offered}.`,
3283
- unmatched
3284
- };
3285
- }
3286
- return { value: kind === "multi_select" ? matched : matched[0] };
3287
- }
3288
- case "member":
3289
- return {
3290
- refusal: `${label} names a person in this organization, so it is chosen rather than typed. Open the cell and pick them.`
3291
- };
3292
- case "attachment":
3293
- return {
3294
- refusal: `${label} holds a file, which cannot arrive on a clipboard. Open the cell and attach it.`
3295
- };
3296
- case "relation":
3297
- return {
3298
- refusal: `${label} points at another record, so it is chosen rather than typed. Open the cell and pick the record.`
3299
- };
3300
- case "formula":
3301
- case "lookup":
3302
- case "rollup":
3303
- return {
3304
- refusal: `${label} is worked out by the system, so nothing can be pasted into it.`
3305
- };
3306
- default:
3307
- return { value: raw };
3308
- }
3347
+ return (0, import_records5.coerceTypedAnswer)(field, raw, {
3348
+ label: fieldName(field),
3349
+ ...options ? { options } : {}
3350
+ });
3309
3351
  }
3310
3352
  function planPastedBlock(args) {
3311
3353
  const byKey = new Map(args.fields.map((f) => [f.key, f]));
@@ -4475,19 +4517,23 @@ var import_react27 = require("react");
4475
4517
  var import_react28 = require("@ai-matrx/records/react");
4476
4518
  var import_design_system13 = require("@ai-matrx/design-system");
4477
4519
  var import_jsx_runtime16 = require("react/jsx-runtime");
4478
- var ADDABLE_TYPES = [
4479
- { value: "text", label: "Text" },
4480
- { value: "long_text", label: "Long text" },
4481
- { value: "number", label: "Number" },
4482
- { value: "currency", label: "Money" },
4483
- { value: "percent", label: "Percentage" },
4484
- { value: "datetime", label: "Date" },
4485
- { value: "email", label: "Email" },
4486
- { value: "phone", label: "Phone" },
4487
- { value: "url", label: "Link" },
4488
- { value: "member", label: "Person" },
4489
- { value: "attachment", label: "File" }
4520
+ var ADDABLE_TYPE_IDS = [
4521
+ "text",
4522
+ "long_text",
4523
+ "number",
4524
+ "currency",
4525
+ "percent",
4526
+ "datetime",
4527
+ "email",
4528
+ "phone",
4529
+ "url",
4530
+ "member",
4531
+ "attachment"
4490
4532
  ];
4533
+ var ADDABLE_TYPES = ADDABLE_TYPE_IDS.map((value) => ({
4534
+ value,
4535
+ label: fieldTypeChoice(value)?.label ?? value
4536
+ }));
4491
4537
  function asField(f) {
4492
4538
  return {
4493
4539
  id: f.id,
@@ -4651,41 +4697,20 @@ function CustomFieldsSection({
4651
4697
  var import_react29 = require("react");
4652
4698
  var import_react30 = require("@ai-matrx/records/react");
4653
4699
  var import_design_system14 = require("@ai-matrx/design-system");
4700
+ var import_records6 = require("@ai-matrx/records");
4654
4701
  var import_jsx_runtime17 = require("react/jsx-runtime");
4655
- var RULE_KINDS = [
4656
- { kind: "min", label: "At least" },
4657
- { kind: "max", label: "At most" },
4658
- { kind: "length", label: "No longer than" },
4659
- { kind: "pattern", label: "Looks like" },
4660
- { kind: "equals_field", label: "Same as the column" },
4661
- { kind: "differs_from_field", label: "Different from the column" }
4662
- ];
4663
- var SENSITIVITIES = [
4664
- { value: "public", label: "Anyone who can open the record" },
4665
- { value: "internal", label: "People in this organization" },
4666
- { value: "confidential", label: "People who have been given the record" },
4667
- { value: "restricted", label: "Owners and administrators only" }
4668
- ];
4669
- var AGENT_VISIBILITY = [
4670
- { value: "include", label: "Yes, an assistant can read it" },
4671
- { value: "summarize", label: "Only a summary of it" },
4672
- { value: "on_request", label: "Only when somebody asks for it" },
4673
- { value: "exclude", label: "No, keep it away from assistants" }
4674
- ];
4675
- var ROLLUP_OPERATIONS = [
4676
- { value: "count", label: "How many there are" },
4677
- { value: "sum", label: "The total of" },
4678
- { value: "avg", label: "The average of" },
4679
- { value: "min", label: "The smallest" },
4680
- { value: "max", label: "The largest" }
4681
- ];
4682
- var FORMULA_OPERATIONS = [
4683
- { value: "concat", label: "Join them together", joins: true },
4684
- { value: "add", label: "Add them up", joins: true },
4685
- { value: "sub", label: "Take the second from the first", joins: false },
4686
- { value: "mul", label: "Multiply them", joins: true },
4687
- { value: "div", label: "Divide the first by the second", joins: false }
4688
- ];
4702
+ var RULE_KINDS = FIELD_RULE_KIND_VALUES.map((kind) => ({ kind, label: FIELD_RULE_KIND_LABEL[kind] }));
4703
+ var SENSITIVITIES = import_records6.FIELD_SENSITIVITIES.map((value) => ({ value, label: FIELD_SENSITIVITY_LABEL[value] }));
4704
+ var AGENT_VISIBILITY = import_records6.CONTEXT_POLICIES.map((value) => ({ value, label: CONTEXT_POLICY_LABEL[value] }));
4705
+ var ROLLUP_OPERATIONS = ROLLUP_AGG_VALUES.map((value) => ({
4706
+ value,
4707
+ label: ROLLUP_AGG_LABEL[value]
4708
+ }));
4709
+ var FORMULA_OPERATIONS = FORMULA_OP_VALUES.map((value) => ({
4710
+ value,
4711
+ label: FORMULA_OP_LABEL[value],
4712
+ joins: FORMULA_OP_JOINS[value]
4713
+ }));
4689
4714
  function keyFor(label) {
4690
4715
  const token = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
4691
4716
  if (token === "") return "";
@@ -5250,6 +5275,9 @@ var CONDITION_OPS = [
5250
5275
  { op: "gt", label: "is more than" },
5251
5276
  { op: "lt", label: "is less than" }
5252
5277
  ];
5278
+ function pointsAtARecord(f) {
5279
+ return !!f?.field && f.field.type === "relation" && !!f.field.relation_target;
5280
+ }
5253
5281
  function conditionIsSimple(expr) {
5254
5282
  if (expr === null || expr === void 0) return true;
5255
5283
  const node = expr;
@@ -5320,6 +5348,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5320
5348
  const op = node?.op ?? "";
5321
5349
  const onField = node?.args?.[0]?.field ?? "";
5322
5350
  const value = node?.args?.[1]?.const ?? "";
5351
+ const chosen = fields.find((f) => f.id === onField);
5323
5352
  function write(next) {
5324
5353
  const chosenOp = next.op ?? op;
5325
5354
  const chosenField = next.field ?? onField;
@@ -5358,7 +5387,24 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5358
5387
  children: CONDITION_OPS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: c.op, children: c.label }, c.op))
5359
5388
  }
5360
5389
  ),
5361
- op !== "present" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5390
+ op === "present" ? null : pointsAtARecord(chosen) ? (
5391
+ // A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
5392
+ // cell uses, on the same `relation_words_many` the cell reads, so a filter and
5393
+ // the column it filters can never disagree about what a record is called. It
5394
+ // is asked for ONE record even when the column holds several: a clause compares
5395
+ // against one value, and a picker that let somebody choose three would be
5396
+ // writing an expression this row cannot draw back.
5397
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5398
+ RelationPicker,
5399
+ {
5400
+ field: { ...chosen.field, multi: false },
5401
+ value: value === "" ? null : value,
5402
+ onChange: (next) => write({ value: next === null || next === void 0 ? "" : String(next) }),
5403
+ id: "condition-value-record",
5404
+ className: "min-w-[8rem]"
5405
+ }
5406
+ )
5407
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5362
5408
  import_design_system15.BasicInput,
5363
5409
  {
5364
5410
  className: "h-8 w-28 text-xs",
@@ -5366,7 +5412,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5366
5412
  value: String(value ?? ""),
5367
5413
  onChange: (e) => write({ value: conditionValue(e.target.value) })
5368
5414
  }
5369
- ) : null
5415
+ )
5370
5416
  ] }) : null
5371
5417
  ] });
5372
5418
  }
@@ -5526,10 +5572,10 @@ var NO_RULES_YET = "Nothing has to be true to get here yet.";
5526
5572
  var NEEDS_A_SENTENCE = "Write the sentence somebody reads when this stops them, the way you would say it out loud.";
5527
5573
  var NOT_DRAWN_HERE = "This part of the rule is not something this builder can draw yet";
5528
5574
  var ON_FAIL = [
5529
- { value: "refuse", label: "Turn it away", note: "The card stays where it is and the person is told why." },
5575
+ { value: "refuse", label: STAGE_RULE_ON_FAIL_LABEL.refuse, note: "The card stays where it is and the person is told why." },
5530
5576
  {
5531
5577
  value: "require_approval",
5532
- label: "Ask for approval",
5578
+ label: STAGE_RULE_ON_FAIL_LABEL.require_approval,
5533
5579
  note: "The card stays where it is, and the people who can approve it are asked. Nobody is turned away."
5534
5580
  }
5535
5581
  ];
@@ -5580,7 +5626,7 @@ function StageRulesSection({ tableId, stage, className }) {
5580
5626
  const stageLabel = stages.find((s) => s.key === stageKey)?.label ?? stageKey ?? "";
5581
5627
  const gates = (0, import_react32.useMemo)(() => stageKey ? gatesOf(pipeline, stageKey) : [], [pipeline, stageKey]);
5582
5628
  const conditionFields = (0, import_react32.useMemo)(
5583
- () => (fields.data ?? []).map((f) => ({ id: String(f.id), key: f.key, label: fieldName(f) })),
5629
+ () => (fields.data ?? []).map((f) => ({ id: String(f.id), key: f.key, label: fieldName(f), field: f })),
5584
5630
  [fields.data]
5585
5631
  );
5586
5632
  (0, import_react32.useEffect)(() => {
@@ -6005,7 +6051,7 @@ var import_react36 = require("react");
6005
6051
  var import_react37 = require("@ai-matrx/records/react");
6006
6052
  var import_design_system18 = require("@ai-matrx/design-system");
6007
6053
  var import_jsx_runtime21 = require("react/jsx-runtime");
6008
- var NO_CHAT_REASON = "No chat port is bound, so this panel is absent rather than showing a composer that goes nowhere. Bind `chat` on <RecordsUiProvider> with your host's own chat surface \u2014 in AI Matrx that is `AgentConversationColumn`, launched through a Mandate \u2014 and this panel becomes that surface with this record as its context. This package deliberately ships no second chat UI.";
6054
+ var NO_CHAT_REASON = "Chatting about this record is not switched on here yet, so this panel is empty rather than showing a box that goes nowhere. It is on our list, not yours.";
6009
6055
  function RecordChat({ tableId, recordId, className }) {
6010
6056
  const client = (0, import_react37.useRecordsClient)();
6011
6057
  const host = useRecordsUi();
@@ -7268,7 +7314,7 @@ function offerableFields(all, want) {
7268
7314
  return all.filter((field) => {
7269
7315
  const kind = editorKindFor(field);
7270
7316
  if (want === "choice") return kind === "select" || kind === "multi_select";
7271
- return kind === "datetime" || kind === "date";
7317
+ return kind === "datetime";
7272
7318
  });
7273
7319
  }
7274
7320
  function useStageField(tableId) {
@@ -7686,11 +7732,7 @@ var import_react54 = require("react");
7686
7732
  var import_react55 = require("@ai-matrx/records/react");
7687
7733
  var import_design_system25 = require("@ai-matrx/design-system");
7688
7734
  var import_jsx_runtime28 = require("react/jsx-runtime");
7689
- var ACT_WORD = {
7690
- add: "Add",
7691
- update: "Update",
7692
- remove: "Remove"
7693
- };
7735
+ var ACT_WORD = PROPOSED_CHANGE_ACT_LABEL;
7694
7736
  function ProposalRow({
7695
7737
  change,
7696
7738
  outcome,
@@ -7797,21 +7839,8 @@ var import_react56 = require("react");
7797
7839
  var import_react57 = require("@ai-matrx/records/react");
7798
7840
  var import_design_system26 = require("@ai-matrx/design-system");
7799
7841
  var import_jsx_runtime29 = require("react/jsx-runtime");
7800
- var DUE_WORD = {
7801
- overdue: "Overdue",
7802
- due_today: "Today",
7803
- scheduled: "Scheduled",
7804
- undated: "No date",
7805
- finished: "Done"
7806
- };
7807
- var REQUIRES_WORD = {
7808
- none: "",
7809
- note: "Say what you did",
7810
- answer: "Fill this in",
7811
- record_field: "This has to be filled in on the record",
7812
- form: "This step is a form",
7813
- document: "This step is a document"
7814
- };
7842
+ var DUE_WORD = DUE_STATE_LABEL;
7843
+ var REQUIRES_WORD = CHECKLIST_REQUIREMENT_LABEL;
7815
7844
  var NO_TEMPLATES = "No checklist has been written for this table yet. Ask an agent for one in a sentence \u2014 the steps, who each belongs to, and how many days in each is due \u2014 and it is stored whole, judged before a single step is written.";
7816
7845
  function ChecklistRunner({
7817
7846
  tableId,
@@ -8519,18 +8548,8 @@ function roleKey(said) {
8519
8548
  // src/ActionInbox.tsx
8520
8549
  var import_jsx_runtime30 = require("react/jsx-runtime");
8521
8550
  var ACTION_KINDS = ["approval", "assignment", "proposal"];
8522
- var KIND_WORD = {
8523
- assignment: "Yours",
8524
- approval: "Approve",
8525
- proposal: "Agent"
8526
- };
8527
- var DUE_WORD2 = {
8528
- overdue: "Overdue",
8529
- due_today: "Today",
8530
- scheduled: "Scheduled",
8531
- undated: "No date",
8532
- finished: "Finished"
8533
- };
8551
+ var KIND_WORD = WORK_INBOX_KIND_LABEL;
8552
+ var DUE_WORD2 = DUE_STATE_LABEL;
8534
8553
  function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className }) {
8535
8554
  const client = (0, import_react59.useRecordsClient)();
8536
8555
  const [items, setItems] = (0, import_react58.useState)(null);
@@ -9751,7 +9770,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
9751
9770
  // src/PortalsPanel.tsx
9752
9771
  var import_react68 = require("react");
9753
9772
  var import_react69 = require("@ai-matrx/records/react");
9754
- var import_records6 = require("@ai-matrx/records");
9773
+ var import_records7 = require("@ai-matrx/records");
9755
9774
  var import_design_system33 = require("@ai-matrx/design-system");
9756
9775
 
9757
9776
  // src/BuildOrAsk.tsx
@@ -9768,18 +9787,18 @@ function BuildOrAsk({
9768
9787
  buildLabel,
9769
9788
  className
9770
9789
  }) {
9790
+ const mayAsk = mayBuild && Boolean(onAsk);
9771
9791
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: (0, import_design_system32.cn)("rounded-md border border-dashed p-3", className), children: [
9772
9792
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-xs text-muted-foreground", children }),
9773
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "mt-2.5 flex flex-wrap items-center gap-2", children: [
9774
- onAsk ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_design_system32.Button, { size: "sm", variant: "outline", onClick: onAsk, children: "Ask an agent" }) : null,
9775
- mayBuild ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_design_system32.Button, { size: "sm", onClick: onBuild, children: buildLabel }) : null
9776
- ] }),
9777
- onAsk ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("p", { className: "mt-2 text-xs text-muted-foreground", children: [
9793
+ mayBuild ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "mt-2.5 flex flex-wrap items-center gap-2", children: [
9794
+ mayAsk ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_design_system32.Button, { size: "sm", variant: "outline", onClick: onAsk, children: "Ask an agent" }) : null,
9795
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_design_system32.Button, { size: "sm", onClick: onBuild, children: buildLabel })
9796
+ ] }) : null,
9797
+ !mayBuild ? whyNot ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "mt-2 text-xs text-muted-foreground", children: whyNot }) : null : mayAsk ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("p", { className: "mt-2 text-xs text-muted-foreground", children: [
9778
9798
  "You would say something like: \u201C",
9779
9799
  suggestion,
9780
9800
  "\u201D"
9781
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "mt-2 text-xs text-muted-foreground", children: NO_AGENT_PORT }),
9782
- !mayBuild && whyNot ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "mt-2 text-xs text-muted-foreground", children: whyNot }) : null
9801
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "mt-2 text-xs text-muted-foreground", children: NO_AGENT_PORT })
9783
9802
  ] });
9784
9803
  }
9785
9804
 
@@ -9818,30 +9837,41 @@ function PortalsPanel({ tableId, className }) {
9818
9837
  const client = (0, import_react69.useRecordsClient)();
9819
9838
  const host = useRecordsUi();
9820
9839
  const [portals, setPortals] = (0, import_react68.useState)(null);
9840
+ const [exposures, setExposures] = (0, import_react68.useState)([]);
9821
9841
  const [listError, setListError] = (0, import_react68.useState)(null);
9822
9842
  const [openId, setOpenId] = (0, import_react68.useState)(null);
9823
9843
  const [building, setBuilding] = (0, import_react68.useState)(false);
9844
+ const [adding, setAdding] = (0, import_react68.useState)(null);
9824
9845
  const load = (0, import_react68.useCallback)(async () => {
9825
- const answered = await client.portals();
9846
+ const [answered, mapped] = await Promise.all([client.portals(), client.portalTables()]);
9826
9847
  if (!answered.ok) {
9827
9848
  setListError(answered.error);
9828
9849
  setPortals([]);
9850
+ setExposures([]);
9829
9851
  return;
9830
9852
  }
9831
9853
  setListError(null);
9832
9854
  setPortals(answered.data);
9855
+ setExposures(mapped.ok ? mapped.data : []);
9833
9856
  }, [client]);
9834
9857
  (0, import_react68.useEffect)(() => {
9835
9858
  void load();
9836
9859
  }, [load]);
9837
9860
  if (portals === null) return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_design_system33.Skeleton, { className: (0, import_design_system33.cn)("h-32 w-full", className) });
9861
+ const onATable = Boolean(tableId);
9862
+ const holdingThis = new Set(
9863
+ exposures.filter((e) => e.table_id === tableId).map((e) => e.portal_id)
9864
+ );
9865
+ const shown = onATable ? portals.filter((p) => holdingThis.has(p.portal_id)) : portals;
9866
+ const couldTakeIt = onATable ? portals.filter((p) => !holdingThis.has(p.portal_id)) : [];
9867
+ const elsewhere = couldTakeIt.length;
9838
9868
  const origin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
9839
9869
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { className: (0, import_design_system33.cn)("flex flex-col gap-3", className), children: [
9840
9870
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("header", { className: "flex items-center gap-2", children: [
9841
9871
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h3", { className: "text-sm font-medium", children: "Portals" }),
9842
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-xs text-muted-foreground", children: portals.length === 0 ? "none yet" : `${portals.length}` }),
9872
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-xs text-muted-foreground", children: shown.length === 0 ? "none for this table" : `${shown.length}` }),
9843
9873
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex-1" }),
9844
- portals.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_design_system33.Button, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a portal" }) : null
9874
+ shown.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_design_system33.Button, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a portal" }) : null
9845
9875
  ] }),
9846
9876
  listError ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(RefusalNotice, { error: listError }) : null,
9847
9877
  building ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
@@ -9854,7 +9884,19 @@ function PortalsPanel({ tableId, className }) {
9854
9884
  onClose: () => setBuilding(false)
9855
9885
  }
9856
9886
  ) : null,
9857
- portals.length === 0 && !listError && !building ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9887
+ adding ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9888
+ PortalBuilder,
9889
+ {
9890
+ ...tableId ? { tableId } : {},
9891
+ portalId: adding,
9892
+ onSaved: () => {
9893
+ setAdding(null);
9894
+ void load();
9895
+ },
9896
+ onClose: () => setAdding(null)
9897
+ }
9898
+ ) : null,
9899
+ shown.length === 0 && !listError && !building && !adding ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
9858
9900
  BuildOrAsk,
9859
9901
  {
9860
9902
  mayBuild: true,
@@ -9863,12 +9905,38 @@ function PortalsPanel({ tableId, className }) {
9863
9905
  onAsk: () => host.onAskForOne?.({ kind: "portal", tableId, suggestion: PORTAL_SUGGESTION })
9864
9906
  } : {},
9865
9907
  suggestion: PORTAL_SUGGESTION,
9866
- buildLabel: "Build the portal",
9867
- children: "A portal is how one of your clients signs in and sees the records that name them \u2014 their jobs, their invoices \u2014 and nothing else."
9908
+ buildLabel: elsewhere > 0 ? "Build a new portal" : "Build the portal",
9909
+ children: [
9910
+ "A portal is how one of your clients signs in and sees the records that name them \u2014 their jobs, their invoices \u2014 and nothing else.",
9911
+ elsewhere > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
9912
+ " ",
9913
+ "This table is not in",
9914
+ " ",
9915
+ elsewhere === 1 ? "the portal this organization already has" : `any of the ${elsewhere} portals this organization already has`,
9916
+ "."
9917
+ ] }) : null
9918
+ ]
9868
9919
  }
9869
9920
  ) : null,
9870
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "flex flex-col gap-2", children: portals.map((portal) => {
9871
- const url = `${origin}${(0, import_records6.portalPath)(portal.slug)}`;
9921
+ shown.length === 0 && !listError && !building && !adding && elsewhere > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-1.5 rounded-md border border-dashed border-border p-2.5", children: [
9922
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-xs text-muted-foreground", children: "Or add this table to a portal your clients already sign in to:" }),
9923
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex flex-wrap gap-1.5", children: couldTakeIt.map((portal) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
9924
+ import_design_system33.Button,
9925
+ {
9926
+ size: "sm",
9927
+ variant: "outline",
9928
+ onClick: () => setAdding(portal.portal_id),
9929
+ children: [
9930
+ "Add to \u201C",
9931
+ portal.title,
9932
+ "\u201D"
9933
+ ]
9934
+ },
9935
+ portal.portal_id
9936
+ )) })
9937
+ ] }) : null,
9938
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
9939
+ const url = `${origin}${(0, import_records7.portalPath)(portal.slug)}`;
9872
9940
  const open = openId === portal.portal_id;
9873
9941
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
9874
9942
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center gap-2", children: [
@@ -10594,11 +10662,7 @@ function whenItFires(subscription) {
10594
10662
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
10595
10663
  return subscription.schedule ? `${every}, ${subscription.schedule}` : every;
10596
10664
  }
10597
- var CHANNEL_WORDS2 = {
10598
- in_app: "in the app",
10599
- email: "by email \u2014 only if an address is on the account",
10600
- sms: "by text \u2014 only if a number is on the account"
10601
- };
10665
+ var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
10602
10666
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
10603
10667
  function SubscriptionsPanel({ tableId, className }) {
10604
10668
  const client = (0, import_react73.useRecordsClient)();
@@ -10772,7 +10836,7 @@ function SubscriptionsPanel({ tableId, className }) {
10772
10836
  // src/FormBuilder.tsx
10773
10837
  var import_react76 = require("react");
10774
10838
  var import_react77 = require("@ai-matrx/records/react");
10775
- var import_records7 = require("@ai-matrx/records");
10839
+ var import_records8 = require("@ai-matrx/records");
10776
10840
  var import_design_system37 = require("@ai-matrx/design-system");
10777
10841
 
10778
10842
  // src/FormRunner.tsx
@@ -11247,7 +11311,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
11247
11311
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
11248
11312
  PublishRow,
11249
11313
  {
11250
- url: `${publicOrigin}${(0, import_records7.publicFormPath)(draft.id)}`,
11314
+ url: `${publicOrigin}${(0, import_records8.publicFormPath)(draft.id)}`,
11251
11315
  state: draft.state,
11252
11316
  mayPublish: rights.structure,
11253
11317
  why: rights.why("structure"),
@@ -11366,7 +11430,12 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
11366
11430
  Condition,
11367
11431
  {
11368
11432
  question,
11369
- fieldKeys: fields.data ?? [],
11433
+ fieldKeys: (fields.data ?? []).map((f) => ({
11434
+ id: String(f.id),
11435
+ key: f.key,
11436
+ label: f.label || f.key,
11437
+ field: f
11438
+ })),
11370
11439
  onChange: (showIf) => patchQuestion(index, { showIf })
11371
11440
  }
11372
11441
  )
@@ -12099,17 +12168,8 @@ var import_react86 = require("react");
12099
12168
  var import_react87 = require("@ai-matrx/records/react");
12100
12169
  var import_design_system42 = require("@ai-matrx/design-system");
12101
12170
  var import_jsx_runtime45 = require("react/jsx-runtime");
12102
- var CADENCE_WORDS2 = {
12103
- instant: "as it happens",
12104
- hourly: "hourly summary",
12105
- daily: "daily summary",
12106
- weekly: "weekly summary"
12107
- };
12108
- var CHANNEL_WORDS3 = {
12109
- in_app: "in the app",
12110
- email: "by email",
12111
- sms: "by text"
12112
- };
12171
+ var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
12172
+ var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
12113
12173
  function NotifyRuleEditor({ tableId, seed, className }) {
12114
12174
  const client = (0, import_react87.useRecordsClient)();
12115
12175
  const host = useRecordsUi();
@@ -12313,7 +12373,7 @@ function NotifyRuleEditor({ tableId, seed, className }) {
12313
12373
  // src/ChartBlock.tsx
12314
12374
  var import_react88 = require("react");
12315
12375
  var import_recharts = require("recharts");
12316
- var import_records8 = require("@ai-matrx/records");
12376
+ var import_records9 = require("@ai-matrx/records");
12317
12377
  var import_core6 = require("@ai-matrx/records/core");
12318
12378
  var import_design_system43 = require("@ai-matrx/design-system");
12319
12379
  var import_jsx_runtime46 = require("react/jsx-runtime");
@@ -12334,7 +12394,7 @@ function ChartBlock({ block, subject, className }) {
12334
12394
  const kind = block.kind;
12335
12395
  const tableId = block.table_id ?? subject;
12336
12396
  const measures = block.measures && block.measures.length > 0 ? block.measures : [{ op: "count" }];
12337
- const primary = (0, import_records8.measureKey)(measures[0]);
12397
+ const primary = (0, import_records9.measureKey)(measures[0]);
12338
12398
  const series = [primary];
12339
12399
  const points = (0, import_react88.useMemo)(() => {
12340
12400
  if (kind === "stuck") return [];
@@ -12868,7 +12928,7 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
12868
12928
  // src/FormsPanel.tsx
12869
12929
  var import_react91 = require("react");
12870
12930
  var import_react92 = require("@ai-matrx/records/react");
12871
- var import_records9 = require("@ai-matrx/records");
12931
+ var import_records10 = require("@ai-matrx/records");
12872
12932
  var import_design_system45 = require("@ai-matrx/design-system");
12873
12933
  var import_jsx_runtime48 = require("react/jsx-runtime");
12874
12934
  var WHAT_A_FORM_IS = "A form asks for this table's own fields, and its answers land here as ordinary records stamped with the form they came through.";
@@ -12963,7 +13023,7 @@ function FormsPanel({ tableId, className }) {
12963
13023
  }
12964
13024
  ) : null,
12965
13025
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
12966
- const url = `${origin}${(0, import_records9.publicFormPath)(form.form_id)}`;
13026
+ const url = `${origin}${(0, import_records10.publicFormPath)(form.form_id)}`;
12967
13027
  return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("li", { className: "rounded-md border p-2.5", children: [
12968
13028
  /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex items-center gap-2", children: [
12969
13029
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
@@ -13015,7 +13075,7 @@ function FormsPanel({ tableId, className }) {
13015
13075
  // src/BookingBuilder.tsx
13016
13076
  var import_react93 = require("react");
13017
13077
  var import_react94 = require("@ai-matrx/records/react");
13018
- var import_records10 = require("@ai-matrx/records");
13078
+ var import_records11 = require("@ai-matrx/records");
13019
13079
  var import_design_system46 = require("@ai-matrx/design-system");
13020
13080
  var import_jsx_runtime49 = require("react/jsx-runtime");
13021
13081
  var STORE_ANSWERS_THESE = ["slot", "status", "booked_with"];
@@ -13158,7 +13218,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
13158
13218
  if (!rights.structure) {
13159
13219
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: (0, import_design_system46.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") ?? "Making a booking page needs the admin level on this table, because it lets people with no account take time in your calendar." });
13160
13220
  }
13161
- const url = formId ? `${publicOrigin}${(0, import_records10.bookingPath)(formId)}` : null;
13221
+ const url = formId ? `${publicOrigin}${(0, import_records11.bookingPath)(formId)}` : null;
13162
13222
  const shown = offer ?? null;
13163
13223
  return (
13164
13224
  // THE RAIL IS 384px WIDE AND THE VIEWPORT IS NOT (walk 1, 2026-09-21):
@@ -13360,7 +13420,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
13360
13420
  // src/BookingSlots.tsx
13361
13421
  var import_react95 = require("react");
13362
13422
  var import_react96 = require("@ai-matrx/records/react");
13363
- var import_records11 = require("@ai-matrx/records");
13423
+ var import_records12 = require("@ai-matrx/records");
13364
13424
  var import_design_system47 = require("@ai-matrx/design-system");
13365
13425
  var import_jsx_runtime50 = require("react/jsx-runtime");
13366
13426
  var WHAT_A_BOOKING_PAGE_IS = "A booking page offers times you are free and writes each appointment into this table as an ordinary record.";
@@ -13369,12 +13429,7 @@ function bookingSuggestion(tableName2) {
13369
13429
  return `Let clients book a 30-minute ${subject} slot on Tuesday and Thursday afternoons.`;
13370
13430
  }
13371
13431
  var NO_ADMIN2 = "Opening a booking page lets people with no account take time in your calendar, so it needs the admin level on the table the appointments land in.";
13372
- var STATE_WORDS = {
13373
- draft: "Not open yet \u2014 nobody can book until you open it.",
13374
- open: "Open. Anyone with the link can book a time.",
13375
- closed: "Closed. The link still works and says you are not taking bookings.",
13376
- full: "Every time you offered is taken."
13377
- };
13432
+ var STATE_WORDS = BOOKING_PAGE_STATE_LABEL;
13378
13433
  function BookingSlots({ tableId, className }) {
13379
13434
  const client = (0, import_react96.useRecordsClient)();
13380
13435
  const host = useRecordsUi();
@@ -13398,10 +13453,21 @@ function BookingSlots({ tableId, className }) {
13398
13453
  void load();
13399
13454
  }, [load]);
13400
13455
  const subjectIds = (0, import_react95.useMemo)(
13401
- () => Array.from(new Set((pages ?? []).map((p) => p.table_id))),
13402
- [pages]
13456
+ () => Array.from(
13457
+ /* @__PURE__ */ new Set([
13458
+ ...(pages ?? []).map((p) => p.table_id),
13459
+ // THE EMPTY STATE ASKS THE SAME QUESTION. There is no page to read a
13460
+ // table off yet, and the empty state's two buttons need the answer
13461
+ // about the table this rail is standing in — otherwise the rail
13462
+ // offers a viewer both ways into an act the door will refuse.
13463
+ ...tableId ? [tableId] : []
13464
+ ])
13465
+ ),
13466
+ [pages, tableId]
13403
13467
  );
13404
13468
  const levels = (0, import_react96.useMyLevels)(subjectIds);
13469
+ const levelsKnown = !levels.loading && Boolean(levels.data);
13470
+ const mayBuildHere = levelsKnown && Boolean(tableId) && levels.data?.[tableId] === "admin";
13405
13471
  const toggle = (0, import_react95.useCallback)(
13406
13472
  async (page) => {
13407
13473
  setBusy(page.form_id);
@@ -13450,7 +13516,8 @@ function BookingSlots({ tableId, className }) {
13450
13516
  pages.length === 0 && !building ? tableId ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
13451
13517
  BuildOrAsk,
13452
13518
  {
13453
- mayBuild: true,
13519
+ mayBuild: mayBuildHere,
13520
+ whyNot: mayBuildHere ? null : levelsKnown ? NO_ADMIN2 : null,
13454
13521
  onBuild: () => setBuilding(true),
13455
13522
  ...host.onAskForOne ? {
13456
13523
  onAsk: () => host.onAskForOne?.({
@@ -13468,7 +13535,7 @@ function BookingSlots({ tableId, className }) {
13468
13535
  " Open the table the appointments should land in to make one."
13469
13536
  ] }) : null,
13470
13537
  /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("ul", { className: "flex flex-col gap-2", children: pages.map((page) => {
13471
- const url = `${origin}${(0, import_records11.bookingPath)(page.form_id)}`;
13538
+ const url = `${origin}${(0, import_records12.bookingPath)(page.form_id)}`;
13472
13539
  const mayOpen = levels.data?.[page.table_id] === "admin";
13473
13540
  const open = page.published_at !== null && page.closed_at === null;
13474
13541
  return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("li", { className: "rounded-md border p-2.5", "data-testid": "booking-page", children: [
@@ -13557,7 +13624,7 @@ var import_design_system49 = require("@ai-matrx/design-system");
13557
13624
 
13558
13625
  // src/CaptureRun.tsx
13559
13626
  var import_react97 = require("react");
13560
- var import_records12 = require("@ai-matrx/records");
13627
+ var import_records13 = require("@ai-matrx/records");
13561
13628
  var import_react98 = require("@ai-matrx/records/react");
13562
13629
  var import_design_system48 = require("@ai-matrx/design-system");
13563
13630
  var import_jsx_runtime51 = require("react/jsx-runtime");
@@ -13612,7 +13679,7 @@ function CaptureRun({ sheetId, face: given, className }) {
13612
13679
  const [sending, setSending] = (0, import_react97.useState)(false);
13613
13680
  const queueRef = (0, import_react97.useRef)(null);
13614
13681
  (0, import_react97.useEffect)(() => {
13615
- const q2 = (0, import_records12.openCaptureQueue)({
13682
+ const q2 = (0, import_records13.openCaptureQueue)({
13616
13683
  onChange: (c, all) => {
13617
13684
  setCounts(c);
13618
13685
  setItems(all);
@@ -13861,7 +13928,11 @@ function CaptureRun({ sheetId, face: given, className }) {
13861
13928
  value: String(answers[q.field] ?? ""),
13862
13929
  onChange: (e) => {
13863
13930
  const raw = e.target.value;
13864
- setAnswers((held) => ({ ...held, [q.field]: raw === "" ? "" : Number(raw) }));
13931
+ const answered2 = field ? (0, import_records13.coerceTypedAnswer)(field, raw, { label: q.ask ?? (0, import_records13.coercionLabel)(field) }) : { value: raw };
13932
+ setAnswers((held) => ({
13933
+ ...held,
13934
+ [q.field]: raw === "" ? "" : "refusal" in answered2 ? raw : answered2.value
13935
+ }));
13865
13936
  setMissing(null);
13866
13937
  }
13867
13938
  }
@@ -13918,7 +13989,7 @@ function CaptureRun({ sheetId, face: given, className }) {
13918
13989
  // src/CaptureSheet.tsx
13919
13990
  var import_jsx_runtime52 = require("react/jsx-runtime");
13920
13991
  var CAPTURE_MODES = ["reading", "photo", "voice"];
13921
- var IN_MEMORY_QUEUE_REASON = "No capture queue is bound, so anything that cannot be sent right now is held in this page's memory and is lost if the page closes. Bind `captureQueue` on <RecordsUiProvider> with your host's own durable store (IndexedDB in a browser) and a queued capture survives the tab, the reload and the flight. It is said here rather than discovered later.";
13992
+ var IN_MEMORY_QUEUE_REASON = "Anything you capture while offline is held only in this page until it can be sent, and it is lost if you close the tab. Keep this page open until you are back on a connection. It is said here rather than discovered later.";
13922
13993
  function mintClientKey() {
13923
13994
  const c = globalThis.crypto;
13924
13995
  if (typeof c?.randomUUID === "function") return `capture:${c.randomUUID()}`;
@@ -14794,6 +14865,10 @@ var import_react111 = require("react");
14794
14865
  var import_react112 = require("@ai-matrx/records/react");
14795
14866
  var import_design_system54 = require("@ai-matrx/design-system");
14796
14867
  var import_jsx_runtime58 = require("react/jsx-runtime");
14868
+ function cameFromLine(label, groupField) {
14869
+ const grouped = groupField ? `grouped into the same columns the chart used` : `for this table`;
14870
+ return `You came here from \u201C${label}\u201D. This is every record ${grouped} \u2014 not only the ones that number counted \u2014 so the column you clicked is here with the rest beside it.`;
14871
+ }
14797
14872
  var TABLE_NOT_REACHABLE = "This table is not in the organization you are working in, so there is nothing here to show. Switch to the organization that owns it and open it again \u2014 or it may have been deleted.";
14798
14873
  var DEFAULT_VIEW_NAME = "All records";
14799
14874
  var VIEW_NOT_SAVED_YET = "This table has no saved view yet, so the layout you pick here is not being kept. Save one from the bar above and it is remembered.";
@@ -14837,6 +14912,8 @@ function TablePage({
14837
14912
  activeRecordId,
14838
14913
  activeView,
14839
14914
  onViewChanged,
14915
+ activeGroupField,
14916
+ cameFrom,
14840
14917
  className
14841
14918
  }) {
14842
14919
  const client = (0, import_react112.useRecordsClient)();
@@ -15030,10 +15107,24 @@ function TablePage({
15030
15107
  ] }),
15031
15108
  main === "dashboards" ? /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(DashboardCanvas, { tableId, activeDashboardId: activeDashboardId ?? null }) : /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(import_jsx_runtime58.Fragment, { children: [
15032
15109
  opened.unknown ? /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: unknownViewLine(opened.unknown) }) : null,
15110
+ cameFrom ? /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
15111
+ "p",
15112
+ {
15113
+ "data-testid": "came-from-a-number",
15114
+ className: "rounded-md border px-3 py-2 text-xs leading-relaxed text-muted-foreground",
15115
+ children: cameFromLine(cameFrom, activeGroupField ?? null)
15116
+ }
15117
+ ) : null,
15033
15118
  /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
15034
15119
  ViewSwitcher,
15035
15120
  {
15036
- view: { ...view ?? defaultView(tableId), layout: shownLayout },
15121
+ view: {
15122
+ ...view ?? defaultView(tableId),
15123
+ layout: shownLayout,
15124
+ // The address's field wins for this visit; silence keeps the
15125
+ // saved view's own choice, exactly as it always did.
15126
+ ...activeGroupField ? { groupField: activeGroupField } : {}
15127
+ },
15037
15128
  pageSize,
15038
15129
  onLayoutChange: (layout) => {
15039
15130
  setLayoutFromLink(layout);