From 5d6e4bba5e245d69333e8ee46001cc4d6585d823 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 04:38:11 +0000 Subject: [PATCH 1/6] Initial plan From d0d6ee3db55a269f19ecbf6e7bf3fb1ceba370cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 04:45:47 +0000 Subject: [PATCH 2/6] feat: implement P0 core protocol gaps - data/grouping/rowColor/pageSizeOptions/bulkActions - Add data (ViewDataSchema), grouping (GroupingConfig), rowColor (RowColorConfig) to ListViewSchema type - Add pinned and summary to ListViewSchema column type - ListView now supports schema.data with provider: 'value' for inline data - Group button enabled with field picker popover + wires grouping config to grid - Color button enabled with field/color picker popover + wires rowColor config to grid - Bulk actions bar rendered when schema.bulkActions configured and rows selected - pageSizeOptions selector now dynamically updates page size for re-fetch Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/plugin-list/src/ListView.tsx | 206 ++++++++++++++++++++++---- packages/types/src/objectql.ts | 28 +++- 2 files changed, 206 insertions(+), 28 deletions(-) diff --git a/packages/plugin-list/src/ListView.tsx b/packages/plugin-list/src/ListView.tsx index 493d7675d..99fd4e0e0 100644 --- a/packages/plugin-list/src/ListView.tsx +++ b/packages/plugin-list/src/ListView.tsx @@ -308,6 +308,21 @@ export const ListView: React.FC = ({ const [refreshKey, setRefreshKey] = React.useState(0); const [dataLimitReached, setDataLimitReached] = React.useState(false); + // Dynamic page size state (wired from pageSizeOptions selector) + const [dynamicPageSize, setDynamicPageSize] = React.useState(undefined); + const effectivePageSize = dynamicPageSize ?? schema.pagination?.pageSize ?? 100; + + // Grouping state (initialized from schema, user can add/remove via popover) + const [groupingConfig, setGroupingConfig] = React.useState(schema.grouping); + const [showGroupPopover, setShowGroupPopover] = React.useState(false); + + // Row color state (initialized from schema, user can configure via popover) + const [rowColorConfig, setRowColorConfig] = React.useState(schema.rowColor); + const [showColorPopover, setShowColorPopover] = React.useState(false); + + // Bulk action state + const [selectedRows, setSelectedRows] = React.useState([]); + // Request counter for debounce — only the latest request writes data const fetchRequestIdRef = React.useRef(0); @@ -413,10 +428,28 @@ export const ListView: React.FC = ({ return () => { isMounted = false; }; }, [schema.objectName, dataSource]); - // Fetch data effect + // Fetch data effect — supports schema.data (ViewDataSchema) provider modes React.useEffect(() => { let isMounted = true; const requestId = ++fetchRequestIdRef.current; + + // Check for inline data via schema.data provider: 'value' + if (schema.data && typeof schema.data === 'object' && !Array.isArray(schema.data)) { + const dataConfig = schema.data as any; + if (dataConfig.provider === 'value' && Array.isArray(dataConfig.items)) { + setData(dataConfig.items); + setLoading(false); + setDataLimitReached(false); + return; + } + } + // Also support schema.data as a plain array (shorthand for value provider) + if (Array.isArray(schema.data)) { + setData(schema.data as any[]); + setLoading(false); + setDataLimitReached(false); + return; + } const fetchData = async () => { if (!dataSource || !schema.objectName) return; @@ -463,13 +496,10 @@ export const ListView: React.FC = ({ .map(item => ({ field: item.field, order: item.order })) : undefined; - // Configurable page size from schema.pagination, default 100 - const pageSize = schema.pagination?.pageSize || 100; - const results = await dataSource.find(schema.objectName, { $filter: finalFilter, $orderby: sort, - $top: pageSize, + $top: effectivePageSize, ...(searchTerm ? { $search: searchTerm, ...(schema.searchableFields && schema.searchableFields.length > 0 @@ -493,7 +523,7 @@ export const ListView: React.FC = ({ } setData(items); - setDataLimitReached(items.length >= pageSize); + setDataLimitReached(items.length >= effectivePageSize); } catch (err) { // Only log errors from the latest request if (requestId === fetchRequestIdRef.current) { @@ -509,7 +539,7 @@ export const ListView: React.FC = ({ fetchData(); return () => { isMounted = false; }; - }, [schema.objectName, dataSource, schema.filters, schema.pagination?.pageSize, currentSort, currentFilters, activeQuickFilters, userFilterConditions, refreshKey, searchTerm, schema.searchableFields]); // Re-fetch on filter/sort/search change + }, [schema.objectName, schema.data, dataSource, schema.filters, effectivePageSize, currentSort, currentFilters, activeQuickFilters, userFilterConditions, refreshKey, searchTerm, schema.searchableFields]); // Re-fetch on filter/sort/search change // Available view types based on schema configuration const availableViews = React.useMemo(() => { @@ -667,6 +697,9 @@ export const ListView: React.FC = ({ ...(schema.resizable != null ? { resizable: schema.resizable } : {}), ...(schema.selection ? { selection: schema.selection } : {}), ...(schema.pagination ? { pagination: schema.pagination } : {}), + ...(groupingConfig ? { grouping: groupingConfig } : {}), + ...(rowColorConfig ? { rowColor: rowColorConfig } : {}), + ...(schema.rowActions ? { rowActions: schema.rowActions } : {}), ...(schema.options?.grid || {}), }; case 'kanban': @@ -1020,15 +1053,62 @@ export const ListView: React.FC = ({ {/* Group */} {toolbarFlags.showGroup && ( - + + + + + +
+
+

Group By

+ {groupingConfig && ( + + )} +
+
+ {allFields.map(field => { + const isGrouped = groupingConfig?.fields?.some(f => f.field === field.name); + return ( + + ); + })} +
+
+
+
)} {/* Sort */} @@ -1072,15 +1152,54 @@ export const ListView: React.FC = ({ {/* Color */} {toolbarFlags.showColor && ( - + + + + + +
+
+

Row Color

+ {rowColorConfig && ( + + )} +
+
+ + +
+
+
+
)} {/* Row Height / Density Mode */} @@ -1288,6 +1407,38 @@ export const ListView: React.FC = ({ )} + {/* Bulk Actions Bar */} + {schema.bulkActions && schema.bulkActions.length > 0 && selectedRows.length > 0 && ( +
+ {selectedRows.length} selected +
+ {schema.bulkActions.map(action => ( + + ))} +
+ +
+ )} + {/* Record count status bar (Airtable-style) */} {!loading && data.length > 0 && schema.showRecordCount !== false && (
= ({ {data.length === 1 ? t('list.recordCountOne', { count: data.length }) : t('list.recordCount', { count: data.length })} {dataLimitReached && ( - {t('list.dataLimitReached', { limit: schema.pagination?.pageSize || 100 })} + {t('list.dataLimitReached', { limit: effectivePageSize })} )} {schema.pagination?.pageSizeOptions && schema.pagination.pageSizeOptions.length > 0 && (