From fb8de2dbe75713c63d10357806198ea4e2bf661f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?=
 <jaroslaw.sadzinski@gmail.com>
Date: Wed, 2 Oct 2024 10:00:43 +0200
Subject: [PATCH 1/7] Sorting mappable columns

---
 app/client/ui/CustomSectionConfig.ts | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/app/client/ui/CustomSectionConfig.ts b/app/client/ui/CustomSectionConfig.ts
index 9163bb11..52e0f78c 100644
--- a/app/client/ui/CustomSectionConfig.ts
+++ b/app/client/ui/CustomSectionConfig.ts
@@ -75,11 +75,13 @@ class ColumnPicker extends Disposable {
       void use(refreshTrigger);
 
       const columnsAsOptions: IOption<number|null>[] = use(canBeMapped)
-                                              .map((col) => ({
-                                                value: col.getRowId(),
-                                                label: col.label.peek(),
-                                                icon: 'FieldColumn',
-                                              }));
+        .map((col) => ({
+          value: col.getRowId(),
+          label: col.label.peek() || '',
+          icon: 'FieldColumn',
+        }));
+      // Order it by label.
+      columnsAsOptions.sort((a, b) => a.label.localeCompare(b.label));
 
       // For optional mappings, add 'Blank' option but only if the value is set.
       // This option will allow to clear the selection.

From 179272e3f26601e4021fd516fa6473107d9b21cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?=
 <jaroslaw.sadzinski@gmail.com>
Date: Wed, 2 Oct 2024 13:16:10 +0200
Subject: [PATCH 2/7] Sorting column names

---
 app/client/models/entities/ColumnRec.ts | 17 +++++++++++++++++
 app/client/ui/CustomSectionConfig.ts    |  5 +++--
 app/client/ui/PageWidgetPicker.ts       |  3 ++-
 3 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/app/client/models/entities/ColumnRec.ts b/app/client/models/entities/ColumnRec.ts
index 3791baa6..8a4b8aa7 100644
--- a/app/client/models/entities/ColumnRec.ts
+++ b/app/client/models/entities/ColumnRec.ts
@@ -230,6 +230,23 @@ export function formatterForRec(
   return func(args);
 }
 
+export function labelsOrder(a: ColumnRec, b: ColumnRec): number {
+  const left  = a.label.peek().toLowerCase();
+  const right = b.label.peek().toLowerCase();
+
+  // Order is as follows:
+  // - First columns with normal labels starting with a letter.
+  // - Second all columns starting with '_' (treated as private)
+  // - Third all columns starting with '#' (treated as private)
+  // - Rest.
+  if (left[0] === '_' && right[0] !== '_') { return 1; }
+  if (left[0] !== '_' && right[0] === '_') { return -1; }
+  if (left[0] === '#' && right[0] !== '#') { return 1; }
+  if (left[0] !== '#' && right[0] === '#') { return -1; }
+  return left.localeCompare(right);
+}
+
+
 /**
  * A chat message. Either send by the user or by the AI.
  */
diff --git a/app/client/ui/CustomSectionConfig.ts b/app/client/ui/CustomSectionConfig.ts
index 52e0f78c..4ce4bf77 100644
--- a/app/client/ui/CustomSectionConfig.ts
+++ b/app/client/ui/CustomSectionConfig.ts
@@ -7,6 +7,7 @@ import {makeT} from 'app/client/lib/localization';
 import {localStorageBoolObs} from 'app/client/lib/localStorageObs';
 import {ColumnToMapImpl} from 'app/client/models/ColumnToMap';
 import {ColumnRec, ViewSectionRec} from 'app/client/models/DocModel';
+import {labelsOrder} from 'app/client/models/entities/ColumnRec';
 import {
   cssDeveloperLink,
   cssWidgetMetadata,
@@ -75,13 +76,12 @@ class ColumnPicker extends Disposable {
       void use(refreshTrigger);
 
       const columnsAsOptions: IOption<number|null>[] = use(canBeMapped)
+        .sort(labelsOrder)
         .map((col) => ({
           value: col.getRowId(),
           label: col.label.peek() || '',
           icon: 'FieldColumn',
         }));
-      // Order it by label.
-      columnsAsOptions.sort((a, b) => a.label.localeCompare(b.label));
 
       // For optional mappings, add 'Blank' option but only if the value is set.
       // This option will allow to clear the selection.
@@ -205,6 +205,7 @@ class ColumnListPicker extends Disposable {
             const wrongTypeCount = notMapped.get().length - typedColumns.get().length;
             return [
               ...typedColumns.get()
+              .sort(labelsOrder)
               .map((col) => menuItem(
                 () => this._addColumn(col),
                 col.label.peek(),
diff --git a/app/client/ui/PageWidgetPicker.ts b/app/client/ui/PageWidgetPicker.ts
index dbd7cf84..8c64b736 100644
--- a/app/client/ui/PageWidgetPicker.ts
+++ b/app/client/ui/PageWidgetPicker.ts
@@ -32,6 +32,7 @@ import {
 import Popper from 'popper.js';
 import {IOpenController, popupOpen, setPopupToCreateDom} from 'popweasel';
 import without = require('lodash/without');
+import {labelsOrder} from 'app/client/models/entities/ColumnRec';
 
 const t = makeT('PageWidgetPicker');
 
@@ -407,7 +408,7 @@ export class PageWidgetSelect extends Disposable {
             (use) => use(this._columns)
               .filter((col) => !col.isHiddenCol() && col.parentId() === use(this._value.table)),
             (cols) => cols ?
-              dom.forEach(cols, (col) =>
+              dom.forEach([...cols].sort(labelsOrder), (col) =>
                 cssEntry(cssIcon('FieldColumn'), cssFieldLabel(dom.text(col.label)),
                   dom.on('click', () => this._toggleColumnId(col.id())),
                   cssEntry.cls('-selected', (use) => use(this._value.columns).includes(col.id())),

From 9806e1a4c0ddfdb94fd3cc6e3aecf1388cb1d941 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?=
 <jaroslaw.sadzinski@gmail.com>
Date: Mon, 7 Oct 2024 02:42:38 +0200
Subject: [PATCH 3/7] inputs

---
 app/client/models/entities/ViewSectionRec.ts | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/app/client/models/entities/ViewSectionRec.ts b/app/client/models/entities/ViewSectionRec.ts
index 181fcd86..2dc36c77 100644
--- a/app/client/models/entities/ViewSectionRec.ts
+++ b/app/client/models/entities/ViewSectionRec.ts
@@ -16,7 +16,7 @@ import {
   ViewFieldRec,
   ViewRec
 } from 'app/client/models/DocModel';
-import {BEHAVIOR} from 'app/client/models/entities/ColumnRec';
+import {BEHAVIOR, labelsOrder} from 'app/client/models/entities/ColumnRec';
 import * as modelUtil from 'app/client/models/modelUtil';
 import {removeRule, RuleOwner} from 'app/client/models/RuleOwner';
 import {LinkConfig} from 'app/client/ui/selectBy';
@@ -698,7 +698,9 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
   // Evaluates to an array of column models, which are not referenced by anything in viewFields.
   this.hiddenColumns = this.autoDispose(ko.pureComputed(() => {
     const included = new Set(this.viewFields().all().map((f) => f.column().origColRef()));
-    return this.columns().filter(c => !included.has(c.getRowId()));
+    return this.columns()
+      .filter(c => !included.has(c.getRowId()))
+      .sort(labelsOrder);
   }));
 
   this.hasFocus = ko.pureComputed({

From 4cde2202b545f4ecb665beb136e2c1df7f5af1db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?=
 <jaroslaw.sadzinski@gmail.com>
Date: Wed, 9 Oct 2024 11:12:21 +0200
Subject: [PATCH 4/7] Allowing new row on recrod card

---
 app/client/components/GristDoc.ts        | 2 +-
 app/client/components/RecordCardPopup.ts | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/app/client/components/GristDoc.ts b/app/client/components/GristDoc.ts
index 9eff9dbc..a3416052 100644
--- a/app/client/components/GristDoc.ts
+++ b/app/client/components/GristDoc.ts
@@ -701,7 +701,7 @@ export class GristDoc extends DisposableWithEvents {
 
                 const {recordCard, rowId} = popupOptions.hash;
                 if (recordCard) {
-                  if (!rowId || rowId === 'new') {
+                  if (!rowId) {
                     // Should be unreachable, but just to be sure (and to satisfy type checking)...
                     throw new Error('Unable to open Record Card: undefined row id');
                   }
diff --git a/app/client/components/RecordCardPopup.ts b/app/client/components/RecordCardPopup.ts
index 7fc1efac..de30ed10 100644
--- a/app/client/components/RecordCardPopup.ts
+++ b/app/client/components/RecordCardPopup.ts
@@ -7,13 +7,14 @@ import {ViewSectionRec} from 'app/client/models/DocModel';
 import {ChangeType, RowList} from 'app/client/models/rowset';
 import {theme} from 'app/client/ui2018/cssVars';
 import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
+import {UIRowId} from 'app/plugin/GristAPI';
 import {dom, makeTestId, styled} from 'grainjs';
 
 const testId = makeTestId('test-record-card-popup-');
 
 interface RecordCardPopupOptions {
   gristDoc: GristDoc;
-  rowId: number;
+  rowId: UIRowId;
   viewSection: ViewSectionRec;
   onClose(): void;
 }

From 1315cc366fa634f750292aeaa4c5ffe470017705 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?=
 <jaroslaw.sadzinski@gmail.com>
Date: Wed, 9 Oct 2024 11:13:31 +0200
Subject: [PATCH 5/7] Adding sort order for filters

---
 app/client/models/entities/ColumnRec.ts      | 11 ++++++++---
 app/client/models/entities/ViewSectionRec.ts |  2 +-
 app/client/ui/CustomSectionConfig.ts         |  7 ++++---
 app/client/ui/SortConfig.ts                  |  3 ++-
 4 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/app/client/models/entities/ColumnRec.ts b/app/client/models/entities/ColumnRec.ts
index 8a4b8aa7..2ecdd471 100644
--- a/app/client/models/entities/ColumnRec.ts
+++ b/app/client/models/entities/ColumnRec.ts
@@ -230,9 +230,14 @@ export function formatterForRec(
   return func(args);
 }
 
-export function labelsOrder(a: ColumnRec, b: ColumnRec): number {
-  const left  = a.label.peek().toLowerCase();
-  const right = b.label.peek().toLowerCase();
+type ColumnInfo = {label: string}|{label: ko.Observable<string>};
+function peekLabel(info: ColumnInfo): string {
+  return typeof info.label === 'string' ? info.label : info.label.peek();
+}
+
+export function labelsOrder(a: ColumnInfo, b: ColumnInfo): number {
+  const left  = peekLabel(a).toLowerCase();
+  const right = peekLabel(b).toLowerCase();
 
   // Order is as follows:
   // - First columns with normal labels starting with a letter.
diff --git a/app/client/models/entities/ViewSectionRec.ts b/app/client/models/entities/ViewSectionRec.ts
index 2dc36c77..c99849f0 100644
--- a/app/client/models/entities/ViewSectionRec.ts
+++ b/app/client/models/entities/ViewSectionRec.ts
@@ -521,7 +521,7 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
     const savedFiltersByColRef = new Map(this._savedFilters().all().map(f => [f.colRef(), f]));
     const viewFieldsByColRef = new Map(this.viewFields().all().map(f => [f.origCol().getRowId(), f]));
 
-    return this.columns().map(column => {
+    return [...this.columns()].sort(labelsOrder).map(column => {
       const savedFilter = savedFiltersByColRef.get(column.origColRef());
       // Initialize with a saved filter, if one exists. Otherwise, use a blank filter.
       const filter = modelUtil.customComputed({
diff --git a/app/client/ui/CustomSectionConfig.ts b/app/client/ui/CustomSectionConfig.ts
index 4ce4bf77..82245f3e 100644
--- a/app/client/ui/CustomSectionConfig.ts
+++ b/app/client/ui/CustomSectionConfig.ts
@@ -76,12 +76,13 @@ class ColumnPicker extends Disposable {
       void use(refreshTrigger);
 
       const columnsAsOptions: IOption<number|null>[] = use(canBeMapped)
-        .sort(labelsOrder)
         .map((col) => ({
           value: col.getRowId(),
           label: col.label.peek() || '',
-          icon: 'FieldColumn',
-        }));
+          icon: 'FieldColumn' as const,
+        }))
+        .sort(labelsOrder);
+
 
       // For optional mappings, add 'Blank' option but only if the value is set.
       // This option will allow to clear the selection.
diff --git a/app/client/ui/SortConfig.ts b/app/client/ui/SortConfig.ts
index 44c4f43c..ec675188 100644
--- a/app/client/ui/SortConfig.ts
+++ b/app/client/ui/SortConfig.ts
@@ -4,6 +4,7 @@ import * as kf from 'app/client/lib/koForm';
 import {makeT} from 'app/client/lib/localization';
 import {addToSort, updatePositions} from 'app/client/lib/sortUtil';
 import {ViewSectionRec} from 'app/client/models/DocModel';
+import {labelsOrder} from 'app/client/models/entities/ColumnRec';
 import {ObjObservable} from 'app/client/models/modelUtil';
 import {dropdownWithSearch} from 'app/client/ui/searchDropdown';
 import {cssIcon, cssRow, cssSortFilterColumn} from 'app/client/ui/RightPanelStyles';
@@ -215,7 +216,7 @@ export class SortConfig extends Disposable {
       const currentSection = this._section;
       const currentSortSpec = use(currentSection.activeSortSpec);
       const specRowIds = new Set(currentSortSpec.map(_sortRef => Sort.getColRef(_sortRef)));
-      return use(columns).filter(_col => !specRowIds.has(_col.value));
+      return use(columns).filter(_col => !specRowIds.has(_col.value)).sort(labelsOrder);
     });
     const {menuOptions} = this._options;
     return cssButtonRow(

From 3718883bc95f3fffc00369473a4da4170e342bab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?=
 <jaroslaw.sadzinski@gmail.com>
Date: Tue, 15 Oct 2024 13:00:13 +0200
Subject: [PATCH 6/7] Refactoring

---
 app/client/models/entities/ColumnRec.ts      | 18 +++++++-----------
 app/client/models/entities/ViewSectionRec.ts |  6 +++---
 app/client/ui/CustomSectionConfig.ts         |  8 ++++----
 app/client/ui/PageWidgetPicker.ts            |  4 ++--
 app/client/ui/SortConfig.ts                  |  4 ++--
 5 files changed, 18 insertions(+), 22 deletions(-)

diff --git a/app/client/models/entities/ColumnRec.ts b/app/client/models/entities/ColumnRec.ts
index 2ecdd471..8f0b4e09 100644
--- a/app/client/models/entities/ColumnRec.ts
+++ b/app/client/models/entities/ColumnRec.ts
@@ -235,17 +235,13 @@ function peekLabel(info: ColumnInfo): string {
   return typeof info.label === 'string' ? info.label : info.label.peek();
 }
 
-export function labelsOrder(a: ColumnInfo, b: ColumnInfo): number {
-  const left  = peekLabel(a).toLowerCase();
-  const right = peekLabel(b).toLowerCase();
-
-  // Order is as follows:
-  // - First columns with normal labels starting with a letter.
-  // - Second all columns starting with '_' (treated as private)
-  // - Third all columns starting with '#' (treated as private)
-  // - Rest.
-  if (left[0] === '_' && right[0] !== '_') { return 1; }
-  if (left[0] !== '_' && right[0] === '_') { return -1; }
+/**
+ * Helper function to sort columns based on the label. Puts # columns at the end as this is
+ * treated as private columns.
+ */
+export function columnsOrder(a: ColumnInfo, b: ColumnInfo): number {
+  const left  = peekLabel(a)?.toLowerCase() || '';
+  const right = peekLabel(b)?.toLowerCase() || '';
   if (left[0] === '#' && right[0] !== '#') { return 1; }
   if (left[0] !== '#' && right[0] === '#') { return -1; }
   return left.localeCompare(right);
diff --git a/app/client/models/entities/ViewSectionRec.ts b/app/client/models/entities/ViewSectionRec.ts
index c99849f0..37e12176 100644
--- a/app/client/models/entities/ViewSectionRec.ts
+++ b/app/client/models/entities/ViewSectionRec.ts
@@ -16,7 +16,7 @@ import {
   ViewFieldRec,
   ViewRec
 } from 'app/client/models/DocModel';
-import {BEHAVIOR, labelsOrder} from 'app/client/models/entities/ColumnRec';
+import {BEHAVIOR, columnsOrder} from 'app/client/models/entities/ColumnRec';
 import * as modelUtil from 'app/client/models/modelUtil';
 import {removeRule, RuleOwner} from 'app/client/models/RuleOwner';
 import {LinkConfig} from 'app/client/ui/selectBy';
@@ -521,7 +521,7 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
     const savedFiltersByColRef = new Map(this._savedFilters().all().map(f => [f.colRef(), f]));
     const viewFieldsByColRef = new Map(this.viewFields().all().map(f => [f.origCol().getRowId(), f]));
 
-    return [...this.columns()].sort(labelsOrder).map(column => {
+    return [...this.columns()].sort(columnsOrder).map(column => {
       const savedFilter = savedFiltersByColRef.get(column.origColRef());
       // Initialize with a saved filter, if one exists. Otherwise, use a blank filter.
       const filter = modelUtil.customComputed({
@@ -700,7 +700,7 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
     const included = new Set(this.viewFields().all().map((f) => f.column().origColRef()));
     return this.columns()
       .filter(c => !included.has(c.getRowId()))
-      .sort(labelsOrder);
+      .sort(columnsOrder);
   }));
 
   this.hasFocus = ko.pureComputed({
diff --git a/app/client/ui/CustomSectionConfig.ts b/app/client/ui/CustomSectionConfig.ts
index 82245f3e..7c60548a 100644
--- a/app/client/ui/CustomSectionConfig.ts
+++ b/app/client/ui/CustomSectionConfig.ts
@@ -7,7 +7,7 @@ import {makeT} from 'app/client/lib/localization';
 import {localStorageBoolObs} from 'app/client/lib/localStorageObs';
 import {ColumnToMapImpl} from 'app/client/models/ColumnToMap';
 import {ColumnRec, ViewSectionRec} from 'app/client/models/DocModel';
-import {labelsOrder} from 'app/client/models/entities/ColumnRec';
+import {columnsOrder} from 'app/client/models/entities/ColumnRec';
 import {
   cssDeveloperLink,
   cssWidgetMetadata,
@@ -81,7 +81,7 @@ class ColumnPicker extends Disposable {
           label: col.label.peek() || '',
           icon: 'FieldColumn' as const,
         }))
-        .sort(labelsOrder);
+        .sort(columnsOrder);
 
 
       // For optional mappings, add 'Blank' option but only if the value is set.
@@ -205,8 +205,8 @@ class ColumnListPicker extends Disposable {
           menu(() => {
             const wrongTypeCount = notMapped.get().length - typedColumns.get().length;
             return [
-              ...typedColumns.get()
-              .sort(labelsOrder)
+              ...typedColumns.get() // returns a temp table.
+              .sort(columnsOrder)
               .map((col) => menuItem(
                 () => this._addColumn(col),
                 col.label.peek(),
diff --git a/app/client/ui/PageWidgetPicker.ts b/app/client/ui/PageWidgetPicker.ts
index 8c64b736..bf1b5ede 100644
--- a/app/client/ui/PageWidgetPicker.ts
+++ b/app/client/ui/PageWidgetPicker.ts
@@ -4,6 +4,7 @@ import {FocusLayer} from 'app/client/lib/FocusLayer';
 import {makeT} from 'app/client/lib/localization';
 import {reportError} from 'app/client/models/AppModel';
 import {ColumnRec, TableRec, ViewSectionRec} from 'app/client/models/DocModel';
+import {columnsOrder} from 'app/client/models/entities/ColumnRec';
 import {PERMITTED_CUSTOM_WIDGETS} from "app/client/models/features";
 import {linkId, NoLink} from 'app/client/ui/selectBy';
 import {overflowTooltip, withInfoTooltip} from 'app/client/ui/tooltips';
@@ -32,7 +33,6 @@ import {
 import Popper from 'popper.js';
 import {IOpenController, popupOpen, setPopupToCreateDom} from 'popweasel';
 import without = require('lodash/without');
-import {labelsOrder} from 'app/client/models/entities/ColumnRec';
 
 const t = makeT('PageWidgetPicker');
 
@@ -408,7 +408,7 @@ export class PageWidgetSelect extends Disposable {
             (use) => use(this._columns)
               .filter((col) => !col.isHiddenCol() && col.parentId() === use(this._value.table)),
             (cols) => cols ?
-              dom.forEach([...cols].sort(labelsOrder), (col) =>
+              dom.forEach([...cols].sort(columnsOrder), (col) =>
                 cssEntry(cssIcon('FieldColumn'), cssFieldLabel(dom.text(col.label)),
                   dom.on('click', () => this._toggleColumnId(col.id())),
                   cssEntry.cls('-selected', (use) => use(this._value.columns).includes(col.id())),
diff --git a/app/client/ui/SortConfig.ts b/app/client/ui/SortConfig.ts
index ec675188..94d1ffc4 100644
--- a/app/client/ui/SortConfig.ts
+++ b/app/client/ui/SortConfig.ts
@@ -4,7 +4,7 @@ import * as kf from 'app/client/lib/koForm';
 import {makeT} from 'app/client/lib/localization';
 import {addToSort, updatePositions} from 'app/client/lib/sortUtil';
 import {ViewSectionRec} from 'app/client/models/DocModel';
-import {labelsOrder} from 'app/client/models/entities/ColumnRec';
+import {columnsOrder} from 'app/client/models/entities/ColumnRec';
 import {ObjObservable} from 'app/client/models/modelUtil';
 import {dropdownWithSearch} from 'app/client/ui/searchDropdown';
 import {cssIcon, cssRow, cssSortFilterColumn} from 'app/client/ui/RightPanelStyles';
@@ -216,7 +216,7 @@ export class SortConfig extends Disposable {
       const currentSection = this._section;
       const currentSortSpec = use(currentSection.activeSortSpec);
       const specRowIds = new Set(currentSortSpec.map(_sortRef => Sort.getColRef(_sortRef)));
-      return use(columns).filter(_col => !specRowIds.has(_col.value)).sort(labelsOrder);
+      return use(columns).filter(_col => !specRowIds.has(_col.value)).sort(columnsOrder);
     });
     const {menuOptions} = this._options;
     return cssButtonRow(

From 9b27e3fd2bde855e3f601087e81c0d21c6173e88 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?=
 <jaroslaw.sadzinski@gmail.com>
Date: Tue, 15 Oct 2024 13:20:08 +0200
Subject: [PATCH 7/7] Updating other places

---
 app/client/ui/GridViewMenus.ts | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/app/client/ui/GridViewMenus.ts b/app/client/ui/GridViewMenus.ts
index f816f723..6f7d0d8a 100644
--- a/app/client/ui/GridViewMenus.ts
+++ b/app/client/ui/GridViewMenus.ts
@@ -2,7 +2,7 @@ import {allCommands} from 'app/client/components/commands';
 import {GristDoc} from 'app/client/components/GristDoc';
 import GridView from 'app/client/components/GridView';
 import {makeT} from 'app/client/lib/localization';
-import {ColumnRec} from "app/client/models/entities/ColumnRec";
+import {ColumnRec, columnsOrder} from "app/client/models/entities/ColumnRec";
 import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
 import {withInfoTooltip} from 'app/client/ui/tooltips';
 import {isNarrowScreen, testId, theme, vars} from 'app/client/ui2018/cssVars';
@@ -293,7 +293,7 @@ function buildDetectDuplicatesMenuItems(gridView: GridView, index?: number) {
   const {viewSection} = gridView;
   return menuItemSubmenu(
     () => searchableMenu(
-      viewSection.columns().map((col) => {
+      [...viewSection.columns()].sort(columnsOrder).map((col) => {
         function buildFormula() {
           if (isListType(col.type())) {
             return `any([len(${col.table().tableId()}.lookupRecords(${col.colId()}` +
@@ -568,7 +568,9 @@ function buildLookupSection(gridView: GridView, index?: number){
 
     return references.map((ref) => menuItemSubmenu(
       () => searchableMenu(
-        ref.refTable()?.visibleColumns().map(buildRefColMenu.bind(null, ref)) ?? [],
+        (ref.refTable()?.visibleColumns() ?? [])
+          .sort(columnsOrder)
+          .map(buildRefColMenu.bind(null, ref)),
         {
           searchInputPlaceholder: t('Search columns')
         }