@ai-matrx/records-ui 0.62.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/CHANGELOG.md +133 -0
- package/dist/index.cjs +372 -357
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +110 -10
- package/dist/index.d.ts +110 -10
- package/dist/index.js +366 -345
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -276,6 +276,12 @@ function looksLikeId(value) {
|
|
|
276
276
|
function rowName(row, titleKey, fallback = "Untitled") {
|
|
277
277
|
return recordName(row?.document, titleKey, fallback);
|
|
278
278
|
}
|
|
279
|
+
function recordNameIn(table, document2, fallback = "Untitled") {
|
|
280
|
+
return recordName(document2, table?.title_field ?? null, fallback);
|
|
281
|
+
}
|
|
282
|
+
function rowNameIn(table, row, fallback = "Untitled") {
|
|
283
|
+
return recordName(row?.document, table?.title_field ?? null, fallback);
|
|
284
|
+
}
|
|
279
285
|
var WITHHELD_RECORD_LABEL = "A record you have not been given access to";
|
|
280
286
|
|
|
281
287
|
// src/ReferenceBuilder.tsx
|
|
@@ -518,7 +524,11 @@ import {
|
|
|
518
524
|
} from "@ai-matrx/design-system";
|
|
519
525
|
|
|
520
526
|
// src/parity.ts
|
|
521
|
-
import {
|
|
527
|
+
import {
|
|
528
|
+
KERNEL_TABLES,
|
|
529
|
+
PARITY_FIELD_TYPES as PARITY_FIELD_TYPES2,
|
|
530
|
+
fieldKindFor
|
|
531
|
+
} from "@ai-matrx/records";
|
|
522
532
|
|
|
523
533
|
// src/fieldTypes.ts
|
|
524
534
|
import { PARITY_FIELD_TYPES } from "@ai-matrx/records";
|
|
@@ -585,50 +595,9 @@ var PARITY_LABEL = {
|
|
|
585
595
|
var PARITY_MADE_OF = Object.fromEntries(
|
|
586
596
|
PARITY_FIELD_TYPES2.map((p) => [p.parity_type, p.made_of])
|
|
587
597
|
);
|
|
598
|
+
var KERNEL = { file: KERNEL_FILE_TABLE, person: KERNEL_PERSON_TABLE };
|
|
588
599
|
function editorKindFor(field) {
|
|
589
|
-
|
|
590
|
-
const config = field.config ?? {};
|
|
591
|
-
switch (field.type) {
|
|
592
|
-
// LIMITS-FIX 2026-09-21: the tick box is a BEHAVIOUR of its own, so its editor is
|
|
593
|
-
// decided by the behaviour alone — no format, no unit, no target to read. A record
|
|
594
|
-
// that never answered holds no key at all, and `CheckboxControl` draws that third
|
|
595
|
-
// state rather than pretending it is an unticked box.
|
|
596
|
-
case "boolean":
|
|
597
|
-
return "checkbox";
|
|
598
|
-
case "text":
|
|
599
|
-
if (format === "email") return "email";
|
|
600
|
-
if (format === "phone") return "phone";
|
|
601
|
-
if (format === "url") return "url";
|
|
602
|
-
if (format === "json" || config["json"] === true) return "json";
|
|
603
|
-
if (format === "long" || config["multiline"] === true) return "long_text";
|
|
604
|
-
return "text";
|
|
605
|
-
case "range": {
|
|
606
|
-
const kind = String(config["kind"] ?? "number");
|
|
607
|
-
if (kind === "date" || kind === "datetime") return "datetime";
|
|
608
|
-
if (format === "currency") return "currency";
|
|
609
|
-
if (format === "percent" || field.unit === "%") return "percent";
|
|
610
|
-
return "number";
|
|
611
|
-
}
|
|
612
|
-
case "list":
|
|
613
|
-
return field.multi ? "multi_select" : "select";
|
|
614
|
-
case "relation":
|
|
615
|
-
return relationEditor(field);
|
|
616
|
-
case "formula": {
|
|
617
|
-
const via = config["via"];
|
|
618
|
-
if (via && config["agg"]) return "rollup";
|
|
619
|
-
if (via) return "lookup";
|
|
620
|
-
return "formula";
|
|
621
|
-
}
|
|
622
|
-
default:
|
|
623
|
-
return "text";
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
function relationEditor(field) {
|
|
627
|
-
const parity = String(field.config?.["parity"] ?? "");
|
|
628
|
-
if (parity === "attachment" || parity === "member") return parity;
|
|
629
|
-
if (field.relation_target === KERNEL_FILE_TABLE) return "attachment";
|
|
630
|
-
if (field.relation_target === KERNEL_PERSON_TABLE) return "member";
|
|
631
|
-
return "relation";
|
|
600
|
+
return fieldKindFor(field, KERNEL);
|
|
632
601
|
}
|
|
633
602
|
function fieldTypeLabel(field) {
|
|
634
603
|
const kind = editorKindFor(field);
|
|
@@ -661,8 +630,7 @@ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
|
661
630
|
function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
662
631
|
const client = useRecordsClient();
|
|
663
632
|
const [rows, setRows] = useState2(null);
|
|
664
|
-
const [
|
|
665
|
-
const [elsewhere, setElsewhere] = useState2({});
|
|
633
|
+
const [words, setWords] = useState2({});
|
|
666
634
|
const [refusal, setRefusal] = useState2(null);
|
|
667
635
|
const [search, setSearch] = useState2("");
|
|
668
636
|
const [open, setOpen] = useState2(false);
|
|
@@ -679,47 +647,37 @@ function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
679
647
|
if (result.ok) setRows(result.data.rows);
|
|
680
648
|
else setRefusal(refusalLineForAPerson(result.error));
|
|
681
649
|
});
|
|
682
|
-
void client.tableList().then((result) => {
|
|
683
|
-
if (cancelled || !result.ok) return;
|
|
684
|
-
setTitleKey(result.data.find((table) => table.id === target)?.title_field ?? null);
|
|
685
|
-
});
|
|
686
650
|
return () => {
|
|
687
651
|
cancelled = true;
|
|
688
652
|
};
|
|
689
653
|
}, [client, field.relation_target]);
|
|
690
654
|
useEffect(() => {
|
|
691
655
|
if (!rows) return;
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
656
|
+
const wanted = [.../* @__PURE__ */ new Set([...rows.map((row) => row.id), ...picked])].filter(
|
|
657
|
+
(id2) => words[id2] === void 0
|
|
658
|
+
);
|
|
659
|
+
if (wanted.length === 0) return;
|
|
695
660
|
let cancelled = false;
|
|
696
|
-
void
|
|
697
|
-
missing.map(async (id2) => {
|
|
698
|
-
const result = await client.recordRead({ record_id: id2 });
|
|
699
|
-
return [id2, result.ok ? rowName({ document: result.data.document }, titleKey) : WITHHELD_RECORD_LABEL];
|
|
700
|
-
})
|
|
701
|
-
).then((pairs) => {
|
|
661
|
+
void client.relationWordsMany({ field_id: field.id, record_ids: wanted }).then((result) => {
|
|
702
662
|
if (cancelled) return;
|
|
703
|
-
|
|
663
|
+
const learned = {};
|
|
664
|
+
for (const id2 of wanted) {
|
|
665
|
+
const said = result.ok ? result.data[id2] : void 0;
|
|
666
|
+
learned[id2] = typeof said === "string" && said !== "" ? said : WITHHELD_RECORD_LABEL;
|
|
667
|
+
}
|
|
668
|
+
setWords((current) => ({ ...current, ...learned }));
|
|
704
669
|
});
|
|
705
670
|
return () => {
|
|
706
671
|
cancelled = true;
|
|
707
672
|
};
|
|
708
|
-
}, [client, rows, picked,
|
|
673
|
+
}, [client, field.id, rows, picked, words]);
|
|
709
674
|
if (!field.relation_target) {
|
|
710
675
|
return /* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground", children: [
|
|
711
676
|
fieldName(field),
|
|
712
677
|
" points at records, but it does not say which Table yet, so there is nothing to pick from. A table admin sets its target in the field editor."
|
|
713
678
|
] });
|
|
714
679
|
}
|
|
715
|
-
const
|
|
716
|
-
const titleOf = (recordId) => {
|
|
717
|
-
const row = byId.get(recordId);
|
|
718
|
-
if (row) return rowName(row, titleKey, "Untitled");
|
|
719
|
-
const away = elsewhere[recordId];
|
|
720
|
-
if (away !== void 0) return away;
|
|
721
|
-
return "Loading\u2026";
|
|
722
|
-
};
|
|
680
|
+
const titleOf = (recordId) => words[recordId] ?? "Loading\u2026";
|
|
723
681
|
const full = field.multi && field.relation_max !== null && picked.length >= field.relation_max;
|
|
724
682
|
const toggle = (recordId) => {
|
|
725
683
|
if (!field.multi) {
|
|
@@ -1466,28 +1424,12 @@ function RecordLabelProvider({ children }) {
|
|
|
1466
1424
|
stopWaitClock(field.id);
|
|
1467
1425
|
return;
|
|
1468
1426
|
}
|
|
1469
|
-
|
|
1470
|
-
if (!target) {
|
|
1427
|
+
if (!field.relation_target) {
|
|
1471
1428
|
setSettled((current) => ({ ...current, [field.id]: true }));
|
|
1472
1429
|
stopWaitClock(field.id);
|
|
1473
1430
|
return;
|
|
1474
1431
|
}
|
|
1475
|
-
const [rows, tables] = await Promise.all([
|
|
1476
|
-
client.list({ table_id: target, limit: 200 }),
|
|
1477
|
-
client.tableList()
|
|
1478
|
-
]);
|
|
1479
|
-
const titleKey = tables.ok ? tables.data.find((table) => table.id === target)?.title_field ?? null : null;
|
|
1480
|
-
if (rows.ok) {
|
|
1481
|
-
setNames((current) => {
|
|
1482
|
-
const next = { ...current };
|
|
1483
|
-
for (const row of rows.data.rows) {
|
|
1484
|
-
next[row.id] = recordName(row.document, titleKey, "Untitled");
|
|
1485
|
-
}
|
|
1486
|
-
return next;
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
1432
|
setSettled((current) => ({ ...current, [field.id]: true }));
|
|
1490
|
-
stopWaitClock(field.id);
|
|
1491
1433
|
})();
|
|
1492
1434
|
},
|
|
1493
1435
|
[client, startWaitClock, stopWaitClock]
|
|
@@ -1496,27 +1438,26 @@ function RecordLabelProvider({ children }) {
|
|
|
1496
1438
|
const flushing = useRef2(null);
|
|
1497
1439
|
const flush = useCallback3(() => {
|
|
1498
1440
|
flushing.current = null;
|
|
1499
|
-
const batch = [...queued.current.
|
|
1441
|
+
const batch = [...queued.current.values()];
|
|
1500
1442
|
queued.current.clear();
|
|
1501
1443
|
if (batch.length === 0) return;
|
|
1502
1444
|
void (async () => {
|
|
1503
1445
|
const learned = {};
|
|
1504
|
-
const LANES = 6;
|
|
1505
|
-
let at = 0;
|
|
1506
1446
|
await Promise.all(
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1447
|
+
batch.map(async ({ field, ids }) => {
|
|
1448
|
+
const wanted = [...ids];
|
|
1449
|
+
const result = await client.relationWordsMany({
|
|
1450
|
+
field_id: field.id,
|
|
1451
|
+
record_ids: wanted
|
|
1452
|
+
});
|
|
1453
|
+
for (const id of wanted) {
|
|
1454
|
+
const words = result.ok ? result.data[id] : void 0;
|
|
1455
|
+
learned[id] = typeof words === "string" && words !== "" ? words : WITHHELD_RECORD_LABEL;
|
|
1515
1456
|
}
|
|
1516
1457
|
})
|
|
1517
1458
|
);
|
|
1518
1459
|
setNames((current) => ({ ...current, ...learned }));
|
|
1519
|
-
for (const
|
|
1460
|
+
for (const { field } of batch) stopWaitClock(field.id);
|
|
1520
1461
|
})();
|
|
1521
1462
|
}, [client, stopWaitClock]);
|
|
1522
1463
|
const chase = useCallback3(
|
|
@@ -1524,7 +1465,9 @@ function RecordLabelProvider({ children }) {
|
|
|
1524
1465
|
const key = `${field.id}:${id}`;
|
|
1525
1466
|
if (chased.current.has(key)) return;
|
|
1526
1467
|
chased.current.add(key);
|
|
1527
|
-
queued.current.
|
|
1468
|
+
const pending = queued.current.get(field.id);
|
|
1469
|
+
if (pending) pending.ids.add(id);
|
|
1470
|
+
else queued.current.set(field.id, { field, ids: /* @__PURE__ */ new Set([id]) });
|
|
1528
1471
|
startWaitClock(field, null);
|
|
1529
1472
|
if (flushing.current === null) flushing.current = setTimeout(flush, 0);
|
|
1530
1473
|
},
|
|
@@ -1561,18 +1504,137 @@ function useLabelWait(field) {
|
|
|
1561
1504
|
return field ? waiting(field) : null;
|
|
1562
1505
|
}
|
|
1563
1506
|
|
|
1564
|
-
// src/
|
|
1565
|
-
|
|
1566
|
-
function
|
|
1567
|
-
|
|
1568
|
-
|
|
1507
|
+
// src/enumLabels.ts
|
|
1508
|
+
function exhaustiveValuesOf() {
|
|
1509
|
+
return function(values) {
|
|
1510
|
+
return values;
|
|
1511
|
+
};
|
|
1569
1512
|
}
|
|
1570
|
-
var
|
|
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 = {
|
|
1571
1625
|
"never asked": "never asked",
|
|
1572
1626
|
none: "none",
|
|
1573
1627
|
refused: "refused",
|
|
1574
1628
|
conflicting: "conflicting"
|
|
1575
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;
|
|
1576
1638
|
function renderValue(field, value, document2, labels, stillWaitingFor) {
|
|
1577
1639
|
const envelope = envelopeFor(document2, field.key);
|
|
1578
1640
|
if (envelope?.absent) {
|
|
@@ -3002,7 +3064,7 @@ function confirmLabel(plan, noun, nounPlural) {
|
|
|
3002
3064
|
|
|
3003
3065
|
// src/pasteValues.ts
|
|
3004
3066
|
import { predictValueRefusals } from "@ai-matrx/records/core";
|
|
3005
|
-
import { optionKey as optionKey2 } from "@ai-matrx/records";
|
|
3067
|
+
import { coerceTypedAnswer, optionKey as optionKey2 } from "@ai-matrx/records";
|
|
3006
3068
|
function choiceOptionsOf(options) {
|
|
3007
3069
|
const byWord = /* @__PURE__ */ new Map();
|
|
3008
3070
|
const labels = [];
|
|
@@ -3019,113 +3081,11 @@ function choiceOptionsOf(options) {
|
|
|
3019
3081
|
}
|
|
3020
3082
|
return { byWord, labels };
|
|
3021
3083
|
}
|
|
3022
|
-
var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
|
|
3023
|
-
var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
|
|
3024
3084
|
function valueFromPastedText(field, raw, options) {
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
switch (kind) {
|
|
3030
|
-
case "checkbox": {
|
|
3031
|
-
const word = text.toLowerCase();
|
|
3032
|
-
if (TRUE_WORDS.has(word)) return { value: true };
|
|
3033
|
-
if (FALSE_WORDS.has(word)) return { value: word === "" ? null : false };
|
|
3034
|
-
return {
|
|
3035
|
-
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.`
|
|
3036
|
-
};
|
|
3037
|
-
}
|
|
3038
|
-
case "number":
|
|
3039
|
-
case "currency":
|
|
3040
|
-
case "percent": {
|
|
3041
|
-
const negative = /^\(.*\)$/.test(text);
|
|
3042
|
-
const bare = text.replace(/^\(|\)$/g, "").replace(/[\s,]/g, "").replace(/^[^\d.\-+]+/, "").replace(/[^\d.\-+eE]+$/, "");
|
|
3043
|
-
const n = Number(bare);
|
|
3044
|
-
if (bare === "" || Number.isNaN(n)) {
|
|
3045
|
-
return {
|
|
3046
|
-
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.`
|
|
3047
|
-
};
|
|
3048
|
-
}
|
|
3049
|
-
return { value: negative ? -n : n };
|
|
3050
|
-
}
|
|
3051
|
-
case "datetime": {
|
|
3052
|
-
const wantsTime = String(field.config?.["kind"] ?? "date") === "datetime";
|
|
3053
|
-
const plainDay = /^\s*(\d{4})-(\d{2})-(\d{2})\s*$/.exec(raw);
|
|
3054
|
-
if (!wantsTime && plainDay) return { value: `${plainDay[1]}-${plainDay[2]}-${plainDay[3]}` };
|
|
3055
|
-
const at = Date.parse(text);
|
|
3056
|
-
if (Number.isNaN(at)) {
|
|
3057
|
-
return {
|
|
3058
|
-
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.`
|
|
3059
|
-
};
|
|
3060
|
-
}
|
|
3061
|
-
const parsed = new Date(at);
|
|
3062
|
-
if (wantsTime) return { value: parsed.toISOString() };
|
|
3063
|
-
const y = parsed.getFullYear();
|
|
3064
|
-
const m = String(parsed.getMonth() + 1).padStart(2, "0");
|
|
3065
|
-
const d = String(parsed.getDate()).padStart(2, "0");
|
|
3066
|
-
return { value: `${y}-${m}-${d}` };
|
|
3067
|
-
}
|
|
3068
|
-
case "json": {
|
|
3069
|
-
try {
|
|
3070
|
-
return { value: JSON.parse(text) };
|
|
3071
|
-
} catch {
|
|
3072
|
-
return {
|
|
3073
|
-
refusal: `${label} holds a document, and \u201C${raw}\u201D is not valid JSON. Paste the whole object, braces and all.`
|
|
3074
|
-
};
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
// A LIST, A PERSON, A FILE OR ANOTHER RECORD IS AN IDENTITY, NOT A WORD.
|
|
3078
|
-
// The store keeps the id of the option or the record, and a word off a
|
|
3079
|
-
// clipboard is not that id — writing the word would put a string where an
|
|
3080
|
-
// id belongs and every filter, colour and rollup built on that column would
|
|
3081
|
-
// stop agreeing with it. So the cell is left alone and the person is told
|
|
3082
|
-
// which control does hold it.
|
|
3083
|
-
case "select":
|
|
3084
|
-
case "multi_select": {
|
|
3085
|
-
if (!options) {
|
|
3086
|
-
return {
|
|
3087
|
-
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.`
|
|
3088
|
-
};
|
|
3089
|
-
}
|
|
3090
|
-
const wanted = kind === "multi_select" ? text.split(/[,;]/).map((w) => w.trim()).filter((w) => w.length > 0) : [text];
|
|
3091
|
-
if (wanted.length === 0) return { value: kind === "multi_select" ? [] : null };
|
|
3092
|
-
const matched = [];
|
|
3093
|
-
const unmatched = [];
|
|
3094
|
-
for (const word of wanted) {
|
|
3095
|
-
const hit = options.byWord.get(word.toLowerCase());
|
|
3096
|
-
if (hit === void 0) unmatched.push(word);
|
|
3097
|
-
else matched.push(hit);
|
|
3098
|
-
}
|
|
3099
|
-
if (unmatched.length > 0) {
|
|
3100
|
-
const offered = options.labels.length > 0 ? options.labels.join(", ") : "it has none yet";
|
|
3101
|
-
return {
|
|
3102
|
-
refusal: `${label} does not have ${unmatched.map((w) => `\u201C${w}\u201D`).join(" or ")} among its options. It offers: ${offered}.`,
|
|
3103
|
-
unmatched
|
|
3104
|
-
};
|
|
3105
|
-
}
|
|
3106
|
-
return { value: kind === "multi_select" ? matched : matched[0] };
|
|
3107
|
-
}
|
|
3108
|
-
case "member":
|
|
3109
|
-
return {
|
|
3110
|
-
refusal: `${label} names a person in this organization, so it is chosen rather than typed. Open the cell and pick them.`
|
|
3111
|
-
};
|
|
3112
|
-
case "attachment":
|
|
3113
|
-
return {
|
|
3114
|
-
refusal: `${label} holds a file, which cannot arrive on a clipboard. Open the cell and attach it.`
|
|
3115
|
-
};
|
|
3116
|
-
case "relation":
|
|
3117
|
-
return {
|
|
3118
|
-
refusal: `${label} points at another record, so it is chosen rather than typed. Open the cell and pick the record.`
|
|
3119
|
-
};
|
|
3120
|
-
case "formula":
|
|
3121
|
-
case "lookup":
|
|
3122
|
-
case "rollup":
|
|
3123
|
-
return {
|
|
3124
|
-
refusal: `${label} is worked out by the system, so nothing can be pasted into it.`
|
|
3125
|
-
};
|
|
3126
|
-
default:
|
|
3127
|
-
return { value: raw };
|
|
3128
|
-
}
|
|
3085
|
+
return coerceTypedAnswer(field, raw, {
|
|
3086
|
+
label: fieldName(field),
|
|
3087
|
+
...options ? { options } : {}
|
|
3088
|
+
});
|
|
3129
3089
|
}
|
|
3130
3090
|
function planPastedBlock(args) {
|
|
3131
3091
|
const byKey = new Map(args.fields.map((f) => [f.key, f]));
|
|
@@ -3670,9 +3630,11 @@ function Grid({
|
|
|
3670
3630
|
onClick: () => host.talkToRecord?.({
|
|
3671
3631
|
tableId,
|
|
3672
3632
|
recordId: row.id,
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3633
|
+
// ONE NAMING CALL (`names.ts`), not this file's own join
|
|
3634
|
+
// of a Table's title column to a document.
|
|
3635
|
+
title: recordNameIn(
|
|
3636
|
+
table.data,
|
|
3637
|
+
row.document ?? {}
|
|
3676
3638
|
)
|
|
3677
3639
|
}),
|
|
3678
3640
|
children: "Talk"
|
|
@@ -4300,19 +4262,23 @@ import {
|
|
|
4300
4262
|
} from "@ai-matrx/records/react";
|
|
4301
4263
|
import { Button as Button11, Separator as Separator3, cn as cn12 } from "@ai-matrx/design-system";
|
|
4302
4264
|
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4303
|
-
var
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
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"
|
|
4315
4277
|
];
|
|
4278
|
+
var ADDABLE_TYPES = ADDABLE_TYPE_IDS.map((value) => ({
|
|
4279
|
+
value,
|
|
4280
|
+
label: fieldTypeChoice(value)?.label ?? value
|
|
4281
|
+
}));
|
|
4316
4282
|
function asField(f) {
|
|
4317
4283
|
return {
|
|
4318
4284
|
id: f.id,
|
|
@@ -4491,41 +4457,20 @@ import {
|
|
|
4491
4457
|
Separator as Separator4,
|
|
4492
4458
|
cn as cn13
|
|
4493
4459
|
} from "@ai-matrx/design-system";
|
|
4460
|
+
import { FIELD_SENSITIVITIES, CONTEXT_POLICIES } from "@ai-matrx/records";
|
|
4494
4461
|
import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4495
|
-
var RULE_KINDS = [
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
{ value: "restricted", label: "Owners and administrators only" }
|
|
4508
|
-
];
|
|
4509
|
-
var AGENT_VISIBILITY = [
|
|
4510
|
-
{ value: "include", label: "Yes, an assistant can read it" },
|
|
4511
|
-
{ value: "summarize", label: "Only a summary of it" },
|
|
4512
|
-
{ value: "on_request", label: "Only when somebody asks for it" },
|
|
4513
|
-
{ value: "exclude", label: "No, keep it away from assistants" }
|
|
4514
|
-
];
|
|
4515
|
-
var ROLLUP_OPERATIONS = [
|
|
4516
|
-
{ value: "count", label: "How many there are" },
|
|
4517
|
-
{ value: "sum", label: "The total of" },
|
|
4518
|
-
{ value: "avg", label: "The average of" },
|
|
4519
|
-
{ value: "min", label: "The smallest" },
|
|
4520
|
-
{ value: "max", label: "The largest" }
|
|
4521
|
-
];
|
|
4522
|
-
var FORMULA_OPERATIONS = [
|
|
4523
|
-
{ value: "concat", label: "Join them together", joins: true },
|
|
4524
|
-
{ value: "add", label: "Add them up", joins: true },
|
|
4525
|
-
{ value: "sub", label: "Take the second from the first", joins: false },
|
|
4526
|
-
{ value: "mul", label: "Multiply them", joins: true },
|
|
4527
|
-
{ value: "div", label: "Divide the first by the second", joins: false }
|
|
4528
|
-
];
|
|
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
|
+
}));
|
|
4529
4474
|
function keyFor(label) {
|
|
4530
4475
|
const token = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
|
|
4531
4476
|
if (token === "") return "";
|
|
@@ -5090,6 +5035,9 @@ var CONDITION_OPS = [
|
|
|
5090
5035
|
{ op: "gt", label: "is more than" },
|
|
5091
5036
|
{ op: "lt", label: "is less than" }
|
|
5092
5037
|
];
|
|
5038
|
+
function pointsAtARecord(f) {
|
|
5039
|
+
return !!f?.field && f.field.type === "relation" && !!f.field.relation_target;
|
|
5040
|
+
}
|
|
5093
5041
|
function conditionIsSimple(expr) {
|
|
5094
5042
|
if (expr === null || expr === void 0) return true;
|
|
5095
5043
|
const node = expr;
|
|
@@ -5160,6 +5108,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
5160
5108
|
const op = node?.op ?? "";
|
|
5161
5109
|
const onField = node?.args?.[0]?.field ?? "";
|
|
5162
5110
|
const value = node?.args?.[1]?.const ?? "";
|
|
5111
|
+
const chosen = fields.find((f) => f.id === onField);
|
|
5163
5112
|
function write(next) {
|
|
5164
5113
|
const chosenOp = next.op ?? op;
|
|
5165
5114
|
const chosenField = next.field ?? onField;
|
|
@@ -5198,7 +5147,24 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
5198
5147
|
children: CONDITION_OPS.map((c) => /* @__PURE__ */ jsx18("option", { value: c.op, children: c.label }, c.op))
|
|
5199
5148
|
}
|
|
5200
5149
|
),
|
|
5201
|
-
op
|
|
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(
|
|
5202
5168
|
BasicInput6,
|
|
5203
5169
|
{
|
|
5204
5170
|
className: "h-8 w-28 text-xs",
|
|
@@ -5206,7 +5172,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
5206
5172
|
value: String(value ?? ""),
|
|
5207
5173
|
onChange: (e) => write({ value: conditionValue(e.target.value) })
|
|
5208
5174
|
}
|
|
5209
|
-
)
|
|
5175
|
+
)
|
|
5210
5176
|
] }) : null
|
|
5211
5177
|
] });
|
|
5212
5178
|
}
|
|
@@ -5366,10 +5332,10 @@ var NO_RULES_YET = "Nothing has to be true to get here yet.";
|
|
|
5366
5332
|
var NEEDS_A_SENTENCE = "Write the sentence somebody reads when this stops them, the way you would say it out loud.";
|
|
5367
5333
|
var NOT_DRAWN_HERE = "This part of the rule is not something this builder can draw yet";
|
|
5368
5334
|
var ON_FAIL = [
|
|
5369
|
-
{ value: "refuse", label:
|
|
5335
|
+
{ value: "refuse", label: STAGE_RULE_ON_FAIL_LABEL.refuse, note: "The card stays where it is and the person is told why." },
|
|
5370
5336
|
{
|
|
5371
5337
|
value: "require_approval",
|
|
5372
|
-
label:
|
|
5338
|
+
label: STAGE_RULE_ON_FAIL_LABEL.require_approval,
|
|
5373
5339
|
note: "The card stays where it is, and the people who can approve it are asked. Nobody is turned away."
|
|
5374
5340
|
}
|
|
5375
5341
|
];
|
|
@@ -5420,7 +5386,7 @@ function StageRulesSection({ tableId, stage, className }) {
|
|
|
5420
5386
|
const stageLabel = stages.find((s) => s.key === stageKey)?.label ?? stageKey ?? "";
|
|
5421
5387
|
const gates = useMemo13(() => stageKey ? gatesOf(pipeline, stageKey) : [], [pipeline, stageKey]);
|
|
5422
5388
|
const conditionFields = useMemo13(
|
|
5423
|
-
() => (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 })),
|
|
5424
5390
|
[fields.data]
|
|
5425
5391
|
);
|
|
5426
5392
|
useEffect8(() => {
|
|
@@ -6361,7 +6327,7 @@ function Peek({ tableId, recordId, onClose, className }) {
|
|
|
6361
6327
|
if (record.error) return /* @__PURE__ */ jsx24(RefusalNotice, { error: record.error, className });
|
|
6362
6328
|
if (fields.error) return /* @__PURE__ */ jsx24(RefusalNotice, { error: fields.error, className });
|
|
6363
6329
|
const document2 = record.data?.document;
|
|
6364
|
-
const title = document2 ?
|
|
6330
|
+
const title = document2 ? recordNameIn(table.data, document2) : "";
|
|
6365
6331
|
const href = host.hrefForRecord && table.data ? host.hrefForRecord({ table: table.data, recordId }) : void 0;
|
|
6366
6332
|
return /* @__PURE__ */ jsxs20("div", { className: cn18("flex min-w-0 flex-col gap-2 text-xs", className), children: [
|
|
6367
6333
|
/* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
|
|
@@ -7121,7 +7087,7 @@ function offerableFields(all, want) {
|
|
|
7121
7087
|
return all.filter((field) => {
|
|
7122
7088
|
const kind = editorKindFor(field);
|
|
7123
7089
|
if (want === "choice") return kind === "select" || kind === "multi_select";
|
|
7124
|
-
return kind === "datetime"
|
|
7090
|
+
return kind === "datetime";
|
|
7125
7091
|
});
|
|
7126
7092
|
}
|
|
7127
7093
|
function useStageField(tableId) {
|
|
@@ -7539,11 +7505,7 @@ import { useState as useState26 } from "react";
|
|
|
7539
7505
|
import { useRecordsClient as useRecordsClient18 } from "@ai-matrx/records/react";
|
|
7540
7506
|
import { Badge as Badge7, Button as Button22, cn as cn22 } from "@ai-matrx/design-system";
|
|
7541
7507
|
import { jsx as jsx28, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
7542
|
-
var ACT_WORD =
|
|
7543
|
-
add: "Add",
|
|
7544
|
-
update: "Update",
|
|
7545
|
-
remove: "Remove"
|
|
7546
|
-
};
|
|
7508
|
+
var ACT_WORD = PROPOSED_CHANGE_ACT_LABEL;
|
|
7547
7509
|
function ProposalRow({
|
|
7548
7510
|
change,
|
|
7549
7511
|
outcome,
|
|
@@ -7650,21 +7612,8 @@ import { useCallback as useCallback12, useEffect as useEffect15, useMemo as useM
|
|
|
7650
7612
|
import { useRecordsClient as useRecordsClient19, useTable as useTable9 } from "@ai-matrx/records/react";
|
|
7651
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";
|
|
7652
7614
|
import { Fragment as Fragment13, jsx as jsx29, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
7653
|
-
var DUE_WORD =
|
|
7654
|
-
|
|
7655
|
-
due_today: "Today",
|
|
7656
|
-
scheduled: "Scheduled",
|
|
7657
|
-
undated: "No date",
|
|
7658
|
-
finished: "Done"
|
|
7659
|
-
};
|
|
7660
|
-
var REQUIRES_WORD = {
|
|
7661
|
-
none: "",
|
|
7662
|
-
note: "Say what you did",
|
|
7663
|
-
answer: "Fill this in",
|
|
7664
|
-
record_field: "This has to be filled in on the record",
|
|
7665
|
-
form: "This step is a form",
|
|
7666
|
-
document: "This step is a document"
|
|
7667
|
-
};
|
|
7615
|
+
var DUE_WORD = DUE_STATE_LABEL;
|
|
7616
|
+
var REQUIRES_WORD = CHECKLIST_REQUIREMENT_LABEL;
|
|
7668
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.";
|
|
7669
7618
|
function ChecklistRunner({
|
|
7670
7619
|
tableId,
|
|
@@ -8372,18 +8321,8 @@ function roleKey(said) {
|
|
|
8372
8321
|
// src/ActionInbox.tsx
|
|
8373
8322
|
import { jsx as jsx30, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
8374
8323
|
var ACTION_KINDS = ["approval", "assignment", "proposal"];
|
|
8375
|
-
var KIND_WORD =
|
|
8376
|
-
|
|
8377
|
-
approval: "Approve",
|
|
8378
|
-
proposal: "Agent"
|
|
8379
|
-
};
|
|
8380
|
-
var DUE_WORD2 = {
|
|
8381
|
-
overdue: "Overdue",
|
|
8382
|
-
due_today: "Today",
|
|
8383
|
-
scheduled: "Scheduled",
|
|
8384
|
-
undated: "No date",
|
|
8385
|
-
finished: "Finished"
|
|
8386
|
-
};
|
|
8324
|
+
var KIND_WORD = WORK_INBOX_KIND_LABEL;
|
|
8325
|
+
var DUE_WORD2 = DUE_STATE_LABEL;
|
|
8387
8326
|
function ActionInbox({ tableId, includeSettled = false, onOpenRecord, className }) {
|
|
8388
8327
|
const client = useRecordsClient20();
|
|
8389
8328
|
const [items, setItems] = useState28(null);
|
|
@@ -9635,18 +9574,18 @@ function BuildOrAsk({
|
|
|
9635
9574
|
buildLabel,
|
|
9636
9575
|
className
|
|
9637
9576
|
}) {
|
|
9577
|
+
const mayAsk = mayBuild && Boolean(onAsk);
|
|
9638
9578
|
return /* @__PURE__ */ jsxs31("div", { className: cn29("rounded-md border border-dashed p-3", className), children: [
|
|
9639
9579
|
/* @__PURE__ */ jsx35("p", { className: "text-xs text-muted-foreground", children }),
|
|
9640
|
-
/* @__PURE__ */ jsxs31("div", { className: "mt-2.5 flex flex-wrap items-center gap-2", children: [
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
] }),
|
|
9644
|
-
|
|
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: [
|
|
9645
9585
|
"You would say something like: \u201C",
|
|
9646
9586
|
suggestion,
|
|
9647
9587
|
"\u201D"
|
|
9648
|
-
] }) : /* @__PURE__ */ jsx35("p", { className: "mt-2 text-xs text-muted-foreground", children: NO_AGENT_PORT })
|
|
9649
|
-
!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 })
|
|
9650
9589
|
] });
|
|
9651
9590
|
}
|
|
9652
9591
|
|
|
@@ -9685,30 +9624,41 @@ function PortalsPanel({ tableId, className }) {
|
|
|
9685
9624
|
const client = useRecordsClient25();
|
|
9686
9625
|
const host = useRecordsUi();
|
|
9687
9626
|
const [portals, setPortals] = useState33(null);
|
|
9627
|
+
const [exposures, setExposures] = useState33([]);
|
|
9688
9628
|
const [listError, setListError] = useState33(null);
|
|
9689
9629
|
const [openId, setOpenId] = useState33(null);
|
|
9690
9630
|
const [building, setBuilding] = useState33(false);
|
|
9631
|
+
const [adding, setAdding] = useState33(null);
|
|
9691
9632
|
const load = useCallback18(async () => {
|
|
9692
|
-
const answered = await client.portals();
|
|
9633
|
+
const [answered, mapped] = await Promise.all([client.portals(), client.portalTables()]);
|
|
9693
9634
|
if (!answered.ok) {
|
|
9694
9635
|
setListError(answered.error);
|
|
9695
9636
|
setPortals([]);
|
|
9637
|
+
setExposures([]);
|
|
9696
9638
|
return;
|
|
9697
9639
|
}
|
|
9698
9640
|
setListError(null);
|
|
9699
9641
|
setPortals(answered.data);
|
|
9642
|
+
setExposures(mapped.ok ? mapped.data : []);
|
|
9700
9643
|
}, [client]);
|
|
9701
9644
|
useEffect21(() => {
|
|
9702
9645
|
void load();
|
|
9703
9646
|
}, [load]);
|
|
9704
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;
|
|
9705
9655
|
const origin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
|
|
9706
9656
|
return /* @__PURE__ */ jsxs32("section", { className: cn30("flex flex-col gap-3", className), children: [
|
|
9707
9657
|
/* @__PURE__ */ jsxs32("header", { className: "flex items-center gap-2", children: [
|
|
9708
9658
|
/* @__PURE__ */ jsx36("h3", { className: "text-sm font-medium", children: "Portals" }),
|
|
9709
|
-
/* @__PURE__ */ jsx36("span", { className: "text-xs text-muted-foreground", children:
|
|
9659
|
+
/* @__PURE__ */ jsx36("span", { className: "text-xs text-muted-foreground", children: shown.length === 0 ? "none for this table" : `${shown.length}` }),
|
|
9710
9660
|
/* @__PURE__ */ jsx36("div", { className: "flex-1" }),
|
|
9711
|
-
|
|
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
|
|
9712
9662
|
] }),
|
|
9713
9663
|
listError ? /* @__PURE__ */ jsx36(RefusalNotice, { error: listError }) : null,
|
|
9714
9664
|
building ? /* @__PURE__ */ jsx36(
|
|
@@ -9721,7 +9671,19 @@ function PortalsPanel({ tableId, className }) {
|
|
|
9721
9671
|
onClose: () => setBuilding(false)
|
|
9722
9672
|
}
|
|
9723
9673
|
) : null,
|
|
9724
|
-
|
|
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(
|
|
9725
9687
|
BuildOrAsk,
|
|
9726
9688
|
{
|
|
9727
9689
|
mayBuild: true,
|
|
@@ -9730,11 +9692,37 @@ function PortalsPanel({ tableId, className }) {
|
|
|
9730
9692
|
onAsk: () => host.onAskForOne?.({ kind: "portal", tableId, suggestion: PORTAL_SUGGESTION })
|
|
9731
9693
|
} : {},
|
|
9732
9694
|
suggestion: PORTAL_SUGGESTION,
|
|
9733
|
-
buildLabel: "Build the portal",
|
|
9734
|
-
children:
|
|
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
|
+
]
|
|
9735
9706
|
}
|
|
9736
9707
|
) : null,
|
|
9737
|
-
/* @__PURE__ */
|
|
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) => {
|
|
9738
9726
|
const url = `${origin}${portalPath(portal.slug)}`;
|
|
9739
9727
|
const open = openId === portal.portal_id;
|
|
9740
9728
|
return /* @__PURE__ */ jsxs32("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
|
|
@@ -10049,7 +10037,7 @@ function Preview({
|
|
|
10049
10037
|
function Invite({ card, onInvited }) {
|
|
10050
10038
|
const client = useRecordsClient25();
|
|
10051
10039
|
const [rows, setRows] = useState33(null);
|
|
10052
|
-
const [
|
|
10040
|
+
const [clientTable, setClientTable] = useState33(null);
|
|
10053
10041
|
const [search, setSearch] = useState33("");
|
|
10054
10042
|
const [picked, setPicked] = useState33(null);
|
|
10055
10043
|
const [email, setEmail] = useState33("");
|
|
@@ -10068,17 +10056,17 @@ function Invite({ card, onInvited }) {
|
|
|
10068
10056
|
});
|
|
10069
10057
|
void client.tableList().then((answered) => {
|
|
10070
10058
|
if (cancelled || !answered.ok) return;
|
|
10071
|
-
|
|
10059
|
+
setClientTable(answered.data.find((t) => t.id === card.client_table_id) ?? null);
|
|
10072
10060
|
});
|
|
10073
10061
|
return () => {
|
|
10074
10062
|
cancelled = true;
|
|
10075
10063
|
};
|
|
10076
10064
|
}, [client, card.client_table_id]);
|
|
10077
10065
|
const options = useMemo23(() => {
|
|
10078
|
-
const all = (rows ?? []).map((row) => ({ id: row.id, name:
|
|
10066
|
+
const all = (rows ?? []).map((row) => ({ id: row.id, name: rowNameIn(clientTable, row) }));
|
|
10079
10067
|
const needle = search.trim().toLowerCase();
|
|
10080
10068
|
return needle === "" ? all.slice(0, 25) : all.filter((o) => o.name.toLowerCase().includes(needle)).slice(0, 25);
|
|
10081
|
-
}, [rows,
|
|
10069
|
+
}, [rows, clientTable, search]);
|
|
10082
10070
|
const send = useCallback18(async () => {
|
|
10083
10071
|
if (!picked) return;
|
|
10084
10072
|
setBusy(true);
|
|
@@ -10461,11 +10449,7 @@ function whenItFires(subscription) {
|
|
|
10461
10449
|
const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
|
|
10462
10450
|
return subscription.schedule ? `${every}, ${subscription.schedule}` : every;
|
|
10463
10451
|
}
|
|
10464
|
-
var CHANNEL_WORDS2 =
|
|
10465
|
-
in_app: "in the app",
|
|
10466
|
-
email: "by email \u2014 only if an address is on the account",
|
|
10467
|
-
sms: "by text \u2014 only if a number is on the account"
|
|
10468
|
-
};
|
|
10452
|
+
var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
|
|
10469
10453
|
var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
|
|
10470
10454
|
function SubscriptionsPanel({ tableId, className }) {
|
|
10471
10455
|
const client = useRecordsClient27();
|
|
@@ -11241,7 +11225,12 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
|
|
|
11241
11225
|
Condition,
|
|
11242
11226
|
{
|
|
11243
11227
|
question,
|
|
11244
|
-
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
|
+
})),
|
|
11245
11234
|
onChange: (showIf) => patchQuestion(index, { showIf })
|
|
11246
11235
|
}
|
|
11247
11236
|
)
|
|
@@ -11979,17 +11968,8 @@ import { useCallback as useCallback26, useEffect as useEffect30, useState as use
|
|
|
11979
11968
|
import { useRecordsClient as useRecordsClient32, useTable as useTable16 } from "@ai-matrx/records/react";
|
|
11980
11969
|
import { Button as Button38, Skeleton as Skeleton23, cn as cn39 } from "@ai-matrx/design-system";
|
|
11981
11970
|
import { jsx as jsx45, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
11982
|
-
var CADENCE_WORDS2 =
|
|
11983
|
-
|
|
11984
|
-
hourly: "hourly summary",
|
|
11985
|
-
daily: "daily summary",
|
|
11986
|
-
weekly: "weekly summary"
|
|
11987
|
-
};
|
|
11988
|
-
var CHANNEL_WORDS3 = {
|
|
11989
|
-
in_app: "in the app",
|
|
11990
|
-
email: "by email",
|
|
11991
|
-
sms: "by text"
|
|
11992
|
-
};
|
|
11971
|
+
var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
|
|
11972
|
+
var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
|
|
11993
11973
|
function NotifyRuleEditor({ tableId, seed, className }) {
|
|
11994
11974
|
const client = useRecordsClient32();
|
|
11995
11975
|
const host = useRecordsUi();
|
|
@@ -13266,12 +13246,7 @@ function bookingSuggestion(tableName2) {
|
|
|
13266
13246
|
return `Let clients book a 30-minute ${subject} slot on Tuesday and Thursday afternoons.`;
|
|
13267
13247
|
}
|
|
13268
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.";
|
|
13269
|
-
var STATE_WORDS =
|
|
13270
|
-
draft: "Not open yet \u2014 nobody can book until you open it.",
|
|
13271
|
-
open: "Open. Anyone with the link can book a time.",
|
|
13272
|
-
closed: "Closed. The link still works and says you are not taking bookings.",
|
|
13273
|
-
full: "Every time you offered is taken."
|
|
13274
|
-
};
|
|
13249
|
+
var STATE_WORDS = BOOKING_PAGE_STATE_LABEL;
|
|
13275
13250
|
function BookingSlots({ tableId, className }) {
|
|
13276
13251
|
const client = useRecordsClient36();
|
|
13277
13252
|
const host = useRecordsUi();
|
|
@@ -13295,10 +13270,21 @@ function BookingSlots({ tableId, className }) {
|
|
|
13295
13270
|
void load();
|
|
13296
13271
|
}, [load]);
|
|
13297
13272
|
const subjectIds = useMemo30(
|
|
13298
|
-
() => Array.from(
|
|
13299
|
-
|
|
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]
|
|
13300
13284
|
);
|
|
13301
13285
|
const levels = useMyLevels2(subjectIds);
|
|
13286
|
+
const levelsKnown = !levels.loading && Boolean(levels.data);
|
|
13287
|
+
const mayBuildHere = levelsKnown && Boolean(tableId) && levels.data?.[tableId] === "admin";
|
|
13302
13288
|
const toggle = useCallback30(
|
|
13303
13289
|
async (page) => {
|
|
13304
13290
|
setBusy(page.form_id);
|
|
@@ -13347,7 +13333,8 @@ function BookingSlots({ tableId, className }) {
|
|
|
13347
13333
|
pages.length === 0 && !building ? tableId ? /* @__PURE__ */ jsx50(
|
|
13348
13334
|
BuildOrAsk,
|
|
13349
13335
|
{
|
|
13350
|
-
mayBuild:
|
|
13336
|
+
mayBuild: mayBuildHere,
|
|
13337
|
+
whyNot: mayBuildHere ? null : levelsKnown ? NO_ADMIN2 : null,
|
|
13351
13338
|
onBuild: () => setBuilding(true),
|
|
13352
13339
|
...host.onAskForOne ? {
|
|
13353
13340
|
onAsk: () => host.onAskForOne?.({
|
|
@@ -13455,6 +13442,8 @@ import { Button as Button44, Input as Input4, Skeleton as Skeleton29, Textarea a
|
|
|
13455
13442
|
// src/CaptureRun.tsx
|
|
13456
13443
|
import { useCallback as useCallback31, useEffect as useEffect35, useMemo as useMemo31, useRef as useRef13, useState as useState47 } from "react";
|
|
13457
13444
|
import {
|
|
13445
|
+
coerceTypedAnswer as coerceTypedAnswer2,
|
|
13446
|
+
coercionLabel,
|
|
13458
13447
|
openCaptureQueue
|
|
13459
13448
|
} from "@ai-matrx/records";
|
|
13460
13449
|
import { useRecordsClient as useRecordsClient37 } from "@ai-matrx/records/react";
|
|
@@ -13760,7 +13749,11 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
13760
13749
|
value: String(answers[q.field] ?? ""),
|
|
13761
13750
|
onChange: (e) => {
|
|
13762
13751
|
const raw = e.target.value;
|
|
13763
|
-
|
|
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
|
+
}));
|
|
13764
13757
|
setMissing(null);
|
|
13765
13758
|
}
|
|
13766
13759
|
}
|
|
@@ -15034,8 +15027,12 @@ function TablePage({
|
|
|
15034
15027
|
] });
|
|
15035
15028
|
}
|
|
15036
15029
|
export {
|
|
15030
|
+
ABSENCE_WORD_LABEL,
|
|
15031
|
+
ABSENCE_WORD_VALUES,
|
|
15037
15032
|
ACTION_KINDS,
|
|
15038
15033
|
ActionInbox,
|
|
15034
|
+
BOOKING_PAGE_STATE_LABEL,
|
|
15035
|
+
BOOKING_PAGE_STATE_VALUES,
|
|
15039
15036
|
BookingBuilder,
|
|
15040
15037
|
BookingSlots,
|
|
15041
15038
|
BuildOrAsk,
|
|
@@ -15043,7 +15040,10 @@ export {
|
|
|
15043
15040
|
CHART_KINDS,
|
|
15044
15041
|
CHART_KIND_LABEL,
|
|
15045
15042
|
CHART_NEEDS,
|
|
15043
|
+
CHECKLIST_REQUIREMENT_KINDS,
|
|
15044
|
+
CHECKLIST_REQUIREMENT_LABEL,
|
|
15046
15045
|
CONDITION_OPS,
|
|
15046
|
+
CONTEXT_POLICY_LABEL,
|
|
15047
15047
|
CaptureRun,
|
|
15048
15048
|
CaptureSheet,
|
|
15049
15049
|
ChartBlock,
|
|
@@ -15059,6 +15059,7 @@ export {
|
|
|
15059
15059
|
CustomFieldsSection,
|
|
15060
15060
|
DEFAULT_FIELDS,
|
|
15061
15061
|
DEFAULT_VIEW_NAME,
|
|
15062
|
+
DUE_STATE_LABEL,
|
|
15062
15063
|
DashboardCanvas,
|
|
15063
15064
|
DigestScheduler,
|
|
15064
15065
|
DocRender,
|
|
@@ -15068,8 +15069,14 @@ export {
|
|
|
15068
15069
|
EnrichBadge,
|
|
15069
15070
|
EnrichPanel,
|
|
15070
15071
|
ExportMenu,
|
|
15072
|
+
FIELD_RULE_KIND_LABEL,
|
|
15073
|
+
FIELD_RULE_KIND_VALUES,
|
|
15074
|
+
FIELD_SENSITIVITY_LABEL,
|
|
15071
15075
|
FIELD_TYPE_CHOICES,
|
|
15072
15076
|
FIELD_TYPE_GROUPS,
|
|
15077
|
+
FORMULA_OP_JOINS,
|
|
15078
|
+
FORMULA_OP_LABEL,
|
|
15079
|
+
FORMULA_OP_VALUES,
|
|
15073
15080
|
FORM_FLOWS,
|
|
15074
15081
|
FROZEN_COLUMN_WIDTH,
|
|
15075
15082
|
FieldControl,
|
|
@@ -15117,6 +15124,8 @@ export {
|
|
|
15117
15124
|
PAGE_VIEW_LABEL,
|
|
15118
15125
|
PARITY_LABEL,
|
|
15119
15126
|
PARITY_MADE_OF,
|
|
15127
|
+
PROPOSED_CHANGE_ACT_LABEL,
|
|
15128
|
+
PROPOSED_CHANGE_ACT_VALUES,
|
|
15120
15129
|
Peek,
|
|
15121
15130
|
PersonPicker,
|
|
15122
15131
|
PipelineBoard,
|
|
@@ -15127,6 +15136,8 @@ export {
|
|
|
15127
15136
|
ProposalRow,
|
|
15128
15137
|
ProvenanceBadge,
|
|
15129
15138
|
PublicViewPage,
|
|
15139
|
+
ROLLUP_AGG_LABEL,
|
|
15140
|
+
ROLLUP_AGG_VALUES,
|
|
15130
15141
|
RecordChat,
|
|
15131
15142
|
RecordChip,
|
|
15132
15143
|
RecordForm,
|
|
@@ -15142,8 +15153,13 @@ export {
|
|
|
15142
15153
|
SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
|
|
15143
15154
|
SERIES_COLORS,
|
|
15144
15155
|
SOMEBODY,
|
|
15156
|
+
STAGE_RULE_ON_FAIL_LABEL,
|
|
15145
15157
|
STORE_ANSWERS_THESE,
|
|
15146
15158
|
STORE_DECIDES_REASON,
|
|
15159
|
+
SUBSCRIPTION_CADENCES,
|
|
15160
|
+
SUBSCRIPTION_CADENCE_LABEL,
|
|
15161
|
+
SUBSCRIPTION_CHANNEL_LABEL,
|
|
15162
|
+
SUBSCRIPTION_CHANNEL_VALUES,
|
|
15147
15163
|
ShareControl,
|
|
15148
15164
|
SignBlock,
|
|
15149
15165
|
StageRulesSection,
|
|
@@ -15161,6 +15177,9 @@ export {
|
|
|
15161
15177
|
ViewBar,
|
|
15162
15178
|
ViewSwitcher,
|
|
15163
15179
|
WITHHELD_RECORD_LABEL,
|
|
15180
|
+
WORK_DUE_STATES,
|
|
15181
|
+
WORK_INBOX_KINDS,
|
|
15182
|
+
WORK_INBOX_KIND_LABEL,
|
|
15164
15183
|
WhoChangedSource,
|
|
15165
15184
|
actorBadge,
|
|
15166
15185
|
actorWords,
|
|
@@ -15226,12 +15245,14 @@ export {
|
|
|
15226
15245
|
previewWords,
|
|
15227
15246
|
publiclyAnswerable,
|
|
15228
15247
|
recordName,
|
|
15248
|
+
recordNameIn,
|
|
15229
15249
|
recordsDataSource,
|
|
15230
15250
|
refusalForAPerson,
|
|
15231
15251
|
refusalLineForAPerson,
|
|
15232
15252
|
renderValue,
|
|
15233
15253
|
revokeConsequence,
|
|
15234
15254
|
rowName,
|
|
15255
|
+
rowNameIn,
|
|
15235
15256
|
scalarText,
|
|
15236
15257
|
shareUnavailableReason,
|
|
15237
15258
|
specFromBlock,
|