Migrate from ESLint to OxLint (#18443)

## Summary

Fully replaces ESLint with OxLint across the entire monorepo:

- **Replaced all ESLint configs** (`eslint.config.mjs`) with OxLint
configs (`.oxlintrc.json`) for every package: `twenty-front`,
`twenty-server`, `twenty-emails`, `twenty-ui`, `twenty-shared`,
`twenty-sdk`, `twenty-zapier`, `twenty-docs`, `twenty-website`,
`twenty-apps/*`, `create-twenty-app`
- **Migrated custom lint rules** from ESLint plugin format to OxLint JS
plugin system (`@oxlint/plugins`), including
`styled-components-prefixed-with-styled`, `no-hardcoded-colors`,
`sort-css-properties-alphabetically`,
`graphql-resolvers-should-be-guarded`,
`rest-api-methods-should-be-guarded`, `max-consts-per-file`, and
Jotai-related rules
- **Migrated custom rule tests** from ESLint `RuleTester` + Jest to
`oxlint/plugins-dev` `RuleTester` + Vitest
- **Removed all ESLint dependencies** from `package.json` files and
regenerated lockfiles
- **Updated Nx targets** (`lint`, `lint:diff-with-main`, `fmt`) in
`nx.json` and per-project `project.json` to use `oxlint` commands with
proper `dependsOn` for plugin builds
- **Updated CI workflows** (`.github/workflows/ci-*.yaml`) — no more
ESLint executor
- **Updated IDE setup**: replaced `dbaeumer.vscode-eslint` with
`oxc.oxc-vscode` extension, configured `source.fixAll.oxc` and
format-on-save with Prettier
- **Replaced all `eslint-disable` comments** with `oxlint-disable`
equivalents across the codebase
- **Updated docs** (`twenty-docs`) to reference OxLint instead of ESLint
- **Renamed** `twenty-eslint-rules` package to `twenty-oxlint-rules`

### Temporarily disabled rules (tracked in `OXLINT_MIGRATION_TODO.md`)

| Rule | Package | Violations | Auto-fixable |
|------|---------|-----------|-------------|
| `twenty/sort-css-properties-alphabetically` | twenty-front | 578 | Yes
|
| `typescript/consistent-type-imports` | twenty-server | 3814 | Yes |
| `twenty/max-consts-per-file` | twenty-server | 94 | No |

### Dropped plugins (no OxLint equivalent)

`eslint-plugin-project-structure`, `lingui/*`, `@stylistic/*`,
`import/order`, `prefer-arrow/prefer-arrow-functions`,
`eslint-plugin-mdx`, `@next/eslint-plugin-next`,
`eslint-plugin-storybook`, `eslint-plugin-react-refresh`. Partial
coverage for `jsx-a11y` and `unused-imports`.

### Additional fixes (pre-existing issues exposed by merge)

- Fixed `EmailThreadPreview.tsx` broken import from main rename
(`useOpenEmailThreadInSidePanel`)
- Restored truthiness guard in `getActivityTargetObjectRecords.ts`
- Fixed `AgentTurnResolver` return types to match entity (virtual
`fileMediaType`/`fileUrl` are resolved via `@ResolveField()`)

## Test plan

- [x] `npx nx lint twenty-front` passes
- [x] `npx nx lint twenty-server` passes
- [x] `npx nx lint twenty-docs` passes
- [x] Custom oxlint rules validated with Vitest: `npx nx test
twenty-oxlint-rules`
- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx typecheck twenty-server` passes
- [x] CI workflows trigger correctly with `dependsOn:
["twenty-oxlint-rules:build"]`
- [x] IDE linting works with `oxc.oxc-vscode` extension
This commit is contained in:
Charles Bochet
2026-03-06 01:03:50 +01:00
committed by GitHub
parent b421efbff7
commit 9d57bc39e5
880 changed files with 4711 additions and 9687 deletions
@@ -48,7 +48,7 @@ export const useAggregateRecords = <T extends AggregateRecordsData>({
const { data, loading, error } = useQuery<RecordGqlOperationFindManyResult>(
aggregateQuery,
{
skip: skip || !objectMetadataItem || !hasReadPermission,
skip: skip || !isDefined(objectMetadataItem) || !hasReadPermission,
variables: {
filter,
},
@@ -142,26 +142,30 @@ export const useCreateManyRecords = <
}
});
const recordsCreatedInCache = recordOptimisticRecordsInput
.map((recordToCreate) =>
createOneRecordInCache({
const recordsCreatedInCache = recordOptimisticRecordsInput.flatMap(
(recordToCreate) => {
const created = createOneRecordInCache({
...recordToCreate,
__typename: getObjectTypename(objectMetadataItem.nameSingular),
}),
)
.filter(isDefined);
});
return created !== undefined && created !== null ? [created] : [];
},
);
if (recordsCreatedInCache.length > 0) {
const recordNodeCreatedInCache = recordsCreatedInCache
.map((record) =>
getRecordNodeFromRecord({
const recordNodeCreatedInCache = recordsCreatedInCache.flatMap(
(record) => {
const node = getRecordNodeFromRecord({
objectMetadataItem,
objectMetadataItems,
record: record,
computeReferences: false,
}),
)
.filter(isDefined);
});
return node !== undefined && node !== null ? [node] : [];
},
);
triggerCreateRecordsOptimisticEffect({
cache: apolloCoreClient.cache,
@@ -162,7 +162,7 @@ export const useCreateOneRecord = <
},
})
.catch((error: Error) => {
if (!recordCreatedInCache) {
if (!isDefined(recordCreatedInCache)) {
throw error;
}
@@ -1,4 +1,5 @@
import { useQuery } from '@apollo/client';
import { isDefined } from 'twenty-shared/utils';
import { useMemo } from 'react';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
@@ -70,7 +71,7 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
const results = useMemo(
() =>
objectResults?.map((result: RecordGqlConnectionEdgesRequired) => {
return result
return isDefined(result)
? (getRecordsFromRecordConnection({
recordConnection: result,
}) as T[])
@@ -1,4 +1,5 @@
import { useQuery, type WatchQueryFetchPolicy } from '@apollo/client';
import { isDefined } from 'twenty-shared/utils';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
@@ -89,7 +90,7 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
const { data, loading, error, fetchMore, refetch } =
useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
skip: skip || !objectMetadataItem || !hasReadPermission,
skip: skip || !isDefined(objectMetadataItem) || !hasReadPermission,
variables: {
filter: withSoftDeleteFilter,
orderBy,
@@ -56,7 +56,7 @@ export const useFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
const { data, loading, error, refetch } = useQuery<{
[nameSingular: string]: RecordGqlNode;
}>(findOneRecordQuery, {
skip: !objectMetadataItem || !objectRecordId || skip || !hasReadPermission,
skip: !isDefined(objectMetadataItem) || !objectRecordId || skip || !hasReadPermission,
variables: { objectRecordId },
client: apolloCoreClient,
onCompleted: (data) => {
@@ -102,12 +102,12 @@ export const ObjectFilterDropdownDateInput = () => {
: null;
const relativeDate =
resolvedValue && typeof resolvedValue === 'object'
isDefined(resolvedValue) && typeof resolvedValue === 'object'
? resolvedValue
: undefined;
const safePlainDateValue: string | undefined =
resolvedValue && typeof resolvedValue === 'string'
isDefined(resolvedValue) && typeof resolvedValue === 'string'
? resolvedValue
: undefined;
@@ -11,7 +11,7 @@ export const getRelativeDateDisplayValue = (
relativeDate: RelativeDateFilter,
shouldDisplayTimeZoneAbbreviation?: boolean,
) => {
if (!relativeDate) return '';
if (!isDefined(relativeDate)) return '';
const { direction, amount, unit } = relativeDate;
const directionFormatted = capitalize(direction.toLowerCase());
@@ -1,11 +1,12 @@
import { useDropdownContextStateManagement } from '@/dropdown-context-state-management/hooks/useDropdownContextStateManagement';
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const useObjectOptionsDropdown = () => {
const context = useContext(ObjectOptionsDropdownContext);
if (!context) {
if (!isDefined(context)) {
throw new Error('useObjectOptionsDropdown must be used within a context');
}
@@ -85,7 +85,7 @@ export const useObjectOptionsForBoard = ({
recordIndexFieldDefinitionsByKey[fieldMetadataId];
return {
...(existingBoardField || availableColumnDefinition),
...(existingBoardField ?? availableColumnDefinition),
isVisible: false,
};
}),
@@ -53,9 +53,9 @@ export const RecordBoardCardDraggableContainer = ({
<StyledDraggableContainer
id={`record-board-card-${columnIndex}-${rowIndex}`}
ref={draggableProvided?.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.dragHandleProps}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.draggableProps}
data-selectable-id={recordId}
data-select-disable
@@ -47,7 +47,7 @@ export const RecordBoardColumnCardsContainer = ({
return (
<StyledColumnCardsContainer
ref={droppableProvided?.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...droppableProvided?.droppableProps}
>
{recordIndexRecordIdsByGroup.map((recordId, index) => (
@@ -68,7 +68,7 @@ export const RecordBoardColumnCardsContainer = ({
{(draggableProvided) => (
<div
ref={draggableProvided.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.draggableProps}
></div>
)}
@@ -3,6 +3,7 @@ import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDrop
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { styled } from '@linaria/react';
import { type Nullable } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Tag } from 'twenty-ui/components';
import { AppTooltip, TooltipDelay } from 'twenty-ui/display';
@@ -36,7 +37,7 @@ export const RecordBoardColumnHeaderAggregateDropdownButton = ({
<>
<StyledTagContainer>
<Tag
text={value ? value.toString() : '-'}
text={isDefined(value) ? value.toString() : '-'}
color="transparent"
weight="regular"
/>
@@ -146,7 +146,7 @@ export const RecordCalendarMonthBodyDay = ({
<Droppable droppableId={dayKey}>
{(droppableProvided, droppableSnapshot) => (
<StyledCardsContainer
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...droppableProvided.droppableProps}
ref={droppableProvided.innerRef}
isDraggedOver={droppableSnapshot.isDraggingOver}
@@ -77,9 +77,9 @@ export const RecordCalendarCardDraggableContainer = ({
<StyledDraggableContainer
id={`record-calendar-card-${recordId}`}
ref={draggableProvided?.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.dragHandleProps}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.draggableProps}
data-selectable-id={recordId}
>
@@ -1,6 +1,7 @@
import { type DropResult } from '@hello-pangea/dnd';
import { useStore } from 'jotai';
import { useCallback, useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { processGroupDrop } from '@/object-record/record-drag/utils/processGroupDrop';
@@ -22,7 +23,7 @@ export const useProcessBoardCardDrop = () => {
const processBoardCardDrop = useCallback(
(boardCardDropResult: DropResult, selectedRecordIds: string[]) => {
if (!selectFieldMetadataItem) return;
if (!isDefined(selectFieldMetadataItem)) return;
processGroupDrop({
groupDropResult: boardCardDropResult,
@@ -40,7 +40,11 @@ export const RecordDetailDuplicatesSection = ({
objectRecordIds: duplicateRecordIds,
});
if (!queryResults || !queryResults[0] || queryResults[0].length === 0)
if (
!isDefined(queryResults) ||
!isDefined(queryResults[0]) ||
queryResults[0].length === 0
)
return null;
return (
@@ -49,7 +49,7 @@ export const RecordDetailRelationSectionDropdownToMany = ({
objectMetadataItems,
});
if (!fieldMetadataItem || !objectMetadataItem) {
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
throw new CustomError(
'Field metadata item or object metadata item not found',
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
@@ -25,7 +25,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
import { getFieldMetadataItemById } from '@/object-metadata/utils/getFieldMetadataItemById';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { CustomError } from 'twenty-shared/utils';
import { CustomError, isDefined } from 'twenty-shared/utils';
import { IconForbid, IconPencil } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
@@ -56,7 +56,7 @@ export const RecordDetailRelationSectionDropdownToOne = ({
objectMetadataItems,
});
if (!fieldMetadataItem || !objectMetadataItem) {
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
throw new CustomError(
'Field metadata item or object metadata item not found',
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
@@ -1,7 +1,7 @@
import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation';
import { recordStoreMorphOneToManyValueWithObjectNameFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreMorphOneToManyValueWithObjectNameFamilySelector';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { CustomError, isNonEmptyArray } from 'twenty-shared/utils';
import { CustomError, isDefined, isNonEmptyArray } from 'twenty-shared/utils';
export const useGetMorphRelationRelatedRecordsWithObjectNameSingular = ({
recordId,
@@ -28,7 +28,7 @@ export const useGetMorphRelationRelatedRecordsWithObjectNameSingular = ({
...recordWithObjectNameSingular,
value: Array.isArray(recordWithObjectNameSingular.value)
? recordWithObjectNameSingular.value
: recordWithObjectNameSingular.value
: isDefined(recordWithObjectNameSingular.value)
? [recordWithObjectNameSingular.value]
: [],
}))
@@ -9,6 +9,7 @@ import { type VariablePickerComponent } from '@/object-record/record-field/ui/fo
import { type FieldPhonesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
type FormPhoneFieldInputProps = {
label?: string;
@@ -39,7 +40,7 @@ export const FormPhoneFieldInput = ({
onChange({
primaryPhoneCountryCode: defaultValue?.primaryPhoneCountryCode ?? '',
primaryPhoneCallingCode: defaultValue?.primaryPhoneCallingCode ?? '',
primaryPhoneNumber: number ? `${number}` : '',
primaryPhoneNumber: isDefined(number) ? `${number}` : '',
});
};
@@ -8,6 +8,7 @@ import { VariableChipStandalone } from '@/object-record/record-field/ui/form-typ
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
import { styled } from '@linaria/react';
import { isDefined } from 'twenty-shared/utils';
import { t } from '@lingui/core/macro';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -43,7 +44,6 @@ export const FormSingleRecordFieldChip = ({
disabled,
}: FormSingleRecordFieldChipProps) => {
if (
!!draftValue &&
draftValue.type === 'variable' &&
isStandaloneVariableString(draftValue.value)
) {
@@ -56,7 +56,7 @@ export const FormSingleRecordFieldChip = ({
);
}
if (!!draftValue && draftValue.type === 'static' && !!selectedRecord) {
if (draftValue.type === 'static' && isDefined(selectedRecord)) {
return (
<StyledRecordChipContainer>
<RecordChip
@@ -88,7 +88,7 @@ export const FormSingleRecordPicker = ({
}
: {
type: 'static',
value: defaultValue || '',
value: (defaultValue as string | undefined) ?? '',
};
if (objectNameSingulars.length === 0) {
@@ -3,6 +3,7 @@ import { useMultiSelectFieldDisplay } from '@/object-record/record-field/ui/meta
import { MultiSelectDisplay } from '@/ui/field/display/components/MultiSelectDisplay';
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
import { Tag } from 'twenty-ui/components';
import { isDefined } from 'twenty-shared/utils';
export const MultiSelectFieldDisplay = () => {
const { fieldValue, fieldDefinition } = useMultiSelectFieldDisplay();
@@ -15,7 +16,7 @@ export const MultiSelectFieldDisplay = () => {
)
: [];
if (!selectedOptions) return null;
if (!isDefined(selectedOptions)) return null;
return isFocused ? (
<ExpandableList isChipCountDisplayed={isFocused}>
@@ -5,9 +5,9 @@ import {
type InputHTMLAttributes,
type ReactNode,
} from 'react';
import { useRegisterInputEvents } from '@/object-record/record-field/ui/meta-types/input/hooks/useRegisterInputEvents';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { isDefined } from 'twenty-shared/utils';
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
const StyledInput = styled.input<{
@@ -159,13 +159,13 @@ export const MultiItemBaseInput = forwardRef<
placeholder={placeholder}
onChange={(event) => onChange(event.target.value)}
ref={combinedRef}
withRightComponent={!!rightComponent}
withRightComponent={isDefined(rightComponent)}
hasItem={hasItem}
onFocus={onFocus}
onBlur={onBlur}
/>
)}
{!!rightComponent && (
{isDefined(rightComponent) && (
<StyledRightContainer>{rightComponent}</StyledRightContainer>
)}
</StyledInputContainer>
@@ -29,7 +29,7 @@ export const RelationManyToOneFieldInput = () => {
fieldMetadataId: fieldDefinition.fieldMetadataId,
objectMetadataItems,
});
if (!fieldMetadataItem || !objectMetadataItem) {
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
throw new CustomError(
'Field metadata item or object metadata item not found',
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
@@ -53,7 +53,7 @@ export const RelationOneToManyFieldInput = () => {
fieldMetadataId: fieldDefinition.fieldMetadataId,
objectMetadataItems,
});
if (!fieldMetadataItem || !objectMetadataItem) {
if (!isDefined(fieldMetadataItem) || !isDefined(objectMetadataItem)) {
throw new CustomError(
'Field metadata item or object metadata item not found',
'FIELD_METADATA_ITEM_OR_OBJECT_METADATA_ITEM_NOT_FOUND',
@@ -209,7 +209,7 @@ export const RelationOneToManyFieldInput = () => {
const { targetFields, sourceField } = junctionConfig;
const targetField = targetFields[0];
if (!targetField || !sourceField) {
if (!isDefined(targetField) || !isDefined(sourceField)) {
return;
}
@@ -1,10 +1,11 @@
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const useCurrentRecordGroupId = (): string => {
const context = useContext(RecordGroupContext);
if (!context) {
if (!isDefined(context)) {
throw new Error(
'useCurrentRecordGroupId must be used within a RecordGroupContextProvider.',
);
@@ -31,7 +31,7 @@ export const RecordIndexPageHeaderIcon = ({
const { getIcon } = useIcons();
const ObjectIcon = getIcon(objectMetadataItem?.icon);
if (!ObjectIcon) {
if (!isDefined(ObjectIcon)) {
return null;
}
@@ -28,5 +28,5 @@ export const getPositionNumberIcon = (index: number) => {
IconSquareNumber9,
];
return iconMapping[index] || IconSquareNumber1;
return iconMapping[index] ?? IconSquareNumber1;
};
@@ -11,6 +11,7 @@ import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledEditableTitleContainer = styled.div`
@@ -92,7 +93,7 @@ export const ObjectRecordShowPageBreadcrumb = ({
navigateToIndexView();
}}
>
{HeaderIcon && <HeaderIcon size={theme.icon.size.md} />}
{isDefined(HeaderIcon) && <HeaderIcon size={theme.icon.size.md} />}
{objectLabel}
<span>{' / '}</span>
</StyledEditableTitlePrefix>
@@ -42,7 +42,7 @@ const RelationFieldValueSetterEffect = () => {
mockPerformance.recordId,
);
// eslint-disable-next-line twenty/matching-state-variable
// oxlint-disable-next-line twenty/matching-state-variable
const setRelationRecordStore = useSetAtomFamilyState(
recordStoreFamilyState,
mockPerformance.relationRecordId,
@@ -1,4 +1,4 @@
/* eslint-disable twenty/no-navigate-prefer-link */
/* oxlint-disable twenty/no-navigate-prefer-link */
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
@@ -32,7 +32,7 @@ export const RecordTableBodyNoRecordGroupDroppable = ({
{(provided) => (
<RecordTableBody
ref={provided.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...provided.droppableProps}
>
<RecordTableBodyDroppableContextProvider
@@ -23,7 +23,7 @@ export const RecordTableBodyRecordGroupDroppable = ({
{(provided) => (
<RecordTableBody
ref={provided.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...provided.droppableProps}
>
<RecordTableBodyDroppableContextProvider
@@ -166,7 +166,7 @@ export const RecordTableBodyVirtualizedDraggableClone = ({
recordId={recordId}
focusIndex={realIndex}
ref={draggableProvided.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.draggableProps}
style={{
...draggableProvided.draggableProps.style,
@@ -35,7 +35,7 @@ export const RecordTableCellDragAndDrop = () => {
return (
<RecordTableCellStyleWrapper
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...dragHandleProps}
data-select-disable
hasRightBorder={false}
@@ -69,7 +69,7 @@ export const RecordTableCellFirstRowFirstColumn = ({
hasRightBorder={hasRightBorder}
hasBottomBorder={hasBottomBorder}
zIndex={zIndex}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...dragHandleProps}
className={cx(
'table-cell-0-0',
@@ -63,7 +63,7 @@ export const RecordTableCellStyleWrapper = ({
fontColor={fontColor}
hasRightBorder={hasRightBorder}
hasBottomBorder={hasBottomBorder}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...dragHandleProps}
className={cx('table-cell', widthClassName)}
>
@@ -44,7 +44,7 @@ export const RecordTableDraggableTr = ({
focusIndex={focusIndex}
ref={draggableProvided.innerRef}
className={className}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.draggableProps}
style={{
...draggableProvided.draggableProps.style,
@@ -50,7 +50,7 @@ export const RecordTableDraggableTrFirstRowOfGroup = ({
focusIndex={focusIndex}
ref={draggableProvided.innerRef}
className={className}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.draggableProps}
style={{
...draggableProvided.draggableProps.style,
@@ -1,7 +1,6 @@
import { RecordTableRowDiv } from '@/object-record/record-table/record-table-row/components/RecordTableRowDiv';
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { isActive } from '@tiptap/core';
import { forwardRef, type ReactNode } from 'react';
type RecordTableTrProps = {
@@ -43,10 +42,10 @@ export const RecordTableFirstRowOfGroup = forwardRef<
data-virtualized-id={recordId}
isDragging={isDragging}
ref={ref}
data-active={isActive}
data-focused={isRowFocusActive && isFocused && !isActive}
data-active={false}
data-focused={isRowFocusActive && isFocused}
data-next-row-active-or-focused={isNextRowActiveOrFocused}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...props}
isScrolledVertically={isRecordTableScrolledVertically}
isFirstRowOfGroup={true}
@@ -50,7 +50,7 @@ export const RecordTableTr = forwardRef<HTMLDivElement, RecordTableTrProps>(
focusIndex,
);
// eslint-disable-next-line twenty/matching-state-variable
// oxlint-disable-next-line twenty/matching-state-variable
const isNextRecordTableRowActive = useAtomComponentFamilyStateValue(
isRecordTableRowActiveComponentFamilyState,
focusIndex + 1,
@@ -75,7 +75,7 @@ export const RecordTableTr = forwardRef<HTMLDivElement, RecordTableTrProps>(
isRecordTableRowFocusActiveComponentState,
);
// eslint-disable-next-line twenty/matching-state-variable
// oxlint-disable-next-line twenty/matching-state-variable
const isNextRecordTableRowFocused = useAtomComponentFamilyStateValue(
isRecordTableRowFocusedComponentFamilyState,
focusIndex + 1,
@@ -123,7 +123,7 @@ export const RecordTableTr = forwardRef<HTMLDivElement, RecordTableTrProps>(
isFocused={isRecordTableRowFocused}
isRowFocusActive={isRecordTableRowFocusActive}
recordId={recordId}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...props}
>
{children}
@@ -141,7 +141,7 @@ export const RecordTableTr = forwardRef<HTMLDivElement, RecordTableTrProps>(
!isRecordTableRowActive
}
data-next-row-active-or-focused={isNextRowActiveOrFocused}
// eslint-disable-next-line react/jsx-props-no-spreading
// oxlint-disable-next-line react/jsx-props-no-spreading
{...props}
>
{children}
@@ -93,8 +93,9 @@ export const RecordTableVirtualizedInitialDataLoadEffect = () => {
JSON.stringify(lastContextStoreVirtualizedVisibleRecordFields) !==
JSON.stringify(visibleRecordFields)
) {
const lastFields = lastContextStoreVirtualizedVisibleRecordFields || [];
const currentFields = visibleRecordFields || [];
const lastFields =
lastContextStoreVirtualizedVisibleRecordFields ?? [];
const currentFields = visibleRecordFields ?? [];
setLastContextStoreVirtualizedVisibleRecordFields(visibleRecordFields);
@@ -53,10 +53,12 @@ export const computeOptimisticRecordFromInput = ({
objectMetadataItem.fields.find((field) => {
if (!isFieldMorphRelation(field)) return false;
return getFieldMetadataFromGqlField({
objectMetadataItem,
gqlField: recordKey,
});
return isDefined(
getFieldMetadataFromGqlField({
objectMetadataItem,
gqlField: recordKey,
}),
);
});
const isUnknownField =
@@ -10,6 +10,6 @@ export const filterUniqueRecordEdgesByCursor = (
return seenCursors.has(currentCursor)
? false
: seenCursors.add(currentCursor);
: Boolean(seenCursors.add(currentCursor));
});
};
@@ -13,7 +13,7 @@ export const generateAggregateQuery = ({
recordGqlFields: RecordGqlFields;
}) => {
const selectedFields = Object.entries(recordGqlFields)
.filter(([_, shouldBeQueried]) => shouldBeQueried)
.filter(([_, shouldBeQueried]) => Boolean(shouldBeQueried))
.map(([fieldName]) => fieldName)
.join('\n ');