Skip to content

Add record table widget to dashboards#18747

Merged
charlesBochet merged 13 commits intomainfrom
feat/dashboard-table-widget
Mar 24, 2026
Merged

Add record table widget to dashboards#18747
charlesBochet merged 13 commits intomainfrom
feat/dashboard-table-widget

Conversation

@lucasbordeau
Copy link
Contributor

@lucasbordeau lucasbordeau commented Mar 18, 2026

Demo

demo-table-widget-compressed.mp4

Problem

  • Dashboards only supported chart widgets — tabular record data had no inline widget type
  • RecordTable was 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 collide
  • updateRecordTableCSSVariable, RECORD_TABLE_HTML_ID, and cell portal IDs were hardcoded — placing two tables caused hover portals and CSS column widths to bleed across instances
  • Grid drag-select captured record UUIDs as cell IDs, producing NaN layout coordinates and a full-page freeze on second widget creation

Fix

  • RECORD_TABLE is now a valid widget type across the full stack — server DTOs, DB enum migration, universal config mapping, GraphQL codegen, shared types (RecordTableConfigurationDto, WidgetType, addRecordTableWidgetType migration)
  • A record table widget can be placed on a dashboard and boots from a View ID with no record index dependency — StandaloneRecordTableProvider + StandaloneRecordTableViewLoadEffect (wraps existing RecordTableWithWrappers unchanged)
  • Selecting a data source auto-creates a dedicated View with up to 6 initial fields; switching source or deleting the widget cleans up the View — useCreateViewForRecordTableWidget + useDeleteViewForRecordTableWidget
  • The settings panel exposes source, field visibility/reorder, filter conditions, sort rules, and editable widget title — SidePanelPageLayoutRecordTableSettings + sub-pages, matching chart widget pattern
  • Filters, sorts, and aggregate operations update the table in real time but only persist to the View on explicit dashboard save — useSaveRecordTableWidgetsViewDataOnDashboardSave (diff + flush on save)
  • Headers are always non-interactive (no dropdown, no cursor pointer); columns are resizable only in edit mode; cells are non-editable in both modes — isRecordTableColumnHeadersReadOnlyComponentState, isRecordTableColumnResizableComponentState, isRecordTableCellsNonEditableComponentState (Jotai component states)
  • Hover portals and CSS column widths no longer bleed between multiple table widgets — getRecordTableHtmlId(tableId), getRecordTableCellId(tableId, …), updateRecordTableCSSVariable(tableId, …) scope all DOM IDs and CSS variables per instance
  • Clicking inside a widget's content area no longer opens the settings panel — WidgetCardContent stops click propagation when editable, limiting settings-open to the card header and chrome
  • Second widget creation no longer freezes the page — PageLayoutGridLayout drag-select filters by cell- prefix to exclude record UUIDs from grid cell detection

Follow-up fixes

Widget save flow

  • Saving a dashboard silently dropped record table widget changes (column visibility, order, filters, sorts, aggregates) because widget data save was bundled inside the layout save and only ran when layout structure changed
  • Widget data now persists independently via useSavePageLayoutWidgetsData, called in all save paths (dashboard save, record page save, layout customization save); saves are also skipped when nothing has changed

Drag-and-drop / checkbox columns in widget

  • Record table widgets showed the drag handle column and checkbox selection column even though row reordering and multi-select are meaningless in a read-only widget
  • Two new component states (isRecordTableDragColumnHiddenComponentState, isRecordTableCheckboxColumnHiddenComponentState) hide each column independently; widget tables now display only data columns

Sticky column layout

  • Sticky positioning of the first three columns used :nth-of-type CSS selectors — when drag or checkbox columns were hidden, the selector targeted the wrong column and the first data column didn't stick
  • Sticky CSS now targets semantic class names (RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME, etc.) so sticky behavior is correct regardless of which columns are hidden

