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 */