@ai-matrx/records-ui 0.103.0 → 0.103.2

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.d.cts CHANGED
@@ -2993,6 +2993,12 @@ interface ConditionGroupProps {
2993
2993
  * person builds them — the depth the store compiles.
2994
2994
  */
2995
2995
  allowNesting?: boolean;
2996
+ /**
2997
+ * How many levels of groups a person may make below the whole condition (Notion's advanced
2998
+ * filter: 2 — a group, and a group inside it). Left out, no limit. A deeper saved question still
2999
+ * draws; only "Add a group" stops at the limit.
3000
+ */
3001
+ maxDepth?: number | undefined;
2996
3002
  /** How deep this group sits (0 = the whole condition). Set by the builder itself. */
2997
3003
  depth?: number;
2998
3004
  /** A nested group can take itself away; the outermost one cannot. */
@@ -3005,7 +3011,7 @@ interface ConditionGroupProps {
3005
3011
  * writing one condition never meets a control for a problem they do not have. A nested group
3006
3012
  * always shows its joiner: it is a box with its own and / or, and that is the whole point of it.
3007
3013
  */
3008
- declare function ConditionGroup({ lead, expr, fields, onChange, emptyLabel, allowNesting, depth, onRemove, className, }: ConditionGroupProps): react.JSX.Element;
3014
+ declare function ConditionGroup({ lead, expr, fields, onChange, emptyLabel, allowNesting, maxDepth, depth, onRemove, className, }: ConditionGroupProps): react.JSX.Element;
3009
3015
 
3010
3016
  /** The longest name a table keeps; a longer one is said, never cut. */
3011
3017
  declare const TABLE_NAME_MAX = 120;
@@ -3224,6 +3230,12 @@ interface PickOrAddChip {
3224
3230
  interface PickOrAddProps {
3225
3231
  /** The trigger's accessible name, e.g. "Pick for Category". */
3226
3232
  triggerLabel: string;
3233
+ /**
3234
+ * The trigger's height. "field" (the default) is the height of the text box beside it in a form
3235
+ * (WALK3-FIX, 2026-10-07: Pick/Choose sat 4px shorter than the date box in the same grid row);
3236
+ * "compact" keeps the small button a filter row or a chooser draws.
3237
+ */
3238
+ triggerSize?: "field" | "compact" | undefined;
3227
3239
  /** The trigger's visible word, e.g. "Pick", "Change", "Choose". */
3228
3240
  triggerText: string;
3229
3241
  triggerId?: string | undefined;
@@ -3276,7 +3288,7 @@ interface PickOrAddProps {
3276
3288
  */
3277
3289
  multi?: boolean | undefined;
3278
3290
  }
3279
- declare function PickOrAdd({ triggerLabel, triggerText, triggerId, open, onOpenChange, search, onSearch, items, onPick, notice, empty, add, foot, footNote, onEnterWithNoMatch, anchored, children, chips, multi, onEscape, }: PickOrAddProps): react.JSX.Element;
3291
+ declare function PickOrAdd({ triggerSize, triggerLabel, triggerText, triggerId, open, onOpenChange, search, onSearch, items, onPick, notice, empty, add, foot, footNote, onEnterWithNoMatch, anchored, children, chips, multi, onEscape, }: PickOrAddProps): react.JSX.Element;
3280
3292
 
3281
3293
  /** What the panel says while the chosen list is empty. */
3282
3294
  declare const ENTITY_REFERENCE_KINDS_MISSING = "Choose at least one kind of thing this column points at.";
package/dist/index.d.ts CHANGED
@@ -2993,6 +2993,12 @@ interface ConditionGroupProps {
2993
2993
  * person builds them — the depth the store compiles.
2994
2994
  */
2995
2995
  allowNesting?: boolean;
2996
+ /**
2997
+ * How many levels of groups a person may make below the whole condition (Notion's advanced
2998
+ * filter: 2 — a group, and a group inside it). Left out, no limit. A deeper saved question still
2999
+ * draws; only "Add a group" stops at the limit.
3000
+ */
3001
+ maxDepth?: number | undefined;
2996
3002
  /** How deep this group sits (0 = the whole condition). Set by the builder itself. */
2997
3003
  depth?: number;
2998
3004
  /** A nested group can take itself away; the outermost one cannot. */
@@ -3005,7 +3011,7 @@ interface ConditionGroupProps {
3005
3011
  * writing one condition never meets a control for a problem they do not have. A nested group
3006
3012
  * always shows its joiner: it is a box with its own and / or, and that is the whole point of it.
3007
3013
  */
3008
- declare function ConditionGroup({ lead, expr, fields, onChange, emptyLabel, allowNesting, depth, onRemove, className, }: ConditionGroupProps): react.JSX.Element;
3014
+ declare function ConditionGroup({ lead, expr, fields, onChange, emptyLabel, allowNesting, maxDepth, depth, onRemove, className, }: ConditionGroupProps): react.JSX.Element;
3009
3015
 
3010
3016
  /** The longest name a table keeps; a longer one is said, never cut. */
3011
3017
  declare const TABLE_NAME_MAX = 120;
@@ -3224,6 +3230,12 @@ interface PickOrAddChip {
3224
3230
  interface PickOrAddProps {
3225
3231
  /** The trigger's accessible name, e.g. "Pick for Category". */
3226
3232
  triggerLabel: string;
3233
+ /**
3234
+ * The trigger's height. "field" (the default) is the height of the text box beside it in a form
3235
+ * (WALK3-FIX, 2026-10-07: Pick/Choose sat 4px shorter than the date box in the same grid row);
3236
+ * "compact" keeps the small button a filter row or a chooser draws.
3237
+ */
3238
+ triggerSize?: "field" | "compact" | undefined;
3227
3239
  /** The trigger's visible word, e.g. "Pick", "Change", "Choose". */
3228
3240
  triggerText: string;
3229
3241
  triggerId?: string | undefined;
@@ -3276,7 +3288,7 @@ interface PickOrAddProps {
3276
3288
  */
3277
3289
  multi?: boolean | undefined;
3278
3290
  }
3279
- declare function PickOrAdd({ triggerLabel, triggerText, triggerId, open, onOpenChange, search, onSearch, items, onPick, notice, empty, add, foot, footNote, onEnterWithNoMatch, anchored, children, chips, multi, onEscape, }: PickOrAddProps): react.JSX.Element;
3291
+ declare function PickOrAdd({ triggerSize, triggerLabel, triggerText, triggerId, open, onOpenChange, search, onSearch, items, onPick, notice, empty, add, foot, footNote, onEnterWithNoMatch, anchored, children, chips, multi, onEscape, }: PickOrAddProps): react.JSX.Element;
3280
3292
 
3281
3293
  /** What the panel says while the chosen list is empty. */
3282
3294
  declare const ENTITY_REFERENCE_KINDS_MISSING = "Choose at least one kind of thing this column points at.";
package/dist/index.js CHANGED
@@ -1115,6 +1115,7 @@ function PickChips({ chips }) {
1115
1115
  ) });
1116
1116
  }
1117
1117
  function PickOrAdd({
1118
+ triggerSize = "field",
1118
1119
  triggerLabel,
1119
1120
  triggerText,
1120
1121
  triggerId,
@@ -1173,7 +1174,7 @@ function PickOrAdd({
1173
1174
  }
1174
1175
  ) : null;
1175
1176
  return /* @__PURE__ */ jsxs5(Popover, { open, onOpenChange, children: [
1176
- anchored ? /* @__PURE__ */ jsx6(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx6("div", { className: "flex min-h-6 w-full min-w-0 flex-wrap items-center gap-1", "aria-label": triggerLabel, children }) }) : /* @__PURE__ */ jsx6(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx6(Button2, { id: triggerId, type: "button", size: "sm", variant: "outline", "aria-label": triggerLabel, children: triggerText }) }),
1177
+ anchored ? /* @__PURE__ */ jsx6(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx6("div", { "data-pick-anchor": "", className: "flex min-h-6 w-full min-w-0 flex-wrap items-center gap-1", "aria-label": triggerLabel, children }) }) : /* @__PURE__ */ jsx6(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx6(Button2, { id: triggerId, type: "button", size: "sm", variant: "outline", className: triggerSize === "field" ? "h-9" : void 0, "aria-label": triggerLabel, children: triggerText }) }),
1177
1178
  /* @__PURE__ */ jsxs5(
1178
1179
  PopoverContent,
1179
1180
  {
@@ -3595,6 +3596,7 @@ function PickOne({
3595
3596
  return /* @__PURE__ */ jsx14("span", { className: cn11("inline-flex min-w-0", className), "data-records-pick-one": "search", "data-pick-value": value, ...attrs ?? {}, children: /* @__PURE__ */ jsx14(
3596
3597
  PickOrAdd,
3597
3598
  {
3599
+ triggerSize: "compact",
3598
3600
  triggerLabel: label,
3599
3601
  triggerText: current?.label ?? placeholder ?? "",
3600
3602
  triggerId: id,
@@ -4680,7 +4682,7 @@ function TypedWordsControl({ field: field2, value, onChange, disabled, id }) {
4680
4682
  disabled,
4681
4683
  autoComplete: "off",
4682
4684
  "aria-invalid": refusal4 ? true : void 0,
4683
- placeholder: helper === "time" ? "2:30 PM" : helper === "datetime" ? `${typedDateText2("2026-10-03")} 2:30 PM` : helper ? typedDateText2("2026-10-03") : void 0,
4685
+ placeholder: helper ? typedDatePlaceholder(helper) : void 0,
4684
4686
  onChange: (e) => retype(e.target.value),
4685
4687
  onBlur: () => {
4686
4688
  if (!refusal4) return;
@@ -4694,6 +4696,12 @@ function TypedWordsControl({ field: field2, value, onChange, disabled, id }) {
4694
4696
  refusal4 ? /* @__PURE__ */ jsx19("span", { "data-records-typed-refusal": "", "data-records-cell-note": "", role: "status", title: why, className: "text-[11px] leading-snug text-amber-700 dark:text-amber-400", children: refusal4 }) : null
4695
4697
  ] });
4696
4698
  }
4699
+ function typedDatePlaceholder(helper) {
4700
+ const time = "h:mm AM";
4701
+ if (helper === "time") return time;
4702
+ const pattern = typedDateText2("2026-11-22").replace("2026", "YYYY").replace("11", "MM").replace("22", "DD");
4703
+ return helper === "datetime" ? `${pattern} ${time}` : pattern;
4704
+ }
4697
4705
  function CheckboxControl({ field: field2, value, onChange, disabled, id }) {
4698
4706
  const unanswered = value === null || value === void 0;
4699
4707
  return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
@@ -6347,16 +6355,16 @@ function cellMarkSentence(field2, document2, value) {
6347
6355
  const kept = keptAsideFor(document2, field2.key);
6348
6356
  return kept ? keptAsideReason(field2, kept) : null;
6349
6357
  }
6350
- var WORKED_OUT_ERROR_WORD = "#ERROR";
6351
- function WorkedOutError({ field: field2, sentence: sentence2 }) {
6358
+ function WorkedOutError({ field: field2, sentence: sentence2, floated = false }) {
6352
6359
  return /* @__PURE__ */ jsx26(
6353
6360
  "span",
6354
6361
  {
6355
6362
  "data-records-worked-out-error": field2.key,
6363
+ role: "img",
6356
6364
  title: sentence2,
6357
- "aria-label": `Error. ${sentence2}`,
6358
- className: "cursor-help font-medium text-amber-700 dark:text-amber-300",
6359
- children: WORKED_OUT_ERROR_WORD
6365
+ "aria-label": sentence2,
6366
+ className: (floated ? "absolute right-1 top-1/2 z-10 flex -translate-y-1/2 " : "inline-flex align-middle ") + "h-4 w-4 shrink-0 cursor-help items-center justify-center rounded-full bg-destructive text-[10px] font-bold leading-none text-destructive-foreground",
6367
+ children: "!"
6360
6368
  }
6361
6369
  );
6362
6370
  }
@@ -6585,7 +6593,7 @@ function RecordValue({
6585
6593
  if (withheld && (value === null || value === void 0)) return /* @__PURE__ */ jsx27(WithheldValue, { field: field2, notice: withheld });
6586
6594
  if (value === null || value === void 0) {
6587
6595
  const failed = workedOutErrorFor(document2, field2.key);
6588
- if (failed) return /* @__PURE__ */ jsx27(WorkedOutError, { field: field2, sentence: failed });
6596
+ if (failed) return /* @__PURE__ */ jsx27(WorkedOutError, { field: field2, sentence: failed, floated: Boolean(inCell) });
6589
6597
  }
6590
6598
  if (inCell) {
6591
6599
  const kept = keptAsideFor(document2, field2.key);
@@ -7208,6 +7216,22 @@ function GridCell({
7208
7216
  const open = Boolean(
7209
7217
  editing && editing.editing?.rowId === row.id && editing.editing.key === field2.key
7210
7218
  );
7219
+ const closedControl = useRef15(null);
7220
+ const wasOpen = useRef15(false);
7221
+ useEffect15(() => {
7222
+ if (open) {
7223
+ wasOpen.current = true;
7224
+ return;
7225
+ }
7226
+ if (!wasOpen.current) return;
7227
+ wasOpen.current = false;
7228
+ if (!clickOpens) return;
7229
+ const frame = requestAnimationFrame(() => {
7230
+ const active = document.activeElement;
7231
+ if (!active || active === document.body) closedControl.current?.focus({ preventScroll: true });
7232
+ });
7233
+ return () => cancelAnimationFrame(frame);
7234
+ }, [open, clickOpens]);
7211
7235
  const wasActive = useRef15(false);
7212
7236
  const singlePress = useRef15(null);
7213
7237
  const cancelSinglePress = () => {
@@ -7351,9 +7375,13 @@ function GridCell({
7351
7375
  /* @__PURE__ */ jsx28(
7352
7376
  "span",
7353
7377
  {
7378
+ ref: closedControl,
7354
7379
  role: editable ? "button" : void 0,
7355
7380
  "aria-label": editable ? `Edit ${fieldName(field2)}` : void 0,
7356
- title: cellMark ? cellMark : editable ? `${fieldName(field2)} \u2014 double-click or press Enter to edit` : fieldIsEditable(field2) ? (
7381
+ title: cellMark ? cellMark : editable ? (
7382
+ // The words match the gesture (WALK3-FIX): one click opens a record-page field.
7383
+ clickOpens ? `${fieldName(field2)} \u2014 click to edit` : `${fieldName(field2)} \u2014 double-click or press Enter to edit`
7384
+ ) : fieldIsEditable(field2) ? (
7357
7385
  // NOT A DEAD CONTROL AND NOT A SILENT ONE: the cell is plain
7358
7386
  // text, and hovering it says exactly why, in the level's own
7359
7387
  // words. The editor was never offered, so nothing is refused
@@ -8655,6 +8683,7 @@ function ChoiceValue({ field: field2, value, onChange }) {
8655
8683
  return /* @__PURE__ */ jsx34("span", { className: "inline-flex min-w-0", "data-records-condition-choice": "", "data-pick-value": value, children: /* @__PURE__ */ jsx34(
8656
8684
  PickOrAdd,
8657
8685
  {
8686
+ triggerSize: "compact",
8658
8687
  triggerLabel: `Choice for ${field2.label || field2.key}`,
8659
8688
  triggerText: value || "Choose",
8660
8689
  open,
@@ -8878,6 +8907,7 @@ function ConditionGroup({
8878
8907
  onChange,
8879
8908
  emptyLabel = "always",
8880
8909
  allowNesting = true,
8910
+ maxDepth,
8881
8911
  depth = 0,
8882
8912
  onRemove,
8883
8913
  className
@@ -8948,6 +8978,7 @@ function ConditionGroup({
8948
8978
  onChange: (next) => replaceAt(index, next),
8949
8979
  onRemove: () => replaceAt(index, null),
8950
8980
  emptyLabel,
8981
+ maxDepth,
8951
8982
  depth: depth + 1
8952
8983
  }
8953
8984
  ) }, key);
@@ -8991,7 +9022,7 @@ function ConditionGroup({
8991
9022
  children: "Add another condition"
8992
9023
  }
8993
9024
  ),
8994
- allowNesting ? /* @__PURE__ */ jsx34(
9025
+ allowNesting && (maxDepth === void 0 || depth < maxDepth) ? /* @__PURE__ */ jsx34(
8995
9026
  Button14,
8996
9027
  {
8997
9028
  size: "sm",
@@ -10663,6 +10694,7 @@ function FieldEditor(props) {
10663
10694
  const [formulaWritten, setFormulaWritten] = useState31(() => startingFormulaText.trim() !== "" || Boolean(field2));
10664
10695
  const [formulaOk, setFormulaOk] = useState31(null);
10665
10696
  const [askingToRemove, setAskingToRemove] = useState31(false);
10697
+ const [attempted, setAttempted] = useState31(false);
10666
10698
  const [showsAs, setShowsAs] = useState31(() => field2 ? startingChoice(field2) : { format: null, retype: null });
10667
10699
  const [backfillError, setBackfillError] = useState31(null);
10668
10700
  const [notLanded, setNotLanded] = useState31(false);
@@ -10712,6 +10744,8 @@ function FieldEditor(props) {
10712
10744
  const missing = editing ? formulaMissing ?? (listsChoices ? whatTheChoiceListIsMissing(choiceList) : null) : formulaMissing ?? (listsChoices ? whatTheChoiceListIsMissing(newChoices) : null) ?? (type === "formula" && formulaWritten ? null : whatIsMissing(
10713
10745
  type === "count" ? { type: "rollup", label, options, via, pick, agg: "count", of, relations: totalSources, formulaFields, relationTarget, allowedTypes, noun } : { type, label, options, via, pick, agg, of, relations: sourcesFor(type), formulaFields, relationTarget, allowedTypes, noun }
10714
10746
  ));
10747
+ const missingShown = missing && (attempted || formulaMissing) ? missing : null;
10748
+ const removeConsequence = field2 ? `${archiveConfirmSentence(`the field \u201C${fieldName(field2)}\u201D`)} Its values stay in every record and come back with it.${linkReaders.length > 0 ? ` ${readersSentence(linkReaders.length, farTable.data?.name ?? null)}` : ""}` : "";
10715
10749
  const save = async () => {
10716
10750
  if (missing) return;
10717
10751
  if (editing && field2) {
@@ -10859,8 +10893,8 @@ function FieldEditor(props) {
10859
10893
  const remove = async () => {
10860
10894
  if (!field2) return;
10861
10895
  const gone = await tableMutation.retire({ field_id: field2.id });
10862
- setAskingToRemove(false);
10863
10896
  if (gone) (onRemoved ?? onSaved)?.();
10897
+ else setAskingToRemove(false);
10864
10898
  };
10865
10899
  return /* @__PURE__ */ jsxs35("div", { className: cn24("flex min-w-0 flex-col gap-3 text-xs", className), "data-records-field-editor": "", children: [
10866
10900
  /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2", children: [
@@ -10877,7 +10911,12 @@ function FieldEditor(props) {
10877
10911
  autoFocus: true,
10878
10912
  "aria-label": "Field name",
10879
10913
  placeholder: "Day rate",
10880
- onChange: (e) => setLabel(e.target.value)
10914
+ onChange: (e) => setLabel(e.target.value),
10915
+ onBlur: (e) => {
10916
+ const to = e.relatedTarget;
10917
+ if (to?.textContent === "Cancel") return;
10918
+ setAttempted(true);
10919
+ }
10881
10920
  }
10882
10921
  )
10883
10922
  ] }),
@@ -11165,7 +11204,6 @@ function FieldEditor(props) {
11165
11204
  ] }),
11166
11205
  editing && field2 && !onEntity && rulesChanged(savedRules, editedRules) ? /* @__PURE__ */ jsx40(FieldRulePreview, { field: field2, tableId, rules: editedRules }) : null,
11167
11206
  mutation.error ? /* @__PURE__ */ jsx40(RefusalNotice, { error: mutation.error }) : null,
11168
- missing ? /* @__PURE__ */ jsx40("p", { id: "field-editor-missing", className: "text-[11px] text-muted-foreground", "data-field-editor-missing": "", children: missing }) : null,
11169
11207
  notLanded ? /* @__PURE__ */ jsx40(
11170
11208
  RefusalNotice,
11171
11209
  {
@@ -11210,27 +11248,61 @@ function FieldEditor(props) {
11210
11248
  /* @__PURE__ */ jsx40("p", { className: "text-[11px]", children: "The column is made. The records already in the table were not numbered:" }),
11211
11249
  /* @__PURE__ */ jsx40(RefusalNotice, { error: backfillError })
11212
11250
  ] }) : null,
11213
- /* @__PURE__ */ jsxs35("div", { className: "flex flex-wrap items-center gap-2", children: [
11251
+ /* @__PURE__ */ jsxs35("div", { className: "flex min-w-0 flex-nowrap items-center gap-2", "data-field-editor-footer": "", children: [
11214
11252
  /* @__PURE__ */ jsx40(
11215
11253
  Button17,
11216
11254
  {
11217
11255
  size: "sm",
11218
- disabled: mutation.saving || missing !== null,
11219
- ...missing ? { "aria-describedby": "field-editor-missing" } : {},
11220
- onClick: () => void save(),
11256
+ disabled: mutation.saving || Boolean(formulaMissing),
11257
+ ...missingShown ? { "aria-describedby": "field-editor-missing" } : {},
11258
+ onClick: () => {
11259
+ if (missing) {
11260
+ setAttempted(true);
11261
+ return;
11262
+ }
11263
+ void save();
11264
+ },
11221
11265
  children: mutation.saving ? "Saving\u2026" : field2 ? EDIT_FIELD_SAVE_LABEL : onEntity ? ADD_STANDARD_FIELD_SAVE_LABEL : ADD_FIELD_SAVE_LABEL
11222
11266
  }
11223
11267
  ),
11224
11268
  onCancel ? /* @__PURE__ */ jsx40(Button17, { size: "sm", variant: "ghost", onClick: onCancel, children: "Cancel" }) : null,
11225
- field2 ? /* @__PURE__ */ jsx40("div", { className: "ml-auto flex items-center gap-2", children: askingToRemove ? /* @__PURE__ */ jsxs35(Fragment12, { children: [
11226
- /* @__PURE__ */ jsxs35("span", { className: "text-destructive", children: [
11227
- archiveConfirmSentence(`the field \u201C${fieldName(field2)}\u201D`),
11228
- " Its values stay in every record and come back with it.",
11229
- linkReaders.length > 0 ? ` ${readersSentence(linkReaders.length, farTable.data?.name ?? null)}` : ""
11230
- ] }),
11231
- /* @__PURE__ */ jsx40(Button17, { size: "sm", variant: "destructive", disabled: mutation.saving, onClick: () => void remove(), children: "Remove it" }),
11232
- /* @__PURE__ */ jsx40(Button17, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(false), children: "Keep it" })
11233
- ] }) : /* @__PURE__ */ jsx40(Button17, { size: "sm", variant: "outline", onClick: () => setAskingToRemove(true), children: "Remove this field" }) }) : null
11269
+ /* @__PURE__ */ jsx40(
11270
+ "span",
11271
+ {
11272
+ id: "field-editor-missing",
11273
+ "data-field-editor-missing": missingShown ? "" : void 0,
11274
+ title: missingShown ?? void 0,
11275
+ className: "min-w-0 flex-1 truncate text-[11px] text-muted-foreground",
11276
+ children: missingShown
11277
+ }
11278
+ ),
11279
+ field2 ? (
11280
+ /* WALK3-FIX (2026-10-07): the confirm appears IN PLACE. Both states share one grid cell,
11281
+ the idle one drawn invisibly under the asking one and the other way round, so the
11282
+ footer is always as wide and as tall as its larger state: opening the confirm moved
11283
+ the dialog card 0.2278 and closing it 0.1303. The consequence is the tooltip of
11284
+ "Remove it" (interface-text: the slot holds a label, never a paragraph). */
11285
+ /* @__PURE__ */ jsxs35("div", { className: "ml-auto grid shrink-0 items-center", "data-field-editor-remove": askingToRemove ? "asking" : "idle", children: [
11286
+ /* @__PURE__ */ jsx40("div", { className: cn24("col-start-1 row-start-1 flex items-center justify-end gap-2", askingToRemove && "invisible"), "aria-hidden": askingToRemove || void 0, children: /* @__PURE__ */ jsx40(Button17, { size: "sm", variant: "outline", tabIndex: askingToRemove ? -1 : void 0, onClick: () => setAskingToRemove(true), children: "Remove this field" }) }),
11287
+ /* @__PURE__ */ jsxs35("div", { className: cn24("col-start-1 row-start-1 flex items-center justify-end gap-2", !askingToRemove && "invisible"), "aria-hidden": !askingToRemove || void 0, children: [
11288
+ /* @__PURE__ */ jsx40("span", { className: "whitespace-nowrap text-destructive", "data-field-editor-remove-ask": "", children: "Archive this field?" }),
11289
+ /* @__PURE__ */ jsx40(
11290
+ Button17,
11291
+ {
11292
+ size: "sm",
11293
+ variant: "destructive",
11294
+ tabIndex: askingToRemove ? void 0 : -1,
11295
+ disabled: mutation.saving,
11296
+ title: removeConsequence,
11297
+ "aria-description": removeConsequence,
11298
+ onClick: () => void remove(),
11299
+ children: "Remove it"
11300
+ }
11301
+ ),
11302
+ /* @__PURE__ */ jsx40(Button17, { size: "sm", variant: "ghost", tabIndex: askingToRemove ? void 0 : -1, onClick: () => setAskingToRemove(false), children: "Keep it" })
11303
+ ] })
11304
+ ] })
11305
+ ) : null
11234
11306
  ] })
11235
11307
  ] });
11236
11308
  }
@@ -14137,6 +14209,13 @@ function useMergedGridControls(args) {
14137
14209
  const tableVersion = useTableVersion(client, tableId, table);
14138
14210
  const mutation = useFieldMutation2();
14139
14211
  const [dialog, setDialog] = useState42(null);
14212
+ const lastColumnDialog = useRef22(null);
14213
+ const insertEpoch = useRef22(0);
14214
+ if (dialog?.kind === "settings" || dialog?.kind === "insert") {
14215
+ if (dialog !== lastColumnDialog.current) insertEpoch.current += 1;
14216
+ lastColumnDialog.current = dialog;
14217
+ }
14218
+ const columnDialog = dialog?.kind === "settings" || dialog?.kind === "insert" ? dialog : lastColumnDialog.current;
14140
14219
  const [renameTo, setRenameTo] = useState42("");
14141
14220
  const [deleteBusy, setDeleteBusy] = useState42(false);
14142
14221
  const [labelling, setLabelling] = useState42(false);
@@ -14626,27 +14705,27 @@ function useMergedGridControls(args) {
14626
14705
  },
14627
14706
  children: /* @__PURE__ */ jsxs47(DialogContent8, { className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl", children: [
14628
14707
  /* @__PURE__ */ jsxs47(DialogHeader8, { children: [
14629
- /* @__PURE__ */ jsx52(DialogTitle8, { children: dialog?.kind === "settings" ? `Column settings \xB7 ${fieldName(dialog.field)}` : dialog?.kind === "insert" && dialog.anchor ? `Add column ${dialog.side === "left" ? "left" : "right"} of ${fieldOf2(dialog.anchor) ? fieldName(fieldOf2(dialog.anchor)) : dialog.anchor}` : ADD_FIELD_OPEN_LABEL }),
14708
+ /* @__PURE__ */ jsx52(DialogTitle8, { children: columnDialog?.kind === "settings" ? `Column settings \xB7 ${fieldName(columnDialog.field)}` : columnDialog?.kind === "insert" && columnDialog.anchor ? `Add column ${columnDialog.side === "left" ? "left" : "right"} of ${fieldOf2(columnDialog.anchor) ? fieldName(fieldOf2(columnDialog.anchor)) : columnDialog.anchor}` : ADD_FIELD_OPEN_LABEL }),
14630
14709
  /* @__PURE__ */ jsx52(DialogDescription6, { className: "sr-only", children: "Name, type and look" })
14631
14710
  ] }),
14632
- dialog?.kind === "settings" ? /* @__PURE__ */ jsx52(
14711
+ columnDialog?.kind === "settings" ? /* @__PURE__ */ jsx52(
14633
14712
  FieldEditor,
14634
14713
  {
14635
14714
  tableId,
14636
- field: dialog.field,
14715
+ field: columnDialog.field,
14637
14716
  onSaved: () => {
14638
- notify?.success(`${fieldName(dialog.field)} is saved.`);
14717
+ notify?.success(`${fieldName(columnDialog.field)} is saved.`);
14639
14718
  close();
14640
14719
  },
14641
14720
  onRemoved: () => {
14642
- if (args.onColumnRetired) args.onColumnRetired(dialog.field);
14643
- else notify?.success(`${fieldName(dialog.field)} is gone from the table. It can be brought back from Archived.`);
14721
+ if (args.onColumnRetired) args.onColumnRetired(columnDialog.field);
14722
+ else notify?.success(`${fieldName(columnDialog.field)} is gone from the table. It can be brought back from Archived.`);
14644
14723
  close();
14645
14724
  },
14646
14725
  onCancel: close
14647
14726
  },
14648
- dialog.field.id
14649
- ) : dialog?.kind === "insert" ? /* @__PURE__ */ jsx52(
14727
+ `${columnDialog.field.id}-${insertEpoch.current}`
14728
+ ) : columnDialog?.kind === "insert" ? /* @__PURE__ */ jsx52(
14650
14729
  FieldEditor,
14651
14730
  {
14652
14731
  tableId,
@@ -14655,10 +14734,11 @@ function useMergedGridControls(args) {
14655
14734
  close();
14656
14735
  },
14657
14736
  onSaved: () => {
14658
- pendingInsert.current = { before: new Set(fieldKeys), anchor: dialog.anchor, side: dialog.side };
14737
+ pendingInsert.current = { before: new Set(fieldKeys), anchor: columnDialog.anchor, side: columnDialog.side };
14659
14738
  close();
14660
14739
  }
14661
- }
14740
+ },
14741
+ `insert-${insertEpoch.current}`
14662
14742
  ) : null
14663
14743
  ] })
14664
14744
  }
@@ -20228,7 +20308,7 @@ function UndoRedoPair({ face, onUndo, onRedo }) {
20228
20308
  )
20229
20309
  ] });
20230
20310
  }
20231
- function UndoNotice({ undo }) {
20311
+ function UndoNotice({ undo, placement = "frame" }) {
20232
20312
  const notice = undo.notice;
20233
20313
  const dismiss = undo.dismiss;
20234
20314
  useEffect44(() => {
@@ -20242,7 +20322,8 @@ function UndoNotice({ undo }) {
20242
20322
  {
20243
20323
  role: "status",
20244
20324
  "data-records-undo-notice": notice.refusal ? "refused" : "done",
20245
- className: "pointer-events-auto absolute bottom-3 left-1/2 z-30 flex max-w-[min(36rem,calc(100%-2rem))] -translate-x-1/2 items-center gap-2 rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-lg",
20325
+ "data-records-undo-placement": placement,
20326
+ className: `pointer-events-auto ${placement === "viewport" ? "fixed bottom-4 z-50" : "absolute bottom-3 z-30"} left-1/2 flex max-w-[min(36rem,calc(100%-2rem))] -translate-x-1/2 items-center gap-2 rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-lg`,
20246
20327
  children: [
20247
20328
  /* @__PURE__ */ jsx66("span", { children: notice.says }),
20248
20329
  notice.canUndo && undo.canUndo ? /* @__PURE__ */ jsx66(Button38, { size: "sm", variant: "outline", className: "ml-auto h-6", "data-records-undo-now": "", onClick: () => void undo.undo(), children: "Undo" }) : null,
@@ -21597,6 +21678,11 @@ function GridBody({
21597
21678
  page: query.page
21598
21679
  });
21599
21680
  }, [reportQuery, query.sort, query.columnFilters, query.search, query.page, openingKey]);
21681
+ const liveKeys = useMemo35(() => fields.data ? new Set(fields.data.map((f) => f.key)) : null, [fields.data]);
21682
+ useEffect47(() => {
21683
+ if (!liveKeys) return;
21684
+ setQuery((held) => withoutGoneColumns(held, liveKeys, openingSort));
21685
+ }, [liveKeys]);
21600
21686
  const resetEpoch = address?.resetEpoch ?? 0;
21601
21687
  const resetSeen = useRef31(resetEpoch);
21602
21688
  useEffect47(() => {
@@ -21650,7 +21736,9 @@ function GridBody({
21650
21736
  page: classicInCard ? 0 : page,
21651
21737
  ...readFilter ? { filter: readFilter } : {}
21652
21738
  });
21653
- const pageAsked = !storePaged || Boolean(fields.data && !table.loading);
21739
+ const pageAskedFor = useRef31(null);
21740
+ if (fields.data && !table.loading) pageAskedFor.current = tableId;
21741
+ const pageAsked = !storePaged || Boolean(fields.data) && pageAskedFor.current === tableId;
21654
21742
  const serverRead = useRecordPage(storePaged && pageAsked ? tableId : null, {
21655
21743
  pageSize: query.pageSize,
21656
21744
  page: Math.max(0, query.page - 1),
@@ -23102,7 +23190,7 @@ function GridBody({
23102
23190
  }, [rowsAnswered, rowMemoryKey, rows.length, embedded, tableId]);
23103
23191
  if (table.error) return /* @__PURE__ */ jsx70(RefusalNotice, { error: table.error, className });
23104
23192
  if (fields.error) return /* @__PURE__ */ jsx70(RefusalNotice, { error: fields.error, className });
23105
- const nothingToShowYet = table.loading || fields.loading || !rights.known || records.loading && rows.length === 0 || // The page read has neither answered nor refused: nothing has been said yet, so nothing is empty.
23193
+ const nothingToShowYet = table.loading && !table.data || fields.loading && !fields.data || !rights.known || records.loading && rows.length === 0 || // The page read has neither answered nor refused: nothing has been said yet, so nothing is empty.
23106
23194
  !records.data && !records.error || countedButNotReturned || manual && hand.loading && rows.length === 0;
23107
23195
  const refreshing = !nothingToShowYet && (records.loading || manual && hand.loading);
23108
23196
  const noColumnsYet = !nothingToShowYet && (fields.data ?? []).length === 0;
@@ -23557,6 +23645,15 @@ function withHeaderMenus(columns, menuFor, filterFor) {
23557
23645
  return filter ? { ...withMenu, ...filter } : withMenu;
23558
23646
  });
23559
23647
  }
23648
+ function withoutGoneColumns(query, liveKeys, openingSort) {
23649
+ const sortGone = Boolean(query.sort && !liveKeys.has(query.sort.id));
23650
+ const goneFilters = Object.keys(query.columnFilters ?? {}).filter((key) => !liveKeys.has(key));
23651
+ if (!sortGone && goneFilters.length === 0) return query;
23652
+ const columnFilters = { ...query.columnFilters };
23653
+ for (const key of goneFilters) delete columnFilters[key];
23654
+ const fallback = openingSort && liveKeys.has(openingSort.id) ? openingSort : null;
23655
+ return { ...query, sort: sortGone ? fallback : query.sort, columnFilters, page: 1 };
23656
+ }
23560
23657
 
23561
23658
  // src/RecordsTableCard.tsx
23562
23659
  import { MatrxTableCard, MatrxTableCardEmpty, MatrxTableCardShowMore as MatrxTableCardShowMore2, TABLE_CARD_DEFAULT_INITIAL_ROWS } from "@ai-matrx/design-system/data-table";
@@ -28780,7 +28877,7 @@ function RecordFieldsInPlace({
28780
28877
  /* @__PURE__ */ jsx94(
28781
28878
  "dd",
28782
28879
  {
28783
- className: "min-w-0 rounded hover:bg-muted/50 [&_[data-matrx-cell-editor]_input:not([type=checkbox]):not([type=radio])]:!h-7",
28880
+ className: "min-w-0 rounded hover:bg-muted/50 [&_[data-matrx-cell-editor]_input:not([type=checkbox]):not([type=radio])]:!h-7 [&_[data-pick-anchor]]:!w-fit",
28784
28881
  "data-records-in-place-field": field2.key,
28785
28882
  children: /* @__PURE__ */ jsx94(
28786
28883
  GridCell,
@@ -28798,7 +28895,7 @@ function RecordFieldsInPlace({
28798
28895
  )
28799
28896
  ] }, field2.id);
28800
28897
  }) }),
28801
- /* @__PURE__ */ jsx94(UndoNotice, { undo })
28898
+ /* @__PURE__ */ jsx94(UndoNotice, { undo, placement: "viewport" })
28802
28899
  ]
28803
28900
  }
28804
28901
  );
@@ -29024,6 +29121,7 @@ function Peek({ tableId, recordId, onClose, className, onPage = false }) {
29024
29121
  if (record.error) return /* @__PURE__ */ jsx97(RefusalNotice, { error: record.error, className });
29025
29122
  if (fields.error) return /* @__PURE__ */ jsx97(RefusalNotice, { error: fields.error, className });
29026
29123
  const document2 = record.data?.document;
29124
+ const computedShown = computedLinesWorthShowing(record.data?.computed, document2);
29027
29125
  const title = document2 ? recordNameIn(table.data, document2) : "";
29028
29126
  const bodyKey = onPage ? bodyFieldOf(fields.data ?? [])?.key : void 0;
29029
29127
  const shownFields = (fields.data ?? []).filter((field2) => !onPage || !drawnBelowOnPage(field2, bodyKey));
@@ -29123,15 +29221,20 @@ function Peek({ tableId, recordId, onClose, className, onPage = false }) {
29123
29221
  /* @__PURE__ */ jsx97(Separator9, {}),
29124
29222
  /* @__PURE__ */ jsx97("p", { className: "text-muted-foreground", children: "Nothing here can say which columns the system worked out for itself: that part of the record is not readable from your seat. Everything above is the record as it stands." })
29125
29223
  ] }) : null,
29126
- record.data?.computed?.length ? /* @__PURE__ */ jsxs88(Fragment36, { children: [
29224
+ computedShown.length ? /* @__PURE__ */ jsxs88(Fragment36, { children: [
29127
29225
  /* @__PURE__ */ jsx97(Separator9, {}),
29128
29226
  /* @__PURE__ */ jsxs88("div", { className: "flex flex-col gap-1", children: [
29129
29227
  /* @__PURE__ */ jsx97("span", { className: "font-medium", children: "Worked out by the system" }),
29130
- record.data.computed.map((row, i) => /* @__PURE__ */ jsx97("p", { className: "text-muted-foreground", children: computedInPlainWords(row, (fields.data ?? []).find((f) => f.key === row.field_key)) }, i))
29228
+ computedShown.map((row, i) => /* @__PURE__ */ jsx97("p", { className: "text-muted-foreground", children: computedInPlainWords(row, (fields.data ?? []).find((f) => f.key === row.field_key)) }, i))
29131
29229
  ] })
29132
29230
  ] }) : null
29133
29231
  ] });
29134
29232
  }
29233
+ function computedLinesWorthShowing(rows, document2) {
29234
+ return (rows ?? []).filter(
29235
+ (row) => !((row.value === null || row.value === void 0 || row.value === "") && workedOutErrorFor(document2, row.field_key))
29236
+ );
29237
+ }
29135
29238
  function computedInPlainWords(row, field2) {
29136
29239
  const name = field2 ? fieldName(field2) : humanizeIdentifier12(row.field_key);
29137
29240
  const shown4 = row.value === null || row.value === void 0 || row.value === "" ? "nothing" : field2 ? scalarText(field2, row.value) : String(row.value);
@@ -36279,7 +36382,8 @@ function HistoryPanel({ tableId, recordId, onAskAboutField, className }) {
36279
36382
  ref: listRef,
36280
36383
  role: "listbox",
36281
36384
  "aria-label": "Versions of this record, newest first",
36282
- className: "flex min-h-0 flex-col divide-y overflow-y-auto rounded border",
36385
+ "data-records-history-list": "",
36386
+ className: "flex max-h-[9.25rem] min-h-0 flex-col divide-y overflow-y-auto rounded border",
36283
36387
  onKeyDown: (event) => {
36284
36388
  const at = entries.findIndex((e) => e.version === open);
36285
36389
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
@@ -47712,7 +47816,7 @@ function TablePage({
47712
47816
  if (hostFromLink || opened.layout || layoutChosen) return;
47713
47817
  setHostLayout(face.hostLayout);
47714
47818
  }, [face.kind, face.kind === "designated" ? face.hostLayout : null, layoutChosen]);
47715
- const faceUnknown = !hostFromLink && !opened.layout && (views === void 0 || Array.isArray(views) && views.length > 0 && (view === null || looks === void 0));
47819
+ const faceUnknown = !hostFromLink && !opened.layout && (views === void 0 || looks === void 0 || Array.isArray(views) && views.length > 0 && view === null);
47716
47820
  const shownLayout = layoutFromLink ?? shown4?.layout ?? "grid";
47717
47821
  const shownFilter = filter ?? (shown4?.where ? shown4.where : null) ?? (shown4?.filters && Object.keys(shown4.filters).length > 0 ? shown4.filters : null);
47718
47822
  const press = (pressed) => {