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
62 changes: 32 additions & 30 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,11 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind
- ✅ `gridSchema` in plugin-view includes `striped`/`bordered` from active view config (Grid only)
- ✅ Plugin `renderContent` passes `rowHeight`, `densityMode`, `groupBy` to `renderListView` schema
- ✅ `useMemo` dependency arrays expanded to cover full view config
- ⚠️ `showSort`/`showSearch`/`showFilters` only wired to Grid — not propagated to Kanban/Calendar/Timeline/Gallery/Map/Gantt
- ⚠️ `striped`/`bordered` only applied via `gridSchema` — not passed to non-grid views through `renderListView`
- ⚠️ `generateViewSchema` sets `showSearch: false` unconditionally for all non-grid types
- ⚠️ Console `renderListView` callback does not pass `showSort`/`showSearch`/`showFilters`/`striped`/`bordered` to `fullSchema`
- ✅ `generateViewSchema` propagates `showSearch`/`showSort`/`showFilters`/`striped`/`bordered`/`color` from `activeView` for all view types (hardcoded `showSearch: false` removed)
- ✅ Console `renderListView` passes `showSort`/`showSearch`/`showFilters`/`striped`/`bordered`/`color`/`filter`/`sort` to `fullSchema`
- ✅ `NamedListView` type declares `showSearch`/`showSort`/`showFilters`/`striped`/`bordered`/`color` as first-class properties
- ✅ `ListViewSchema` TypeScript interface and Zod schema include `showSearch`/`showSort`/`showFilters`/`color`
- ✅ ViewConfigPanel refactored into Page Config (toolbar/shell) and ListView Config (data/appearance) sections
- ⚠️ No per-view-type integration tests verifying config properties reach non-grid renderers
- [ ] Conditional formatting rules

Expand All @@ -160,57 +161,58 @@ ObjectUI is a universal Server-Driven UI (SDUI) engine built on React + Tailwind

| Property | Grid | Kanban | Calendar | Timeline | Gallery | Map | Gantt |
|----------|:----:|:------:|:--------:|:--------:|:-------:|:---:|:-----:|
| `showSearch` | ✅ | | | | | | |
| `showSort` | ✅ | | | | | | |
| `showFilters` | ✅ | | | | | | |
| `showSearch` | ✅ | | | | | | |
| `showSort` | ✅ | | | | | | |
| `showFilters` | ✅ | | | | | | |
| `rowHeight` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `densityMode` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `striped` | ✅ | | | | | | |
| `bordered` | ✅ | | | | | | |
| `striped` | ✅ | | | | | | |
| `bordered` | ✅ | | | | | | |
| `groupBy` | N/A | ✅ | N/A | N/A | N/A | N/A | N/A |
| `color` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| `color` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `filter`/`sort` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Type-specific options | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |

**Root Causes:**
1. **`generateViewSchema` (plugin-view):** Hardcodes `showSearch: false` for non-grid views; does not propagate `showSort`/`showFilters`/`striped`/`bordered`/`color` from `activeView`
2. **Console `renderListView`:** Omits `showSort`/`showSearch`/`showFilters`/`striped`/`bordered` from the `fullSchema` passed to `ListView`
3. **`NamedListView` type:** Does not declare `showSearch`/`showSort`/`showFilters`/`striped`/`bordered`/`color` as first-class properties
4. **No per-view-type integration tests:** Tests verify config reaches the `ViewConfigPanel` switch, but not that non-grid renderers actually receive and apply the properties
**Root Causes (resolved):**
1. ~~**`generateViewSchema` (plugin-view):** Hardcodes `showSearch: false` for non-grid views~~ → Now propagates from `activeView`
2. ~~**Console `renderListView`:** Omits toolbar/display flags from `fullSchema`~~ → Now passes all config properties
3. ~~**`NamedListView` type:** Missing toolbar/display properties~~ → Added as first-class properties
4. **No per-view-type integration tests:** Pending — tests verify config reaches `fullSchema`, but per-renderer integration tests still needed

**Phase 1 — Grid/Table View (baseline, already complete):**
- [x] `gridSchema` includes `striped`/`bordered` from `activeView`
- [x] `showSort`/`showSearch`/`showFilters` passed via `ObjectViewSchema`
- [x] `useMemo` dependency arrays cover all grid config

