Skip to content

Commit b0dfbda

Browse files
authored
Merge pull request #889 from objectstack-ai/copilot/optimize-listview-ui-again
2 parents 72c9709 + 0ffa0f3 commit b0dfbda

File tree

6 files changed

+42
-25
lines changed

6 files changed

+42
-25
lines changed

ROADMAP.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,18 @@ The `FlowDesigner` is a canvas-based flow editor that bridges the gap between th
10941094

10951095
All 313 `@object-ui/fields` tests pass.
10961096

1097+
### ListView Airtable-Style Toolbar Opt-In & Duplicate Record Count (February 2026)
1098+
1099+
**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.
1100+
1101+
**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`.
1102+
1103+
**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.
1104+
1105+
**Fix:** Removed the `record-count-footer` from `ObjectView.tsx` since `ListView` already renders the authoritative `record-count-bar`.
1106+
1107+
**Tests:** Updated 11 tests across `ListView.test.tsx` and `ObjectView.test.tsx`. All 112 ListView tests and 32 ObjectView tests pass.
1108+
10971109
---
10981110

10991111
## ⚠️ Risk Management

apps/console/src/__tests__/ObjectView.test.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ describe('ObjectView Component', () => {
359359
expect(screen.queryByTestId('view-config-panel')).not.toBeInTheDocument();
360360
});
361361

362-
it('shows record count footer when data is available', async () => {
362+
it('does not render duplicate record count footer (ListView handles it)', async () => {
363363
const mockDsWithTotal = {
364364
...mockDataSource,
365365
find: vi.fn().mockResolvedValue({ data: [], total: 42 }),
@@ -368,9 +368,11 @@ describe('ObjectView Component', () => {
368368

369369
render(<ObjectView dataSource={mockDsWithTotal} objects={mockObjects} onEdit={vi.fn()} />);
370370

371-
// Wait for the record count to appear
372-
const footer = await screen.findByTestId('record-count-footer');
373-
expect(footer).toBeInTheDocument();
371+
// The record-count-footer should no longer exist in ObjectView
372+
// (ListView's record-count-bar handles this)
373+
await vi.waitFor(() => {
374+
expect(screen.queryByTestId('record-count-footer')).not.toBeInTheDocument();
375+
});
374376
});
375377

376378
it('calls dataSource.updateViewConfig when saving view config', async () => {

apps/console/src/components/ObjectView.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -839,12 +839,7 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) {
839839
renderListView={renderListView}
840840
/>
841841
</div>
842-
{/* Footer — Record count */}
843-
{typeof recordCount === 'number' && (
844-
<div data-testid="record-count-footer" className="border-t px-3 sm:px-4 py-1.5 text-xs text-muted-foreground bg-muted/5 shrink-0">
845-
{t('console.objectView.recordCount', { count: recordCount })}
846-
</div>
847-
)}
842+
{/* Record count footer removed — ListView already renders record-count-bar */}
848843
</div>
849844
)}
850845
{/* Metadata panel only shows for admin users */}

packages/plugin-list/src/ListView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,10 @@ export const ListView: React.FC<ListViewProps> = ({
274274
showSearch: ua?.search !== undefined ? ua.search : schema.showSearch !== false,
275275
showSort: ua?.sort !== undefined ? ua.sort : schema.showSort !== false,
276276
showFilters: ua?.filter !== undefined ? ua.filter : schema.showFilters !== false,
277-
showDensity: ua?.rowHeight !== undefined ? ua.rowHeight : schema.showDensity !== false,
278-
showHideFields: schema.showHideFields !== false,
277+
showDensity: ua?.rowHeight !== undefined ? ua.rowHeight : schema.showDensity === true,
278+
showHideFields: schema.showHideFields === true,
279279
showGroup: schema.showGroup !== false,
280-
showColor: schema.showColor !== false,
280+
showColor: schema.showColor === true,
281281
showAddRecord: addRecordEnabled,
282282
addRecordPosition: (schema.addRecord?.position === 'bottom' ? 'bottom' : 'top') as 'top' | 'bottom',
283283
};

packages/plugin-list/src/__tests__/ListView.test.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ describe('ListView', () => {
279279
objectName: 'contacts',
280280
viewType: 'grid',
281281
fields: ['name', 'email', 'phone'],
282+
showHideFields: true,
282283
};
283284

284285
renderWithProvider(<ListView schema={schema} />);
@@ -293,6 +294,7 @@ describe('ListView', () => {
293294
objectName: 'contacts',
294295
viewType: 'grid',
295296
fields: ['name', 'email'],
297+
showDensity: true,
296298
};
297299

298300
renderWithProvider(<ListView schema={schema} />);
@@ -353,6 +355,7 @@ describe('ListView', () => {
353355
viewType: 'grid',
354356
fields: ['name', 'email'],
355357
rowHeight: 'compact',
358+
showDensity: true,
356359
};
357360

358361
renderWithProvider(<ListView schema={schema} />);
@@ -368,6 +371,7 @@ describe('ListView', () => {
368371
fields: ['name', 'email'],
369372
rowHeight: 'compact',
370373
densityMode: 'spacious',
374+
showDensity: true,
371375
};
372376

373377
renderWithProvider(<ListView schema={schema} />);
@@ -699,7 +703,7 @@ describe('ListView', () => {
699703
expect(screen.queryByRole('button', { name: /hide fields/i })).not.toBeInTheDocument();
700704
});
701705

702-
it('should show Hide Fields button by default (showHideFields undefined)', () => {
706+
it('should hide Hide Fields button by default (showHideFields undefined)', () => {
703707
const schema: ListViewSchema = {
704708
type: 'list-view',
705709
objectName: 'contacts',
@@ -708,7 +712,7 @@ describe('ListView', () => {
708712
};
709713

710714
renderWithProvider(<ListView schema={schema} />);
711-
expect(screen.getByRole('button', { name: /hide fields/i })).toBeInTheDocument();
715+
expect(screen.queryByRole('button', { name: /hide fields/i })).not.toBeInTheDocument();
712716
});
713717

714718
// Group visibility
@@ -751,7 +755,7 @@ describe('ListView', () => {
751755
expect(screen.queryByRole('button', { name: /color/i })).not.toBeInTheDocument();
752756
});
753757

754-
it('should show Color button by default (showColor undefined)', () => {
758+
it('should hide Color button by default (showColor undefined)', () => {
755759
const schema: ListViewSchema = {
756760
type: 'list-view',
757761
objectName: 'contacts',
@@ -760,7 +764,7 @@ describe('ListView', () => {
760764
};
761765

762766
renderWithProvider(<ListView schema={schema} />);
763-
expect(screen.getByRole('button', { name: /color/i })).toBeInTheDocument();
767+
expect(screen.queryByRole('button', { name: /color/i })).not.toBeInTheDocument();
764768
});
765769

766770
// Density visibility
@@ -777,7 +781,7 @@ describe('ListView', () => {
777781
expect(screen.queryByTitle(/density/i)).not.toBeInTheDocument();
778782
});
779783

780-
it('should show Density button by default (showDensity undefined)', () => {
784+
it('should hide Density button by default (showDensity undefined)', () => {
781785
const schema: ListViewSchema = {
782786
type: 'list-view',
783787
objectName: 'contacts',
@@ -786,7 +790,7 @@ describe('ListView', () => {
786790
};
787791

788792
renderWithProvider(<ListView schema={schema} />);
789-
expect(screen.getByTitle(/density/i)).toBeInTheDocument();
793+
expect(screen.queryByTitle(/density/i)).not.toBeInTheDocument();
790794
});
791795

792796
// Export + allowExport
@@ -925,6 +929,7 @@ describe('ListView', () => {
925929
viewType: 'grid',
926930
fields: ['name', 'email'],
927931
rowHeight: 'short',
932+
showDensity: true,
928933
};
929934

930935
renderWithProvider(<ListView schema={schema} />);
@@ -939,6 +944,7 @@ describe('ListView', () => {
939944
viewType: 'grid',
940945
fields: ['name', 'email'],
941946
rowHeight: 'extra_tall',
947+
showDensity: true,
942948
};
943949

944950
renderWithProvider(<ListView schema={schema} />);
@@ -1595,6 +1601,7 @@ describe('ListView', () => {
15951601
objectName: 'contacts',
15961602
viewType: 'grid',
15971603
fields: ['name', 'email'],
1604+
showColor: true,
15981605
};
15991606

16001607
renderWithProvider(<ListView schema={schema} />);
@@ -1609,6 +1616,7 @@ describe('ListView', () => {
16091616
objectName: 'contacts',
16101617
viewType: 'grid',
16111618
fields: ['name', 'email'],
1619+
showColor: true,
16121620
};
16131621

16141622
renderWithProvider(<ListView schema={schema} />);

packages/types/src/objectql.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,16 +1119,16 @@ export interface NamedListView {
11191119
/** Show filter controls in toolbar @default true */
11201120
showFilters?: boolean;
11211121

1122-
/** Show hide-fields button in toolbar @default true */
1122+
/** Show hide-fields button in toolbar @default false */
11231123
showHideFields?: boolean;
11241124

11251125
/** Show group button in toolbar @default true */
11261126
showGroup?: boolean;
11271127

1128-
/** Show color button in toolbar @default true */
1128+
/** Show color button in toolbar @default false */
11291129
showColor?: boolean;
11301130

1131-
/** Show density/row-height button in toolbar @default true */
1131+
/** Show density/row-height button in toolbar @default false */
11321132
showDensity?: boolean;
11331133

11341134
/** Allow data export @default undefined */
@@ -1394,16 +1394,16 @@ export interface ListViewSchema extends BaseSchema {
13941394
/** Show filter controls in toolbar @default true */
13951395
showFilters?: boolean;
13961396

1397-
/** Show hide-fields button in toolbar @default true */
1397+
/** Show hide-fields button in toolbar @default false */
13981398
showHideFields?: boolean;
13991399

14001400
/** Show group button in toolbar @default true */
14011401
showGroup?: boolean;
14021402

1403-
/** Show color button in toolbar @default true */
1403+
/** Show color button in toolbar @default false */
14041404
showColor?: boolean;
14051405

1406-
/** Show density/row-height button in toolbar @default true */
1406+
/** Show density/row-height button in toolbar @default false */
14071407
showDensity?: boolean;
14081408

14091409
/** Allow data export @default undefined */

0 commit comments

Comments
 (0)