Add record table widget to dashboards#18747
Conversation
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
...s/object-record/record-table/virtualization/components/RecordTableRowVirtualizedFullData.tsx
Fixed
Show fixed
Hide fixed
There was a problem hiding this comment.
Pull request overview
This PR introduces a new RECORD_TABLE widget type end-to-end (shared types, server validation/migrations, and front-end creation/settings/rendering) so dashboards can embed a standalone record table with configurable source, fields, filters, and sorts.
Changes:
- Add
RECORD_TABLEwidget types/configuration across shared/server/front and update validation + migration to persist the new enum value. - Add a new dashboard-side “Record Table Settings” UI with sub-pages for fields/filters/sorts and data-source selection (including view creation/deletion).
- Add a standalone record-table provider/renderer for widgets and hook dashboard save to persist per-widget view field/filter/sort changes.
Reviewed changes
Copilot reviewed 69 out of 70 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/twenty-shared/src/types/page-layout/page-layout-widget-configuration.type.ts | Adds RecordTableConfiguration to shared widget configuration union. |
| packages/twenty-shared/src/types/index.ts | Re-exports RecordTableConfiguration. |
| packages/twenty-shared/src/types/SidePanelPages.ts | Adds side-panel page enum for record table settings. |
| packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-widget/services/utils/from-universal-configuration-to-flat-page-layout-widget-configuration.util.ts | Allows RECORD_TABLE configuration to pass through migration flattening. |
| packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/build-standard-flat-page-layout-widget-metadata-maps.util.ts | Maps WidgetType.RECORD_TABLE to WidgetConfigurationType.RECORD_TABLE. |
| packages/twenty-server/src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-input.util.ts | Adds DTO-based validation for RECORD_TABLE configs. |
| packages/twenty-server/src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum.ts | Adds RECORD_TABLE to widget type enum. |
| packages/twenty-server/src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type.ts | Adds RECORD_TABLE to configuration type enum. |
| packages/twenty-server/src/engine/metadata-modules/page-layout-widget/dtos/record-table-configuration.dto.ts | New GraphQL DTO + class-validator rules for record table configuration. |
| packages/twenty-server/src/engine/metadata-modules/page-layout-widget/constants/all-widget-configuration-type-validator-by-widget-configuration-type.constant.ts | Registers RECORD_TABLE DTO in validator map. |
| packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts | Adds RECORD_TABLE to universal config conversion. |
| packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service.ts | Enables create/update validation routing for RECORD_TABLE widgets. |
| packages/twenty-server/src/database/typeorm/core/migrations/common/1774072000000-addRecordTableWidgetType.ts | DB migration to add enum value RECORD_TABLE to pageLayoutWidget.type. |
| packages/twenty-front/src/modules/side-panel/utils/getSidePanelSubPageTitle.ts | Adds titles for record-table sub-pages (fields/filters/sorts). |
| packages/twenty-front/src/modules/side-panel/types/SidePanelSubPages.ts | Adds record-table sub-page enum values. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/utils/isWidgetConfigurationOfType.ts | Extends widget config typename map to include RecordTableConfiguration. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/utils/getPageLayoutPageTitle.ts | Adds title for record-table settings page. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/utils/getPageLayoutIcon.ts | Adds icon mapping for record-table settings page. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/types/PageLayoutSidePanelPage.ts | Adds record-table settings page to page union. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/SidePanelRecordTableSortSubPage.tsx | New side-panel sub-page to edit sorts. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/SidePanelRecordTableFilterSubPage.tsx | New side-panel sub-page to edit filters. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/SidePanelRecordTableFieldsSubPage.tsx | New side-panel sub-page to edit field visibility/order. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSortsInitializeStateEffect.tsx | Initializes record-sort state from view sorts. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSortsContent.tsx | UI to add/remove/edit sorts. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsSorts.tsx | Container wiring view + object metadata into sorts UI. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFiltersInitializeStateEffect.tsx | Initializes record-filter state from view filters/groups. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFilters.tsx | Container wiring advanced filter UI for widget view. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFieldVisibility.tsx | Field visibility + drag reorder UI driven by view fields. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsDataSourceSelect.tsx | New (currently unused) object source picker component. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableFieldsDropdownContent.tsx | Dropdown UI to manage visible/hidden fields (record-field state). |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableDataSourceDropdownContent.tsx | Dropdown UI to select object source and create/delete widget views. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutWidgetTypeSelect.tsx | Adds RECORD_TABLE option and creation/navigation flow. |
| packages/twenty-front/src/modules/side-panel/pages/page-layout/components/SidePanelPageLayoutRecordTableSettings.tsx | New main record-table settings page (source/fields/filter/sort). |
| packages/twenty-front/src/modules/side-panel/constants/SidePanelSubPagesConfig.tsx | Registers record-table sub-pages. |
| packages/twenty-front/src/modules/side-panel/constants/SidePanelPagesConfig.tsx | Registers record-table settings page. |
| packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useSaveRecordTableWidgetsViewDataOnDashboardSave.ts | Persists widget view fields/filters/groups/sorts on dashboard save. |
| packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useSaveRecordTableWidgetSortsToView.ts | Hook to persist sorts to a view. |
| packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useSaveRecordTableWidgetFiltersToView.ts | Hook to persist filters/groups to a view. |
| packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useRecordTableWidgetViewFields.ts | Hook to read/update/reorder view fields for the widget. |
| packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useDeleteViewForRecordTableWidget.ts | Hook to delete the backing view when widget/source changes. |
| packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useCreateViewForRecordTableWidget.ts | Hook to create default table view + view fields for the widget. |
| packages/twenty-front/src/modules/page-layout/widgets/record-table/components/RecordTableWidgetRendererContent.tsx | Renders record table using standalone provider. |
| packages/twenty-front/src/modules/page-layout/widgets/record-table/components/RecordTableWidgetRenderer.tsx | Widget renderer that extracts viewId/object metadata id. |
| packages/twenty-front/src/modules/page-layout/widgets/components/WidgetContentRenderer.tsx | Routes WidgetType.RECORD_TABLE to the new renderer. |
| packages/twenty-front/src/modules/page-layout/utils/createDefaultRecordTableWidget.ts | Creates default RECORD_TABLE widget draft object. |
| packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts | Invokes record-table view persistence during save. |
| packages/twenty-front/src/modules/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition.ts | Deletes backing view when removing a record-table widget. |
| packages/twenty-front/src/modules/page-layout/hooks/useEditPageLayoutWidget.ts | Routes edit flow to record-table settings page. |
| packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutWidget.ts | Deletes backing view when deleting a record-table widget. |
| packages/twenty-front/src/modules/page-layout/hooks/useCreatePageLayoutRecordTableWidget.ts | Creates record-table widget + layout entry in draft. |
| packages/twenty-front/src/modules/page-layout/graphql/fragments/pageLayoutWidgetFragment.ts | Adds GraphQL fragment selection for RecordTableConfiguration (viewId). |
| packages/twenty-front/src/modules/page-layout/constants/WidgetSizes.ts | Adds size config for WidgetType.RECORD_TABLE. |
| packages/twenty-front/src/modules/page-layout/constants/GraphWidgetSizes.ts | Adds size config for WidgetConfigurationType.RECORD_TABLE for getWidgetSize. |
| packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableRowVirtualizedFullData.tsx | Adds read-only state access for record table rows (currently unused). |
| packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableColumnHeadersReadOnlyComponentState.ts | New component state to toggle read-only behavior. |
| packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderFirstScrollableCell.tsx | Disables dropdown/resize in read-only header mode. |
| packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderFirstCell.tsx | Disables dropdown/resize/plus-button in read-only header mode. |
| packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderEmptyLastColumn.tsx | New empty header cell shown in read-only mode instead of add-column button. |
| packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer.tsx | Adds isReadOnly styling behavior for header cells. |
| packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx | Disables dropdown/resize in read-only header mode. |
| packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx | Swaps add-column button with empty column when read-only. |
| packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx | Adds CSS vars to collapse drag/checkbox columns in read-only mode. |
| packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx | Passes read-only state to column width style helper. |
| packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableViewLoadEffect.tsx | Loads record-index state when view changes for standalone widget tables. |
| packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableSetReadOnlyColumnHeadersEffect.tsx | Sets record-table read-only state based on page layout edit mode. |
| packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableProvider.tsx | Standalone provider wiring record-index + view + context-store for widgets. |
| packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableContextStoreInitEffect.tsx | Initializes context-store view/object metadata for standalone record table. |
| packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTable.tsx | Standalone record table wrapper component used by the widget renderer. |
| packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts | Loads filter-group state from view into record-index component state. |
Comments suppressed due to low confidence (1)
packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableRowVirtualizedFullData.tsx:63
isReadOnlyis read from state but never used. More importantly, in read-only mode the drag handle and checkbox cells are still rendered (only hidden via 0px width), which can leave invisible interactive elements in the DOM and be reachable via keyboard navigation. Consider usingisReadOnlyto conditionally omit the drag/checkbox cells (and any related row interactions) when the table is read-only.
const isReadOnly = useAtomComponentStateValue(
isRecordTableColumnHeadersReadOnlyComponentState,
);
const isRecordTableRowFocused = useAtomComponentFamilyStateValue(
isRecordTableRowFocusedComponentFamilyState,
realIndex,
);
const isRecordTableRowFocusActive = useAtomComponentStateValue(
isRecordTableRowFocusActiveComponentState,
recordTableId,
);
const recordId = useAtomComponentFamilySelectorValue(
recordIdByRealIndexComponentFamilySelector,
realIndex,
);
if (!isDefined(recordId)) {
return <RecordTableRowVirtualizedSkeleton />;
}
return (
<RecordTableDraggableTr
recordId={recordId}
draggableIndex={realIndex}
focusIndex={realIndex}
>
<RecordTableCellDragAndDrop />
<RecordTableCellCheckbox />
<RecordTableFieldsCells />
<RecordTablePlusButtonCellPlaceholder />
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
...ject-record/record-table/record-table-header/components/RecordTableHeaderEmptyLastColumn.tsx
Outdated
Show resolved
Hide resolved
...-layout/components/record-table-settings/RecordTableSettingsFiltersInitializeStateEffect.tsx
Show resolved
Hide resolved
packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutWidget.ts
Show resolved
Hide resolved
...y-front/src/modules/page-layout/widgets/record-table/hooks/useRecordTableWidgetViewFields.ts
Outdated
Show resolved
Hide resolved
...l/pages/page-layout/components/record-table-settings/RecordTableSettingsDataSourceSelect.tsx
Outdated
Show resolved
Hide resolved
...s/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx
Outdated
Show resolved
Hide resolved
...ge-layout/components/record-table-settings/RecordTableSettingsSortsInitializeStateEffect.tsx
Outdated
Show resolved
Hide resolved
...s/twenty-front/src/modules/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
13 issues found across 70 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/twenty-server/src/database/typeorm/core/migrations/common/1774072000000-addRecordTableWidgetType.ts">
<violation number="1" location="packages/twenty-server/src/database/typeorm/core/migrations/common/1774072000000-addRecordTableWidgetType.ts:37">
P1: Down migration can fail when `pageLayoutWidget.type` contains `RECORD_TABLE`, because it is cast directly into an enum that does not contain that value.</violation>
</file>
<file name="packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFieldVisibility.tsx">
<violation number="1" location="packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsFieldVisibility.tsx:74">
P2: These draggable rows are inside a scrollable container, but they do not enable the scroll-offset handling used by `DraggableItem`.</violation>
</file>
<file name="packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableDataSourceDropdownContent.tsx">
<violation number="1" location="packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableDataSourceDropdownContent.tsx:57">
P2: This adds a second copy of record-table data-source selection logic that already exists, increasing drift risk for permissions/filter/view-update behavior.</violation>
</file>
<file name="packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsDataSourceSelect.tsx">
<violation number="1" location="packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableSettingsDataSourceSelect.tsx:97">
P1: `handleSelectSource` is vulnerable to concurrent clicks, which can race delete/create operations and apply a stale `viewId` to the widget config.</violation>
</file>
<file name="packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useSaveRecordTableWidgetSortsToView.ts">
<violation number="1" location="packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useSaveRecordTableWidgetSortsToView.ts:46">
P1: Check mutation results and stop when a sort persistence step fails; these APIs return `status: 'failed'` without throwing, so continuing to update/delete can cause partial writes.</violation>
</file>
<file name="packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutWidget.ts">
<violation number="1" location="packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutWidget.ts:65">
P1: This permanently deletes the record-table view during a draft edit. If the user removes the widget and then cancels/reset the page-layout changes, the persisted widget comes back pointing to a view that no longer exists.</violation>
</file>
<file name="packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableViewLoadEffect.tsx">
<violation number="1" location="packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableViewLoadEffect.tsx:28">
P2: The load guard only keys on `viewId`, so changes to `objectMetadataItem` with the same view id are ignored and record-index state can become stale.</violation>
</file>
<file name="packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderEmptyLastColumn.tsx">
<violation number="1" location="packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderEmptyLastColumn.tsx:24">
P2: `border-right` is declared with only a color value, so the separator border will not be rendered. Use a full border declaration with width and style.</violation>
</file>
<file name="packages/twenty-front/src/modules/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition.ts">
<violation number="1" location="packages/twenty-front/src/modules/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition.ts:68">
P1: The record-table view is deleted before confirming the widget can actually be removed, which can leave a widget in state with its view already destroyed if the function exits early on missing layout.</violation>
</file>
<file name="packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-widget/services/utils/from-universal-configuration-to-flat-page-layout-widget-configuration.util.ts">
<violation number="1" location="packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/page-layout-widget/services/utils/from-universal-configuration-to-flat-page-layout-widget-configuration.util.ts:328">
P2: Resolve `RECORD_TABLE.viewId` from universal identifier to flat view id, like the `FIELDS` branch. Returning the universal configuration directly can leave an unresolved identifier in persisted flat widget configuration.</violation>
</file>
<file name="packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useRecordTableWidgetViewFields.ts">
<violation number="1" location="packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useRecordTableWidgetViewFields.ts:70">
P2: Reordering visible fields rewrites positions as `0..n-1`, which can conflict with hidden field positions and corrupt overall field order.</violation>
</file>
<file name="packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts">
<violation number="1" location="packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util.ts:338">
P1: RECORD_TABLE configuration is returned without converting `viewId` to a universal identifier, causing non-portable universal configuration data.</violation>
</file>
<file name="packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableSetReadOnlyColumnHeadersEffect.tsx">
<violation number="1" location="packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableSetReadOnlyColumnHeadersEffect.tsx:15">
P2: Reset the read-only atom in the effect cleanup; otherwise the cached atom-family value can leak across unmounts/instance changes and keep headers read-only unexpectedly.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
...server/src/database/typeorm/core/migrations/common/1774072000000-addRecordTableWidgetType.ts
Show resolved
Hide resolved
...l/pages/page-layout/components/record-table-settings/RecordTableSettingsDataSourceSelect.tsx
Outdated
Show resolved
Hide resolved
...nt/src/modules/page-layout/widgets/record-table/hooks/useSaveRecordTableWidgetSortsToView.ts
Show resolved
Hide resolved
packages/twenty-front/src/modules/page-layout/hooks/useDeletePageLayoutWidget.ts
Show resolved
Hide resolved
...s/twenty-front/src/modules/page-layout/hooks/useRemovePageLayoutWidgetAndPreservePosition.ts
Show resolved
Hide resolved
...les/object-record/record-table-standalone/components/StandaloneRecordTableViewLoadEffect.tsx
Outdated
Show resolved
Hide resolved
...ject-record/record-table/record-table-header/components/RecordTableHeaderEmptyLastColumn.tsx
Outdated
Show resolved
Hide resolved
...services/utils/from-universal-configuration-to-flat-page-layout-widget-configuration.util.ts
Outdated
Show resolved
Hide resolved
...y-front/src/modules/page-layout/widgets/record-table/hooks/useRecordTableWidgetViewFields.ts
Outdated
Show resolved
Hide resolved
...ct-record/record-table-widget/components/RecordTableWidgetSetReadOnlyColumnHeadersEffect.tsx
Show resolved
Hide resolved
e785a3c to
b0f52ad
Compare
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
3 issues found across 100 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableViewLoadEffect.tsx">
<violation number="1" location="packages/twenty-front/src/modules/object-record/record-table-standalone/components/StandaloneRecordTableViewLoadEffect.tsx:48">
P2: Set lastLoadedStandaloneRecordTableViewId only after loadRecordIndexStates succeeds; setting it before the load can suppress retries if the load throws (e.g., metadata not ready yet).
(Based on your team's feedback about setting guard flags only after successful operations.) [FEEDBACK_USED]</violation>
</file>
<file name="packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableDataSourceDropdownContent.tsx">
<violation number="1" location="packages/twenty-front/src/modules/side-panel/pages/page-layout/components/record-table-settings/RecordTableDataSourceDropdownContent.tsx:97">
P1: Deleting the current view before confirming the replacement view was created can permanently lose the widget’s existing table configuration when create fails.</violation>
</file>
<file name="packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useRecordTableWidgetViewFields.ts">
<violation number="1" location="packages/twenty-front/src/modules/page-layout/widgets/record-table/hooks/useRecordTableWidgetViewFields.ts:70">
P2: Reordering visible fields reindexes positions without accounting for hidden fields, which can create duplicate positions and unstable order when hidden fields are shown again. Preserve positions relative to the full viewField list (or compute a single new position like the record-table reorder path does).</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
.../pages/page-layout/components/record-table-settings/RecordTableDataSourceDropdownContent.tsx
Show resolved
Hide resolved
...les/object-record/record-table-standalone/components/StandaloneRecordTableViewLoadEffect.tsx
Outdated
Show resolved
Hide resolved
...y-front/src/modules/page-layout/widgets/record-table/hooks/useRecordTableWidgetViewFields.ts
Outdated
Show resolved
Hide resolved
52af2fa to
b6e7c3b
Compare
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/twenty-front/src/modules/page-layout/widgets/record-table/components/RecordTableWidgetRenderer.tsx">
<violation number="1" location="packages/twenty-front/src/modules/page-layout/widgets/record-table/components/RecordTableWidgetRenderer.tsx:23">
P2: Avoid unconditional `console.log` in this render path; gate it to development-only debugging to prevent noisy logs and metadata leakage.
(Based on your team's feedback about suppressing noisy logs by environment.) [FEEDBACK_USED]</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
...-front/src/modules/page-layout/widgets/record-table/components/RecordTableWidgetRenderer.tsx
Outdated
Show resolved
Hide resolved
...s/page-layout/widgets/record-table/hooks/useSaveRecordTableWidgetsViewDataOnDashboardSave.ts
Show resolved
Hide resolved
...ront/src/modules/page-layout/widgets/record-table/hooks/useCreateViewForRecordTableWidget.ts
Outdated
Show resolved
Hide resolved
ade83fb to
fd9bd14
Compare
|
|
||
| private formatError(error: any, req: Request): GraphQLFormattedError { | ||
| private formatError(error: unknown, req: Request): GraphQLFormattedError { | ||
| if (!(error instanceof Error)) { |
There was a problem hiding this comment.
@etiennejouan Are you ok with this fix ?
There was a problem hiding this comment.
Sorry for that. You will have conflict with nitin here #18874 (comment)
This logic is gated behind feature flag, I'll fix this later.
...s/page-layout/widgets/record-table/hooks/useSaveRecordTableWidgetsViewDataOnDashboardSave.ts
Show resolved
Hide resolved
ddc3b5f to
08d3cc7
Compare
…oping, widget title, footer aggregate, column width
703c08c to
a2f8bea
Compare
| import { type ObjectPermissions } from 'twenty-shared/types'; | ||
| import { isDefined } from 'twenty-shared/utils'; | ||
|
|
||
| export const filterReadableActiveObjectMetadataItems = ( |
| throw new Error( | ||
| `Field ${fieldMetadata.name} is missing, please refresh the page. If the problem persists, please contact support.`, | ||
| ); | ||
| return []; |
There was a problem hiding this comment.
revert this should not be needed
| } | null>({ | ||
| key: 'lastLoadedRecordTableWidgetViewIdComponentState', | ||
| defaultValue: null, | ||
| componentInstanceContext: ContextStoreComponentInstanceContext, |
There was a problem hiding this comment.
should be RecordTableWidgetInstanceContext
| z-index: 1; | ||
| `; | ||
|
|
||
| export const RecordTableHeaderEmptyLastColumn = () => { |
| rowIndexForFocus: number; | ||
| }; | ||
|
|
||
| export const RecordTableRowCells = ({ |
| newLayout, | ||
| ); | ||
|
|
||
| store.set(pageLayoutCurrentLayoutsState, updatedLayouts); |
| import { useCallback } from 'react'; | ||
| import { isDefined } from 'twenty-shared/utils'; | ||
|
|
||
| export const useMapRecordFieldToViewFieldWithCurrentAggregateOperation = () => { |
There was a problem hiding this comment.
was it broken on the existing table?
| const { getFieldMetadataItemByIdOrThrow } = | ||
| useGetFieldMetadataItemByIdOrThrow(); | ||
|
|
||
| const mapViewFieldToRecordTableWidgetViewFieldItem = useCallback( |
| import { isDefined } from 'twenty-shared/utils'; | ||
| import { sortByProperty } from '~/utils/array/sortByProperty'; | ||
|
|
||
| export const useRecordTableWidgetViewFieldItems = (viewId: string) => { |
| import { isDefined } from 'twenty-shared/utils'; | ||
| import { WidgetConfigurationType } from '~/generated-metadata/graphql'; | ||
|
|
||
| export const useSaveRecordTableWidgetsViewDataOnDashboardSave = () => { |
There was a problem hiding this comment.
could we move all these hook to a settings-edit subfolder and make clear in their naming to avoid confusion
| import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; | ||
| import { type ViewField } from '@/views/types/ViewField'; | ||
|
|
||
| export type RecordTableWidgetViewFieldItem = { |
| import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; | ||
| import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField'; | ||
|
|
||
| export const filterFieldsForRecordTableViewCreation = ( |
There was a problem hiding this comment.
I feel this should already exist in the codebase, nothing specific to record table
| import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; | ||
| import { FieldMetadataType, RelationType } from 'twenty-shared/types'; | ||
|
|
||
| export const sortFieldsByRelevanceForRecordTableWidget = ( |
There was a problem hiding this comment.
same here, I feel this should be unify even when we want to add a field on regular table (even on any index view
| @Field(() => String, { nullable: true }) | ||
| @IsOptional() | ||
| @IsUUID() | ||
| viewId?: string; |
There was a problem hiding this comment.
is this expected to be optional?
|
Hey @lucasbordeau! After you've done the QA of your Pull Request, you can mark it as done here. Thank you! |
…pdown content - Replace custom useReadableObjectMetadataItems hook with the filterReadableActiveObjectMetadataItems utility from #18747 - Extract SidePanelObjectFilterDropdownContent into its own file - Update all consumers (search hooks, mention search, default home page) Made-with: Cursor
Demo
demo-table-widget-compressed.mp4
Problem
RecordTablewas tightly coupled to the record index: HTML IDs, CSS variables, and hover portals were global strings with no per-instance scoping, so multiple tables on the same page would collideupdateRecordTableCSSVariable,RECORD_TABLE_HTML_ID, and cell portal IDs were hardcoded — placing two tables caused hover portals and CSS column widths to bleed across instancesNaNlayout coordinates and a full-page freeze on second widget creationFix
RECORD_TABLEis now a valid widget type across the full stack — server DTOs, DB enum migration, universal config mapping, GraphQL codegen, shared types (RecordTableConfigurationDto,WidgetType,addRecordTableWidgetTypemigration)StandaloneRecordTableProvider+StandaloneRecordTableViewLoadEffect(wraps existingRecordTableWithWrappersunchanged)useCreateViewForRecordTableWidget+useDeleteViewForRecordTableWidgetSidePanelPageLayoutRecordTableSettings+ sub-pages, matching chart widget patternuseSaveRecordTableWidgetsViewDataOnDashboardSave(diff + flush on save)isRecordTableColumnHeadersReadOnlyComponentState,isRecordTableColumnResizableComponentState,isRecordTableCellsNonEditableComponentState(Jotai component states)getRecordTableHtmlId(tableId),getRecordTableCellId(tableId, …),updateRecordTableCSSVariable(tableId, …)scope all DOM IDs and CSS variables per instanceWidgetCardContentstops click propagation when editable, limiting settings-open to the card header and chromePageLayoutGridLayoutdrag-select filters bycell-prefix to exclude record UUIDs from grid cell detectionFollow-up fixes
Widget save flow
useSavePageLayoutWidgetsData, called in all save paths (dashboard save, record page save, layout customization save); saves are also skipped when nothing has changedDrag-and-drop / checkbox columns in widget
isRecordTableDragColumnHiddenComponentState,isRecordTableCheckboxColumnHiddenComponentState) hide each column independently; widget tables now display only data columnsSticky column layout
:nth-of-typeCSS selectors — when drag or checkbox columns were hidden, the selector targeted the wrong column and the first data column didn't stickRECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME, etc.) so sticky behavior is correct regardless of which columns are hiddenSave/Cancel buttons during edit mode
isPageInEditModewas trueisPageInEditModeare now exempted from the unpin rule; Save/Cancel stay pinned during editingTitle input auto-focus
focusTitleInputis nowfalsewhen navigating to record table settingsMorph relation field error
morphRelationsmetadata crashed the page with a "refresh" error frommapObjectMetadataToGraphQLQueryupdateRecordMutationprop removalRecordTableWithWrappersrequired callers to pass anupdateRecordMutationcallback, duplicatinguseUpdateOneRecordat every usage siteRecordTableContextProviderviaRecordTableUpdateContext; the prop is goneStandalone → Widget module rename
record-table-standalonemodule renamed torecord-table-widget—StandaloneRecordTable→RecordTableWidget,StandaloneRecordTableViewLoadEffect→RecordTableWidgetViewLoadEffect, etc.RecordTableRow cell extraction
RecordTableCellDragAndDrop,RecordTableCellCheckbox,RecordTableFieldsCells, hotkey/arrow-key effects) was duplicated betweenRecordTableRowandRecordTableRowVirtualizedFullDataRecordTableRowCells(shared cell content) andRecordTableStaticTr(non-draggable<tr>wrapper); when drag column is hidden, rows render inside a static<tr>instead of the draggable wrapperView load effect metadata tracking
RecordTableWidgetViewLoadEffectnow tracksobjectMetadataItem.updatedAtalongsideviewIdto re-load states when metadata changes (e.g. field additions), preventing stale column dataData source dropdown deduplication
filterReadableActiveObjectMetadataItemsutil, shared by both chart and record table data source dropdowns — removes duplicated permission-filtering logicRECORD_TABLE view identifier mapping (server)
RECORD_TABLEcase tofromPageLayoutWidgetConfigurationToUniversalConfigurationandfromUniversalConfigurationToFlatPageLayoutWidgetConfigurationso widget views are properly mapped during workspace import/exportGraphQL error handler typing (server)
formatErrorparameter changed fromanytounknown;workspaceQueryRunnerGraphqlApiExceptionHandlerbroadened fromQueryFailedErrorWithCodetoError | QueryFailedError— removes unsafe type castsSave hook signature
useSaveRecordTableWidgetsViewDataOnDashboardSaveno longer takespageLayoutIdin constructor; receives it as a callback parameter, eliminating the need foruseAtomComponentStateCallbackStateCustomize Dashboard hidden during edit mode
conditionalAvailabilityExpressionnow includesnot isPageInEditModeFields dropdown split
RecordTableFieldsDropdownContent(300+ lines) split intoRecordTableFieldsDropdownVisibleFieldsContentandRecordTableFieldsDropdownHiddenFieldsContentCheckbox placeholder cleanup
StyledRecordTableTdContainerwrapper fromRecordTableCellCheckboxPlaceholder