**Phase 2 — Kanban Live Preview:**
- [ ] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` kanban branch
- [ ] Pass `color`/`striped`/`bordered` in `renderContent` → `renderListView` for kanban
- [ ] Ensure `groupBy` config changes reflect immediately (currently ✅ via `renderListView`)
- [x] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` kanban branch
- [x] Pass `color`/`striped`/`bordered` in `renderContent` → `renderListView` for kanban
- [x] Ensure `groupBy` config changes reflect immediately (currently ✅ via `renderListView`)
- [ ] Add integration test: ViewConfigPanel kanban config change → Kanban renderer receives updated props

**Phase 3 — Calendar Live Preview:**
- [ ] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` calendar branch
- [ ] Pass `filter`/`sort`/appearance properties to calendar renderer in real-time
- [ ] Verify `startDateField`/`endDateField` config changes trigger re-render via `useMemo` deps
- [x] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` calendar branch
- [x] Pass `filter`/`sort`/appearance properties to calendar renderer in real-time
- [x] Verify `startDateField`/`endDateField` config changes trigger re-render via `useMemo` deps
- [ ] Add integration test: ViewConfigPanel calendar config change → Calendar renderer receives updated props

**Phase 4 — Timeline/Gantt Live Preview:**
- [ ] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` timeline/gantt branches
- [ ] Pass appearance properties (`color`, `striped`, `bordered`) through `renderListView` schema
- [ ] Ensure `dateField`/`startDateField`/`endDateField` config changes trigger re-render
- [x] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` timeline/gantt branches
- [x] Pass appearance properties (`color`, `striped`, `bordered`) through `renderListView` schema
- [x] Ensure `dateField`/`startDateField`/`endDateField` config changes trigger re-render
- [ ] Add integration tests for timeline and gantt config sync

