Skip to content

Migrate ViewConfigPanel to Schema-Driven architecture (1655→170 lines)#735

Merged
hotlong merged 7 commits intomainfrom
copilot/migrate-view-config-panel-schema
Feb 23, 2026
Merged

Migrate ViewConfigPanel to Schema-Driven architecture (1655→170 lines)#735
hotlong merged 7 commits intomainfrom
copilot/migrate-view-config-panel-schema

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 23, 2026

ViewConfigPanel was ~1655 lines of imperative UI code. Migrated to the existing Schema-Driven config panel framework (ConfigPanelRenderer + useConfigDraft + ConfigPanelSchema).

Changes

New files

  • apps/console/src/utils/view-config-utils.ts — Extracted shared utilities: operator mappings (SPEC_TO_BUILDER_OP/BUILDER_TO_SPEC_OP), normalizeFieldType, parseSpecFilter/toSpecFilter, field option derivation
  • apps/console/src/utils/view-config-schema.tsx — Schema factory producing a ConfigPanelSchema with all 6 sections. Complex widgets (column selector, filter/sort builders, conditional formatting, quick filters) use type: 'custom' render functions. Includes ExpandableWidget component to avoid hooks-in-render violations for expandable sub-sections

Rewritten

  • apps/console/src/components/ViewConfigPanel.tsx — Now a ~170-line wrapper: useConfigDraft for state, buildViewConfigSchema for structure, ConfigPanelRenderer for rendering
// Before: 1655 lines of imperative JSX with manual useState/useEffect/useCallback
// After:
const { draft, isDirty, updateField, discard } = useConfigDraft(effectiveActiveView, { mode, onUpdate: onViewUpdate });
const schema = buildViewConfigSchema({ t, fieldOptions, objectDef, updateField, filterGroupValue, sortItemsValue });
return <ConfigPanelRenderer open={open} schema={dynamicSchema} draft={draft} ... />;

Enhanced

  • ConfigPanelRenderer — Added optional props for panel customization: panelRef, role, ariaLabel, tabIndex, testId, closeTitle, footerTestId, saveTestId, discardTestId. Enables backward-compatible test IDs when wrapping in domain-specific panels

Tests

  • All 122 existing ViewConfigPanel tests pass (mock updated for ConfigPanelRenderer + useConfigDraft)
  • All 24 ObjectView integration tests pass
  • 53 new tests covering utils and schema factory
  • 2,457 total tests across 81 files pass
Original prompt

This section details on the original issue you should resolve

<issue_title>Console 右侧面板改造为通用 Schema-Driven 配置体系(包含全部迁移与优化任务)</issue_title>
<issue_description>## 背景
当前 console 的 ViewConfigPanel 右侧面板为命令式实现(>1600行),维护成本高,缺乏声明式抽象,未来难以快速迭代或支持新场景。通用 Schema-Driven Panel 框架已成熟,应全面迁移并优化 console 面板。


改造目标

  • 将 ViewConfigPanel 以及全部右侧面板(UserActions/Sharing/Accessibility/Data/Appearance/PageConfig)迁移为 Schema-Driven 配置体系,彻底消除命令式冗余代码
  • 保留所有现有功能,同时提升一致性、扩展性与可维护性
  • 支持复杂/定制交互采用 type="custom" 或 escape hatch
  • 统一草稿态、脏检测、保存/丢弃等交互逻辑,适配 useConfigDraft

具体任务拆解

1. 公共逻辑抽取与框架接入

  • 抽取/迁移 Operator Mapping(SPEC_TO_BUILDER_OP, BUILDER_TO_SPEC_OP)、normalizeFieldType、parseSpecFilter、toSpecFilter 至 @object-ui/components 或共用 utils 层
  • ViewConfigPanel 改造为轻量外壳,只负责 schema factory、hook 调用与渲染
  • i18n label 用 t 函数参数化注入各字段
  • 保证所有字段变化均通过 onViewUpdate 实时同步草稿与预览
  • 保证 useConfigDraft 支持 create/edit/dirty/discard/save 全流程

2. 按 Section 迁移 ViewConfigPanel → Schema-Driven

2.1 UserActions / Sharing / Accessibility (Phase 1)

  • 全部字段直接映射为 ConfigPanelSchema 中 input/select/switch 类型,无 custom widget
  • 预计1天完成
  • 保证 testid/单元测试兼容

2.2 Page Config Section (Phase 2)

  • label/description/type/showSearch/showFilters/showSort/showHideFields/showGroup/showColor/showDensity/allowExport/showRecordCount/allowPrinting/selection.type/densityMode等映射
  • Navigation/AddRecord/ExportOptions等复合字段以 type:'custom' 或拆分+ visibleWhen 处理

2.3 Appearance Section (Phase 3)

  • rowHeight 用 type:'icon-group' 直接映射
  • conditionalFormatting 用 custom widget
  • EmptyState 分为3个 input 字段

