@ai-matrx/records-ui 0.93.63 → 0.93.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -350,6 +350,7 @@ __export(src_exports, {
350
350
  scalarText: () => scalarText,
351
351
  seedIsSatisfied: () => seedIsSatisfied,
352
352
  shareUnavailableReason: () => shareUnavailableReason,
353
+ singularNoun: () => singularNoun,
353
354
  snoozeChoices: () => snoozeChoices,
354
355
  specFromBlock: () => specFromBlock,
355
356
  storeDecidesRights: () => storeDecidesRights,
@@ -749,6 +750,10 @@ function realPlural(table) {
749
750
  }
750
751
  return plural3;
751
752
  }
753
+ function singularNoun(table, fallback = "record") {
754
+ const word = (table?.label_singular ?? "").trim() || (table?.name ?? "").trim() || fallback;
755
+ return word.split(/\s+/).map((w) => /^[A-Z][a-z'’-]*$/.test(w) ? w.toLowerCase() : w).join(" ");
756
+ }
752
757
  function pluralNoun(table, fallback = "record") {
753
758
  const real = table ? realPlural(table) : null;
754
759
  if (real) return real;
@@ -2795,7 +2800,7 @@ function NumberInFormat({ format, value, onChange, disabled, id }) {
2795
2800
  function formatControl(args) {
2796
2801
  const def = (0, import_field_formats4.getFieldFormat)(args.format.id);
2797
2802
  if (!def) return null;
2798
- const { format, value, onChange, disabled, id, label } = args;
2803
+ const { format, value, onChange, disabled, id, label, ownDetails } = args;
2799
2804
  const text = value === null || value === void 0 ? "" : String(value);
2800
2805
  const commit = (raw) => onChange(raw === "" ? null : (0, import_field_formats4.parseFieldInput)(raw, format));
2801
2806
  if (def.id === "rating") {
@@ -2818,7 +2823,17 @@ function formatControl(args) {
2818
2823
  case "email":
2819
2824
  case "url":
2820
2825
  case "tel":
2821
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_design_system9.BasicInput, { id, type: def.editor, value: text, disabled, onChange: (e) => commit(e.target.value) });
2826
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2827
+ import_design_system9.BasicInput,
2828
+ {
2829
+ id,
2830
+ type: def.editor,
2831
+ ...ownDetails && def.editor !== "url" ? { autoComplete: def.editor === "email" ? "email" : "tel" } : {},
2832
+ value: text,
2833
+ disabled,
2834
+ onChange: (e) => commit(e.target.value)
2835
+ }
2836
+ );
2822
2837
  case "color":
2823
2838
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "flex items-center gap-2", children: [
2824
2839
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
@@ -3072,14 +3087,19 @@ function TypedTextControl({ field: field2, value, onChange, disabled, id, onComm
3072
3087
  ] })
3073
3088
  ] });
3074
3089
  }
3090
+ function isPersonsName(field2) {
3091
+ const words2 = `${field2.key} ${field2.label ?? ""}`.toLowerCase();
3092
+ return /(^|[\s_])(full[\s_]?)?name(\s|$)/.test(words2) && !/(therapist|doctor|physician|company|business|pet|room)/.test(words2);
3093
+ }
3075
3094
  function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose, onCommit, onCancel, addsChoices, initialSearch }) {
3076
3095
  const host = useRecordsUi();
3077
3096
  const kind = editorKindFor(field2);
3078
3097
  const inputId = id ?? `field-${field2.key}`;
3098
+ const ownDetails = (0, import_react19.useOptionalRecordsClient)() === null;
3079
3099
  if (isTypedDraft(value)) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TypedTextControl, { field: field2, value, onChange, disabled, id: inputId, onCommit, onCancel });
3080
3100
  const format = formatForField(field2);
3081
3101
  if (format && !FORMAT_BLIND_KINDS.has(String(kind))) {
3082
- const own2 = formatControl({ format, value, onChange, disabled, id: inputId, label: fieldName(field2) });
3102
+ const own2 = formatControl({ format, value, onChange, disabled, id: inputId, label: fieldName(field2), ownDetails });
3083
3103
  if (own2) return own2;
3084
3104
  }
