Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions apps/console/src/__tests__/ObjectView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
Expand All @@ -367,9 +367,11 @@ describe('ObjectView Component', () => {

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

// 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 () => {
Expand Down
7 changes: 1 addition & 6 deletions apps/console/src/components/ObjectView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -782,12 +782,7 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) {
renderListView={renderListView}
/>
</div>
{/* Footer — Record count */}
{typeof recordCount === 'number' && (
<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">
{t('console.objectView.recordCount', { count: recordCount })}
</div>
)}
{/* Record count footer removed — ListView already renders record-count-bar */}
</div>
{/* Metadata panel only shows for admin users */}
<MetadataPanel
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-list/src/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,10 @@ export const ListView: React.FC<ListViewProps> = ({
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',
};
Expand Down
20 changes: 14 additions & 6 deletions packages/plugin-list/src/__tests__/ListView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ describe('ListView', () => {
objectName: 'contacts',
viewType: 'grid',
fields: ['name', 'email', 'phone'],
showHideFields: true,
};

renderWithProvider(<ListView schema={schema} />);
Expand All @@ -293,6 +294,7 @@ describe('ListView', () => {
objectName: 'contacts',
viewType: 'grid',
fields: ['name', 'email'],
showDensity: true,
};

renderWithProvider(<ListView schema={schema} />);
Expand Down Expand Up @@ -353,6 +355,7 @@ describe('ListView', () => {
viewType: 'grid',
fields: ['name', 'email'],
rowHeight: 'compact',
showDensity: true,
};

renderWithProvider(<ListView schema={schema} />);
Expand All @@ -368,6 +371,7 @@ describe('ListView', () => {
fields: ['name', 'email'],
rowHeight: 'compact',
densityMode: 'spacious',
showDensity: true,
};

renderWithProvider(<ListView schema={schema} />);
Expand Down Expand Up @@ -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',
Expand All @@ -708,7 +712,7 @@ describe('ListView', () => {
};

renderWithProvider(<ListView schema={schema} />);
expect(screen.getByRole('button', { name: /hide fields/i })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: /hide fields/i })).not.toBeInTheDocument();
});

// Group visibility
Expand Down Expand Up @@ -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',
Expand All @@ -760,7 +764,7 @@ describe('ListView', () => {
};

renderWithProvider(<ListView schema={schema} />);
expect(screen.getByRole('button', { name: /color/i })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: /color/i })).not.toBeInTheDocument();
});

// Density visibility
Expand All @@ -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',
Expand All @@ -786,7 +790,7 @@ describe('ListView', () => {
};

renderWithProvider(<ListView schema={schema} />);
expect(screen.getByTitle(/density/i)).toBeInTheDocument();
expect(screen.queryByTitle(/density/i)).not.toBeInTheDocument();
});

// Export + allowExport
Expand Down Expand Up @@ -925,6 +929,7 @@ describe('ListView', () => {
viewType: 'grid',
fields: ['name', 'email'],
rowHeight: 'short',
showDensity: true,
};

renderWithProvider(<ListView schema={schema} />);
Expand All @@ -939,6 +944,7 @@ describe('ListView', () => {
viewType: 'grid',
fields: ['name', 'email'],
rowHeight: 'extra_tall',
showDensity: true,
};

renderWithProvider(<ListView schema={schema} />);
Expand Down Expand Up @@ -1595,6 +1601,7 @@ describe('ListView', () => {
objectName: 'contacts',
viewType: 'grid',
fields: ['name', 'email'],
showColor: true,
};

renderWithProvider(<ListView schema={schema} />);
Expand All @@ -1609,6 +1616,7 @@ describe('ListView', () => {
objectName: 'contacts',
viewType: 'grid',
fields: ['name', 'email'],
showColor: true,
};

renderWithProvider(<ListView schema={schema} />);
Expand Down
12 changes: 6 additions & 6 deletions packages/types/src/objectql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down