2.4 Data Section (Phase 4)

  • source/groupBy/prefixField/pageSize/virtualScroll等直映射
  • Fields/Columns 选择器(多选+可排序)抽为 ColumnSelectorWidget(type:'custom')
  • Searchable/Filterable/Hidden Fields 用 FieldMultiSelectWidget(type:'custom')
  • Sort/Filter 用框架 type:'sort' / 'filter' 并桥接 spec <-> builder
  • Type-Specific Options 用 visibleWhen+动态 section+schema工厂注入

2.5 清理冗余与优化(Phase 5)

  • 删除旧命令式 ViewConfigPanel 代码
  • 移动公共Utils
  • 单元测试、E2E与 Storybook用例覆盖

风险重点

  • 保证所有 spec 格式桥接逻辑保留,不丢失业务细节
  • Create Mode 默认值/流程需全部兼容
  • onViewUpdate 实时同步需按字段粒度
  • testid/快照测试需要兼容原有用例
  • i18n 字段标签全量参数化

验收标准

  • 所有 Section 均为 Schema-Driven 实现,冗余命令式代码减少 >50%
  • 配置面板可用性、交互一致性与现有体验至少持平
  • 所有主要配置项、面板均有 Storybook+测试覆盖
  • 支持后续平台扩展与自定义场景

预估时间

  • 总计 8.5 工作日,分阶段推进

相关链接:


@hotlong 本任务涵盖所有 Console 配置面板迁移、共用逻辑抽取、全平台 Schema-Driven 流程设计与测试。建议完成后运行 test 并更新 Roadmap。
</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectui Ready Ready Preview, Comment Feb 23, 2026 4:52am
objectui-demo Error Error Feb 23, 2026 4:52am
objectui-storybook Error Error Feb 23, 2026 4:52am

Request Review

Copilot AI and others added 2 commits February 23, 2026 04:01
…idget

Replace all inline useState expand/collapse patterns in view-config-schema.tsx
render functions with the existing ExpandableWidget component. This eliminates
8 occurrences of React.useState inside render callbacks (sort, fields/columns,
filter, quick filters, conditional formatting, row actions, bulk actions, and
buildFieldMultiSelect helper).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Extract shared utilities (operator mappings, filter/sort bridge, field normalization) to view-config-utils.ts
- Create schema factory (view-config-schema.tsx) with all sections: Page, Data, Appearance, UserActions, Sharing, Accessibility
- Rewrite ViewConfigPanel as thin wrapper using useConfigDraft + ConfigPanelRenderer
- Enhance ConfigPanelRenderer with accessibility props (panelRef, role, ariaLabel, tabIndex, testId)
- Add ExpandableWidget component for proper hook-safe expandable sub-sections
- Update test mock to support ConfigPanelRenderer and useConfigDraft
- All 122 existing tests pass

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…props to ConfigPanelRenderer

- Add closeTitle, footerTestId, saveTestId, discardTestId props to ConfigPanelRenderer
- ViewConfigPanel passes view-specific test IDs for backward compatibility
- Remove old backup file
- All 2404 tests pass across 80 test files

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Add view-config-schema.test.tsx covering:
- normalizeFieldType: all field type normalizations
- parseSpecFilter: all filter parsing formats (triplet, and/or, nested, object)
- toSpecFilter: filter conversion back to spec format
- parseCommaSeparated / parseNumberList: basic parsing
- deriveFieldOptions: field options from objectDef
- toFilterGroup / toSortItems: bridge functions
- Operator mapping constants
- buildViewConfigSchema: schema structure, sections, collapsible flags
- visibleWhen predicates for navigation and sharing fields

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- 53 new tests for view-config-utils and view-config-schema
- ROADMAP.md updated with P1.11 Schema-Driven View Config Panel Migration section
- Full test suite: 2457 tests across 81 files, all pass

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
- Replace ternary in useMemo deps with computed stableKey variable

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Migrate console ViewConfigPanel to schema-driven configuration Migrate ViewConfigPanel to Schema-Driven architecture (1655→170 lines) Feb 23, 2026
Copilot AI requested a review from hotlong February 23, 2026 04:27
@hotlong hotlong marked this pull request as ready for review February 23, 2026 04:34
Copilot AI review requested due to automatic review settings February 23, 2026 04:34
@hotlong hotlong merged commit 18d479d into main Feb 23, 2026
3 of 6 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the Console ViewConfigPanel from a large imperative implementation to the existing schema-driven config panel framework (ConfigPanelRenderer + useConfigDraft + ConfigPanelSchema), aiming to reduce complexity while preserving behavior and test coverage.