3085
3105
  switch (kind) {
@@ -3140,6 +3160,7 @@ function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen,
3140
3160
  {
3141
3161
  id: inputId,
3142
3162
  type: kind === "email" ? "email" : kind === "phone" ? "tel" : "url",
3163
+ ...ownDetails && kind !== "url" ? { autoComplete: kind === "email" ? "email" : "tel" } : {},
3143
3164
  value: value === null || value === void 0 ? "" : String(value),
3144
3165
  disabled,
3145
3166
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
@@ -3177,6 +3198,7 @@ function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen,
3177
3198
  import_design_system10.BasicInput,
3178
3199
  {
3179
3200
  id: inputId,
3201
+ ...ownDetails && isPersonsName(field2) ? { autoComplete: "name" } : {},
3180
3202
  value: value === null || value === void 0 ? "" : String(value),
3181
3203
  disabled,
3182
3204
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
@@ -5583,6 +5605,22 @@ function archiveConfirmSentence(what) {
5583
5605
  const subject = what.trim() || "it";
5584
5606
  return `This archives ${subject}. It leaves this list, and you can restore it from Trash.`;
5585
5607
  }
5608
+ var recordsWord = (n) => `${n.toLocaleString()} record${n === 1 ? "" : "s"}`;
5609
+ function tableArchiveSentence(s) {
5610
+ const p = s.progress;
5611
+ if (!p) return "Every record in it goes with it.";
5612
+ const done = p.total - p.remaining;
5613
+ const sofar = `${done.toLocaleString()} of ${recordsWord(p.total)} ${done === 1 ? "is" : "are"} put away`;
5614
+ if (s.running) {
5615
+ return `Archiving \u2014 ${done.toLocaleString()} of ${recordsWord(p.total)} put away.${s.slowedDown ? " The store is busy, so it is taking smaller passes; it will still finish." : ""}`;
5616
+ }
5617
+ if (p.in_progress && !s.failed && !(s.passesHere ?? 0)) {
5618
+ return `Archiving this table was started and not finished: ${sofar}. Carry on and it picks up where it stopped.`;
5619
+ }
5620
+ if (s.interrupted && s.failed) return `That pass did not finish: ${sofar} and nothing was lost. Carry on and it picks up where it stopped.`;
5621
+ if (s.interrupted) return `Stopped: ${sofar}. Carry on whenever you like; it picks up where it stopped.`;
5622
+ return `Its ${recordsWord(p.remaining)} ${p.remaining === 1 ? "goes" : "go"} with it.`;
5623
+ }
5586
5624
 
5587
5625
  // src/tableDecorations.ts
5588
5626
  var import_react27 = require("react");
@@ -16392,6 +16430,17 @@ function TableSettings({
16392
16430
  const rights = useTableRights(table.data);
16393
16431
  const shape = (0, import_react85.useFieldMutation)();
16394
16432
  const archive = (0, import_react85.useTableArchive)(tableId);
16433
+ const client = (0, import_react85.useRecordsClient)();
16434
+ const ui = useRecordsUi();
16435
+ (0, import_react84.useEffect)(() => {
16436
+ let live = true;
16437
+ void archive.preview().then((answer) => {
16438
+ if (live && answer?.in_progress) setAskingToDelete(true);
16439
+ });
16440
+ return () => {
16441
+ live = false;
16442
+ };
16443
+ }, [tableId]);
16395
16444
  const [editing, setEditing] = (0, import_react84.useState)(null);
16396
16445
  const [askingToDelete, setAskingToDelete] = (0, import_react84.useState)(false);
16397
16446
  const [askingToRemove, setAskingToRemove] = (0, import_react84.useState)(null);
@@ -16420,6 +16469,16 @@ function TableSettings({
16420
16469
  const finished = await archive.start();
16421
16470
  if (finished?.done && finished.table_archived) {
16422
16471
  setAskingToDelete(false);
16472
+ const name = finished.table_name || "The table";
16473
+ ui.notify?.success(`\u201C${name}\u201D is archived. You can restore it from Trash.`, {
16474
+ label: "Undo",
16475
+ run: () => {
16476
+ void client.recordRestore({ record_id: tableId }).then((back) => {
16477
+ if (back.ok) ui.notify?.success(`\u201C${name}\u201D is back, with everything in it.`);
16478
+ else ui.notify?.error(back.error.message);
16479
+ });
16480
+ }
16481
+ });
16423
16482
  onDeleted?.();
16424
16483
  }
16425
16484
  };
@@ -16551,31 +16610,20 @@ function TableSettings({
16551
16610
  /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "flex flex-col gap-1", children: [
16552
16611
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "font-medium", children: "This table" }),
16553
16612
  askingToDelete ? /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_jsx_runtime54.Fragment, { children: [
16554
- /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("p", { className: "text-muted-foreground", "data-archive-confirm": "table", children: [
16555
- archiveConfirmSentence(table.data ? `the table \u201C${tableName(table.data)}\u201D` : "this table"),
16556
- " ",
16557
- "Its ",
16558
- rows.length,
16559
- " field",
16560
- rows.length === 1 ? "" : "s",
16561
- " and",
16562
- " ",
16563
- archive.progress ? `its ${archive.progress.remaining.toLocaleString()} record${archive.progress.remaining === 1 ? "" : "s"}` : "every record in it",
16564
- " ",
16565
- "go with it",
16566
- table.data?.retention_days ? `, kept for ${table.data.retention_days} days` : "",
16567
- "."
16568
- ] }),
16569
- archive.progress && (archive.running || archive.passes > 0) ? /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("p", { className: archive.error ? "text-destructive" : "text-muted-foreground", children: [
16570
- archive.progress.total - archive.progress.remaining === archive.progress.total ? `All ${archive.progress.total.toLocaleString()} record${archive.progress.total === 1 ? "" : "s"} are put away.` : `${(archive.progress.total - archive.progress.remaining).toLocaleString()} of ${archive.progress.total.toLocaleString()} record${archive.progress.total === 1 ? "" : "s"} put away so far.`,
16571
- archive.slowedDown ? " The store is busy, so it is taking smaller passes \u2014 it will still finish." : ""
16572
- ] }) : null,
16573
- archive.interrupted && archive.progress ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("p", { className: "text-muted-foreground", children: archive.error ? `That pass did not finish, so the rest is still here. ${archive.progress.archived_total.toLocaleString()} record${archive.progress.archived_total === 1 ? " is" : "s are"} already archived and ${archive.progress.remaining.toLocaleString()} ${archive.progress.remaining === 1 ? "is" : "are"} left. Nothing was lost \u2014 carry on and it picks up where it stopped.` : `Stopped. ${archive.progress.archived_total.toLocaleString()} record${archive.progress.archived_total === 1 ? " is" : "s are"} archived and ${archive.progress.remaining.toLocaleString()} ${archive.progress.remaining === 1 ? "is" : "are"} left. Carry on whenever you like \u2014 it picks up where it stopped.` }) : null,
16613
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("p", { className: "text-muted-foreground", "data-archive-confirm": "table", children: archiveConfirmSentence(table.data ? `the table \u201C${tableName(table.data)}\u201D` : "this table") }),
16614
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("p", { className: "text-muted-foreground", "data-archive-state": "", children: tableArchiveSentence({
16615
+ progress: archive.progress,
16616
+ running: archive.running,
16617
+ interrupted: archive.interrupted,
16618
+ failed: Boolean(archive.error),
16619
+ slowedDown: archive.slowedDown,
16620
+ passesHere: archive.passes
16621
+ }) }),
16574
16622
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { className: "flex gap-1", children: archive.running ? /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_jsx_runtime54.Fragment, { children: [
16575
16623
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system47.Button, { size: "sm", variant: "destructive", disabled: true, children: "Archiving\u2026" }),
16576
16624
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system47.Button, { size: "sm", variant: "ghost", onClick: () => archive.stop(), children: "Stop" })
16577
16625
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_jsx_runtime54.Fragment, { children: [
16578
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system47.Button, { size: "sm", variant: "destructive", onClick: () => void startArchive(), children: archive.interrupted ? "Carry on archiving" : "Archive this table" }),
16626
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system47.Button, { size: "sm", variant: "destructive", onClick: () => void startArchive(), children: archive.interrupted || archive.progress?.in_progress ? "Carry on archiving" : "Archive this table" }),
16579
16627
  /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system47.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToDelete(false), children: "Keep it" })
16580
16628
  ] }) })
16581
16629
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system47.Button, { size: "sm", variant: "outline", onClick: () => void askToArchive(), children: "Archive this table" }) }),
@@ -18187,6 +18235,18 @@ function columnDeclarationFor(need, label, options, fields) {
18187
18235
  if (need.starter.type === "datetime") return { ...base, parity_type: "datetime", kind: "date" };
18188
18236
  return { ...base, parity_type: "attachment" };
18189
18237
  }
