From 9d130a99b28e022481bbba6133f156b5c235bc6f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 27 Feb 2026 05:29:33 +0000
Subject: [PATCH 1/2] Initial plan
From 0ffa0f36be5779498ced7b513a5fe6c228dd6c16 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 27 Feb 2026 05:34:24 +0000
Subject: [PATCH 2/2] fix: Airtable-style toolbar opt-in defaults & remove
duplicate record count
- Change showHideFields/showColor/showDensity from opt-out (!== false) to opt-in (=== true)
- Remove duplicate record-count-footer from ObjectView.tsx
- Update @default JSDoc in NamedListView and ListViewSchema types
- Update 11 tests to match new default behavior
- Document fix in ROADMAP.md
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
ROADMAP.md | 12 +++++++++++
.../console/src/__tests__/ObjectView.test.tsx | 10 ++++++----
apps/console/src/components/ObjectView.tsx | 7 +------
packages/plugin-list/src/ListView.tsx | 6 +++---
.../src/__tests__/ListView.test.tsx | 20 +++++++++++++------
packages/types/src/objectql.ts | 12 +++++------
6 files changed, 42 insertions(+), 25 deletions(-)
diff --git a/ROADMAP.md b/ROADMAP.md
index 8693e3005..59777de9c 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1087,6 +1087,18 @@ The `FlowDesigner` is a canvas-based flow editor that bridges the gap between th
All 313 `@object-ui/fields` tests pass.
+### ListView Airtable-Style Toolbar Opt-In & Duplicate Record Count (February 2026)
+
+**Root Cause (1 — Toolbar defaults):** `showHideFields`, `showColor`, and `showDensity` in `ListView.tsx` used opt-out logic (`!== false`), making secondary toolbar buttons visible by default. Airtable hides these controls unless explicitly enabled.
+
+**Fix:** Changed default logic from `!== false` (opt-out) to `=== true` (opt-in) for `showHideFields`, `showColor`, and `showDensity` in the `toolbarFlags` computation. Updated `@default` JSDoc comments in `NamedListView` and `ListViewSchema` interfaces from `@default true` to `@default false`.
+
+**Root Cause (2 — Duplicate record count):** Both `ListView.tsx` (`record-count-bar`) and `ObjectView.tsx` (`record-count-footer`) independently rendered the record count at the bottom, causing duplicate display.
+
+**Fix:** Removed the `record-count-footer` from `ObjectView.tsx` since `ListView` already renders the authoritative `record-count-bar`.
+
+**Tests:** Updated 11 tests across `ListView.test.tsx` and `ObjectView.test.tsx`. All 112 ListView tests and 32 ObjectView tests pass.
+
---
## ⚠️ Risk Management
diff --git a/apps/console/src/__tests__/ObjectView.test.tsx b/apps/console/src/__tests__/ObjectView.test.tsx
index 1c184463a..29d54d9b7 100644
--- a/apps/console/src/__tests__/ObjectView.test.tsx
+++ b/apps/console/src/__tests__/ObjectView.test.tsx
@@ -358,7 +358,7 @@ describe('ObjectView Component', () => {
expect(screen.queryByTestId('view-config-panel')).not.toBeInTheDocument();
});
- it('shows record count footer when data is available', async () => {
+ it('does not render duplicate record count footer (ListView handles it)', async () => {
const mockDsWithTotal = {
...mockDataSource,
find: vi.fn().mockResolvedValue({ data: [], total: 42 }),
@@ -367,9 +367,11 @@ describe('ObjectView Component', () => {
render();
- // Wait for the record count to appear
- const footer = await screen.findByTestId('record-count-footer');
- expect(footer).toBeInTheDocument();
+ // The record-count-footer should no longer exist in ObjectView
+ // (ListView's record-count-bar handles this)
+ await vi.waitFor(() => {
+ expect(screen.queryByTestId('record-count-footer')).not.toBeInTheDocument();
+ });
});
it('calls dataSource.updateViewConfig when saving view config', async () => {
diff --git a/apps/console/src/components/ObjectView.tsx b/apps/console/src/components/ObjectView.tsx
index 19f66edd9..d9f247b6c 100644
--- a/apps/console/src/components/ObjectView.tsx
+++ b/apps/console/src/components/ObjectView.tsx
@@ -782,12 +782,7 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) {
renderListView={renderListView}
/>
- {/* Footer — Record count */}
- {typeof recordCount === 'number' && (
-
- {t('console.objectView.recordCount', { count: recordCount })}
-
- )}
+ {/* Record count footer removed — ListView already renders record-count-bar */}
{/* Metadata panel only shows for admin users */}
= ({
showSearch: ua?.search !== undefined ? ua.search : schema.showSearch !== false,
showSort: ua?.sort !== undefined ? ua.sort : schema.showSort !== false,
showFilters: ua?.filter !== undefined ? ua.filter : schema.showFilters !== false,
- showDensity: ua?.rowHeight !== undefined ? ua.rowHeight : schema.showDensity !== false,
- showHideFields: schema.showHideFields !== false,
+ showDensity: ua?.rowHeight !== undefined ? ua.rowHeight : schema.showDensity === true,
+ showHideFields: schema.showHideFields === true,
showGroup: schema.showGroup !== false,
- showColor: schema.showColor !== false,
+ showColor: schema.showColor === true,
showAddRecord: addRecordEnabled,
addRecordPosition: (schema.addRecord?.position === 'bottom' ? 'bottom' : 'top') as 'top' | 'bottom',
};
diff --git a/packages/plugin-list/src/__tests__/ListView.test.tsx b/packages/plugin-list/src/__tests__/ListView.test.tsx
index fb85babd4..6dfd4b2b0 100644
--- a/packages/plugin-list/src/__tests__/ListView.test.tsx
+++ b/packages/plugin-list/src/__tests__/ListView.test.tsx
@@ -279,6 +279,7 @@ describe('ListView', () => {
objectName: 'contacts',
viewType: 'grid',
fields: ['name', 'email', 'phone'],
+ showHideFields: true,
};
renderWithProvider();
@@ -293,6 +294,7 @@ describe('ListView', () => {
objectName: 'contacts',
viewType: 'grid',
fields: ['name', 'email'],
+ showDensity: true,
};
renderWithProvider();
@@ -353,6 +355,7 @@ describe('ListView', () => {
viewType: 'grid',
fields: ['name', 'email'],
rowHeight: 'compact',
+ showDensity: true,
};
renderWithProvider();
@@ -368,6 +371,7 @@ describe('ListView', () => {
fields: ['name', 'email'],
rowHeight: 'compact',
densityMode: 'spacious',
+ showDensity: true,
};
renderWithProvider();
@@ -699,7 +703,7 @@ describe('ListView', () => {
expect(screen.queryByRole('button', { name: /hide fields/i })).not.toBeInTheDocument();
});
- it('should show Hide Fields button by default (showHideFields undefined)', () => {
+ it('should hide Hide Fields button by default (showHideFields undefined)', () => {
const schema: ListViewSchema = {
type: 'list-view',
objectName: 'contacts',
@@ -708,7 +712,7 @@ describe('ListView', () => {
};
renderWithProvider();
- expect(screen.getByRole('button', { name: /hide fields/i })).toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: /hide fields/i })).not.toBeInTheDocument();
});
// Group visibility
@@ -751,7 +755,7 @@ describe('ListView', () => {
expect(screen.queryByRole('button', { name: /color/i })).not.toBeInTheDocument();
});
- it('should show Color button by default (showColor undefined)', () => {
+ it('should hide Color button by default (showColor undefined)', () => {
const schema: ListViewSchema = {
type: 'list-view',
objectName: 'contacts',
@@ -760,7 +764,7 @@ describe('ListView', () => {
};
renderWithProvider();
- expect(screen.getByRole('button', { name: /color/i })).toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: /color/i })).not.toBeInTheDocument();
});
// Density visibility
@@ -777,7 +781,7 @@ describe('ListView', () => {
expect(screen.queryByTitle(/density/i)).not.toBeInTheDocument();
});
- it('should show Density button by default (showDensity undefined)', () => {
+ it('should hide Density button by default (showDensity undefined)', () => {
const schema: ListViewSchema = {
type: 'list-view',
objectName: 'contacts',
@@ -786,7 +790,7 @@ describe('ListView', () => {
};
renderWithProvider();
- expect(screen.getByTitle(/density/i)).toBeInTheDocument();
+ expect(screen.queryByTitle(/density/i)).not.toBeInTheDocument();
});
// Export + allowExport
@@ -925,6 +929,7 @@ describe('ListView', () => {
viewType: 'grid',
fields: ['name', 'email'],
rowHeight: 'short',
+ showDensity: true,
};
renderWithProvider();
@@ -939,6 +944,7 @@ describe('ListView', () => {
viewType: 'grid',
fields: ['name', 'email'],
rowHeight: 'extra_tall',
+ showDensity: true,
};
renderWithProvider();
@@ -1595,6 +1601,7 @@ describe('ListView', () => {
objectName: 'contacts',
viewType: 'grid',
fields: ['name', 'email'],
+ showColor: true,
};
renderWithProvider();
@@ -1609,6 +1616,7 @@ describe('ListView', () => {
objectName: 'contacts',
viewType: 'grid',
fields: ['name', 'email'],
+ showColor: true,
};
renderWithProvider();
diff --git a/packages/types/src/objectql.ts b/packages/types/src/objectql.ts
index 72c3cabd8..9f9265ec5 100644
--- a/packages/types/src/objectql.ts
+++ b/packages/types/src/objectql.ts
@@ -1119,16 +1119,16 @@ export interface NamedListView {
/** Show filter controls in toolbar @default true */
showFilters?: boolean;
- /** Show hide-fields button in toolbar @default true */
+ /** Show hide-fields button in toolbar @default false */
showHideFields?: boolean;
/** Show group button in toolbar @default true */
showGroup?: boolean;
- /** Show color button in toolbar @default true */
+ /** Show color button in toolbar @default false */
showColor?: boolean;
- /** Show density/row-height button in toolbar @default true */
+ /** Show density/row-height button in toolbar @default false */
showDensity?: boolean;
/** Allow data export @default undefined */
@@ -1394,16 +1394,16 @@ export interface ListViewSchema extends BaseSchema {
/** Show filter controls in toolbar @default true */
showFilters?: boolean;
- /** Show hide-fields button in toolbar @default true */
+ /** Show hide-fields button in toolbar @default false */
showHideFields?: boolean;
/** Show group button in toolbar @default true */
showGroup?: boolean;
- /** Show color button in toolbar @default true */
+ /** Show color button in toolbar @default false */
showColor?: boolean;
- /** Show density/row-height button in toolbar @default true */
+ /** Show density/row-height button in toolbar @default false */
showDensity?: boolean;
/** Allow data export @default undefined */