Save/Cancel buttons during edit mode

  • Save and Cancel command-menu buttons were unpinned during dashboard edit mode because the pin logic excluded all items while isPageInEditMode was true
  • Items whose availability expression contains isPageInEditMode are now exempted from the unpin rule; Save/Cancel stay pinned during editing

Title input auto-focus

  • Selecting "Record Table" as widget type auto-focused the title input, interrupting the configuration flow
  • focusTitleInput is now false when navigating to record table settings

Morph relation field error

  • A field with missing morphRelations metadata crashed the page with a "refresh" error from mapObjectMetadataToGraphQLQuery
  • Now returns an empty array and silently omits the field from the query instead of crashing

updateRecordMutation prop removal

  • RecordTableWithWrappers required callers to pass an updateRecordMutation callback, duplicating useUpdateOneRecord at every usage site
  • The mutation is now owned inside RecordTableContextProvider via RecordTableUpdateContext; the prop is gone

Standalone → Widget module rename

  • record-table-standalone module renamed to record-table-widgetStandaloneRecordTableRecordTableWidget, StandaloneRecordTableViewLoadEffectRecordTableWidgetViewLoadEffect, etc.

RecordTableRow cell extraction

  • Row rendering logic (RecordTableCellDragAndDrop, RecordTableCellCheckbox, RecordTableFieldsCells, hotkey/arrow-key effects) was duplicated between RecordTableRow and RecordTableRowVirtualizedFullData
  • Extracted RecordTableRowCells (shared cell content) and RecordTableStaticTr (non-draggable <tr> wrapper); when drag column is hidden, rows render inside a static <tr> instead of the draggable wrapper

View load effect metadata tracking

  • RecordTableWidgetViewLoadEffect now tracks objectMetadataItem.updatedAt alongside viewId to re-load states when metadata changes (e.g. field additions), preventing stale column data

Data source dropdown deduplication

  • Extracted filterReadableActiveObjectMetadataItems util, shared by both chart and record table data source dropdowns — removes duplicated permission-filtering logic

RECORD_TABLE view identifier mapping (server)

  • Added RECORD_TABLE case to fromPageLayoutWidgetConfigurationToUniversalConfiguration and fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration so widget views are properly mapped during workspace import/export

GraphQL error handler typing (server)

  • formatError parameter changed from any to unknown; workspaceQueryRunnerGraphqlApiExceptionHandler broadened from QueryFailedErrorWithCode to Error | QueryFailedError — removes unsafe type casts

Save hook signature

  • useSaveRecordTableWidgetsViewDataOnDashboardSave no longer takes pageLayoutId in constructor; receives it as a callback parameter, eliminating the need for useAtomComponentStateCallbackState

Customize Dashboard hidden during edit mode

  • The "Customize Dashboard" command was still visible while already editing — its conditionalAvailabilityExpression now includes not isPageInEditMode

Fields dropdown split

  • RecordTableFieldsDropdownContent (300+ lines) split into RecordTableFieldsDropdownVisibleFieldsContent and RecordTableFieldsDropdownHiddenFieldsContent

Checkbox placeholder cleanup

  • Removed unnecessary StyledRecordTableTdContainer wrapper from RecordTableCellCheckboxPlaceholder

Copilot AI review requested due to automatic review settings March 18, 2026 18:45
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@lucasbordeau lucasbordeau marked this pull request as draft March 18, 2026 18:46
@lucasbordeau lucasbordeau changed the title Add RECORD_TABLE widget type and associated features WIP: Add RECORD_TABLE widget type and associated features Mar 18, 2026
Copy link
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 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_TABLE widget 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

  • isReadOnly is 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 using isReadOnly to 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.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

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.

@lucasbordeau lucasbordeau force-pushed the feat/dashboard-table-widget branch 2 times, most recently from e785a3c to b0f52ad Compare March 20, 2026 16:59
@lucasbordeau lucasbordeau changed the title WIP: Add RECORD_TABLE widget type and associated features Add record table widget to dashboards Mar 20, 2026
@lucasbordeau lucasbordeau marked this pull request as ready for review March 20, 2026 17:15
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

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.