Changes:

  • Added shared ViewConfigPanel utilities (view-config-utils.ts) for filter/sort bridging, operator mappings, parsing helpers, and field option derivation.
  • Added a schema factory (view-config-schema.tsx) that declaratively defines ViewConfigPanel sections/fields, with custom render escape hatches for complex widgets.
  • Rewrote ViewConfigPanel.tsx into a thin wrapper that builds schema + manages draft state via useConfigDraft, and enhanced ConfigPanelRenderer with customization + test id overrides.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/components/src/custom/config-panel-renderer.tsx Adds panel customization props (refs/ARIA/test IDs) for reuse and backward-compatible testing.
apps/console/src/utils/view-config-utils.ts Extracts operator mappings, filter/sort bridge helpers, parsing utilities, and constants for view config.
apps/console/src/utils/view-config-schema.tsx Implements the schema factory for all ViewConfigPanel sections using schema-driven rendering.
apps/console/src/components/ViewConfigPanel.tsx Replaces imperative UI with schema-driven wrapper using useConfigDraft + schema factory.
apps/console/src/tests/view-config-schema.test.tsx Adds unit tests for utilities and schema factory behavior.
apps/console/src/tests/ViewConfigPanel.test.tsx Updates mocks/tests for new schema-driven rendering approach.
ROADMAP.md Updates roadmap to reflect completion of the schema-driven ViewConfigPanel migration.

Comment on lines +419 to +427
function buildDataSection(
t: ViewSchemaFactoryOptions['t'],
fieldOptions: FieldOption[],
fieldSelectWithNone: Array<{ value: string; label: string }>,
objectDef: ViewSchemaFactoryOptions['objectDef'],
updateField: ViewSchemaFactoryOptions['updateField'],
filterGroupValue: FilterGroup,
sortItemsValue: SortItem[],
): ConfigPanelSchema['sections'][number] {
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fieldSelectWithNone is passed into buildDataSection/buildAppearanceSection but never referenced inside those functions. With noUnusedParameters: true, these unused parameters will cause a type-check failure. Either remove fieldSelectWithNone from the signatures/call sites (and delete the local if no longer needed), or actually use it to render the select options.

Copilot uses AI. Check for mistakes.
onClick={onClose}
className="h-7 w-7 p-0"
data-testid="config-panel-close"
title={closeTitle}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The close button is icon-only, but it only receives a title. title is not a reliable accessible name for screen readers, so this control likely needs an explicit aria-label (e.g., derived from closeTitle or a default like "Close").

Suggested change
title={closeTitle}
title={closeTitle}
aria-label={closeTitle ?? 'Close'}

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +96
// Stabilize source reference: only change when view ID changes.
// This prevents useConfigDraft from resetting on every parent re-render
// (same behavior as original useEffect with [activeView.id] dependency).
const stableKey = mode === 'create' ? 'create' : activeView.id;
const stableActiveView = useMemo(
() => ({ ...activeView }),
[stableKey], // eslint-disable-line react-hooks/exhaustive-deps
);
const effectiveActiveView = mode === 'create' ? defaultNewView : stableActiveView;

// Reset draft when switching to a different view (by ID change only).
// We intentionally depend on activeView.id rather than the full activeView
// object so that real-time draft propagation (via onViewUpdate → parent
// setViewDraft → merged activeView) does not reset isDirty to false.
useEffect(() => {
setDraft({ ...effectiveActiveView });
setIsDirty(mode === 'create');
}, [mode, activeView.id]); // eslint-disable-line react-hooks/exhaustive-deps
// Schema-driven draft state management
const { draft, isDirty, updateField, discard } = useConfigDraft(effectiveActiveView, {
mode,
onUpdate: onViewUpdate,
});
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useConfigDraft.discard() resets the draft back to the source object. Here the source is effectiveActiveView, which is memoized to a snapshot keyed only by activeView.id. As a result, calling discard() after Save (and also on Discard) can revert the UI back to stale pre-save values and prevents the panel from picking up updated activeView props with the same id. Consider tracking a local “committedSource” state that you update on activeView.id changes and on successful save (e.g., set committedSource = draft), and pass that committedSource into useConfigDraft so discard/save reset correctly without losing changes.

Copilot uses AI. Check for mistakes.
*/

import React from 'react';
import { Input, Switch, Checkbox, FilterBuilder, SortBuilder, ConfigRow, SectionHeader } from '@object-ui/components';
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SectionHeader is imported but never used in this file. With noUnusedLocals: true in the console tsconfig, this will fail type-check/build. Remove the unused import (or use it if it was intended).

Suggested change
import { Input, Switch, Checkbox, FilterBuilder, SortBuilder, ConfigRow, SectionHeader } from '@object-ui/components';
import { Input, Switch, Checkbox, FilterBuilder, SortBuilder, ConfigRow } from '@object-ui/components';

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Console 右侧面板改造为通用 Schema-Driven 配置体系(包含全部迁移与优化任务)

3 participants