@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.js CHANGED
@@ -524,7 +524,11 @@ import {
524
524
  } from "@ai-matrx/design-system";
525
525
 
526
526
  // src/parity.ts
527
- import { KERNEL_TABLES, PARITY_FIELD_TYPES as PARITY_FIELD_TYPES2 } from "@ai-matrx/records";
527
+ import {
528
+ KERNEL_TABLES,
529
+ PARITY_FIELD_TYPES as PARITY_FIELD_TYPES2,
530
+ fieldKindFor
531
+ } from "@ai-matrx/records";
528
532
 
529
533
  // src/fieldTypes.ts
530
534
  import { PARITY_FIELD_TYPES } from "@ai-matrx/records";
@@ -591,50 +595,9 @@ var PARITY_LABEL = {
591
595
  var PARITY_MADE_OF = Object.fromEntries(
592
596
  PARITY_FIELD_TYPES2.map((p) => [p.parity_type, p.made_of])
593
597
  );
598
+ var KERNEL = { file: KERNEL_FILE_TABLE, person: KERNEL_PERSON_TABLE };
594
599
  function editorKindFor(field) {
595
- const format = (field.format ?? "").toLowerCase();
596
- const config = field.config ?? {};
597
- switch (field.type) {
598
- // LIMITS-FIX 2026-09-21: the tick box is a BEHAVIOUR of its own, so its editor is
599
- // decided by the behaviour alone — no format, no unit, no target to read. A record
600
- // that never answered holds no key at all, and `CheckboxControl` draws that third
601
- // state rather than pretending it is an unticked box.
602
- case "boolean":
603
- return "checkbox";
604
- case "text":
605
- if (format === "email") return "email";
606
- if (format === "phone") return "phone";
607
- if (format === "url") return "url";
608
- if (format === "json" || config["json"] === true) return "json";
609
- if (format === "long" || config["multiline"] === true) return "long_text";
610
- return "text";
611
- case "range": {
612
- const kind = String(config["kind"] ?? "number");
613
- if (kind === "date" || kind === "datetime") return "datetime";
614
- if (format === "currency") return "currency";
615
- if (format === "percent" || field.unit === "%") return "percent";
616
- return "number";
617
- }
618
- case "list":
619
- return field.multi ? "multi_select" : "select";
620
- case "relation":
621
- return relationEditor(field);
622
- case "formula": {
623
- const via = config["via"];
624
- if (via && config["agg"]) return "rollup";
625
- if (via) return "lookup";
626
- return "formula";
627
- }
628
- default:
629
- return "text";
630
- }
631
- }
632
- function relationEditor(field) {
633
- const parity = String(field.config?.["parity"] ?? "");
634
- if (parity === "attachment" || parity === "member") return parity;
635
- if (field.relation_target === KERNEL_FILE_TABLE) return "attachment";
636
- if (field.relation_target === KERNEL_PERSON_TABLE) return "member";
637
- return "relation";
600
+ return fieldKindFor(field, KERNEL);
638
601
  }
639
602
  function fieldTypeLabel(field) {
640
603
  const kind = editorKindFor(field);
@@ -1541,18 +1504,137 @@ function useLabelWait(field) {
1541
1504
  return field ? waiting(field) : null;
1542
1505
  }
1543
1506
 
1544
- // src/values.tsx
1545
- import { Fragment as Fragment3, jsx as jsx9 } from "react/jsx-runtime";
1546
- function envelopeFor(document2, key) {
1547
- const block = document2?._values;
1548
- return block?.[key];
1507
+ // src/enumLabels.ts
1508
+ function exhaustiveValuesOf() {
1509
+ return function(values) {
1510
+ return values;
1511
+ };
1549
1512
  }
1550
- var ABSENCE_WORDS = {
1513
+ var WORK_DUE_STATES = exhaustiveValuesOf()([
1514
+ "overdue",
1515
+ "due_today",
1516
+ "scheduled",
1517
+ "undated",
1518
+ "finished"
1519
+ ]);
1520
+ var DUE_STATE_LABEL = {
1521
+ overdue: "Overdue",
1522
+ due_today: "Today",
1523
+ scheduled: "Scheduled",
1524
+ undated: "No date",
1525
+ finished: "Done"
1526
+ };
1527
+ var WORK_INBOX_KINDS = exhaustiveValuesOf()(["assignment", "approval", "proposal"]);
1528
+ var WORK_INBOX_KIND_LABEL = {
1529
+ assignment: "Yours",
1530
+ approval: "Approve",
1531
+ proposal: "Agent"
1532
+ };
1533
+ var CHECKLIST_REQUIREMENT_KINDS = exhaustiveValuesOf()([
1534
+ "none",
1535
+ "note",
1536
+ "answer",
1537
+ "record_field",
1538
+ "form",
1539
+ "document"
1540
+ ]);
1541
+ var CHECKLIST_REQUIREMENT_LABEL = {
1542
+ none: "",
1543
+ note: "Say what you did",
1544
+ answer: "Fill this in",
1545
+ record_field: "This has to be filled in on the record",
1546
+ form: "This step is a form",
1547
+ document: "This step is a document"
1548
+ };
1549
+ var FIELD_SENSITIVITY_LABEL = {
1550
+ public: "Anyone who can open the record",
1551
+ internal: "People in this organization",
1552
+ confidential: "People who have been given the record",
1553
+ restricted: "Owners and administrators only"
1554
+ };
1555
+ var CONTEXT_POLICY_LABEL = {
1556
+ include: "Yes, an assistant can read it",
1557
+ summarize: "Only a summary of it",
1558
+ on_request: "Only when somebody asks for it",
1559
+ exclude: "No, keep it away from assistants"
1560
+ };
1561
+ var STAGE_RULE_ON_FAIL_LABEL = {
1562
+ refuse: "Turn it away",
1563
+ require_approval: "Ask for approval"
1564
+ };
1565
+ var SUBSCRIPTION_CADENCES = exhaustiveValuesOf()(["instant", "hourly", "daily", "weekly"]);
1566
+ var SUBSCRIPTION_CADENCE_LABEL = {
1567
+ instant: "as it happens",
1568
+ hourly: "hourly summary",
1569
+ daily: "daily summary",
1570
+ weekly: "weekly summary"
1571
+ };
1572
+ var SUBSCRIPTION_CHANNEL_VALUES = ["in_app", "email", "sms"];
1573
+ var SUBSCRIPTION_CHANNEL_LABEL = {
1574
+ in_app: "in the app",
1575
+ email: "by email \u2014 only if an address is on the account",
1576
+ sms: "by text \u2014 only if a number is on the account"
1577
+ };
1578
+ var ROLLUP_AGG_VALUES = ["count", "sum", "avg", "min", "max"];
1579
+ var ROLLUP_AGG_LABEL = {
1580
+ count: "How many there are",
1581
+ sum: "The total of",
1582
+ avg: "The average of",
1583
+ min: "The smallest",
1584
+ max: "The largest"
1585
+ };
1586
+ var FORMULA_OP_VALUES = ["concat", "add", "sub", "mul", "div"];
1587
+ var FORMULA_OP_LABEL = {
1588
+ concat: "Join them together",
1589
+ add: "Add them up",
1590
+ sub: "Take the second from the first",
1591
+ mul: "Multiply them",
1592
+ div: "Divide the first by the second"
1593
+ };
1594
+ var FORMULA_OP_JOINS = {
1595
+ concat: true,
1596
+ add: true,
1597
+ sub: false,
1598
+ mul: true,
1599
+ div: false
1600
+ };
1601
+ var FIELD_RULE_KIND_VALUES = ["min", "max", "length", "pattern", "equals_field", "differs_from_field"];
1602
+ var FIELD_RULE_KIND_LABEL = {
1603
+ min: "At least",
1604
+ max: "At most",
1605
+ length: "No longer than",
1606
+ pattern: "Looks like",
1607
+ equals_field: "Same as the column",
1608
+ differs_from_field: "Different from the column"
1609
+ };
1610
+ var PROPOSED_CHANGE_ACT_VALUES = ["add", "update", "remove"];
1611
+ var PROPOSED_CHANGE_ACT_LABEL = {
1612
+ add: "Add",
1613
+ update: "Update",
1614
+ remove: "Remove"
1615
+ };
1616
+ var BOOKING_PAGE_STATE_VALUES = ["draft", "open", "closed", "full"];
1617
+ var BOOKING_PAGE_STATE_LABEL = {
1618
+ draft: "Not open yet \u2014 nobody can book until you open it.",
1619
+ open: "Open. Anyone with the link can book a time.",
1620
+ closed: "Closed. The link still works and says you are not taking bookings.",
1621
+ full: "Every time you offered is taken."
1622
+ };
1623
+ var ABSENCE_WORD_VALUES = ["never asked", "none", "refused", "conflicting"];
1624
+ var ABSENCE_WORD_LABEL = {
1551
1625
  "never asked": "never asked",
1552
1626
  none: "none",
1553
1627
  refused: "refused",
1554
1628
  conflicting: "conflicting"
1555
1629
  };
1630
+
1631
+ // src/values.tsx
1632
+ import { Fragment as Fragment3, jsx as jsx9 } from "react/jsx-runtime";
1633
+ function envelopeFor(document2, key) {
1634
+ const block = document2?._values;
1635
+ return block?.[key];
1636
+ }
1637
+ var ABSENCE_WORDS = ABSENCE_WORD_LABEL;
1556
1638
  function renderValue(field, value, document2, labels, stillWaitingFor) {
1557
1639
  const envelope = envelopeFor(document2, field.key);
1558
1640
  if (envelope?.absent) {
@@ -2982,7 +3064,7 @@ function confirmLabel(plan, noun, nounPlural) {
2982
3064
 
2983
3065
  // src/pasteValues.ts
2984
3066
  import { predictValueRefusals } from "@ai-matrx/records/core";
2985
- import { optionKey as optionKey2 } from "@ai-matrx/records";
3067
+ import { coerceTypedAnswer, optionKey as optionKey2 } from "@ai-matrx/records";
2986
3068
  function choiceOptionsOf(options) {
2987
3069
  const byWord = /* @__PURE__ */ new Map();
2988
3070
  const labels = [];
@@ -2999,113 +3081,11 @@ function choiceOptionsOf(options) {
2999
3081
  }
3000
3082
  return { byWord, labels };
3001
3083
  }
3002
- var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
3003
- var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
3004
3084
  function valueFromPastedText(field, raw, options) {
3005
- const label = fieldName(field);
3006
- const text = raw.trim();
3007
- const kind = editorKindFor(field);
3008
- if (text === "" && kind !== "checkbox") return { value: null };
3009
- switch (kind) {
3010
- case "checkbox": {
3011
- const word = text.toLowerCase();
3012
- if (TRUE_WORDS.has(word)) return { value: true };
3013
- if (FALSE_WORDS.has(word)) return { value: word === "" ? null : false };
3014
- return {
3015
- 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.`
3016
- };
3017
- }
3018
- case "number":
3019
- case "currency":
3020
- case "percent": {
3021
- const negative = /^\(.*\)$/.test(text);
3022
- const bare = text.replace(/^\(|\)$/g, "").replace(/[\s,]/g, "").replace(/^[^\d.\-+]+/, "").replace(/[^\d.\-+eE]+$/, "");
3023
- const n = Number(bare);
3024
- if (bare === "" || Number.isNaN(n)) {
3025
- return {
3026
- 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.`
3027
- };
3028
- }
3029
- return { value: negative ? -n : n };
3030
- }
3031
- case "datetime": {
3032
- const wantsTime = String(field.config?.["kind"] ?? "date") === "datetime";
3033
- const plainDay = /^\s*(\d{4})-(\d{2})-(\d{2})\s*$/.exec(raw);
3034
- if (!wantsTime && plainDay) return { value: `${plainDay[1]}-${plainDay[2]}-${plainDay[3]}` };
3035
- const at = Date.parse(text);
3036
- if (Number.isNaN(at)) {
3037
- return {
3038
- 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.`
3039
- };
3040
- }
3041
- const parsed = new Date(at);
3042
- if (wantsTime) return { value: parsed.toISOString() };
3043
- const y = parsed.getFullYear();
3044
- const m = String(parsed.getMonth() + 1).padStart(2, "0");
3045
- const d = String(parsed.getDate()).padStart(2, "0");
3046
- return { value: `${y}-${m}-${d}` };
3047
- }
3048
- case "json": {
3049
- try {
3050
- return { value: JSON.parse(text) };
3051
- } catch {
3052
- return {
3053
- refusal: `${label} holds a document, and \u201C${raw}\u201D is not valid JSON. Paste the whole object, braces and all.`
3054
- };
3055
- }
3056
- }
3057
- // A LIST, A PERSON, A FILE OR ANOTHER RECORD IS AN IDENTITY, NOT A WORD.
3058
- // The store keeps the id of the option or the record, and a word off a
3059
- // clipboard is not that id — writing the word would put a string where an
3060
- // id belongs and every filter, colour and rollup built on that column would
3061
- // stop agreeing with it. So the cell is left alone and the person is told
3062
- // which control does hold it.
3063
- case "select":
3064
- case "multi_select": {
3065
- if (!options) {
3066
- return {
3067
- 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.`
3068
- };
3069
- }
3070
- const wanted = kind === "multi_select" ? text.split(/[,;]/).map((w) => w.trim()).filter((w) => w.length > 0) : [text];
3071
- if (wanted.length === 0) return { value: kind === "multi_select" ? [] : null };
3072
- const matched = [];
3073
- const unmatched = [];
3074
- for (const word of wanted) {
3075
- const hit = options.byWord.get(word.toLowerCase());
3076
- if (hit === void 0) unmatched.push(word);
3077
- else matched.push(hit);
3078
- }
3079
- if (unmatched.length > 0) {
3080
- const offered = options.labels.length > 0 ? options.labels.join(", ") : "it has none yet";
3081
- return {
3082
- refusal: `${label} does not have ${unmatched.map((w) => `\u201C${w}\u201D`).join(" or ")} among its options. It offers: ${offered}.`,
3083
- unmatched
3084
- };
3085
- }
3086
- return { value: kind === "multi_select" ? matched : matched[0] };
3087
- }
3088
- case "member":
3089
- return {
3090
- refusal: `${label} names a person in this organization, so it is chosen rather than typed. Open the cell and pick them.`
3091
- };
3092
- case "attachment":
3093
- return {
3094
- refusal: `${label} holds a file, which cannot arrive on a clipboard. Open the cell and attach it.`
3095
- };
3096
- case "relation":
3097
- return {
3098
- refusal: `${label} points at another record, so it is chosen rather than typed. Open the cell and pick the record.`
3099
- };
3100
- case "formula":
3101
- case "lookup":
3102
- case "rollup":
3103
- return {
3104
- refusal: `${label} is worked out by the system, so nothing can be pasted into it.`
3105
- };
3106
- default:
3107
- return { value: raw };
3108
- }
3085
+ return coerceTypedAnswer(field, raw, {
3086
+ label: fieldName(field),
3087
+ ...options ? { options } : {}
3088
+ });
3109
3089
  }
3110
3090
  function planPastedBlock(args) {
3111
3091
  const byKey = new Map(args.fields.map((f) => [f.key, f]));
@@ -4282,19 +4262,23 @@ import {
4282
4262
  } from "@ai-matrx/records/react";
4283
4263
  import { Button as Button11, Separator as Separator3, cn as cn12 } from "@ai-matrx/design-system";
4284
4264
  import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
4285
- var ADDABLE_TYPES = [
4286
- { value: "text", label: "Text" },
4287
- { value: "long_text", label: "Long text" },
4288
- { value: "number", label: "Number" },
4289
- { value: "currency", label: "Money" },
4290
- { value: "percent", label: "Percentage" },
4291
- { value: "datetime", label: "Date" },
4292
- { value: "email", label: "Email" },
4293
- { value: "phone", label: "Phone" },
4294
- { value: "url", label: "Link" },
4295
- { value: "member", label: "Person" },
4296
- { value: "attachment", label: "File" }
4265
+ var ADDABLE_TYPE_IDS = [
4266
+ "text",
4267
+ "long_text",
4268
+ "number",
4269
+ "currency",
4270
+ "percent",
4271
+ "datetime",
4272
+ "email",
4273
+ "phone",
4274
+ "url",
4275
+ "member",
4276
+ "attachment"
4297
4277
  ];
4278
+ var ADDABLE_TYPES = ADDABLE_TYPE_IDS.map((value) => ({
4279
+ value,
4280
+ label: fieldTypeChoice(value)?.label ?? value
4281
+ }));
4298
4282
  function asField(f) {
4299
4283
  return {
4300
4284
  id: f.id,
@@ -4473,41 +4457,20 @@ import {
4473
4457
  Separator as Separator4,
4474
4458
  cn as cn13
4475
4459
  } from "@ai-matrx/design-system";
4460
+ import { FIELD_SENSITIVITIES, CONTEXT_POLICIES } from "@ai-matrx/records";
4476
4461
  import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
4477
- var RULE_KINDS = [
4478
- { kind: "min", label: "At least" },
4479
- { kind: "max", label: "At most" },
4480
- { kind: "length", label: "No longer than" },
4481
- { kind: "pattern", label: "Looks like" },
4482
- { kind: "equals_field", label: "Same as the column" },
4483
- { kind: "differs_from_field", label: "Different from the column" }
4484
- ];
4485
- var SENSITIVITIES = [
4486
- { value: "public", label: "Anyone who can open the record" },
4487
- { value: "internal", label: "People in this organization" },
4488
- { value: "confidential", label: "People who have been given the record" },
4489
- { value: "restricted", label: "Owners and administrators only" }
4490
- ];
4491
- var AGENT_VISIBILITY = [
4492
- { value: "include", label: "Yes, an assistant can read it" },
4493
- { value: "summarize", label: "Only a summary of it" },
4494
- { value: "on_request", label: "Only when somebody asks for it" },
4495
- { value: "exclude", label: "No, keep it away from assistants" }
4496
- ];
4497
- var ROLLUP_OPERATIONS = [
4498
- { value: "count", label: "How many there are" },
4499
- { value: "sum", label: "The total of" },
4500
- { value: "avg", label: "The average of" },
4501
- { value: "min", label: "The smallest" },
4502
- { value: "max", label: "The largest" }
4503
- ];
4504
- var FORMULA_OPERATIONS = [
4505
- { value: "concat", label: "Join them together", joins: true },
4506
- { value: "add", label: "Add them up", joins: true },
4507
- { value: "sub", label: "Take the second from the first", joins: false },
4508
- { value: "mul", label: "Multiply them", joins: true },
4509
- { value: "div", label: "Divide the first by the second", joins: false }
4510
- ];
4462
+ var RULE_KINDS = FIELD_RULE_KIND_VALUES.map((kind) => ({ kind, label: FIELD_RULE_KIND_LABEL[kind] }));
4463
+ var SENSITIVITIES = FIELD_SENSITIVITIES.map((value) => ({ value, label: FIELD_SENSITIVITY_LABEL[value] }));
4464
+ var AGENT_VISIBILITY = CONTEXT_POLICIES.map((value) => ({ value, label: CONTEXT_POLICY_LABEL[value] }));
4465
+ var ROLLUP_OPERATIONS = ROLLUP_AGG_VALUES.map((value) => ({
4466
+ value,
4467
+ label: ROLLUP_AGG_LABEL[value]
4468
+ }));
4469
+ var FORMULA_OPERATIONS = FORMULA_OP_VALUES.map((value) => ({
4470
+ value,
4471
+ label: FORMULA_OP_LABEL[value],
4472
+ joins: FORMULA_OP_JOINS[value]
4473
+ }));
4511
4474
  function keyFor(label) {
4512
4475
  const token = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
4513
4476
  if (token === "") return "";
@@ -5072,6 +5035,9 @@ var CONDITION_OPS = [
5072
5035
  { op: "gt", label: "is more than" },
5073
5036
  { op: "lt", label: "is less than" }
5074
5037
  ];
5038
+ function pointsAtARecord(f) {
5039
+ return !!f?.field && f.field.type === "relation" && !!f.field.relation_target;
5040
+ }
5075
5041
  function conditionIsSimple(expr) {
5076
5042
  if (expr === null || expr === void 0) return true;
5077
5043
  const node = expr;
@@ -5142,6 +5108,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5142
5108
  const op = node?.op ?? "";
5143
5109
  const onField = node?.args?.[0]?.field ?? "";
5144
5110
  const value = node?.args?.[1]?.const ?? "";
5111
+ const chosen = fields.find((f) => f.id === onField);
5145
5112
  function write(next) {
5146
5113
  const chosenOp = next.op ?? op;
5147
5114
  const chosenField = next.field ?? onField;
@@ -5180,7 +5147,24 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5180
5147
  children: CONDITION_OPS.map((c) => /* @__PURE__ */ jsx18("option", { value: c.op, children: c.label }, c.op))
5181
5148
  }
5182
5149
  ),
5183
- op !== "present" ? /* @__PURE__ */ jsx18(
5150
+ op === "present" ? null : pointsAtARecord(chosen) ? (
5151
+ // A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
5152
+ // cell uses, on the same `relation_words_many` the cell reads, so a filter and
5153
+ // the column it filters can never disagree about what a record is called. It
5154
+ // is asked for ONE record even when the column holds several: a clause compares
5155
+ // against one value, and a picker that let somebody choose three would be
5156
+ // writing an expression this row cannot draw back.
5157
+ /* @__PURE__ */ jsx18(
5158
+ RelationPicker,
5159
+ {
5160
+ field: { ...chosen.field, multi: false },
5161
+ value: value === "" ? null : value,
5162
+ onChange: (next) => write({ value: next === null || next === void 0 ? "" : String(next) }),
5163
+ id: "condition-value-record",
5164
+ className: "min-w-[8rem]"
5165
+ }
5166
+ )
5167
+ ) : /* @__PURE__ */ jsx18(
5184
5168
  BasicInput6,
5185
5169
  {
5186
5170
  className: "h-8 w-28 text-xs",
@@ -5188,7 +5172,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5188
5172
  value: String(value ?? ""),
5189
5173
  onChange: (e) => write({ value: conditionValue(e.target.value) })
5190
5174
  }
5191
- ) : null
5175
+ )
5192
5176
  ] }) : null
5193
5177
  ] });
5194
5178
  }
@@ -5348,10 +5332,10 @@ var NO_RULES_YET = "Nothing has to be true to get here yet.";
5348
5332
  var NEEDS_A_SENTENCE = "Write the sentence somebody reads when this stops them, the way you would say it out loud.";
5349
5333
  var NOT_DRAWN_HERE = "This part of the rule is not something this builder can draw yet";
5350
5334
  var ON_FAIL = [
5351
- { value: "refuse", label: "Turn it away", note: "The card stays where it is and the person is told why." },
5335
+ { value: "refuse", label: STAGE_RULE_ON_FAIL_LABEL.refuse, note: "The card stays where it is and the person is told why." },
5352
5336
  {
5353
5337
  value: "require_approval",
5354
- label: "Ask for approval",
5338
+ label: STAGE_RULE_ON_FAIL_LABEL.require_approval,
5355
5339
  note: "The card stays where it is, and the people who can approve it are asked. Nobody is turned away."
5356
5340
  }
5357
5341
  ];
@@ -5402,7 +5386,7 @@ function StageRulesSection({ tableId, stage, className }) {
5402
5386
  const stageLabel = stages.find((s) => s.key === stageKey)?.label ?? stageKey ?? "";
5403
5387
  const gates = useMemo13(() => stageKey ? gatesOf(pipeline, stageKey) : [], [pipeline, stageKey]);
5404
5388
  const conditionFields = useMemo13(
5405
- () => (fields.data ?? []).map((f) => ({ id: String(f.id), key: f.key, label: fieldName(f) })),
5389
+ () => (fields.data ?? []).map((f) => ({ id: String(f.id), key: f.key, label: fieldName(f), field: f })),
5406
5390
  [fields.data]
5407
5391
  );
5408
5392
  useEffect8(() => {
@@ -7103,7 +7087,7 @@ function offerableFields(all, want) {
7103
7087
  return all.filter((field) => {
7104
7088
  const kind = editorKindFor(field);
7105
7089
  if (want === "choice") return kind === "select" || kind === "multi_select";
7106
- return kind === "datetime" || kind === "date";
7090
+ return kind === "datetime";
7107
7091
  });
7108
7092
  }
7109
7093
  function useStageField(tableId) {
@@ -7521,11 +7505,7 @@ import { useState as useState26 } from "react";
7521
7505
  import { useRecordsClient as useRecordsClient18 } from "@ai-matrx/records/react";
7522
7506
  import { Badge as Badge7, Button as Button22, cn as cn22 } from "@ai-matrx/design-system";
7523
7507
  import { jsx as jsx28, jsxs as jsxs24 } from "react/jsx-runtime";
7524
- var ACT_WORD = {
7525
- add: "Add",
7526
- update: "Update",
7527
- remove: "Remove"
7528
- };
7508
+ var ACT_WORD = PROPOSED_CHANGE_ACT_LABEL;
7529
7509
  function ProposalRow({
7530
7510
  change,
7531
7511
  outcome,
@@ -7632,21 +7612,8 @@ import { useCallback as useCallback12, useEffect as useEffect15, useMemo as useM
7632
7612
  import { useRecordsClient as useRecordsClient19, useTable as useTable9 } from "@ai-matrx/records/react";
7633
7613
  import { Badge as Badge8, BasicInput as BasicInput8, BasicTextarea as BasicTextarea4, Button as Button23, Skeleton as Skeleton9, cn as cn23 } from "@ai-matrx/design-system";
7634
7614
  import { Fragment as Fragment13, jsx as jsx29, jsxs as jsxs25 } from "react/jsx-runtime";
7635
- var DUE_WORD = {
7636
- overdue: "Overdue",
7637
- due_today: "Today",
7638
- scheduled: "Scheduled",
7639
- undated: "No date",
7640
- finished: "Done"
7641
- };
7642
- var REQUIRES_WORD = {
7643
- none: "",
7644
- note: "Say what you did",
7645
- answer: "Fill this in",
7646
- record_field: "This has to be filled in on the record",
7647
- form: "This step is a form",
7648
- document: "This step is a document"
7649
- };
7615
+ var DUE_WORD = DUE_STATE_LABEL;
7616
+ var REQUIRES_WORD = CHECKLIST_REQUIREMENT_LABEL;
7650
7617
  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.";
7651
7618
  function ChecklistRunner({
7652
7619
  tableId,
@@ -8354,18 +8321,8 @@ function roleKey(said) {
8354
8321
  // src/ActionInbox.tsx
8355
8322
  import { jsx as jsx30, jsxs as jsxs26 } from "react/jsx-runtime";
8356
8323
  var ACTION_KINDS = ["approval", "assignment", "proposal"];
8357
- var KIND_WORD = {
8358
- assignment: "Yours",
8359
- approval: "Approve",
8360
- proposal: "Agent"
8361
- };
8362
- var DUE_WORD2 = {
8363
- overdue: "Overdue",
8364
- due_today: "Today",
8365
- scheduled: "Scheduled",
8366
- undated: "No date",
8367
- finished: "Finished"
8368
- };
8324
+ var KIND_WORD = WORK_INBOX_KIND_LABEL;
8325
+ var DUE_WORD2 = DUE_STATE_LABEL;
8369
8326
  function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className }) {
8370
8327
  const client = useRecordsClient20();
8371
8328
  const [items, setItems] = useState28(null);
@@ -9617,18 +9574,18 @@ function BuildOrAsk({
9617
9574
  buildLabel,
9618
9575
  className
9619
9576
  }) {
9577
+ const mayAsk = mayBuild && Boolean(onAsk);
9620
9578
  return /* @__PURE__ */ jsxs31("div", { className: cn29("rounded-md border border-dashed p-3", className), children: [
9621
9579
  /* @__PURE__ */ jsx35("p", { className: "text-xs text-muted-foreground", children }),
9622
- /* @__PURE__ */ jsxs31("div", { className: "mt-2.5 flex flex-wrap items-center gap-2", children: [
9623
- onAsk ? /* @__PURE__ */ jsx35(Button29, { size: "sm", variant: "outline", onClick: onAsk, children: "Ask an agent" }) : null,
9624
- mayBuild ? /* @__PURE__ */ jsx35(Button29, { size: "sm", onClick: onBuild, children: buildLabel }) : null
9625
- ] }),
9626
- onAsk ? /* @__PURE__ */ jsxs31("p", { className: "mt-2 text-xs text-muted-foreground", children: [
9580
+ mayBuild ? /* @__PURE__ */ jsxs31("div", { className: "mt-2.5 flex flex-wrap items-center gap-2", children: [
9581
+ mayAsk ? /* @__PURE__ */ jsx35(Button29, { size: "sm", variant: "outline", onClick: onAsk, children: "Ask an agent" }) : null,
9582
+ /* @__PURE__ */ jsx35(Button29, { size: "sm", onClick: onBuild, children: buildLabel })
9583
+ ] }) : null,
9584
+ !mayBuild ? whyNot ? /* @__PURE__ */ jsx35("p", { className: "mt-2 text-xs text-muted-foreground", children: whyNot }) : null : mayAsk ? /* @__PURE__ */ jsxs31("p", { className: "mt-2 text-xs text-muted-foreground", children: [
9627
9585
  "You would say something like: \u201C",
9628
9586
  suggestion,
9629
9587
  "\u201D"
9630
- ] }) : /* @__PURE__ */ jsx35("p", { className: "mt-2 text-xs text-muted-foreground", children: NO_AGENT_PORT }),
9631
- !mayBuild && whyNot ? /* @__PURE__ */ jsx35("p", { className: "mt-2 text-xs text-muted-foreground", children: whyNot }) : null
9588
+ ] }) : /* @__PURE__ */ jsx35("p", { className: "mt-2 text-xs text-muted-foreground", children: NO_AGENT_PORT })
9632
9589
  ] });
9633
9590
  }
9634
9591
 
@@ -9667,30 +9624,41 @@ function PortalsPanel({ tableId, className }) {
9667
9624
  const client = useRecordsClient25();
9668
9625
  const host = useRecordsUi();
9669
9626
  const [portals, setPortals] = useState33(null);
9627
+ const [exposures, setExposures] = useState33([]);
9670
9628
  const [listError, setListError] = useState33(null);
9671
9629
  const [openId, setOpenId] = useState33(null);
9672
9630
  const [building, setBuilding] = useState33(false);
9631
+ const [adding, setAdding] = useState33(null);
9673
9632
  const load = useCallback18(async () => {
9674
- const answered = await client.portals();
9633
+ const [answered, mapped] = await Promise.all([client.portals(), client.portalTables()]);
9675
9634
  if (!answered.ok) {
9676
9635
  setListError(answered.error);
9677
9636
  setPortals([]);
9637
+ setExposures([]);
9678
9638
  return;
9679
9639
  }
9680
9640
  setListError(null);
9681
9641
  setPortals(answered.data);
9642
+ setExposures(mapped.ok ? mapped.data : []);
9682
9643
  }, [client]);
9683
9644
  useEffect21(() => {
9684
9645
  void load();
9685
9646
  }, [load]);
9686
9647
  if (portals === null) return /* @__PURE__ */ jsx36(Skeleton15, { className: cn30("h-32 w-full", className) });
9648
+ const onATable = Boolean(tableId);
9649
+ const holdingThis = new Set(
9650
+ exposures.filter((e) => e.table_id === tableId).map((e) => e.portal_id)
9651
+ );
9652
+ const shown = onATable ? portals.filter((p) => holdingThis.has(p.portal_id)) : portals;
9653
+ const couldTakeIt = onATable ? portals.filter((p) => !holdingThis.has(p.portal_id)) : [];
9654
+ const elsewhere = couldTakeIt.length;
9687
9655
  const origin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
9688
9656
  return /* @__PURE__ */ jsxs32("section", { className: cn30("flex flex-col gap-3", className), children: [
9689
9657
  /* @__PURE__ */ jsxs32("header", { className: "flex items-center gap-2", children: [
9690
9658
  /* @__PURE__ */ jsx36("h3", { className: "text-sm font-medium", children: "Portals" }),
9691
- /* @__PURE__ */ jsx36("span", { className: "text-xs text-muted-foreground", children: portals.length === 0 ? "none yet" : `${portals.length}` }),
9659
+ /* @__PURE__ */ jsx36("span", { className: "text-xs text-muted-foreground", children: shown.length === 0 ? "none for this table" : `${shown.length}` }),
9692
9660
  /* @__PURE__ */ jsx36("div", { className: "flex-1" }),
9693
- portals.length > 0 ? /* @__PURE__ */ jsx36(Button30, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a portal" }) : null
9661
+ shown.length > 0 ? /* @__PURE__ */ jsx36(Button30, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a portal" }) : null
9694
9662
  ] }),
9695
9663
  listError ? /* @__PURE__ */ jsx36(RefusalNotice, { error: listError }) : null,
9696
9664
  building ? /* @__PURE__ */ jsx36(
@@ -9703,7 +9671,19 @@ function PortalsPanel({ tableId, className }) {
9703
9671
  onClose: () => setBuilding(false)
9704
9672
  }
9705
9673
  ) : null,
9706
- portals.length === 0 && !listError && !building ? /* @__PURE__ */ jsx36(
9674
+ adding ? /* @__PURE__ */ jsx36(
9675
+ PortalBuilder,
9676
+ {
9677
+ ...tableId ? { tableId } : {},
9678
+ portalId: adding,
9679
+ onSaved: () => {
9680
+ setAdding(null);
9681
+ void load();
9682
+ },
9683
+ onClose: () => setAdding(null)
9684
+ }
9685
+ ) : null,
9686
+ shown.length === 0 && !listError && !building && !adding ? /* @__PURE__ */ jsxs32(
9707
9687
  BuildOrAsk,
9708
9688
  {
9709
9689
  mayBuild: true,
@@ -9712,11 +9692,37 @@ function PortalsPanel({ tableId, className }) {
9712
9692
  onAsk: () => host.onAskForOne?.({ kind: "portal", tableId, suggestion: PORTAL_SUGGESTION })
9713
9693
  } : {},
9714
9694
  suggestion: PORTAL_SUGGESTION,
9715
- buildLabel: "Build the portal",
9716
- 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."
9695
+ buildLabel: elsewhere > 0 ? "Build a new portal" : "Build the portal",
9696
+ children: [
9697
+ "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.",
9698
+ elsewhere > 0 ? /* @__PURE__ */ jsxs32(Fragment16, { children: [
9699
+ " ",
9700
+ "This table is not in",
9701
+ " ",
9702
+ elsewhere === 1 ? "the portal this organization already has" : `any of the ${elsewhere} portals this organization already has`,
9703
+ "."
9704
+ ] }) : null
9705
+ ]
9717
9706
  }
9718
9707
  ) : null,
9719
- /* @__PURE__ */ jsx36("ul", { className: "flex flex-col gap-2", children: portals.map((portal) => {
9708
+ shown.length === 0 && !listError && !building && !adding && elsewhere > 0 ? /* @__PURE__ */ jsxs32("div", { className: "flex flex-col gap-1.5 rounded-md border border-dashed border-border p-2.5", children: [
9709
+ /* @__PURE__ */ jsx36("p", { className: "text-xs text-muted-foreground", children: "Or add this table to a portal your clients already sign in to:" }),
9710
+ /* @__PURE__ */ jsx36("div", { className: "flex flex-wrap gap-1.5", children: couldTakeIt.map((portal) => /* @__PURE__ */ jsxs32(
9711
+ Button30,
9712
+ {
9713
+ size: "sm",
9714
+ variant: "outline",
9715
+ onClick: () => setAdding(portal.portal_id),
9716
+ children: [
9717
+ "Add to \u201C",
9718
+ portal.title,
9719
+ "\u201D"
9720
+ ]
9721
+ },
9722
+ portal.portal_id
9723
+ )) })
9724
+ ] }) : null,
9725
+ /* @__PURE__ */ jsx36("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
9720
9726
  const url = `${origin}${portalPath(portal.slug)}`;
9721
9727
  const open = openId === portal.portal_id;
9722
9728
  return /* @__PURE__ */ jsxs32("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
@@ -10443,11 +10449,7 @@ function whenItFires(subscription) {
10443
10449
  const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
10444
10450
  return subscription.schedule ? `${every}, ${subscription.schedule}` : every;
10445
10451
  }
10446
- var CHANNEL_WORDS2 = {
10447
- in_app: "in the app",
10448
- email: "by email \u2014 only if an address is on the account",
10449
- sms: "by text \u2014 only if a number is on the account"
10450
- };
10452
+ var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
10451
10453
  var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
10452
10454
  function SubscriptionsPanel({ tableId, className }) {
10453
10455
  const client = useRecordsClient27();
@@ -11223,7 +11225,12 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
11223
11225
  Condition,
11224
11226
  {
11225
11227
  question,
11226
- fieldKeys: fields.data ?? [],
11228
+ fieldKeys: (fields.data ?? []).map((f) => ({
11229
+ id: String(f.id),
11230
+ key: f.key,
11231
+ label: f.label || f.key,
11232
+ field: f
11233
+ })),
11227
11234
  onChange: (showIf) => patchQuestion(index, { showIf })
11228
11235
  }
11229
11236
  )
@@ -11961,17 +11968,8 @@ import { useCallback as useCallback26, useEffect as useEffect30, useState as use
11961
11968
  import { useRecordsClient as useRecordsClient32, useTable as useTable16 } from "@ai-matrx/records/react";
11962
11969
  import { Button as Button38, Skeleton as Skeleton23, cn as cn39 } from "@ai-matrx/design-system";
11963
11970
  import { jsx as jsx45, jsxs as jsxs41 } from "react/jsx-runtime";
11964
- var CADENCE_WORDS2 = {
11965
- instant: "as it happens",
11966
- hourly: "hourly summary",
11967
- daily: "daily summary",
11968
- weekly: "weekly summary"
11969
- };
11970
- var CHANNEL_WORDS3 = {
11971
- in_app: "in the app",
11972
- email: "by email",
11973
- sms: "by text"
11974
- };
11971
+ var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
11972
+ var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
11975
11973
  function NotifyRuleEditor({ tableId, seed, className }) {
11976
11974
  const client = useRecordsClient32();
11977
11975
  const host = useRecordsUi();
@@ -13248,12 +13246,7 @@ function bookingSuggestion(tableName2) {
13248
13246
  return `Let clients book a 30-minute ${subject} slot on Tuesday and Thursday afternoons.`;
13249
13247
  }
13250
13248
  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.";
13251
- var STATE_WORDS = {
13252
- draft: "Not open yet \u2014 nobody can book until you open it.",
13253
- open: "Open. Anyone with the link can book a time.",
13254
- closed: "Closed. The link still works and says you are not taking bookings.",
13255
- full: "Every time you offered is taken."
13256
- };
13249
+ var STATE_WORDS = BOOKING_PAGE_STATE_LABEL;
13257
13250
  function BookingSlots({ tableId, className }) {
13258
13251
  const client = useRecordsClient36();
13259
13252
  const host = useRecordsUi();
@@ -13277,10 +13270,21 @@ function BookingSlots({ tableId, className }) {
13277
13270
  void load();
13278
13271
  }, [load]);
13279
13272
  const subjectIds = useMemo30(
13280
- () => Array.from(new Set((pages ?? []).map((p) => p.table_id))),
13281
- [pages]
13273
+ () => Array.from(
13274
+ /* @__PURE__ */ new Set([
13275
+ ...(pages ?? []).map((p) => p.table_id),
13276
+ // THE EMPTY STATE ASKS THE SAME QUESTION. There is no page to read a
13277
+ // table off yet, and the empty state's two buttons need the answer
13278
+ // about the table this rail is standing in — otherwise the rail
13279
+ // offers a viewer both ways into an act the door will refuse.
13280
+ ...tableId ? [tableId] : []
13281
+ ])
13282
+ ),
13283
+ [pages, tableId]
13282
13284
  );
13283
13285
  const levels = useMyLevels2(subjectIds);
13286
+ const levelsKnown = !levels.loading && Boolean(levels.data);
13287
+ const mayBuildHere = levelsKnown && Boolean(tableId) && levels.data?.[tableId] === "admin";
13284
13288
  const toggle = useCallback30(
13285
13289
  async (page) => {
13286
13290
  setBusy(page.form_id);
@@ -13329,7 +13333,8 @@ function BookingSlots({ tableId, className }) {
13329
13333
  pages.length === 0 && !building ? tableId ? /* @__PURE__ */ jsx50(
13330
13334
  BuildOrAsk,
13331
13335
  {
13332
- mayBuild: true,
13336
+ mayBuild: mayBuildHere,
13337
+ whyNot: mayBuildHere ? null : levelsKnown ? NO_ADMIN2 : null,
13333
13338
  onBuild: () => setBuilding(true),
13334
13339
  ...host.onAskForOne ? {
13335
13340
  onAsk: () => host.onAskForOne?.({
@@ -13437,6 +13442,8 @@ import { Button as Button44, Input as Input4, Skeleton as Skeleton29, Textarea a
13437
13442
  // src/CaptureRun.tsx
13438
13443
  import { useCallback as useCallback31, useEffect as useEffect35, useMemo as useMemo31, useRef as useRef13, useState as useState47 } from "react";
13439
13444
  import {
13445
+ coerceTypedAnswer as coerceTypedAnswer2,
13446
+ coercionLabel,
13440
13447
  openCaptureQueue
13441
13448
  } from "@ai-matrx/records";
13442
13449
  import { useRecordsClient as useRecordsClient37 } from "@ai-matrx/records/react";
@@ -13742,7 +13749,11 @@ function CaptureRun({ sheetId, face: given, className }) {
13742
13749
  value: String(answers[q.field] ?? ""),
13743
13750
  onChange: (e) => {
13744
13751
  const raw = e.target.value;
13745
- setAnswers((held) => ({ ...held, [q.field]: raw === "" ? "" : Number(raw) }));
13752
+ const answered2 = field ? coerceTypedAnswer2(field, raw, { label: q.ask ?? coercionLabel(field) }) : { value: raw };
13753
+ setAnswers((held) => ({
13754
+ ...held,
13755
+ [q.field]: raw === "" ? "" : "refusal" in answered2 ? raw : answered2.value
13756
+ }));
13746
13757
  setMissing(null);
13747
13758
  }
13748
13759
  }
@@ -15016,8 +15027,12 @@ function TablePage({
15016
15027
  ] });
15017
15028
  }
15018
15029
  export {
15030
+ ABSENCE_WORD_LABEL,
15031
+ ABSENCE_WORD_VALUES,
15019
15032
  ACTION_KINDS,
15020
15033
  ActionInbox,
15034
+ BOOKING_PAGE_STATE_LABEL,
15035
+ BOOKING_PAGE_STATE_VALUES,
15021
15036
  BookingBuilder,
15022
15037
  BookingSlots,
15023
15038
  BuildOrAsk,
@@ -15025,7 +15040,10 @@ export {
15025
15040
  CHART_KINDS,
15026
15041
  CHART_KIND_LABEL,
15027
15042
  CHART_NEEDS,
15043
+ CHECKLIST_REQUIREMENT_KINDS,
15044
+ CHECKLIST_REQUIREMENT_LABEL,
15028
15045
  CONDITION_OPS,
15046
+ CONTEXT_POLICY_LABEL,
15029
15047
  CaptureRun,
15030
15048
  CaptureSheet,
15031
15049
  ChartBlock,
@@ -15041,6 +15059,7 @@ export {
15041
15059
  CustomFieldsSection,
15042
15060
  DEFAULT_FIELDS,
15043
15061
  DEFAULT_VIEW_NAME,
15062
+ DUE_STATE_LABEL,
15044
15063
  DashboardCanvas,
15045
15064
  DigestScheduler,
15046
15065
  DocRender,
@@ -15050,8 +15069,14 @@ export {
15050
15069
  EnrichBadge,
15051
15070
  EnrichPanel,
15052
15071
  ExportMenu,
15072
+ FIELD_RULE_KIND_LABEL,
15073
+ FIELD_RULE_KIND_VALUES,
15074
+ FIELD_SENSITIVITY_LABEL,
15053
15075
  FIELD_TYPE_CHOICES,
15054
15076
  FIELD_TYPE_GROUPS,
15077
+ FORMULA_OP_JOINS,
15078
+ FORMULA_OP_LABEL,
15079
+ FORMULA_OP_VALUES,
15055
15080
  FORM_FLOWS,
15056
15081
  FROZEN_COLUMN_WIDTH,
15057
15082
  FieldControl,
@@ -15099,6 +15124,8 @@ export {
15099
15124
  PAGE_VIEW_LABEL,
15100
15125
  PARITY_LABEL,
15101
15126
  PARITY_MADE_OF,
15127
+ PROPOSED_CHANGE_ACT_LABEL,
15128
+ PROPOSED_CHANGE_ACT_VALUES,
15102
15129
  Peek,
15103
15130
  PersonPicker,
15104
15131
  PipelineBoard,
@@ -15109,6 +15136,8 @@ export {
15109
15136
  ProposalRow,
15110
15137
  ProvenanceBadge,
15111
15138
  PublicViewPage,
15139
+ ROLLUP_AGG_LABEL,
15140
+ ROLLUP_AGG_VALUES,
15112
15141
  RecordChat,
15113
15142
  RecordChip,
15114
15143
  RecordForm,
@@ -15124,8 +15153,13 @@ export {
15124
15153
  SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
15125
15154
  SERIES_COLORS,
15126
15155
  SOMEBODY,
15156
+ STAGE_RULE_ON_FAIL_LABEL,
15127
15157
  STORE_ANSWERS_THESE,
15128
15158
  STORE_DECIDES_REASON,
15159
+ SUBSCRIPTION_CADENCES,
15160
+ SUBSCRIPTION_CADENCE_LABEL,
15161
+ SUBSCRIPTION_CHANNEL_LABEL,
15162
+ SUBSCRIPTION_CHANNEL_VALUES,
15129
15163
  ShareControl,
15130
15164
  SignBlock,
15131
15165
  StageRulesSection,
@@ -15143,6 +15177,9 @@ export {
15143
15177
  ViewBar,
15144
15178
  ViewSwitcher,
15145
15179
  WITHHELD_RECORD_LABEL,
15180
+ WORK_DUE_STATES,
15181
+ WORK_INBOX_KINDS,
15182
+ WORK_INBOX_KIND_LABEL,
15146
15183
  WhoChangedSource,
15147
15184
  actorBadge,
15148
15185
  actorWords,