@@ -1086,11 +1091,12 @@ function buildAppearanceSection(
),
},
- // spec: NamedListView.conditionalFormatting
+ // spec: NamedListView.conditionalFormatting (grid-only: conditional formatting applies to grid cells/rows)
{
key: '_conditionalFormatting',
label: t('console.objectView.conditionalFormatting'),
type: 'custom',
+ visibleWhen: isGridView,
render: (_value, _onChange, draft) => {
return (
) => boolean,
+ visibleWhen?: (draft: Record) => boolean,
): ConfigField {
return {
key,
label,
type: 'custom',
disabledWhen,
+ visibleWhen,
render: (value, onChange, draft) => (
) => boolean,
): ConfigField {
return {
key: `_${key}`,
label,
type: 'custom',
+ visibleWhen,
render: (_value, _onChange, draft) => {
return (
Date: Tue, 24 Feb 2026 02:43:51 +0000
Subject: [PATCH 3/6] test: update view-config-schema tests to match schema
changes
- Remove tests for removed fields: prefixField, collapseAllByDefault,
fieldTextColor, clickIntoRecordDetails
- Update data/appearance/userActions section field key assertions
- Convert disabledWhen tests for striped/bordered/wrapHeaders/resizable
to visibleWhen tests (schema changed from disable to hide)
- Update visibleWhen predicates: showGroup now includes gallery,
showColor uses supportsColorField, _groupBy uses supportsGenericGroupBy,
conditionalFormatting uses supportsConditionalFormatting
- Update searchableFields/filterableFields/quickFilters/showDescription
to be universal (no visibleWhen)
- Add color field visibleWhen test for supportsColorField predicate
- Remove spec coverage entries for removed fields
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../src/__tests__/view-config-schema.test.tsx | 154 ++++++++----------
1 file changed, 66 insertions(+), 88 deletions(-)
diff --git a/apps/console/src/__tests__/view-config-schema.test.tsx b/apps/console/src/__tests__/view-config-schema.test.tsx
index e66e97802..163410401 100644
--- a/apps/console/src/__tests__/view-config-schema.test.tsx
+++ b/apps/console/src/__tests__/view-config-schema.test.tsx
@@ -411,13 +411,14 @@ describe('buildViewConfigSchema', () => {
const schema = buildSchema();
const section = schema.sections.find(s => s.key === 'data')!;
const fieldKeys = section.fields.map(f => f.key);
- // Spec order: columns, filter, sort, prefixField, pagination, searchableFields, filterableFields,
+ // Spec order: columns, filter, sort, pagination, searchableFields, filterableFields,
// hiddenFields, quickFilters, virtualScroll
- // _source is UI extension (first), _groupBy is UI extension (after prefixField), _typeOptions is UI extension (last)
+ // _source is UI extension (first), _groupBy is UI extension (after sort), _typeOptions is UI extension (last)
+ // NOTE: prefixField removed — not consumed by runtime
expect(fieldKeys).toEqual([
'_source',
'_columns', '_filterBy', '_sortBy',
- 'prefixField', '_groupBy',
+ '_groupBy',
'_pageSize', '_pageSizeOptions',
'_searchableFields', '_filterableFields', '_hiddenFields',
'_quickFilters',
@@ -442,13 +443,14 @@ describe('buildViewConfigSchema', () => {
const schema = buildSchema();
const section = schema.sections.find(s => s.key === 'appearance')!;
const fieldKeys = section.fields.map(f => f.key);
- // Spec order: striped, bordered, color, wrapHeaders, collapseAllByDefault, fieldTextColor,
+ // Spec order: striped, bordered, color, wrapHeaders,
// showDescription, resizable, rowHeight, conditionalFormatting, emptyState
+ // NOTE: collapseAllByDefault removed — not consumed by runtime
+ // NOTE: fieldTextColor removed — not consumed by runtime
// NOTE: densityMode removed (redundant with rowHeight)
expect(fieldKeys).toEqual([
'striped', 'bordered', 'color',
- 'wrapHeaders', 'collapseAllByDefault',
- 'fieldTextColor', 'showDescription',
+ 'wrapHeaders', 'showDescription',
'resizable', 'rowHeight',
'_conditionalFormatting', '_emptyState',
]);
@@ -470,18 +472,19 @@ describe('buildViewConfigSchema', () => {
const schema = buildSchema();
const section = schema.sections.find(s => s.key === 'userActions')!;
const fieldKeys = section.fields.map(f => f.key);
- // Spec order: inlineEdit, clickIntoRecordDetails, addDeleteRecordsInline, rowActions, bulkActions
+ // Spec order: inlineEdit, addDeleteRecordsInline, rowActions, bulkActions
+ // NOTE: clickIntoRecordDetails removed — controlled via navigation mode
expect(fieldKeys).toEqual([
- 'inlineEdit', 'clickIntoRecordDetails', 'addDeleteRecordsInline',
+ 'inlineEdit', 'addDeleteRecordsInline',
'_rowActions', '_bulkActions',
]);
});
- it('inlineEdit comes before clickIntoRecordDetails per spec', () => {
+ it('inlineEdit comes before addDeleteRecordsInline per spec', () => {
const schema = buildSchema();
const section = schema.sections.find(s => s.key === 'userActions')!;
const fieldKeys = section.fields.map(f => f.key);
- expect(fieldKeys.indexOf('inlineEdit')).toBeLessThan(fieldKeys.indexOf('clickIntoRecordDetails'));
+ expect(fieldKeys.indexOf('inlineEdit')).toBeLessThan(fieldKeys.indexOf('addDeleteRecordsInline'));
});
});
@@ -637,12 +640,12 @@ describe('spec alignment', () => {
color: 'color',
inlineEdit: 'inlineEdit',
wrapHeaders: 'wrapHeaders',
- clickIntoRecordDetails: 'clickIntoRecordDetails',
+ // clickIntoRecordDetails removed — controlled via navigation mode
addRecordViaForm: '_addRecord', // compound field
addDeleteRecordsInline: 'addDeleteRecordsInline',
- collapseAllByDefault: 'collapseAllByDefault',
- fieldTextColor: 'fieldTextColor',
- prefixField: 'prefixField',
+ // collapseAllByDefault removed — not consumed by runtime
+ // fieldTextColor removed — not consumed by runtime
+ // prefixField removed — not consumed by runtime
showDescription: 'showDescription',
navigation: '_navigationMode', // compound: mode/width/openNewTab
selection: '_selectionType',
@@ -693,9 +696,9 @@ describe('spec alignment', () => {
const schema = buildSchema();
const section = schema.sections.find(s => s.key === 'userActions')!;
const keys = section.fields.map(f => f.key);
- // Spec order: inlineEdit → clickIntoRecordDetails → addDeleteRecordsInline
- expect(keys.indexOf('inlineEdit')).toBeLessThan(keys.indexOf('clickIntoRecordDetails'));
- expect(keys.indexOf('clickIntoRecordDetails')).toBeLessThan(keys.indexOf('addDeleteRecordsInline'));
+ // Spec order: inlineEdit → addDeleteRecordsInline
+ // NOTE: clickIntoRecordDetails removed — controlled via navigation mode
+ expect(keys.indexOf('inlineEdit')).toBeLessThan(keys.indexOf('addDeleteRecordsInline'));
});
// Protocol suggestions: UI fields not in NamedListView spec
@@ -744,14 +747,6 @@ describe('spec alignment', () => {
return section.fields.find(f => f.key === fieldKey)!;
}
- it('collapseAllByDefault is an explicitTrue switch field', () => {
- // explicitTrue fields only show checked when value === true
- const field = findField('appearance', 'collapseAllByDefault');
- expect(field.render).toBeDefined();
- expect(field.type).toBe('custom');
- expect(field.key).toBe('collapseAllByDefault');
- });
-
it('showDescription is a defaultOn switch field', () => {
const field = findField('appearance', 'showDescription');
expect(field.render).toBeDefined();
@@ -814,7 +809,7 @@ describe('spec alignment', () => {
});
// ── Toolbar toggle visibility by view type ──────────────────────
- it('showGroup visible for grid (default) and kanban, hidden for others', () => {
+ it('showGroup visible for grid (default), kanban, and gallery, hidden for others', () => {
const schema = buildSpecSchema();
const section = schema.sections.find(s => s.key === 'pageConfig')!;
const field = section.fields.find(f => f.key === 'showGroup')!;
@@ -822,22 +817,26 @@ describe('spec alignment', () => {
expect(field.visibleWhen!({})).toBe(true); // default = grid
expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
expect(field.visibleWhen!({ type: 'kanban' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'gallery' })).toBe(true);
expect(field.visibleWhen!({ type: 'calendar' })).toBe(false);
- expect(field.visibleWhen!({ type: 'gallery' })).toBe(false);
expect(field.visibleWhen!({ type: 'timeline' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'gantt' })).toBe(false);
expect(field.visibleWhen!({ type: 'map' })).toBe(false);
});
- it('showColor visible only for grid (default), hidden for others', () => {
+ it('showColor visible for grid (default), calendar, timeline, gantt, hidden for others', () => {
const schema = buildSpecSchema();
const section = schema.sections.find(s => s.key === 'pageConfig')!;
const field = section.fields.find(f => f.key === 'showColor')!;
expect(field.visibleWhen).toBeDefined();
expect(field.visibleWhen!({})).toBe(true); // default = grid
expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'calendar' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'timeline' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'gantt' })).toBe(true);
expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
- expect(field.visibleWhen!({ type: 'calendar' })).toBe(false);
expect(field.visibleWhen!({ type: 'gallery' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'map' })).toBe(false);
});
it('showDensity visible only for grid (default), hidden for others', () => {
@@ -852,57 +851,40 @@ describe('spec alignment', () => {
});
// ── Data section visibility by view type ────────────────────────
- it('prefixField visible only for grid (default), hidden for others', () => {
- const schema = buildSpecSchema();
- const section = schema.sections.find(s => s.key === 'data')!;
- const field = section.fields.find(f => f.key === 'prefixField')!;
- expect(field.visibleWhen).toBeDefined();
- expect(field.visibleWhen!({})).toBe(true); // default = grid
- expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
- expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
- expect(field.visibleWhen!({ type: 'calendar' })).toBe(false);
- });
-
- it('_groupBy hidden for kanban (uses type-specific kanban.groupByField)', () => {
+ it('_groupBy visible for grid (default) and gallery, hidden for others', () => {
const schema = buildSpecSchema();
const section = schema.sections.find(s => s.key === 'data')!;
const field = section.fields.find(f => f.key === '_groupBy')!;
expect(field.visibleWhen).toBeDefined();
expect(field.visibleWhen!({})).toBe(true); // default = grid
expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
- expect(field.visibleWhen!({ type: 'kanban' })).toBe(false); // kanban uses dedicated groupByField
- expect(field.visibleWhen!({ type: 'calendar' })).toBe(true);
expect(field.visibleWhen!({ type: 'gallery' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'kanban' })).toBe(false); // kanban uses dedicated groupByField
+ expect(field.visibleWhen!({ type: 'calendar' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'timeline' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'gantt' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'map' })).toBe(false);
});
- it('searchableFields visible only for grid', () => {
+ it('searchableFields is universal (no visibleWhen)', () => {
const schema = buildSpecSchema();
const section = schema.sections.find(s => s.key === 'data')!;
const field = section.fields.find(f => f.key === '_searchableFields')!;
- expect(field.visibleWhen).toBeDefined();
- expect(field.visibleWhen!({})).toBe(true);
- expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
- expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
+ expect(field.visibleWhen).toBeUndefined();
});
- it('filterableFields visible only for grid', () => {
+ it('filterableFields is universal (no visibleWhen)', () => {
const schema = buildSpecSchema();
const section = schema.sections.find(s => s.key === 'data')!;
const field = section.fields.find(f => f.key === '_filterableFields')!;
- expect(field.visibleWhen).toBeDefined();
- expect(field.visibleWhen!({})).toBe(true);
- expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
- expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
+ expect(field.visibleWhen).toBeUndefined();
});
- it('quickFilters visible only for grid', () => {
+ it('quickFilters is universal (no visibleWhen)', () => {
const schema = buildSpecSchema();
const section = schema.sections.find(s => s.key === 'data')!;
const field = section.fields.find(f => f.key === '_quickFilters')!;
- expect(field.visibleWhen).toBeDefined();
- expect(field.visibleWhen!({})).toBe(true);
- expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
- expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
+ expect(field.visibleWhen).toBeUndefined();
});
it('virtualScroll visible only for grid', () => {
@@ -916,34 +898,26 @@ describe('spec alignment', () => {
});
// ── Appearance section visibility by view type / state ──────────
- it('collapseAllByDefault visible only when groupBy is set', () => {
+ it('color visible for grid (default), calendar, timeline, gantt, hidden for others', () => {
const schema = buildSpecSchema();
const section = schema.sections.find(s => s.key === 'appearance')!;
- const field = section.fields.find(f => f.key === 'collapseAllByDefault')!;
+ const field = section.fields.find(f => f.key === 'color')!;
expect(field.visibleWhen).toBeDefined();
- expect(field.visibleWhen!({})).toBe(false); // no groupBy → hidden
- expect(field.visibleWhen!({ groupBy: '' })).toBe(false); // empty groupBy → hidden
- expect(field.visibleWhen!({ groupBy: 'status' })).toBe(true); // groupBy set → visible
- });
-
- it('fieldTextColor visible only for grid', () => {
- const schema = buildSpecSchema();
- const section = schema.sections.find(s => s.key === 'appearance')!;
- const field = section.fields.find(f => f.key === 'fieldTextColor')!;
- expect(field.visibleWhen).toBeDefined();
- expect(field.visibleWhen!({})).toBe(true);
+ expect(field.visibleWhen!({})).toBe(true); // default = grid
expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'calendar' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'timeline' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'gantt' })).toBe(true);
expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'gallery' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'map' })).toBe(false);
});
- it('showDescription visible only for grid', () => {
+ it('showDescription is universal (no visibleWhen)', () => {
const schema = buildSpecSchema();
const section = schema.sections.find(s => s.key === 'appearance')!;
const field = section.fields.find(f => f.key === 'showDescription')!;
- expect(field.visibleWhen).toBeDefined();
- expect(field.visibleWhen!({})).toBe(true);
- expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
- expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
+ expect(field.visibleWhen).toBeUndefined();
});
it('rowHeight visible only for grid', () => {
@@ -956,19 +930,23 @@ describe('spec alignment', () => {
expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
});
- it('conditionalFormatting visible only for grid', () => {
+ it('conditionalFormatting visible for grid (default) and kanban, hidden for others', () => {
const schema = buildSpecSchema();
const section = schema.sections.find(s => s.key === 'appearance')!;
const field = section.fields.find(f => f.key === '_conditionalFormatting')!;
expect(field.visibleWhen).toBeDefined();
expect(field.visibleWhen!({})).toBe(true);
expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
- expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'kanban' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'calendar' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'gallery' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'timeline' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'map' })).toBe(false);
});
});
- // ── disabledWhen predicates for grid-only fields ─────────────────────
- describe('disabledWhen predicates for grid-only fields', () => {
+ // ── visibleWhen predicates for grid-only appearance fields ──────────
+ describe('visibleWhen predicates for grid-only appearance fields', () => {
function buildSchema() {
return buildViewConfigSchema({
t: mockT,
@@ -983,32 +961,32 @@ describe('spec alignment', () => {
const gridOnlyFields = ['striped', 'bordered', 'wrapHeaders', 'resizable'];
for (const fieldKey of gridOnlyFields) {
- it(`${fieldKey} should have disabledWhen predicate`, () => {
+ it(`${fieldKey} should have visibleWhen predicate`, () => {
const schema = buildSchema();
const section = schema.sections.find(s => s.key === 'appearance')!;
const field = section.fields.find(f => f.key === fieldKey)!;
- expect(field.disabledWhen).toBeDefined();
+ expect(field.visibleWhen).toBeDefined();
});
- it(`${fieldKey} should be disabled when view type is kanban`, () => {
+ it(`${fieldKey} should be hidden when view type is kanban`, () => {
const schema = buildSchema();
const section = schema.sections.find(s => s.key === 'appearance')!;
const field = section.fields.find(f => f.key === fieldKey)!;
- expect(field.disabledWhen!({ type: 'kanban' })).toBe(true);
+ expect(field.visibleWhen!({ type: 'kanban' })).toBe(false);
});
- it(`${fieldKey} should not be disabled when view type is grid`, () => {
+ it(`${fieldKey} should be visible when view type is grid`, () => {
const schema = buildSchema();
const section = schema.sections.find(s => s.key === 'appearance')!;
const field = section.fields.find(f => f.key === fieldKey)!;
- expect(field.disabledWhen!({ type: 'grid' })).toBe(false);
+ expect(field.visibleWhen!({ type: 'grid' })).toBe(true);
});
- it(`${fieldKey} should not be disabled when view type is undefined (default grid)`, () => {
+ it(`${fieldKey} should be visible when view type is undefined (default grid)`, () => {
const schema = buildSchema();
const section = schema.sections.find(s => s.key === 'appearance')!;
const field = section.fields.find(f => f.key === fieldKey)!;
- expect(field.disabledWhen!({})).toBe(false);
+ expect(field.visibleWhen!({})).toBe(true);
});
}
});
From 23825217fa65ac887d5324fe4b15e84c2ad37076 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 24 Feb 2026 02:47:18 +0000
Subject: [PATCH 4/6] fix: remove tests for removed schema fields in
ViewConfigPanel
- Remove fieldTextColor and collapseAllByDefault from appearance fields test
- Remove prefixField assertion from Data section test
- Delete collapseAllByDefault toggle test (field removed from schema)
- Delete clickIntoRecordDetails toggle test (field removed from schema)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../src/__tests__/ViewConfigPanel.test.tsx | 37 +----
apps/console/src/utils/view-config-schema.tsx | 143 +++++++-----------
2 files changed, 57 insertions(+), 123 deletions(-)
diff --git a/apps/console/src/__tests__/ViewConfigPanel.test.tsx b/apps/console/src/__tests__/ViewConfigPanel.test.tsx
index 65ad0d9d4..36b7b048a 100644
--- a/apps/console/src/__tests__/ViewConfigPanel.test.tsx
+++ b/apps/console/src/__tests__/ViewConfigPanel.test.tsx
@@ -1250,7 +1250,7 @@ describe('ViewConfigPanel', () => {
// ── Appearance fields tests ──
- it('renders new appearance fields: color, fieldTextColor, rowHeight, wrapHeaders, collapseAllByDefault', () => {
+ it('renders new appearance fields: color, rowHeight, wrapHeaders', () => {
render(
{
);
expect(screen.getByTestId('appearance-color')).toBeInTheDocument();
- expect(screen.getByTestId('appearance-fieldTextColor')).toBeInTheDocument();
expect(screen.getByTestId('appearance-rowHeight')).toBeInTheDocument();
expect(screen.getByTestId('toggle-wrapHeaders')).toBeInTheDocument();
- expect(screen.getByTestId('toggle-collapseAllByDefault')).toBeInTheDocument();
});
it('changes row height via icon buttons', () => {
@@ -1346,7 +1344,6 @@ describe('ViewConfigPanel', () => {
);
expect(screen.getByTestId('data-groupBy')).toBeInTheDocument();
- expect(screen.getByTestId('data-prefixField')).toBeInTheDocument();
});
it('changes groupBy for grid view', () => {
@@ -2408,22 +2405,6 @@ describe('ViewConfigPanel', () => {
// ── Spec alignment: toggle interaction tests for all switch fields ──
- it('toggles collapseAllByDefault and calls onViewUpdate', () => {
- const onViewUpdate = vi.fn();
- render(
-
- );
-
- fireEvent.click(screen.getByTestId('toggle-collapseAllByDefault'));
- expect(onViewUpdate).toHaveBeenCalledWith('collapseAllByDefault', true);
- });
-
it('toggles showDescription and calls onViewUpdate', () => {
const onViewUpdate = vi.fn();
render(
@@ -2440,22 +2421,6 @@ describe('ViewConfigPanel', () => {
expect(onViewUpdate).toHaveBeenCalledWith('showDescription', false);
});
- it('toggles clickIntoRecordDetails and calls onViewUpdate', () => {
- const onViewUpdate = vi.fn();
- render(
-
- );
-
- fireEvent.click(screen.getByTestId('toggle-clickIntoRecordDetails'));
- expect(onViewUpdate).toHaveBeenCalledWith('clickIntoRecordDetails', false);
- });
-
it('toggles addDeleteRecordsInline and calls onViewUpdate', () => {
const onViewUpdate = vi.fn();
render(
diff --git a/apps/console/src/utils/view-config-schema.tsx b/apps/console/src/utils/view-config-schema.tsx
index 96a04aa53..3e62deac4 100644
--- a/apps/console/src/utils/view-config-schema.tsx
+++ b/apps/console/src/utils/view-config-schema.tsx
@@ -66,11 +66,20 @@ export interface ViewSchemaFactoryOptions {
/** True when the view type is grid (or unset, since grid is the default) */
const isGridView = (draft: Record) => draft.type == null || draft.type === 'grid';
-/** True when the view type is grid or kanban (both support grouping) */
-const isGridOrKanbanView = (draft: Record) => draft.type == null || draft.type === 'grid' || draft.type === 'kanban';
+/** True for views that support the Group toolbar button (grid/kanban/gallery have grouping support) */
+const supportsGrouping = (draft: Record) => draft.type == null || ['grid', 'kanban', 'gallery'].includes(draft.type);
-/** True when the view type is NOT kanban (kanban has dedicated groupByField in type-specific options) */
-const isNotKanbanView = (draft: Record) => draft.type !== 'kanban';
+/** True for views where the color field is consumed at runtime (grid, calendar, timeline, gantt) */
+const supportsColorField = (draft: Record) => draft.type == null || ['grid', 'calendar', 'timeline', 'gantt'].includes(draft.type);
+
+/** True for views that support conditional formatting (grid, kanban) */
+const supportsConditionalFormatting = (draft: Record) => draft.type == null || ['grid', 'kanban'].includes(draft.type);
+
+/** True for views that support row/bulk actions (grid, kanban) */
+const supportsRowActions = (draft: Record) => draft.type == null || ['grid', 'kanban'].includes(draft.type);
+
+/** True for views that support generic groupBy (grid, gallery — kanban has dedicated groupByField) */
+const supportsGenericGroupBy = (draft: Record) => draft.type == null || ['grid', 'gallery'].includes(draft.type);
// ---------------------------------------------------------------------------
// Schema factory
@@ -179,9 +188,9 @@ function buildPageConfigSection(
buildSwitchField('showFilters', t('console.objectView.enableFilter'), 'toggle-showFilters', true), // spec: NamedListView.showFilters
buildSwitchField('showHideFields', t('console.objectView.enableHideFields'), 'toggle-showHideFields', true), // spec: NamedListView.showHideFields
buildSwitchField('showGroup', t('console.objectView.enableGroup'), 'toggle-showGroup', true, // spec: NamedListView.showGroup
- false, undefined, isGridOrKanbanView),
+ false, undefined, supportsGrouping),
buildSwitchField('showColor', t('console.objectView.enableColor'), 'toggle-showColor', true, // spec: NamedListView.showColor
- false, undefined, isGridView),
+ false, undefined, supportsColorField),
buildSwitchField('showDensity', t('console.objectView.enableDensity'), 'toggle-showDensity', true, // spec: NamedListView.showDensity
false, undefined, isGridView),
// spec: NamedListView.allowExport + NamedListView.exportOptions — export toggle + sub-config
@@ -428,10 +437,12 @@ function buildPageConfigSection(
);
},
},
- // spec: NamedListView.showRecordCount
- buildSwitchField('showRecordCount', t('console.objectView.showRecordCount'), 'toggle-showRecordCount', false, true),
- // spec: NamedListView.allowPrinting
- buildSwitchField('allowPrinting', t('console.objectView.allowPrinting'), 'toggle-allowPrinting', false, true),
+ // spec: NamedListView.showRecordCount (grid-only: record count bar is a grid feature)
+ buildSwitchField('showRecordCount', t('console.objectView.showRecordCount'), 'toggle-showRecordCount', false, true,
+ undefined, isGridView),
+ // spec: NamedListView.allowPrinting (grid-only: print renders grid table)
+ buildSwitchField('allowPrinting', t('console.objectView.allowPrinting'), 'toggle-allowPrinting', false, true,
+ undefined, isGridView),
],
};
}
@@ -637,34 +648,13 @@ function buildDataSection(
);
},
},
- // spec: NamedListView.prefixField (grid-only: prefix column is a grid concept)
- {
- key: 'prefixField',
- label: t('console.objectView.prefixField'),
- type: 'custom',
- visibleWhen: isGridView,
- render: (value, onChange) => (
-
-
-
- ),
- },
- // UI extension: groupBy — not in NamedListView spec. Hidden for kanban (uses dedicated kanban.groupByField in type-specific options).
+ // NOTE: prefixField removed — not consumed by any runtime renderer
+ // UI extension: groupBy — visible for grid/gallery (kanban has dedicated type-specific option).
{
key: '_groupBy',
label: t('console.objectView.groupBy'),
type: 'custom',
- visibleWhen: isNotKanbanView,
+ visibleWhen: supportsGenericGroupBy,
render: (_value, _onChange, draft) => {
const viewType = draft.type || 'grid';
const groupByValue = draft.kanban?.groupByField || draft.kanban?.groupField || draft.groupBy || '';
@@ -732,18 +722,17 @@ function buildDataSection(
),
},
- // spec: NamedListView.searchableFields (grid-only: search field whitelisting is a grid concept)
- buildFieldMultiSelect('searchableFields', t('console.objectView.searchableFields'), 'searchable-fields-selector', 'searchable-field', fieldOptions, updateField, 'selected', isGridView),
- // spec: NamedListView.filterableFields (grid-only: filter field whitelisting is a grid concept)
- buildFieldMultiSelect('filterableFields', t('console.objectView.filterableFields'), 'filterable-fields-selector', 'filterable-field', fieldOptions, updateField, 'selected', isGridView),
+ // spec: NamedListView.searchableFields (universal: search applies at data fetch level for all views)
+ buildFieldMultiSelect('searchableFields', t('console.objectView.searchableFields'), 'searchable-fields-selector', 'searchable-field', fieldOptions, updateField, 'selected'),
+ // spec: NamedListView.filterableFields (universal: filter field whitelisting applies to all views)
+ buildFieldMultiSelect('filterableFields', t('console.objectView.filterableFields'), 'filterable-fields-selector', 'filterable-field', fieldOptions, updateField, 'selected'),
// spec: NamedListView.hiddenFields
buildFieldMultiSelect('hiddenFields', t('console.objectView.hiddenFields'), 'hidden-fields-selector', 'hidden-field', fieldOptions, updateField, 'hidden'),
- // spec: NamedListView.quickFilters (grid-only: quick filter buttons are a grid toolbar feature)
+ // spec: NamedListView.quickFilters (universal: quick filter buttons render in toolbar for all views)
{
key: '_quickFilters',
label: t('console.objectView.quickFilters'),
type: 'custom',
- visibleWhen: isGridView,
render: (_value, _onChange, draft) => {
return (
draft.type != null && draft.type !== 'grid'),
- // spec: NamedListView.bordered (grid-only)
+ undefined, isGridView),
+ // spec: NamedListView.bordered (grid-only: borders are a grid concept)
buildSwitchField('bordered', t('console.objectView.bordered'), 'toggle-bordered', false, true,
- (draft) => draft.type != null && draft.type !== 'grid'),
- // spec: NamedListView.color — field for row/card coloring
+ undefined, isGridView),
+ // spec: NamedListView.color — field for row/card coloring (grid, calendar, timeline, gantt)
{
key: 'color',
label: t('console.objectView.color'),
type: 'custom',
+ visibleWhen: supportsColorField,
render: (value, onChange) => (
),
},
- // spec: NamedListView.conditionalFormatting (grid-only: conditional formatting applies to grid cells/rows)
+ // spec: NamedListView.conditionalFormatting (grid/kanban: both process conditional formatting rules)
{
key: '_conditionalFormatting',
label: t('console.objectView.conditionalFormatting'),
type: 'custom',
- visibleWhen: isGridView,
+ visibleWhen: supportsConditionalFormatting,
render: (_value, _onChange, draft) => {
return (
{
return (
{
return (
Date: Tue, 24 Feb 2026 02:50:20 +0000
Subject: [PATCH 5/6] =?UTF-8?q?fix:=20comprehensive=20view=20config=20pane?=
=?UTF-8?q?l=20cleanup=20=E2=80=94=20remove=20unused=20fields,=20correct?=
=?UTF-8?q?=20view-type=20visibility?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Removed fields not consumed by runtime:
- prefixField (data section)
- collapseAllByDefault (appearance section)
- fieldTextColor (appearance section)
- clickIntoRecordDetails (userActions section)
- densityMode (appearance section, previous commit)
Added semantic view-type visibility predicates:
- supportsGrouping (grid/kanban/gallery)
- supportsColorField (grid/calendar/timeline/gantt)
- supportsConditionalFormatting (grid/kanban)
- supportsRowActions (grid/kanban)
- supportsGenericGroupBy (grid/gallery)
Changed grid-only fields from disabledWhen to visibleWhen:
- striped, bordered, wrapHeaders, resizable
Added visibleWhen to previously universal fields:
- showRecordCount, allowPrinting (grid only)
- inlineEdit, addDeleteRecordsInline (grid only)
- rowActions, bulkActions (grid/kanban)
Corrected visibility for toolbar toggles:
- showGroup: grid/kanban/gallery (was grid/kanban)
- showColor: grid/calendar/timeline/gantt (was grid)
- searchableFields/filterableFields/quickFilters/showDescription: universal (was grid)
Updated 239 tests (103 schema + 136 panel), 10 integration tests pass.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
ROADMAP.md | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/ROADMAP.md b/ROADMAP.md
index b1561a4d6..cce82c546 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -402,9 +402,9 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
**Phase 2 — Schema Factory (All Sections):**
- [x] Page Config section: label, description, viewType, toolbar toggles (7 switches), navigation mode/width/openNewTab, selection, addRecord sub-editor, export + sub-config, showRecordCount, allowPrinting
-- [x] Data section: source, sortBy (expandable), groupBy, prefixField, columns selector (expandable w/ reorder), filterBy (expandable), pagination, searchable/filterable/hidden fields (expandable), quickFilters (expandable), virtualScroll, type-specific options (kanban/calendar/map/gallery/timeline/gantt)
-- [x] Appearance section: color, fieldTextColor (grid only), rowHeight (icon group, grid only), wrapHeaders, showDescription (grid only), collapseAllByDefault (groupBy-dependent), striped, bordered, resizable, conditionalFormatting (expandable, grid only), emptyState (title/message/icon)
-- [x] User Actions section: inlineEdit, addDeleteRecordsInline, rowActions (expandable), bulkActions (expandable)
+- [x] Data section: source, sortBy (expandable), groupBy (grid/gallery), columns selector (expandable w/ reorder), filterBy (expandable), pagination, searchable/filterable/hidden fields (expandable), quickFilters (expandable), virtualScroll (grid only), type-specific options (kanban/calendar/map/gallery/timeline/gantt)
+- [x] Appearance section: color (grid/calendar/timeline/gantt), rowHeight (icon group, grid only), wrapHeaders (grid only), showDescription, striped/bordered (grid only), resizable (grid only), conditionalFormatting (expandable, grid/kanban), emptyState (title/message/icon)
+- [x] User Actions section: inlineEdit (grid only), addDeleteRecordsInline (grid only), rowActions/bulkActions (expandable, grid/kanban)
- [x] Sharing section: sharingEnabled, sharingVisibility (visibleWhen: sharing.enabled)
- [x] Accessibility section: ariaLabel, ariaDescribedBy, ariaLive
- [x] `ExpandableWidget` component for hook-safe expandable sub-sections within custom render functions
@@ -435,14 +435,20 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
**Phase 6 — Config Panel Cleanup (Invalid Items Fix):**
- [x] Remove `densityMode` field from appearance section (redundant with `rowHeight` which provides finer 5-value granularity)
-- [x] Add `visibleWhen` to toolbar toggles: `showGroup` (grid/kanban only), `showColor` (grid only), `showDensity` (grid only)
-- [x] Add `visibleWhen` to data fields: `prefixField` (grid only), `_groupBy` (hidden for kanban — uses dedicated type-specific `kanban.groupByField`), `searchableFields`/`filterableFields`/`quickFilters`/`virtualScroll` (grid only)
-- [x] Add `visibleWhen` to appearance fields: `collapseAllByDefault` (only when `groupBy` is set), `fieldTextColor`/`showDescription`/`rowHeight`/`conditionalFormatting` (grid only)
+- [x] Remove `prefixField` from data section (not consumed by any runtime renderer)
+- [x] Remove `collapseAllByDefault` from appearance section (not consumed by any runtime renderer)
+- [x] Remove `fieldTextColor` from appearance section (not consumed by any runtime renderer)
+- [x] Remove `clickIntoRecordDetails` from userActions section (controlled implicitly via navigation mode, not directly consumed)
+- [x] Add view-type-aware `visibleWhen` to toolbar toggles: `showGroup` (grid/kanban/gallery), `showColor` (grid/calendar/timeline/gantt), `showDensity` (grid only), `showRecordCount` (grid only), `allowPrinting` (grid only)
+- [x] Add view-type-aware `visibleWhen` to data fields: `_groupBy` (grid/gallery — kanban uses dedicated type-specific option), `virtualScroll` (grid only)
+- [x] Add view-type-aware `visibleWhen` to appearance fields: `striped`/`bordered`/`wrapHeaders`/`resizable`/`rowHeight` (grid only, changed from disabledWhen to visibleWhen), `color` (grid/calendar/timeline/gantt), `conditionalFormatting` (grid/kanban)
+- [x] Add view-type-aware `visibleWhen` to userActions fields: `inlineEdit`/`addDeleteRecordsInline` (grid only), `rowActions`/`bulkActions` (grid/kanban)
+- [x] Correct `searchableFields`/`filterableFields`/`quickFilters`/`showDescription` to universal (all view types) — data fetch/toolbar features not view-specific
- [x] Extend `buildSwitchField` and `buildFieldMultiSelect` helpers to accept `visibleWhen` parameter
-- [x] 15 new visibleWhen predicate tests covering all new conditional visibility rules
-- [x] 1 new test verifying `densityMode` removal
-- [x] Updated ViewConfigPanel interaction tests to account for new visibility conditions
-- [x] All 244 schema + panel tests pass, 10 config-sync integration tests pass
+- [x] Define semantic predicates: `supportsGrouping`, `supportsColorField`, `supportsConditionalFormatting`, `supportsRowActions`, `supportsGenericGroupBy`
+- [x] 103 schema tests pass (updated field key lists, visibleWhen predicates for all view types, removed field verification)
+- [x] 136 ViewConfigPanel interaction tests pass (removed tests for deleted fields)
+- [x] 10 config-sync integration tests pass
**Code Reduction:** ~1655 lines imperative → ~170 lines declarative wrapper + ~1100 lines schema factory + ~180 lines shared utils = **>50% net reduction in component code** with significantly improved maintainability
From f43a480dec687967118fd17ba3f551549fdd1007 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 24 Feb 2026 02:52:11 +0000
Subject: [PATCH 6/6] fix: clarify clickIntoRecordDetails removal comment
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
apps/console/src/utils/view-config-schema.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/console/src/utils/view-config-schema.tsx b/apps/console/src/utils/view-config-schema.tsx
index 3e62deac4..de4b38b35 100644
--- a/apps/console/src/utils/view-config-schema.tsx
+++ b/apps/console/src/utils/view-config-schema.tsx
@@ -1206,7 +1206,7 @@ function buildUserActionsSection(
// spec: NamedListView.inlineEdit (grid-only: only ObjectGrid supports inline editing)
buildSwitchField('inlineEdit', t('console.objectView.inlineEdit'), 'toggle-inlineEdit', true,
false, undefined, isGridView),
- // NOTE: clickIntoRecordDetails removed — controlled implicitly via navigation mode, not directly consumed
+ // NOTE: clickIntoRecordDetails removed — behavior is handled by navigation mode config, not consumed as a standalone field by runtime
// spec: NamedListView.addDeleteRecordsInline (grid-only: inline add/delete is a grid feature)
buildSwitchField('addDeleteRecordsInline', t('console.objectView.addDeleteRecordsInline'), 'toggle-addDeleteRecordsInline', true,
false, undefined, isGridView),