18238
+ var askedFor = /* @__PURE__ */ new Map();
18239
+ var askKey = (tableId, layout) => `${tableId}:${layout}`;
18240
+ function useAskedColumnLands(layout, tableId, fields, chosen, offerable, onMade) {
18241
+ const wanted = askedFor.get(askKey(tableId, layout));
18242
+ const landed = wanted && !chosen ? offerable(fields).find((f) => fieldName(f).trim().toLowerCase() === wanted.trim().toLowerCase()) : void 0;
18243
+ const key = landed?.key ?? null;
18244
+ (0, import_react98.useEffect)(() => {
18245
+ if (!key) return;
18246
+ askedFor.delete(askKey(tableId, layout));
18247
+ onMade(key);
18248
+ }, [key, tableId, layout]);
18249
+ }
18190
18250
  var NO_ASSISTANT = "Asking in words needs the AI assistant, which this screen is not connected to.";
18191
18251
  function ViewNeeds({ layout, tableId, fields, onMade, className }) {
18192
18252
  const need = viewNeedFor(layout);
@@ -18277,7 +18337,12 @@ function ViewNeeds({ layout, tableId, fields, onMade, className }) {
18277
18337
  editor,
18278
18338
  noAgentSentence: NO_ASSISTANT,
18279
18339
  suggestion,
18280
- ...host.onAskForOne ? { onAsk: () => host.onAskForOne?.({ kind: "column", tableId, suggestion }) } : {},
18340
+ ...host.onAskForOne ? {
18341
+ onAsk: () => {
18342
+ askedFor.set(askKey(tableId, layout), name);
18343
+ host.onAskForOne?.({ kind: "column", tableId, suggestion });
18344
+ }
18345
+ } : {},
18281
18346
  children: rights.structure ? `${need.missing} ${need.does(name)}` : need.missing
18282
18347
  }
18283
18348
  ),
