@ai-matrx/records-ui 0.63.0 → 0.65.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,6 +63,7 @@ __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,
@@ -65,8 +73,14 @@ __export(src_exports, {
65
73
  EnrichBadge: () => EnrichBadge,
66
74
  EnrichPanel: () => EnrichPanel,
67
75
  ExportMenu: () => ExportMenu,
76
+ FIELD_RULE_KIND_LABEL: () => FIELD_RULE_KIND_LABEL,
77
+ FIELD_RULE_KIND_VALUES: () => FIELD_RULE_KIND_VALUES,
78
+ FIELD_SENSITIVITY_LABEL: () => FIELD_SENSITIVITY_LABEL,
68
79
  FIELD_TYPE_CHOICES: () => FIELD_TYPE_CHOICES,
69
80
  FIELD_TYPE_GROUPS: () => FIELD_TYPE_GROUPS,
81
+ FORMULA_OP_JOINS: () => FORMULA_OP_JOINS,
82
+ FORMULA_OP_LABEL: () => FORMULA_OP_LABEL,
83
+ FORMULA_OP_VALUES: () => FORMULA_OP_VALUES,
70
84
  FORM_FLOWS: () => FORM_FLOWS,
71
85
  FROZEN_COLUMN_WIDTH: () => FROZEN_COLUMN_WIDTH,
72
86
  FieldControl: () => FieldControl,
@@ -114,6 +128,8 @@ __export(src_exports, {
114
128
  PAGE_VIEW_LABEL: () => PAGE_VIEW_LABEL,
115
129
  PARITY_LABEL: () => PARITY_LABEL,
116
130
  PARITY_MADE_OF: () => PARITY_MADE_OF,
131
+ PROPOSED_CHANGE_ACT_LABEL: () => PROPOSED_CHANGE_ACT_LABEL,
132
+ PROPOSED_CHANGE_ACT_VALUES: () => PROPOSED_CHANGE_ACT_VALUES,
117
133
  Peek: () => Peek,
118
134
  PersonPicker: () => PersonPicker,
119
135
  PipelineBoard: () => PipelineBoard,
@@ -124,6 +140,8 @@ __export(src_exports, {
124
140
  ProposalRow: () => ProposalRow,
125
141
  ProvenanceBadge: () => ProvenanceBadge,
126
142
  PublicViewPage: () => PublicViewPage,
143
+ ROLLUP_AGG_LABEL: () => ROLLUP_AGG_LABEL,
144
+ ROLLUP_AGG_VALUES: () => ROLLUP_AGG_VALUES,
127
145
  RecordChat: () => RecordChat,
128
146
  RecordChip: () => RecordChip,
129
147
  RecordForm: () => RecordForm,
@@ -139,8 +157,13 @@ __export(src_exports, {
139
157
  SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS: () => SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
140
158
  SERIES_COLORS: () => SERIES_COLORS,
141
159
  SOMEBODY: () => SOMEBODY,
160
+ STAGE_RULE_ON_FAIL_LABEL: () => STAGE_RULE_ON_FAIL_LABEL,
142
161
  STORE_ANSWERS_THESE: () => STORE_ANSWERS_THESE,
143
162
  STORE_DECIDES_REASON: () => STORE_DECIDES_REASON,
163
+ SUBSCRIPTION_CADENCES: () => SUBSCRIPTION_CADENCES,
164
+ SUBSCRIPTION_CADENCE_LABEL: () => SUBSCRIPTION_CADENCE_LABEL,
165
+ SUBSCRIPTION_CHANNEL_LABEL: () => SUBSCRIPTION_CHANNEL_LABEL,
166
+ SUBSCRIPTION_CHANNEL_VALUES: () => SUBSCRIPTION_CHANNEL_VALUES,
144
167
  ShareControl: () => ShareControl,
145
168
  SignBlock: () => SignBlock,
146
169
  StageRulesSection: () => StageRulesSection,
@@ -158,6 +181,9 @@ __export(src_exports, {
158
181
  ViewBar: () => ViewBar,
159
182
  ViewSwitcher: () => ViewSwitcher,
160
183
  WITHHELD_RECORD_LABEL: () => WITHHELD_RECORD_LABEL,
184
+ WORK_DUE_STATES: () => WORK_DUE_STATES,
185
+ WORK_INBOX_KINDS: () => WORK_INBOX_KINDS,
186
+ WORK_INBOX_KIND_LABEL: () => WORK_INBOX_KIND_LABEL,
161
187
  WhoChangedSource: () => WhoChangedSource,
162
188
  actorBadge: () => actorBadge,
163
189
  actorWords: () => actorWords,
@@ -826,50 +852,9 @@ var PARITY_LABEL = {
826
852
  var PARITY_MADE_OF = Object.fromEntries(
827
853
  import_records3.PARITY_FIELD_TYPES.map((p) => [p.parity_type, p.made_of])
828
854
  );
855
+ var KERNEL = { file: KERNEL_FILE_TABLE, person: KERNEL_PERSON_TABLE };
829
856
  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";
857
+ return (0, import_records3.fieldKindFor)(field, KERNEL);
873
858
  }
874
859
  function fieldTypeLabel(field) {
875
860
  const kind = editorKindFor(field);
@@ -1765,18 +1750,137 @@ function useLabelWait(field) {
1765
1750
  return field ? waiting(field) : null;
1766
1751
  }
1767
1752
 
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];
1753
+ // src/enumLabels.ts
1754
+ function exhaustiveValuesOf() {
1755
+ return function(values) {
1756
+ return values;
1757
+ };
1773
1758
  }
1774
- var ABSENCE_WORDS = {
1759
+ var WORK_DUE_STATES = exhaustiveValuesOf()([
1760
+ "overdue",
1761
+ "due_today",
1762
+ "scheduled",
1763
+ "undated",
1764
+ "finished"
1765
+ ]);
1766
+ var DUE_STATE_LABEL = {
1767
+ overdue: "Overdue",
1768
+ due_today: "Today",
1769
+ scheduled: "Scheduled",
1770
+ undated: "No date",
1771
+ finished: "Done"
1772
+ };
1773
+ var WORK_INBOX_KINDS = exhaustiveValuesOf()(["assignment", "approval", "proposal"]);
1774
+ var WORK_INBOX_KIND_LABEL = {
1775
+ assignment: "Yours",
1776
+ approval: "Approve",
1777
+ proposal: "Agent"
1778
+ };
1779
+ var CHECKLIST_REQUIREMENT_KINDS = exhaustiveValuesOf()([
1780
+ "none",
1781
+ "note",
1782
+ "answer",
1783
+ "record_field",
1784
+ "form",
1785
+ "document"
1786
+ ]);
1787
+ var CHECKLIST_REQUIREMENT_LABEL = {
1788
+ none: "",
1789
+ note: "Say what you did",
1790
+ answer: "Fill this in",
1791
+ record_field: "This has to be filled in on the record",
1792
+ form: "This step is a form",
1793
+ document: "This step is a document"
1794
+ };
1795
+ var FIELD_SENSITIVITY_LABEL = {
1796
+ public: "Anyone who can open the record",
1797
+ internal: "People in this organization",
1798
+ confidential: "People who have been given the record",
1799
+ restricted: "Owners and administrators only"
1800
+ };
1801
+ var CONTEXT_POLICY_LABEL = {
1802
+ include: "Yes, an assistant can read it",
1803
+ summarize: "Only a summary of it",
1804
+ on_request: "Only when somebody asks for it",
1805
+ exclude: "No, keep it away from assistants"
1806
+ };
1807
+ var STAGE_RULE_ON_FAIL_LABEL = {
1808
+ refuse: "Turn it away",
1809
+ require_approval: "Ask for approval"
1810
+ };
1811
+ var SUBSCRIPTION_CADENCES = exhaustiveValuesOf()(["instant", "hourly", "daily", "weekly"]);
1812
+ var SUBSCRIPTION_CADENCE_LABEL = {
1813
+ instant: "as it happens",
1814
+ hourly: "hourly summary",
1815
+ daily: "daily summary",
1816
+ weekly: "weekly summary"
1817
+ };
1818
+ var SUBSCRIPTION_CHANNEL_VALUES = ["in_app", "email", "sms"];
1819
+ var SUBSCRIPTION_CHANNEL_LABEL = {
1820
+ in_app: "in the app",
1821
+ email: "by email \u2014 only if an address is on the account",
1822
+ sms: "by text \u2014 only if a number is on the account"
1823
+ };
1824
+ var ROLLUP_AGG_VALUES = ["count", "sum", "avg", "min", "max"];
1825
+ var ROLLUP_AGG_LABEL = {
1826
+ count: "How many there are",
1827
+ sum: "The total of",
1828
+ avg: "The average of",
1829
+ min: "The smallest",
1830
+ max: "The largest"
1831
+ };
1832
+ var FORMULA_OP_VALUES = ["concat", "add", "sub", "mul", "div"];
1833
+ var FORMULA_OP_LABEL = {
1834
+ concat: "Join them together",
1835
+ add: "Add them up",
1836
+ sub: "Take the second from the first",
1837
+ mul: "Multiply them",
1838
+ div: "Divide the first by the second"
1839
+ };
1840
+ var FORMULA_OP_JOINS = {
1841
+ concat: true,
1842
+ add: true,
1843
+ sub: false,
1844
+ mul: true,
1845
+ div: false
1846
+ };
1847
+ var FIELD_RULE_KIND_VALUES = ["min", "max", "length", "pattern", "equals_field", "differs_from_field"];
1848
+ var FIELD_RULE_KIND_LABEL = {
1849
+ min: "At least",
1850
+ max: "At most",
1851
+ length: "No longer than",
1852
+ pattern: "Looks like",
1853
+ equals_field: "Same as the column",
1854
+ differs_from_field: "Different from the column"
1855
+ };
1856
+ var PROPOSED_CHANGE_ACT_VALUES = ["add", "update", "remove"];
1857
+ var PROPOSED_CHANGE_ACT_LABEL = {
1858
+ add: "Add",
1859
+ update: "Update",
1860
+ remove: "Remove"
1861
+ };
1862
+ var BOOKING_PAGE_STATE_VALUES = ["draft", "open", "closed", "full"];
1863
+ var BOOKING_PAGE_STATE_LABEL = {
1864
+ draft: "Not open yet \u2014 nobody can book until you open it.",
1865
+ open: "Open. Anyone with the link can book a time.",
1866
+ closed: "Closed. The link still works and says you are not taking bookings.",
1867
+ full: "Every time you offered is taken."
1868
+ };
1869
+ var ABSENCE_WORD_VALUES = ["never asked", "none", "refused", "conflicting"];
1870
+ var ABSENCE_WORD_LABEL = {
1775
1871
  "never asked": "never asked",
1776
1872
  none: "none",
1777
1873
  refused: "refused",
1778
1874
  conflicting: "conflicting"
1779
1875
  };
1876
+
1877
+ // src/values.tsx
1878
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1879
+ function envelopeFor(document2, key) {
1880
+ const block = document2?._values;
1881
+ return block?.[key];
1882
+ }
1883
+ var ABSENCE_WORDS = ABSENCE_WORD_LABEL;
1780
1884
  function renderValue(field, value, document2, labels, stillWaitingFor) {
1781
1885
  const envelope = envelopeFor(document2, field.key);
1782
1886
  if (envelope?.absent) {
@@ -3199,113 +3303,11 @@ function choiceOptionsOf(options) {
3199
3303
  }
3200
3304
  return { byWord, labels };
3201
3305
  }
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
3306
  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
- }
3307
+ return (0, import_records5.coerceTypedAnswer)(field, raw, {
3308
+ label: fieldName(field),
3309
+ ...options ? { options } : {}
3310
+ });
3309
3311
  }
3310
3312
  function planPastedBlock(args) {
3311
3313
  const byKey = new Map(args.fields.map((f) => [f.key, f]));
@@ -4475,19 +4477,23 @@ var import_react27 = require("react");
4475
4477
  var import_react28 = require("@ai-matrx/records/react");
4476
4478
  var import_design_system13 = require("@ai-matrx/design-system");
4477
4479
  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" }
4480
+ var ADDABLE_TYPE_IDS = [
4481
+ "text",
4482
+ "long_text",
4483
+ "number",
4484
+ "currency",
4485
+ "percent",
4486
+ "datetime",
4487
+ "email",
4488
+ "phone",
4489
+ "url",
4490
+ "member",
4491
+ "attachment"
4490
4492
  ];
4493
+ var ADDABLE_TYPES = ADDABLE_TYPE_IDS.map((value) => ({
4494
+ value,
4495
+ label: fieldTypeChoice(value)?.label ?? value
4496
+ }));
4491
4497
  function asField(f) {
4492
4498
  return {
4493
4499
  id: f.id,
@@ -4651,41 +4657,20 @@ function CustomFieldsSection({
4651
4657
  var import_react29 = require("react");
4652
4658
  var import_react30 = require("@ai-matrx/records/react");
4653
4659
  var import_design_system14 = require("@ai-matrx/design-system");
4660
+ var import_records6 = require("@ai-matrx/records");
4654
4661
  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
- ];
4662
+ var RULE_KINDS = FIELD_RULE_KIND_VALUES.map((kind) => ({ kind, label: FIELD_RULE_KIND_LABEL[kind] }));
4663
+ var SENSITIVITIES = import_records6.FIELD_SENSITIVITIES.map((value) => ({ value, label: FIELD_SENSITIVITY_LABEL[value] }));
4664
+ var AGENT_VISIBILITY = import_records6.CONTEXT_POLICIES.map((value) => ({ value, label: CONTEXT_POLICY_LABEL[value] }));
4665
+ var ROLLUP_OPERATIONS = ROLLUP_AGG_VALUES.map((value) => ({
4666
+ value,
4667
+ label: ROLLUP_AGG_LABEL[value]
4668
+ }));
4669
+ var FORMULA_OPERATIONS = FORMULA_OP_VALUES.map((value) => ({
4670
+ value,
4671
+ label: FORMULA_OP_LABEL[value],
4672
+ joins: FORMULA_OP_JOINS[value]
4673
+ }));
4689
4674
  function keyFor(label) {
4690
4675
  const token = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
4691
4676
  if (token === "") return "";
@@ -5250,6 +5235,9 @@ var CONDITION_OPS = [
5250
5235
  { op: "gt", label: "is more than" },
5251
5236
  { op: "lt", label: "is less than" }
5252
5237
  ];
5238
+ function pointsAtARecord(f) {
5239
+ return !!f?.field && f.field.type === "relation" && !!f.field.relation_target;
5240
+ }
5253
5241
  function conditionIsSimple(expr) {
5254
5242
  if (expr === null || expr === void 0) return true;
5255
5243
  const node = expr;
@@ -5320,6 +5308,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5320
5308
  const op = node?.op ?? "";
5321
5309
  const onField = node?.args?.[0]?.field ?? "";
5322
5310
  const value = node?.args?.[1]?.const ?? "";
5311
+ const chosen = fields.find((f) => f.id === onField);
5323
5312
  function write(next) {
5324
5313
  const chosenOp = next.op ?? op;
5325
5314
  const chosenField = next.field ?? onField;
@@ -5358,7 +5347,24 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5358
5347
  children: CONDITION_OPS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: c.op, children: c.label }, c.op))
5359
5348
  }
5360
5349
  ),
5361
- op !== "present" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5350
+ op === "present" ? null : pointsAtARecord(chosen) ? (
5351
+ // A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
5352
+ // cell uses, on the same `relation_words_many` the cell reads, so a filter and
5353
+ // the column it filters can never disagree about what a record is called. It
5354
+ // is asked for ONE record even when the column holds several: a clause compares
5355
+ // against one value, and a picker that let somebody choose three would be
5356
+ // writing an expression this row cannot draw back.
5357
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5358
+ RelationPicker,
5359
+ {
5360
+ field: { ...chosen.field, multi: false },
5361
+ value: value === "" ? null : value,
5362
+ onChange: (next) => write({ value: next === null || next === void 0 ? "" : String(next) }),
5363
+ id: "condition-value-record",
5364
+ className: "min-w-[8rem]"
5365
+ }
5366
+ )
5367
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
5362
5368
  import_design_system15.BasicInput,
5363
5369
  {
5364
5370
  className: "h-8 w-28 text-xs",
@@ -5366,7 +5372,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5366
5372
  value: String(value ?? ""),
5367
5373
  onChange: (e) => write({ value: conditionValue(e.target.value) })
5368
5374
  }
5369
- ) : null
5375
+ )
5370
5376
  ] }) : null
5371
5377
  ] });
5372
5378
  }
@@ -5526,10 +5532,10 @@ var NO_RULES_YET = "Nothing has to be true to get here yet.";
5526
5532
  var NEEDS_A_SENTENCE = "Write the sentence somebody reads when this stops them, the way you would say it out loud.";
5527
5533
  var NOT_DRAWN_HERE = "This part of the rule is not something this builder can draw yet";
5528
5534
  var ON_FAIL = [
5529
- { value: "refuse", label: "Turn it away", note: "The card stays where it is and the person is told why." },
5535
+ { value: "refuse", label: STAGE_RULE_ON_FAIL_LABEL.refuse, note: "The card stays where it is and the person is told why." },
5530
5536
  {
5531
5537
  value: "require_approval",
5532
- label: "Ask for approval",
5538
+ label: STAGE_RULE_ON_FAIL_LABEL.require_approval,
5533
5539
  note: "The card stays where it is, and the people who can approve it are asked. Nobody is turned away."
5534
5540
  }
5535
5541
  ];
@@ -5580,7 +5586,7 @@ function StageRulesSection({ tableId, stage, className }) {
5580
5586
  const stageLabel = stages.find((s) => s.key === stageKey)?.label ?? stageKey ?? "";
5581
5587
  const gates = (0, import_react32.useMemo)(() => stageKey ? gatesOf(pipeline, stageKey) : [], [pipeline, stageKey]);
5582
5588
  const conditionFields = (0, import_react32.useMemo)(
5583
- () => (fields.data ?? []).map((f) => ({ id: String(f.id), key: f.key, label: fieldName(f) })),
5589
+ () => (fields.data ?? []).map((f) => ({ id: String(f.id), key: f.key, label: fieldName(f), field: f })),
5584
5590
  [fields.data]
5585
5591
  );
5586
5592
  (0, import_react32.useEffect)(() => {
@@ -7268,7 +7274,7 @@ function offerableFields(all, want) {
7268
7274
  return all.filter((field) => {
7269
7275
  const kind = editorKindFor(field);
7270
7276
  if (want === "choice") return kind === "select" || kind === "multi_select";
7271
- return kind === "datetime" || kind === "date";
7277
+ return kind === "datetime";
7272
7278
  });
7273
7279
  }
7274
7280
  function useStageField(tableId) {
@@ -7686,11 +7692,7 @@ var import_react54 = require("react");
7686
7692
  var import_react55 = require("@ai-matrx/records/react");
7687
7693
  var import_design_system25 = require("@ai-matrx/design-system");
7688
7694
  var import_jsx_runtime28 = require("react/jsx-runtime");
7689
- var ACT_WORD = {
7690
- add: "Add",
7691
- update: "Update",
7692
- remove: "Remove"
7693
- };
7695
+ var ACT_WORD = PROPOSED_CHANGE_ACT_LABEL;
7694
7696
  function ProposalRow({
7695
7697
  change,
7696
7698
  outcome,
@@ -7797,21 +7799,8 @@ var import_react56 = require("react");
7797
7799
  var import_react57 = require("@ai-matrx/records/react");
7798
7800
  var import_design_system26 = require("@ai-matrx/design-system");
7799
7801
  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
- };
7802
+ var DUE_WORD = DUE_STATE_LABEL;
7803
+ var REQUIRES_WORD = CHECKLIST_REQUIREMENT_LABEL;
7815
7804
  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
7805
  function ChecklistRunner({
7817
7806
  tableId,
@@ -8519,18 +8508,8 @@ function roleKey(said) {
8519
8508
  // src/ActionInbox.tsx
8520
8509
  var import_jsx_runtime30 = require("react/jsx-runtime");
8521
8510
  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
- };
8511
+ var KIND_WORD = WORK_INBOX_KIND_LABEL;
8512
+ var DUE_WORD2 = DUE_STATE_LABEL;
8534
8513
  function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className }) {
8535
8514
  const client = (0, import_react59.useRecordsClient)();
8536
8515
  const [items, setItems] = (0, import_react58.useState)(null);
@@ -9751,7 +9730,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
9751
9730
  // src/PortalsPanel.tsx
9752
9731
  var import_react68 = require("react");
9753
9732
  var import_react69 = require("@ai-matrx/records/react");
9754
- var import_records6 = require("@ai-matrx/records");
9733
+ var import_records7 = require("@ai-matrx/records");
9755
9734
  var import_design_system33 = require("@ai-matrx/design-system");
9756
9735
 
9757
9736
  // src/BuildOrAsk.tsx
@@ -9768,18 +9747,18 @@ function BuildOrAsk({
9768
9747
  buildLabel,
9769
9748
  className
9770
9749
  }) {
9750
+ const mayAsk = mayBuild && Boolean(onAsk);
9771
9751
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: (0, import_design_system32.cn)("rounded-md border border-dashed p-3", className), children: [
9772
9752
  /* @__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: [
9753
+ mayBuild ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "mt-2.5 flex flex-wrap items-center gap-2", children: [
9754
+ mayAsk ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_design_system32.Button, { size: "sm", variant: "outline", onClick: onAsk, children: "Ask an agent" }) : null,
9755
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_design_system32.Button, { size: "sm", onClick: onBuild, children: buildLabel })
9756
+ ] }) : null,
9757
+ !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
9758
  "You would say something like: \u201C",
9779
9759
  suggestion,
9780
9760
  "\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
9761
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "mt-2 text-xs text-muted-foreground", children: NO_AGENT_PORT })
9783
9762
  ] });
9784
9763
  }
9785
9764
 
@@ -9818,30 +9797,41 @@ function PortalsPanel({ tableId, className }) {
9818
9797
  const client = (0, import_react69.useRecordsClient)();
9819
9798
  const host = useRecordsUi();
9820
9799
  const [portals, setPortals] = (0, import_react68.useState)(null);
9800
+ const [exposures, setExposures] = (0, import_react68.useState)([]);
9821
9801
  const [listError, setListError] = (0, import_react68.useState)(null);
9822
9802
  const [openId, setOpenId] = (0, import_react68.useState)(null);
9823
9803
  const [building, setBuilding] = (0, import_react68.useState)(false);
9804
+ const [adding, setAdding] = (0, import_react68.useState)(null);
9824
9805
  const load = (0, import_react68.useCallback)(async () => {
9825
- const answered = await client.portals();
9806
+ const [answered, mapped] = await Promise.all([client.portals(), client.portalTables()]);
9826
9807
  if (!answered.ok) {
9827
9808
  setListError(answered.error);
9828
9809
  setPortals([]);
9810
+ setExposures([]);
9829
9811
  return;
9830
9812
  }
9831
9813
  setListError(null);
9832
9814
  setPortals(answered.data);
9815
+ setExposures(mapped.ok ? mapped.data : []);
9833
9816
  }, [client]);
9834
9817
  (0, import_react68.useEffect)(() => {
9835
9818
  void load();
9836
9819
  }, [load]);
9837
9820
  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) });
9821
+ const onATable = Boolean(tableId);
9822
+ const holdingThis = new Set(
9823
+ exposures.filter((e) => e.table_id === tableId).map((e) => e.portal_id)
9824
+ );
9825
+ const shown = onATable ? portals.filter((p) => holdingThis.has(p.portal_id)) : portals;
9826
+ const couldTakeIt = onATable ? portals.filter((p) => !holdingThis.has(p.portal_id)) : [];
9827
+ const elsewhere = couldTakeIt.length;
9838
9828
  const origin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
9839
9829
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { className: (0, import_design_system33.cn)("flex flex-col gap-3", className), children: [
9840
9830
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("header", { className: "flex items-center gap-2", children: [
9841
9831
  /* @__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}` }),
9832
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-xs text-muted-foreground", children: shown.length === 0 ? "none for this table" : `${shown.length}` }),
9843
9833
  /* @__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
9834
+ 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
9835
  ] }),
9846
9836
  listError ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(RefusalNotice, { error: listError }) : null,
9847
9837
  building ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
@@ -9854,7 +9844,19 @@ function PortalsPanel({ tableId, className }) {
9854
9844
  onClose: () => setBuilding(false)
9855
9845
  }
9856
9846
  ) : null,
9857
- portals.length === 0 && !listError && !building ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9847
+ adding ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9848
+ PortalBuilder,
9849
+ {
9850
+ ...tableId ? { tableId } : {},
9851
+ portalId: adding,
9852
+ onSaved: () => {
9853
+ setAdding(null);
9854
+ void load();
9855
+ },
9856
+ onClose: () => setAdding(null)
9857
+ }
9858
+ ) : null,
9859
+ shown.length === 0 && !listError && !building && !adding ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
9858
9860
  BuildOrAsk,
9859
9861
  {
9860
9862
  mayBuild: true,
@@ -9863,12 +9865,38 @@ function PortalsPanel({ tableId, className }) {
9863
9865
  onAsk: () => host.onAskForOne?.({ kind: "portal", tableId, suggestion: PORTAL_SUGGESTION })
9864
9866
  } : {},
9865
9867
  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."
9868
+ buildLabel: elsewhere > 0 ? "Build a new portal" : "Build the portal",
9869
+ children: [
9870
+ "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.",
9871
+ elsewhere > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
9872
+ " ",
9873
+ "This table is not in",
9874
+ " ",
9875
+ elsewhere === 1 ? "the portal this organization already has" : `any of the ${elsewhere} portals this organization already has`,
9876
+ "."
9877
+ ] }) : null
9878
+ ]
9868
9879
  }
9869
9880
  ) : 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)}`;
9881
+ 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: [
9882
+ /* @__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:" }),
9883
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex flex-wrap gap-1.5", children: couldTakeIt.map((portal) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
9884
+ import_design_system33.Button,
9885
+ {
9886
+ size: "sm",
9887
+ variant: "outline",
9888
+ onClick: () => setAdding(portal.portal_id),
9889
+ children: [
9890
+ "Add to \u201C",
9891
+ portal.title,
9892
+ "\u201D"
9893
+ ]
9894
+ },
9895
+ portal.portal_id
9896
+ )) })
9897
+ ] }) : null,
9898
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
9899
+ const url = `${origin}${(0, import_records7.portalPath)(portal.slug)}`;
9872
9900
  const open = openId === portal.portal_id;
9873
9901
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
9874
9902
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center gap-2", children: [
@@ -10594,11 +10622,7 @@ function whenItFires(subscription) {
10594
10622
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
10595
10623
  return subscription.schedule ? `${every}, ${subscription.schedule}` : every;
10596
10624
  }
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
- };
10625
+ var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
10602
10626
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
10603
10627
  function SubscriptionsPanel({ tableId, className }) {
10604
10628
  const client = (0, import_react73.useRecordsClient)();
@@ -10772,7 +10796,7 @@ function SubscriptionsPanel({ tableId, className }) {
10772
10796
  // src/FormBuilder.tsx
10773
10797
  var import_react76 = require("react");
10774
10798
  var import_react77 = require("@ai-matrx/records/react");
10775
- var import_records7 = require("@ai-matrx/records");
10799
+ var import_records8 = require("@ai-matrx/records");
10776
10800
  var import_design_system37 = require("@ai-matrx/design-system");
10777
10801
 
10778
10802
  // src/FormRunner.tsx
@@ -11247,7 +11271,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
11247
11271
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
11248
11272
  PublishRow,
11249
11273
  {
11250
- url: `${publicOrigin}${(0, import_records7.publicFormPath)(draft.id)}`,
11274
+ url: `${publicOrigin}${(0, import_records8.publicFormPath)(draft.id)}`,
11251
11275
  state: draft.state,
11252
11276
  mayPublish: rights.structure,
11253
11277
  why: rights.why("structure"),
@@ -11366,7 +11390,12 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
11366
11390
  Condition,
11367
11391
  {
11368
11392
  question,
11369
- fieldKeys: fields.data ?? [],
11393
+ fieldKeys: (fields.data ?? []).map((f) => ({
11394
+ id: String(f.id),
11395
+ key: f.key,
11396
+ label: f.label || f.key,
11397
+ field: f
11398
+ })),
11370
11399
  onChange: (showIf) => patchQuestion(index, { showIf })
11371
11400
  }
11372
11401
  )
@@ -12099,17 +12128,8 @@ var import_react86 = require("react");
12099
12128
  var import_react87 = require("@ai-matrx/records/react");
12100
12129
  var import_design_system42 = require("@ai-matrx/design-system");
12101
12130
  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
- };
12131
+ var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
12132
+ var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
12113
12133
  function NotifyRuleEditor({ tableId, seed, className }) {
12114
12134
  const client = (0, import_react87.useRecordsClient)();
12115
12135
  const host = useRecordsUi();
@@ -12313,7 +12333,7 @@ function NotifyRuleEditor({ tableId, seed, className }) {
12313
12333
  // src/ChartBlock.tsx
12314
12334
  var import_react88 = require("react");
12315
12335
  var import_recharts = require("recharts");
12316
- var import_records8 = require("@ai-matrx/records");
12336
+ var import_records9 = require("@ai-matrx/records");
12317
12337
  var import_core6 = require("@ai-matrx/records/core");
12318
12338
  var import_design_system43 = require("@ai-matrx/design-system");
12319
12339
  var import_jsx_runtime46 = require("react/jsx-runtime");
@@ -12334,7 +12354,7 @@ function ChartBlock({ block, subject, className }) {
12334
12354
  const kind = block.kind;
12335
12355
  const tableId = block.table_id ?? subject;
12336
12356
  const measures = block.measures && block.measures.length > 0 ? block.measures : [{ op: "count" }];
12337
- const primary = (0, import_records8.measureKey)(measures[0]);
12357
+ const primary = (0, import_records9.measureKey)(measures[0]);
12338
12358
  const series = [primary];
12339
12359
  const points = (0, import_react88.useMemo)(() => {
12340
12360
  if (kind === "stuck") return [];
@@ -12868,7 +12888,7 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
12868
12888
  // src/FormsPanel.tsx
12869
12889
  var import_react91 = require("react");
12870
12890
  var import_react92 = require("@ai-matrx/records/react");
12871
- var import_records9 = require("@ai-matrx/records");
12891
+ var import_records10 = require("@ai-matrx/records");
12872
12892
  var import_design_system45 = require("@ai-matrx/design-system");
12873
12893
  var import_jsx_runtime48 = require("react/jsx-runtime");
12874
12894
  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 +12983,7 @@ function FormsPanel({ tableId, className }) {
12963
12983
  }
12964
12984
  ) : null,
12965
12985
  /* @__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)}`;
12986
+ const url = `${origin}${(0, import_records10.publicFormPath)(form.form_id)}`;
12967
12987
  return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("li", { className: "rounded-md border p-2.5", children: [
12968
12988
  /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex items-center gap-2", children: [
12969
12989
  /* @__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 +13035,7 @@ function FormsPanel({ tableId, className }) {
13015
13035
  // src/BookingBuilder.tsx
13016
13036
  var import_react93 = require("react");
13017
13037
  var import_react94 = require("@ai-matrx/records/react");
13018
- var import_records10 = require("@ai-matrx/records");
13038
+ var import_records11 = require("@ai-matrx/records");
13019
13039
  var import_design_system46 = require("@ai-matrx/design-system");
13020
13040
  var import_jsx_runtime49 = require("react/jsx-runtime");
13021
13041
  var STORE_ANSWERS_THESE = ["slot", "status", "booked_with"];
@@ -13158,7 +13178,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
13158
13178
  if (!rights.structure) {
13159
13179
  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
13180
  }
13161
- const url = formId ? `${publicOrigin}${(0, import_records10.bookingPath)(formId)}` : null;
13181
+ const url = formId ? `${publicOrigin}${(0, import_records11.bookingPath)(formId)}` : null;
13162
13182
  const shown = offer ?? null;
13163
13183
  return (
13164
13184
  // THE RAIL IS 384px WIDE AND THE VIEWPORT IS NOT (walk 1, 2026-09-21):
@@ -13360,7 +13380,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
13360
13380
  // src/BookingSlots.tsx
13361
13381
  var import_react95 = require("react");
13362
13382
  var import_react96 = require("@ai-matrx/records/react");
13363
- var import_records11 = require("@ai-matrx/records");
13383
+ var import_records12 = require("@ai-matrx/records");
13364
13384
  var import_design_system47 = require("@ai-matrx/design-system");
13365
13385
  var import_jsx_runtime50 = require("react/jsx-runtime");
13366
13386
  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 +13389,7 @@ function bookingSuggestion(tableName2) {
13369
13389
  return `Let clients book a 30-minute ${subject} slot on Tuesday and Thursday afternoons.`;
13370
13390
  }
13371
13391
  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
- };
13392
+ var STATE_WORDS = BOOKING_PAGE_STATE_LABEL;
13378
13393
  function BookingSlots({ tableId, className }) {
13379
13394
  const client = (0, import_react96.useRecordsClient)();
13380
13395
  const host = useRecordsUi();
@@ -13398,10 +13413,21 @@ function BookingSlots({ tableId, className }) {
13398
13413
  void load();
13399
13414
  }, [load]);
13400
13415
  const subjectIds = (0, import_react95.useMemo)(
13401
- () => Array.from(new Set((pages ?? []).map((p) => p.table_id))),
13402
- [pages]
13416
+ () => Array.from(
13417
+ /* @__PURE__ */ new Set([
13418
+ ...(pages ?? []).map((p) => p.table_id),
13419
+ // THE EMPTY STATE ASKS THE SAME QUESTION. There is no page to read a
13420
+ // table off yet, and the empty state's two buttons need the answer
13421
+ // about the table this rail is standing in — otherwise the rail
13422
+ // offers a viewer both ways into an act the door will refuse.
13423
+ ...tableId ? [tableId] : []
13424
+ ])
13425
+ ),
13426
+ [pages, tableId]
13403
13427
  );
13404
13428
  const levels = (0, import_react96.useMyLevels)(subjectIds);
13429
+ const levelsKnown = !levels.loading && Boolean(levels.data);
13430
+ const mayBuildHere = levelsKnown && Boolean(tableId) && levels.data?.[tableId] === "admin";
13405
13431
  const toggle = (0, import_react95.useCallback)(
13406
13432
  async (page) => {
13407
13433
  setBusy(page.form_id);
@@ -13450,7 +13476,8 @@ function BookingSlots({ tableId, className }) {
13450
13476
  pages.length === 0 && !building ? tableId ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
13451
13477
  BuildOrAsk,
13452
13478
  {
13453
- mayBuild: true,
13479
+ mayBuild: mayBuildHere,
13480
+ whyNot: mayBuildHere ? null : levelsKnown ? NO_ADMIN2 : null,
13454
13481
  onBuild: () => setBuilding(true),
13455
13482
  ...host.onAskForOne ? {
13456
13483
  onAsk: () => host.onAskForOne?.({
@@ -13468,7 +13495,7 @@ function BookingSlots({ tableId, className }) {
13468
13495
  " Open the table the appointments should land in to make one."
13469
13496
  ] }) : null,
13470
13497
  /* @__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)}`;
13498
+ const url = `${origin}${(0, import_records12.bookingPath)(page.form_id)}`;
13472
13499
  const mayOpen = levels.data?.[page.table_id] === "admin";
13473
13500
  const open = page.published_at !== null && page.closed_at === null;
13474
13501
  return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("li", { className: "rounded-md border p-2.5", "data-testid": "booking-page", children: [
@@ -13557,7 +13584,7 @@ var import_design_system49 = require("@ai-matrx/design-system");
13557
13584
 
13558
13585
  // src/CaptureRun.tsx
13559
13586
  var import_react97 = require("react");
13560
- var import_records12 = require("@ai-matrx/records");
13587
+ var import_records13 = require("@ai-matrx/records");
13561
13588
  var import_react98 = require("@ai-matrx/records/react");
13562
13589
  var import_design_system48 = require("@ai-matrx/design-system");
13563
13590
  var import_jsx_runtime51 = require("react/jsx-runtime");
@@ -13612,7 +13639,7 @@ function CaptureRun({ sheetId, face: given, className }) {
13612
13639
  const [sending, setSending] = (0, import_react97.useState)(false);
13613
13640
  const queueRef = (0, import_react97.useRef)(null);
13614
13641
  (0, import_react97.useEffect)(() => {
13615
- const q2 = (0, import_records12.openCaptureQueue)({
13642
+ const q2 = (0, import_records13.openCaptureQueue)({
13616
13643
  onChange: (c, all) => {
13617
13644
  setCounts(c);
13618
13645
  setItems(all);
@@ -13861,7 +13888,11 @@ function CaptureRun({ sheetId, face: given, className }) {
13861
13888
  value: String(answers[q.field] ?? ""),
13862
13889
  onChange: (e) => {
13863
13890
  const raw = e.target.value;
13864
- setAnswers((held) => ({ ...held, [q.field]: raw === "" ? "" : Number(raw) }));
13891
+ const answered2 = field ? (0, import_records13.coerceTypedAnswer)(field, raw, { label: q.ask ?? (0, import_records13.coercionLabel)(field) }) : { value: raw };
13892
+ setAnswers((held) => ({
13893
+ ...held,
13894
+ [q.field]: raw === "" ? "" : "refusal" in answered2 ? raw : answered2.value
13895
+ }));
13865
13896
  setMissing(null);
13866
13897
  }
13867
13898
  }