**Phase 5 — Gallery & Map Live Preview:**
- [ ] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` gallery/map branches
- [ ] Pass appearance properties through `renderListView` schema for gallery/map
- [ ] Ensure gallery `imageField`/`titleField` and map `locationField`/`zoom`/`center` config changes trigger re-render
- [x] Propagate `showSort`/`showSearch`/`showFilters` through `generateViewSchema` gallery/map branches
- [x] Pass appearance properties through `renderListView` schema for gallery/map
- [x] Ensure gallery `imageField`/`titleField` and map `locationField`/`zoom`/`center` config changes trigger re-render
- [ ] Add integration tests for gallery and map config sync

**Phase 6 — Data Flow & Dependency Refactor:**
- [ ] Add `showSearch`/`showSort`/`showFilters`/`striped`/`bordered`/`color` to `NamedListView` type in `@object-ui/types`
- [ ] Update Console `renderListView` to pass all config properties in `fullSchema`
- [x] Add `showSearch`/`showSort`/`showFilters`/`striped`/`bordered`/`color` to `NamedListView` type in `@object-ui/types`
- [x] Update Console `renderListView` to pass all config properties in `fullSchema`
- [ ] Audit all `useMemo`/`useEffect` dependency arrays in `plugin-view/ObjectView.tsx` for missing `activeView` sub-properties
- [ ] Remove hardcoded `showSearch: false` from `generateViewSchema` — use `activeView.showSearch ?? schema.showSearch` instead
- [x] Remove hardcoded `showSearch: false` from `generateViewSchema` — use `activeView.showSearch ?? schema.showSearch` instead

**Phase 7 — End-to-End Integration Tests:**
- [ ] Per-view-type test: Grid config sync (showSort, showSearch, showFilters, striped, bordered)
Expand Down
91 changes: 91 additions & 0 deletions apps/console/src/__tests__/ViewConfigPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1479,4 +1479,95 @@ describe('ViewConfigPanel', () => {
// The second move should operate on the updated state
expect(onViewUpdate).toHaveBeenCalledTimes(2);
});

// ── Section Layout Tests: Page vs ListView Config ──

it('renders page-level config items in the Page section (showSearch, showFilters, showSort, clickIntoRecordDetails, addRecordViaForm, allowExport)', () => {
render(
<ViewConfigPanel
open={true}
onClose={vi.fn()}
activeView={mockActiveView}
objectDef={mockObjectDef}
/>
);

// Page section should contain toolbar toggles
const panel = screen.getByTestId('view-config-panel');
expect(panel).toBeInTheDocument();

// These toggles should be rendered in the page section (always visible, not behind a collapsible)
expect(screen.getByTestId('toggle-showSearch')).toBeInTheDocument();
expect(screen.getByTestId('toggle-showFilters')).toBeInTheDocument();
expect(screen.getByTestId('toggle-showSort')).toBeInTheDocument();
expect(screen.getByTestId('toggle-clickIntoRecordDetails')).toBeInTheDocument();
expect(screen.getByTestId('toggle-addRecordViaForm')).toBeInTheDocument();
expect(screen.getByTestId('toggle-allowExport')).toBeInTheDocument();
});

it('renders section description hints for Page and ListView config', () => {
render(
<ViewConfigPanel
open={true}
onClose={vi.fn()}
activeView={mockActiveView}
objectDef={mockObjectDef}
/>
);

expect(screen.getByText('console.objectView.pageConfigHint')).toBeInTheDocument();
expect(screen.getByText('console.objectView.listConfigHint')).toBeInTheDocument();
});

it('renders list-level inline action items in the User Actions section (editRecordsInline, addDeleteRecordsInline)', () => {
render(
<ViewConfigPanel
open={true}
onClose={vi.fn()}
activeView={mockActiveView}
objectDef={mockObjectDef}
/>
);

// List-level inline actions should be in the User Actions collapsible section
expect(screen.getByTestId('toggle-editRecordsInline')).toBeInTheDocument();
expect(screen.getByTestId('toggle-addDeleteRecordsInline')).toBeInTheDocument();
});

it('page-level toggles call onViewUpdate correctly for live preview', () => {
const onViewUpdate = vi.fn();
render(
<ViewConfigPanel
open={true}
onClose={vi.fn()}
activeView={mockActiveView}
objectDef={mockObjectDef}
onViewUpdate={onViewUpdate}
/>
);

// Toggle showSearch off
fireEvent.click(screen.getByTestId('toggle-showSearch'));
expect(onViewUpdate).toHaveBeenCalledWith('showSearch', false);

// Toggle showFilters off
fireEvent.click(screen.getByTestId('toggle-showFilters'));
expect(onViewUpdate).toHaveBeenCalledWith('showFilters', false);

// Toggle showSort off
fireEvent.click(screen.getByTestId('toggle-showSort'));
expect(onViewUpdate).toHaveBeenCalledWith('showSort', false);

// Toggle clickIntoRecordDetails off
fireEvent.click(screen.getByTestId('toggle-clickIntoRecordDetails'));
expect(onViewUpdate).toHaveBeenCalledWith('clickIntoRecordDetails', false);

// Toggle addRecordViaForm on
fireEvent.click(screen.getByTestId('toggle-addRecordViaForm'));
expect(onViewUpdate).toHaveBeenCalledWith('addRecordViaForm', true);

// Toggle allowExport off
fireEvent.click(screen.getByTestId('toggle-allowExport'));
expect(onViewUpdate).toHaveBeenCalledWith('allowExport', false);
});
});
10 changes: 10 additions & 0 deletions apps/console/src/components/ObjectView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,16 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) {
appearance: viewDef.showDescription != null
? { showDescription: viewDef.showDescription }
: listSchema.appearance,
// Propagate toolbar/display flags for all view types
showSearch: viewDef.showSearch ?? listSchema.showSearch,
showSort: viewDef.showSort ?? listSchema.showSort,
showFilters: viewDef.showFilters ?? listSchema.showFilters,
striped: viewDef.striped ?? listSchema.striped,
bordered: viewDef.bordered ?? listSchema.bordered,
color: viewDef.color ?? listSchema.color,
// Propagate filter/sort as default filters/sort for data flow
...(viewDef.filter?.length ? { filters: viewDef.filter } : {}),
...(viewDef.sort?.length ? { sort: viewDef.sort } : {}),
Comment on lines +314 to +315
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

The conditional spreads for filters/sort only apply when viewDef.filter/viewDef.sort are non-empty. This makes it impossible to clear an existing filter/sort via live preview (an explicit empty array won’t override listSchema.filters/listSchema.sort). Consider treating undefined as “no override” but allowing an empty array to override (e.g., check Array.isArray(viewDef.filter) / Array.isArray(viewDef.sort) instead of .length).

Suggested change
...(viewDef.filter?.length ? { filters: viewDef.filter } : {}),
...(viewDef.sort?.length ? { sort: viewDef.sort } : {}),
...(Array.isArray(viewDef.filter) ? { filters: viewDef.filter } : {}),
...(Array.isArray(viewDef.sort) ? { sort: viewDef.sort } : {}),

Copilot uses AI. Check for mistakes.
options: {
kanban: {
groupBy: viewDef.kanban?.groupByField || viewDef.kanban?.groupField || 'status',
Expand Down
Loading