@@ -18597,6 +18662,17 @@ function Board({
18597
18662
  () => resolveTableStyle(decorations.data, fields.data ?? [], view.presentation?.style),
18598
18663
  [decorations.data, fields.data, view.presentation?.style]
18599
18664
  );
18665
+ const askedLayout = view.layout === "grid" ? "kanban" : view.layout;
18666
+ const askedKind = askedLayout === "kanban" ? "choice" : askedLayout === "calendar" ? "date" : "file";
18667
+ const askedChosen = askedLayout === "kanban" ? view.groupField : askedLayout === "calendar" ? view.dateField : view.imageField;
18668
+ useAskedColumnLands(
18669
+ askedLayout,
18670
+ view.subject,
18671
+ fields.data ?? NO_FIELDS,
18672
+ askedChosen,
18673
+ (all2) => offerableFields(all2, askedKind),
18674
+ (key) => onChange(askedLayout === "kanban" ? { groupField: key } : askedLayout === "calendar" ? { dateField: key } : { imageField: key })
18675
+ );
18600
18676
  if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(RefusalNotice, { error: fields.error });
18601
18677
  if (records.error) return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(RefusalNotice, { error: records.error });
18602
18678
  if (fields.loading && !fields.data || records.loading && records.rows.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(import_design_system55.Skeleton, { className: "h-40 w-full" });
@@ -18773,15 +18849,19 @@ function Kanban({
18773
18849
  }) {
18774
18850
  const groupFields = (0, import_react102.useMemo)(() => [groupField], [groupField]);
18775
18851
  const labels = useRecordLabels(groupFields);
18852
+ const options = useChoiceOptions(groupFields, isChoiceField(groupField) ? [groupField.key] : []).get(groupField.key);
18776
18853
  const columns = (0, import_react102.useMemo)(() => {
18777
18854
  const map = /* @__PURE__ */ new Map();
18855
+ const order = (options ?? []).map((o) => o.label);
18856
+ for (const label of order) map.set(label, []);
18778
18857
  for (const row of rows) {
18779
18858
  const raw = (row.document ?? {})[groupField.key];
18780
18859
  const key = raw === null || raw === void 0 || raw === "" ? "No value" : scalarText(groupField, raw, labels);
18781
18860
  map.set(key, [...map.get(key) ?? [], row]);
18782
18861
  }
18783
- return [...map.entries()].sort((a, b) => a[0] === "No value" ? 1 : b[0] === "No value" ? -1 : a[0].localeCompare(b[0]));
18784
- }, [rows, groupField, labels]);
18862
+ const rank = (name) => name === "No value" ? Number.MAX_SAFE_INTEGER : order.includes(name) ? order.indexOf(name) : order.length;
18863
+ return [...map.entries()].sort((a, b) => rank(a[0]) - rank(b[0]) || a[0].localeCompare(b[0]));
18864
+ }, [rows, groupField, labels, options]);
18785
18865
  return /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: "flex min-h-0 flex-col gap-2", children: [
18786
18866
  /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(Note, { children: note }),
18787
18867
  /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("div", { className: "flex min-h-0 gap-2 overflow-x-auto pb-2", children: columns.map(([name, cards]) => /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("section", { className: "flex w-64 shrink-0 flex-col rounded border bg-muted/30", children: [
@@ -22230,7 +22310,7 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
22230
22310
  }, [load]);
22231
22311
  (0, import_react129.useEffect)(() => {
22232
22312
  if (title !== "" || !table.data) return;
22233
- setTitle(existing?.title ?? `Book a ${minutes}-minute ${table.data.name} appointment`);
22313
+ setTitle(existing?.title ?? `Book a ${minutes}-minute ${singularNoun(table.data, "appointment")}`);
22234
22314
  }, [table.data, existing]);
22235
22315
  (0, import_react129.useEffect)(() => {
22236
22316
  if (!existing) return;
@@ -22561,6 +22641,7 @@ function BookingSlots({ tableId, activeBookingId, className }) {
22561
22641
  const [copied, setCopied] = (0, import_react131.useState)(null);
22562
22642
  const [shown3, setShown] = (0, import_react131.useState)(null);
22563
22643
  const [building, setBuilding] = (0, import_react131.useState)(false);
22644
+ const [changing, setChanging] = (0, import_react131.useState)(null);
22564
22645
  const load = (0, import_react131.useCallback)(async () => {
22565
22646
  const answered = await client.bookings(tableId ? { table_id: tableId } : {});
22566
22647
  if (!answered.ok) {
@@ -22626,6 +22707,17 @@ function BookingSlots({ tableId, activeBookingId, className }) {
22626
22707
  ] }),
22627
22708
  error ? /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(RefusalNotice, { error }) : null,
22628
22709
  activeBookingId && !pages.some((p) => p.form_id === activeBookingId) ? /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: BOOKING_NOT_HERE_LINE }) : null,
22710
+ changing ? /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
22711
+ BookingBuilder,
22712
+ {
22713
+ tableId: changing.tableId,
22714
+ bookingId: changing.formId,
22715
+ onSaved: () => {
22716
+ void load();
22717
+ },
22718
+ onClose: () => setChanging(null)
22719
+ }
22720
+ ) : null,
22629
22721
  building && tableId ? /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
22630
22722
  BookingBuilder,
22631
22723
  {
@@ -22702,6 +22794,15 @@ function BookingSlots({ tableId, activeBookingId, className }) {
22702
22794
  /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("p", { className: "mt-1 text-xs text-muted-foreground", children: nextInWords(page) }),
22703
22795
  /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
22704
22796
  /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(import_design_system68.Button, { size: "sm", variant: "outline", onClick: () => void copy(url, page.form_id), children: copied === page.form_id ? "Copied" : "Copy link" }),
22797
+ mayOpen ? /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
22798
+ import_design_system68.Button,
22799
+ {
22800
+ size: "sm",
22801
+ variant: "ghost",
22802
+ onClick: () => setChanging({ formId: page.form_id, tableId: page.table_id }),
22803
+ children: "Change it"
22804
+ }
22805
+ ) : null,
22705
22806
  mayOpen ? /* @__PURE__ */ (0, import_jsx_runtime77.jsx)(
22706
22807
  import_design_system68.Button,
22707
22808
  {
@@ -24538,6 +24639,7 @@ function Invite({ card, onInvited }) {
24538
24639
  // src/DigestScheduler.tsx
24539
24640
  var import_react143 = require("react");
24540
24641
  var import_react144 = require("@ai-matrx/records/react");
24642
+ var import_records29 = require("@ai-matrx/records");
24541
24643
  var import_design_system74 = require("@ai-matrx/design-system");
24542
24644
 
24543
24645
  // src/scheduleWords.ts
@@ -24580,6 +24682,12 @@ var CHANNEL_WORDS = {
24580
24682
  email: "by email \u2014 only if an address is on their account",
24581
24683
  sms: "by text \u2014 only if a number is on their account"
24582
24684
  };
24685
+ function scheduleParts(schedule) {
24686
+ const words2 = (schedule ?? "").trim().toLowerCase().split(/\s+/);
24687
+ const time = words2.find((w) => /^\d{1,2}:\d{2}$/.test(w)) ?? null;
24688
+ const day = WEEKDAYS.find((d) => words2.some((w) => w.slice(0, 3) === d.slice(0, 3))) ?? null;
24689
+ return { day, time: time ? time.padStart(5, "0") : null };
24690
+ }
24583
24691
  function DigestScheduler({
24584
24692
  tableId,
24585
24693
  savedViewId,
@@ -24587,7 +24695,8 @@ function DigestScheduler({
24587
24695
  filters,
24588
24696
  onScheduled,
24589
24697
  onClose,
24590
- className
24698
+ className,
24699
+ existing
24591
24700
  }) {
24592
24701
  const client = (0, import_react144.useRecordsClient)();
24593
24702
  const host = useRecordsUi();
@@ -24595,19 +24704,33 @@ function DigestScheduler({
24595
24704
  const [cadences, setCadences] = (0, import_react143.useState)([]);
24596
24705
  const [members, setMembers] = (0, import_react143.useState)(null);
24597
24706
  const [error, setError] = (0, import_react143.useState)(null);
24598
- const [viewId, setViewId] = (0, import_react143.useState)(savedViewId ?? "");
24599
- const [name, setName] = (0, import_react143.useState)(subjectName?.trim() ? `${subjectName.trim()} summary` : "");
24600
- const [cadence, setCadence] = (0, import_react143.useState)("weekly");
24601
- const [weekday, setWeekday] = (0, import_react143.useState)("monday");
24602
- const [time, setTime] = (0, import_react143.useState)("08:00");
24603
- const [timezone, setTimezone] = (0, import_react143.useState)(() => ownTimeZone());
24707
+ const was = scheduleParts(existing?.schedule);
24708
+ const [viewId, setViewId] = (0, import_react143.useState)(
24709
+ existing?.saved_view_id ?? savedViewId ?? ""
24710
+ );
24711
+ const [name, setName] = (0, import_react143.useState)(
24712
+ existing?.name ?? (subjectName?.trim() ? `${subjectName.trim()} summary` : "")
24713
+ );
24714
+ const [cadence, setCadence] = (0, import_react143.useState)(
24715
+ ["hourly", "daily", "weekly"].includes(existing?.cadence ?? "") ? existing.cadence : "weekly"
24716
+ );
24717
+ const [weekday, setWeekday] = (0, import_react143.useState)(was.day ?? "monday");
24718
+ const [time, setTime] = (0, import_react143.useState)(was.time ?? "08:00");
24719
+ const [timezone, setTimezone] = (0, import_react143.useState)(() => {
24720
+ const kept = existing?.quiet_hours?.tz?.trim();
24721
+ return kept && kept.toUpperCase() !== "UTC" ? kept : ownTimeZone();
24722
+ });
24604
24723
  const table = (0, import_react144.useTable)(tableId);
24605
24724
  const tableName2 = table.data?.name?.trim() || null;
24606
- const [channel, setChannel] = (0, import_react143.useState)("in_app");
24607
- const [quiet, setQuiet] = (0, import_react143.useState)(false);
24608
- const [quietFrom, setQuietFrom] = (0, import_react143.useState)("22:00");
24609
- const [quietTo, setQuietTo] = (0, import_react143.useState)("07:00");
24610
- const [recipients, setRecipients] = (0, import_react143.useState)([]);
24725
+ const [channel, setChannel] = (0, import_react143.useState)(
24726
+ existing?.channel === "email" || existing?.channel === "sms" ? existing.channel : "in_app"
24727
+ );
24728
+ const [quiet, setQuiet] = (0, import_react143.useState)((0, import_records29.hasQuietHours)(existing?.quiet_hours));
24729
+ const [quietFrom, setQuietFrom] = (0, import_react143.useState)(existing?.quiet_hours?.start ?? "22:00");
24730
+ const [quietTo, setQuietTo] = (0, import_react143.useState)(existing?.quiet_hours?.end ?? "07:00");
24731
+ const [recipients, setRecipients] = (0, import_react143.useState)(
24732
+ existing?.recipient_user_id ? [existing.recipient_user_id] : []
24733
+ );
24611
24734
  const [saving, setSaving] = (0, import_react143.useState)(false);
24612
24735
  const [outcomes, setOutcomes] = (0, import_react143.useState)(null);
24613
24736
  const [preview, setPreview] = (0, import_react143.useState)(null);
@@ -24678,7 +24801,8 @@ function DigestScheduler({
24678
24801
  schedule,
24679
24802
  channel,
24680
24803
  quiet_hours: quietHours,
24681
- ...userId === "" ? {} : { recipient_user_id: userId }
24804
+ ...userId === "" ? {} : { recipient_user_id: userId },
24805
+ ...existing ? { rule_id: existing.rule_id } : {}
24682
24806
  }
24683
24807
  });
24684
24808
  if (written.ok) done.push({ userId, who: label, ruleId: written.data });
@@ -24701,9 +24825,9 @@ function DigestScheduler({
24701
24825
  if (views === null && !error) return /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(import_design_system74.Skeleton, { className: (0, import_design_system74.cn)("h-56 w-full", className) });
24702
24826
  return /* @__PURE__ */ (0, import_jsx_runtime83.jsxs)("section", { className: (0, import_design_system74.cn)("@container flex flex-col gap-3", className), children: [
24703
24827
  /* @__PURE__ */ (0, import_jsx_runtime83.jsxs)("header", { className: "flex items-center gap-2", children: [
24704
- /* @__PURE__ */ (0, import_jsx_runtime83.jsx)("h3", { className: "text-sm font-medium", children: "Send this on a schedule" }),
24828
+ /* @__PURE__ */ (0, import_jsx_runtime83.jsx)("h3", { className: "text-sm font-medium", children: existing ? "Change this summary" : "Send this on a schedule" }),
24705
24829
  /* @__PURE__ */ (0, import_jsx_runtime83.jsx)("div", { className: "flex-1" }),
24706
- /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(import_design_system74.Button, { size: "sm", disabled: saving, onClick: () => void scheduleIt(), children: saving ? "Scheduling\u2026" : "Schedule it" }),
24830
+ /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(import_design_system74.Button, { size: "sm", disabled: saving, onClick: () => void scheduleIt(), children: saving ? "Scheduling\u2026" : existing ? "Save the change" : "Schedule it" }),
24707
24831
  onClose ? /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(import_design_system74.Button, { size: "sm", variant: "ghost", onClick: onClose, children: "Close" }) : null
24708
24832
  ] }),
24709
24833
  error ? /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(RefusalNotice, { error }) : null,
@@ -24887,7 +25011,7 @@ function DigestScheduler({
24887
25011
  // src/SubscriptionsPanel.tsx
24888
25012
  var import_react147 = require("react");
24889
25013
  var import_react148 = require("@ai-matrx/records/react");
24890
- var import_records29 = require("@ai-matrx/records");
25014
+ var import_records30 = require("@ai-matrx/records");
24891
25015
  var import_design_system76 = require("@ai-matrx/design-system");
24892
25016
 
24893
25017
  // src/WebhooksPanel.tsx
@@ -24924,13 +25048,16 @@ function WebhooksPanel({ tableId, className }) {
24924
25048
  const [busy, setBusy] = (0, import_react145.useState)(false);
24925
25049
  const [declared2, setDeclared] = (0, import_react145.useState)(null);
24926
25050
  const [stopping, setStopping] = (0, import_react145.useState)(null);
25051
+ const [readRefused, setReadRefused] = (0, import_react145.useState)(null);
24927
25052
  const load = (0, import_react145.useCallback)(async () => {
24928
25053
  const answered = await client.tableWebhooks({ table_id: tableId });
24929
25054
  if (!answered.ok) {
25055
+ setReadRefused(answered.error);
24930
25056
  setError(answered.error);
24931
25057
  setHooks([]);
24932
25058
  return;
24933
25059
  }
25060
+ setReadRefused(null);
24934
25061
  setError(null);
24935
25062
  setHooks(answered.data ?? []);
24936
25063
  }, [client, tableId]);
@@ -24959,6 +25086,17 @@ function WebhooksPanel({ tableId, className }) {
24959
25086
  await load();
24960
25087
  };
24961
25088
  if (hooks === null) return /* @__PURE__ */ (0, import_jsx_runtime84.jsx)(import_design_system75.Skeleton, { className: (0, import_design_system75.cn)("h-16 w-full", className) });
25089
+ const notHers = readRefused?.sqlstate === "42501" && rights.known && !rights.structure;
25090
+ if (notHers) {
25091
+ return /* @__PURE__ */ (0, import_jsx_runtime84.jsxs)("section", { "data-records-webhooks": "", className: (0, import_design_system75.cn)("flex flex-col gap-2 border-t pt-3", className), children: [
25092
+ /* @__PURE__ */ (0, import_jsx_runtime84.jsx)("header", { className: "flex items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime84.jsx)("h3", { className: "text-sm font-medium", children: "Webhooks" }) }),
25093
+ /* @__PURE__ */ (0, import_jsx_runtime84.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
25094
+ "Which other systems are told about these records is for this table's admins.",
25095
+ " ",
25096
+ rights.why("structure")
25097
+ ] })
25098
+ ] });
25099
+ }
24962
25100
  const mayChange = rights.structure;
24963
25101
  const live = hooks.filter((h) => h.is_active);
24964
25102
  return /* @__PURE__ */ (0, import_jsx_runtime84.jsxs)("section", { "data-records-webhooks": "", className: (0, import_design_system75.cn)("flex flex-col gap-2 border-t pt-3", className), children: [
@@ -25099,6 +25237,7 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
25099
25237
  const [busy, setBusy] = (0, import_react147.useState)(null);
25100
25238
  const [preview, setPreview] = (0, import_react147.useState)(null);
25101
25239
  const [scheduling, setScheduling] = (0, import_react147.useState)(false);
25240
+ const [changing, setChanging] = (0, import_react147.useState)(null);
25102
25241
  const host = useRecordsUi();
25103
25242
  const load = (0, import_react147.useCallback)(async () => {
25104
25243
  const answered = await client.subscriptions({ table_id: tableId });
@@ -25170,6 +25309,18 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
25170
25309
  onClose: () => setScheduling(false)
25171
25310
  }
25172
25311
  ) : null,
25312
+ changing ? /* @__PURE__ */ (0, import_jsx_runtime85.jsx)(
25313
+ DigestScheduler,
25314
+ {
25315
+ tableId,
25316
+ existing: changing,
25317
+ onScheduled: () => {
25318
+ void load();
25319
+ },
25320
+ onClose: () => setChanging(null)
25321
+ },
25322
+ changing.rule_id
25323
+ ) : null,
25173
25324
  rows.length === 0 && !scheduling ? /* @__PURE__ */ (0, import_jsx_runtime85.jsx)(
25174
25325
  BuildOrAsk,
25175
25326
  {
@@ -25223,7 +25374,7 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
25223
25374
  "Last told you ",
25224
25375
  new Date(subscription.last_sent_at).toLocaleString()
25225
25376
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime85.jsx)("span", { children: "It has not told you anything yet." }),
25226
- (0, import_records29.hasQuietHours)(subscription.quiet_hours) ? /* @__PURE__ */ (0, import_jsx_runtime85.jsxs)("span", { children: [
25377
+ (0, import_records30.hasQuietHours)(subscription.quiet_hours) ? /* @__PURE__ */ (0, import_jsx_runtime85.jsxs)("span", { children: [
25227
25378
  "Not between ",
25228
25379
  subscription.quiet_hours.start,
25229
25380
  " and ",
@@ -25239,7 +25390,8 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
25239
25390
  onClick: () => void showOne(subscription),
25240
25391
  children: "Send me a preview now"
25241
25392
  }
25242
- )
25393
+ ),
25394
+ subscription.mine && subscription.cadence !== "instant" ? /* @__PURE__ */ (0, import_jsx_runtime85.jsx)(import_design_system76.Button, { size: "sm", variant: "ghost", onClick: () => setChanging(subscription), children: "Change it" }) : null
25243
25395
  ] }),
25244
25396
  preview && preview.rule_id === subscription.rule_id ? /* @__PURE__ */ (0, import_jsx_runtime85.jsxs)("div", { className: "mt-2 rounded-md border bg-muted/40 p-2.5 text-xs", children: [
25245
25397
  /* @__PURE__ */ (0, import_jsx_runtime85.jsx)("p", { className: "font-medium", children: preview.subject }),
@@ -25288,7 +25440,7 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
25288
25440
  // src/FormBuilder.tsx
25289
25441
  var import_react153 = require("react");
25290
25442
  var import_react154 = require("@ai-matrx/records/react");
25291
- var import_records31 = require("@ai-matrx/records");
25443
+ var import_records32 = require("@ai-matrx/records");
25292
25444
  var import_design_system79 = require("@ai-matrx/design-system");
25293
25445
 
25294
25446
  // src/FormRunner.tsx
@@ -25653,7 +25805,12 @@ function ThankYou({ form, done, className }) {
25653
25805
  " ",
25654
25806
  /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("a", { href: redirect, className: "underline underline-offset-2", children: "Go now" })
25655
25807
  ] }) : null,
25656
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("p", { className: "mt-3 text-xs text-muted-foreground", children: done === "preview" ? redirect ? `This was a preview, so nothing was written. The live form goes on to ${redirect}.` : "This was a preview, so nothing was written." : done === "sent" ? `Saved to ${form.name}, with this form stamped on it.` : done })
25808
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("p", { className: "mt-3 text-xs text-muted-foreground", children: done === "preview" ? redirect ? `This was a preview, so nothing was written. The live form goes on to ${redirect}.` : "This was a preview, so nothing was written." : done === "sent" ? (
25809
+ // SAID TO THE PERSON WHO ANSWERED (lane HANDOVER, 2026-09-29): "Saved to Form 1, with
25810
+ // this form stamped on it" was the owner's bookkeeping read out to a front-desk member
25811
+ // and to strangers. Where it landed is the owner's to see on the table.
25812
+ "Your answers were received."
25813
+ ) : done })
25657
25814
  ] });
25658
25815
  }
25659
25816
  function Question({
@@ -25701,7 +25858,7 @@ function Question({
25701
25858
  // src/NotifyRuleEditor.tsx
25702
25859
  var import_react151 = require("react");
25703
25860
  var import_react152 = require("@ai-matrx/records/react");
25704
- var import_records30 = require("@ai-matrx/records");
25861
+ var import_records31 = require("@ai-matrx/records");
25705
25862
  var import_design_system78 = require("@ai-matrx/design-system");
25706
25863
  var import_jsx_runtime87 = require("react/jsx-runtime");
25707
25864
  var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
@@ -25852,7 +26009,7 @@ function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className })
25852
26009
  "next ",
25853
26010
  new Date(held.next_digest_at).toLocaleString()
25854
26011
  ] }) : null,
25855
- (0, import_records30.hasQuietHours)(held.quiet_hours) ? /* @__PURE__ */ (0, import_jsx_runtime87.jsxs)("span", { className: "text-muted-foreground", children: [
26012
+ (0, import_records31.hasQuietHours)(held.quiet_hours) ? /* @__PURE__ */ (0, import_jsx_runtime87.jsxs)("span", { className: "text-muted-foreground", children: [
25856
26013
  "quiet ",
25857
26014
  held.quiet_hours.start,
25858
26015
  "\u2013",
@@ -26092,9 +26249,10 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
26092
26249
  (0, import_react153.useEffect)(() => {
26093
26250
  if (!startWithOne || started || forms === null || forms.length > 0) return;
26094
26251
  if (askableFields(fields.data ?? []).length === 0) return;
26252
+ if (!table.data && !table.error) return;
26095
26253
  setStarted(true);
26096
26254
  void create(table.data?.name?.trim() ? `${table.data.name.trim()} form` : "Form 1");
26097
- }, [startWithOne, started, forms, fields.data, create, table.data?.name]);
26255
+ }, [startWithOne, started, forms, fields.data, create, table.data, table.error]);
26098
26256
  if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(RefusalNotice, { error: fields.error, className });
26099
26257
  if (error) {
26100
26258
  return /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(
@@ -26150,7 +26308,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
26150
26308
  /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(
26151
26309
  PublishRow,
26152
26310
  {
26153
- url: `${publicOrigin}${(0, import_records31.publicFormPath)(draft.id)}`,
26311
+ url: `${publicOrigin}${(0, import_records32.publicFormPath)(draft.id)}`,
26154
26312
  state: draft.state,
26155
26313
  mayPublish: rights.structure && !publishGate.blocked,
26156
26314
  why: publishGate.why ?? rights.why("structure"),
@@ -27167,7 +27325,7 @@ function SignBlock({ tableId, recordId, render, className }) {
27167
27325
  // src/ChartBlock.tsx
27168
27326
  var import_react163 = require("react");
27169
27327
  var import_recharts = require("recharts");
27170
- var import_records32 = require("@ai-matrx/records");
27328
+ var import_records33 = require("@ai-matrx/records");
27171
27329
  var import_core17 = require("@ai-matrx/records/core");
27172
27330
  var import_design_system84 = require("@ai-matrx/design-system");
27173
27331
  var import_jsx_runtime93 = require("react/jsx-runtime");
@@ -27238,7 +27396,7 @@ function ChartBlock({ block, subject, className }) {
27238
27396
  const kind = block.kind;
27239
27397
  const tableId = block.table_id ?? subject;
27240
27398
  const measures = block.measures && block.measures.length > 0 ? block.measures : [{ op: "count" }];
27241
- const primary = (0, import_records32.measureKey)(measures[0]);
27399
+ const primary = (0, import_records33.measureKey)(measures[0]);
27242
27400
  const windows = kind === "stuck" ? null : block.compare ?? null;
27243
27401
  const compared = !!windows;
27244
27402
  const grain = block.bucket?.by ?? null;
@@ -28175,7 +28333,7 @@ function TargetControl({
28175
28333
  // src/FormsPanel.tsx
28176
28334
  var import_react166 = require("react");
28177
28335
  var import_react167 = require("@ai-matrx/records/react");
28178
- var import_records33 = require("@ai-matrx/records");
28336
+ var import_records34 = require("@ai-matrx/records");
28179
28337
  var import_design_system86 = require("@ai-matrx/design-system");
28180
28338
  var import_jsx_runtime95 = require("react/jsx-runtime");
28181
28339
  var FORM_NOT_HERE_LINE = "The link named a form this table does not have \u2014 it may have been removed, or it belongs to another table. The forms this table does have are listed here.";
@@ -28280,7 +28438,7 @@ function FormsPanel({ tableId, activeFormId, className }) {
28280
28438
  }
28281
28439
  ) : null,
28282
28440
  /* @__PURE__ */ (0, import_jsx_runtime95.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
28283
- const url = `${origin}${(0, import_records33.publicFormPath)(form.form_id)}`;
28441
+ const url = `${origin}${(0, import_records34.publicFormPath)(form.form_id)}`;
28284
28442
  return /* @__PURE__ */ (0, import_jsx_runtime95.jsxs)(
28285
28443
  "li",
28286
28444
  {
@@ -28349,7 +28507,7 @@ var import_design_system88 = require("@ai-matrx/design-system");
28349
28507
 
28350
28508
  // src/CaptureRun.tsx
28351
28509
  var import_react168 = require("react");
28352
- var import_records34 = require("@ai-matrx/records");
28510
+ var import_records35 = require("@ai-matrx/records");
28353
28511
  var import_react169 = require("@ai-matrx/records/react");
28354
28512
  var import_design_system87 = require("@ai-matrx/design-system");
28355
28513
  var import_jsx_runtime96 = require("react/jsx-runtime");
@@ -28404,7 +28562,7 @@ function CaptureRun({ sheetId, face: given, className }) {
28404
28562
  const [sending, setSending] = (0, import_react168.useState)(false);
28405
28563
  const queueRef = (0, import_react168.useRef)(null);
28406
28564
  (0, import_react168.useEffect)(() => {
28407
- const q2 = (0, import_records34.openCaptureQueue)({
28565
+ const q2 = (0, import_records35.openCaptureQueue)({
28408
28566
  onChange: (c, all) => {
28409
28567
  setCounts(c);
28410
28568
  setItems(all);
@@ -28671,7 +28829,7 @@ function CaptureRun({ sheetId, face: given, className }) {
28671
28829
  value: String(answers[q.field] ?? ""),
28672
28830
  onChange: (e) => {
28673
28831
  const raw = e.target.value;
28674
- const answered2 = field2 ? (0, import_records34.coerceTypedAnswer)(field2, raw, { label: q.ask ?? (0, import_records34.coercionLabel)(field2) }) : { value: raw };
28832
+ const answered2 = field2 ? (0, import_records35.coerceTypedAnswer)(field2, raw, { label: q.ask ?? (0, import_records35.coercionLabel)(field2) }) : { value: raw };
28675
28833
  setAnswers((held) => ({
28676
28834
  ...held,
28677
28835
  [q.field]: raw === "" ? "" : "refusal" in answered2 ? raw : answered2.value
@@ -29343,7 +29501,7 @@ function recordsDataSource(client, fallbackSchema = "custom") {
29343
29501
 
29344
29502
  // src/TablePage.tsx
29345
29503
  var import_react184 = require("react");
29346
- var import_records35 = require("@ai-matrx/records");
29504
+ var import_records36 = require("@ai-matrx/records");
29347
29505
  var import_react185 = require("@ai-matrx/records/react");
29348
29506
  var import_design_system93 = require("@ai-matrx/design-system");
29349
29507
 
@@ -29718,7 +29876,7 @@ function TableNotHere({
29718
29876
  return /* @__PURE__ */ (0, import_jsx_runtime103.jsxs)("div", { className: (0, import_design_system93.cn)("flex flex-col items-start gap-2 rounded-md border border-dashed p-6", className), children: [
29719
29877
  /* @__PURE__ */ (0, import_jsx_runtime103.jsx)("p", { className: "text-sm font-medium", children: "This table is archived" }),
29720
29878
  /* @__PURE__ */ (0, import_jsx_runtime103.jsx)("p", { className: "max-w-prose text-xs text-muted-foreground", children: TABLE_IS_ARCHIVED }),
29721
- /* @__PURE__ */ (0, import_jsx_runtime103.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records35.archivedByLine)(row) }),
29879
+ /* @__PURE__ */ (0, import_jsx_runtime103.jsx)("p", { className: "text-xs text-muted-foreground", children: (0, import_records36.archivedByLine)(row) }),
29722
29880
  refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime103.jsx)(RefusalNotice, { error: refusal2 }) : null,
29723
29881
  /* @__PURE__ */ (0, import_jsx_runtime103.jsxs)("div", { className: "flex gap-2", children: [
29724
29882
  /* @__PURE__ */ (0, import_jsx_runtime103.jsx)(