@lucasbordeau lucasbordeau force-pushed the feat/dashboard-table-widget branch from ade83fb to fd9bd14 Compare March 23, 2026 14:12

private formatError(error: any, req: Request): GraphQLFormattedError {
private formatError(error: unknown, req: Request): GraphQLFormattedError {
if (!(error instanceof Error)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@etiennejouan Are you ok with this fix ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for that. You will have conflict with nitin here #18874 (comment)

This logic is gated behind feature flag, I'll fix this later.

@lucasbordeau lucasbordeau force-pushed the feat/dashboard-table-widget branch 2 times, most recently from ddc3b5f to 08d3cc7 Compare March 24, 2026 09:39
@lucasbordeau lucasbordeau force-pushed the feat/dashboard-table-widget branch from 703c08c to a2f8bea Compare March 24, 2026 14:35
import { type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';

export const filterReadableActiveObjectMetadataItems = (
Copy link
Member

Choose a reason for hiding this comment

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

duplicate with @FelixMalfait PR

throw new Error(
`Field ${fieldMetadata.name} is missing, please refresh the page. If the problem persists, please contact support.`,
);
return [];
Copy link
Member

Choose a reason for hiding this comment

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

revert this should not be needed

} | null>({
key: 'lastLoadedRecordTableWidgetViewIdComponentState',
defaultValue: null,
componentInstanceContext: ContextStoreComponentInstanceContext,
Copy link
Member

Choose a reason for hiding this comment

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

should be RecordTableWidgetInstanceContext

z-index: 1;
`;

export const RecordTableHeaderEmptyLastColumn = () => {
Copy link
Member

Choose a reason for hiding this comment

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

is this a refactor?

rowIndexForFocus: number;
};

export const RecordTableRowCells = ({
Copy link
Member

Choose a reason for hiding this comment

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

refactor?

newLayout,
);

store.set(pageLayoutCurrentLayoutsState, updatedLayouts);
Copy link
Member

Choose a reason for hiding this comment

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

is this expected?

import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';

export const useMapRecordFieldToViewFieldWithCurrentAggregateOperation = () => {
Copy link
Member

Choose a reason for hiding this comment

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

was it broken on the existing table?

const { getFieldMetadataItemByIdOrThrow } =
useGetFieldMetadataItemByIdOrThrow();

const mapViewFieldToRecordTableWidgetViewFieldItem = useCallback(
Copy link
Member

Choose a reason for hiding this comment

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

looks convoluted

Copy link
Member

Choose a reason for hiding this comment

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

we should derive

import { isDefined } from 'twenty-shared/utils';
import { sortByProperty } from '~/utils/array/sortByProperty';

export const useRecordTableWidgetViewFieldItems = (viewId: string) => {
Copy link
Member

Choose a reason for hiding this comment

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

same

import { isDefined } from 'twenty-shared/utils';
import { WidgetConfigurationType } from '~/generated-metadata/graphql';

export const useSaveRecordTableWidgetsViewDataOnDashboardSave = () => {
Copy link
Member

Choose a reason for hiding this comment

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

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 = {
Copy link
Member

Choose a reason for hiding this comment

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

likely not useful

import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';

export const filterFieldsForRecordTableViewCreation = (
Copy link
Member

Choose a reason for hiding this comment

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

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 = (
Copy link
Member

Choose a reason for hiding this comment

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

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;
Copy link
Member

Choose a reason for hiding this comment

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

is this expected to be optional?

@charlesBochet charlesBochet added this pull request to the merge queue Mar 24, 2026
@charlesBochet charlesBochet removed this pull request from the merge queue due to a manual request Mar 24, 2026
@charlesBochet charlesBochet merged commit 96f3ff0 into main Mar 24, 2026
158 checks passed
@charlesBochet charlesBochet deleted the feat/dashboard-table-widget branch March 24, 2026 17:28
@twenty-eng-sync
Copy link

Hey @lucasbordeau! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

FelixMalfait added a commit that referenced this pull request Mar 25, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants