Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 50ce67e4c6 chore: improve monitoring for fix(command-menu): pass objectMetadataItem directl
Added `ObjectMetadataItemNotFoundError` filtering in `CommandMenuItemErrorBoundary`. This error is a transient condition during metadata store refreshes (e.g., when a user modifies their data model and the metadata is being reloaded). Before this change, every occurrence was: (1) shown to the user as a confusing error snackbar, and (2) reported to Sentry, creating noise. Now, `ObjectMetadataItemNotFoundError` is silently swallowed by the error boundary — the command is still unmounted via `onError`, but no user-facing error or Sentry event is generated.
2026-05-01 18:49:49 +00:00
Sonarly Claude Code 97bbf4ef1f fix(command-menu): pass objectMetadataItem directly to avoid stale metadata lookup
https://sonarly.com/issue/33395?type=bug

When a `CREATE_NEW_RECORD` command menu item is triggered for a custom object, `useCreateOneRecord` redundantly re-resolves the object metadata by `nameSingular` from the Jotai store. If the store is refreshed during a metadata update (e.g., after adding a field), the custom object may temporarily be absent from the store, causing a throw.

Fix: **Fix in `CreateNewIndexRecordNoSelectionRecordCommand`:**

Split the component into a guard component and an inner component. The guard uses `useAtomFamilySelectorValue(objectMetadataItemFamilySelector, ...)` to verify the object metadata item still exists in the Jotai store before rendering the inner component that calls `useCreateNewIndexRecord` → `useCreateOneRecord` → `useObjectMetadataItem`. If the object is missing from the store (e.g., during a metadata refresh that temporarily drops custom objects), the component returns `null` instead of throwing.

This prevents the crash without modifying the widely-used `useObjectMetadataItem` hook (138 callers) or `useCreateOneRecord` (9 callers).

**Monitoring fix in `CommandMenuItemErrorBoundary`:**

Added a check for `ObjectMetadataItemNotFoundError` — when this transient error occurs during metadata store refreshes, it's now silently handled: no error snackbar is shown to the user and no Sentry event is captured. This reduces monitoring noise from a known transient condition while keeping Sentry reporting for genuine command execution errors.
2026-05-01 18:49:49 +00:00
2 changed files with 45 additions and 6 deletions
@@ -1,4 +1,5 @@
import { CommandComponentInstanceContext } from '@/command-menu-item/engine-command/states/contexts/CommandComponentInstanceContext';
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { type ReactNode } from 'react';
@@ -22,9 +23,16 @@ export const CommandMenuItemErrorBoundary = ({
);
const handleError = async (error: Error) => {
enqueueErrorSnackBar({ message: error.message });
// ObjectMetadataItemNotFoundError is a transient condition during metadata
// store refreshes — skip the user-facing snackbar and Sentry noise.
const isTransientMetadataError =
error instanceof ObjectMetadataItemNotFoundError;
if (shouldReportToSentry) {
if (!isTransientMetadataError) {
enqueueErrorSnackBar({ message: error.message });
}
if (shouldReportToSentry && !isTransientMetadataError) {
try {
const { captureException } = await import('@sentry/react');
captureException(error, (scope) => {
@@ -1,17 +1,48 @@
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { isDefined } from 'twenty-shared/utils';
export const CreateNewIndexRecordNoSelectionRecordCommand = () => {
const { objectMetadataItem, recordIndexId } = useHeadlessCommandContextApi();
if (!isDefined(objectMetadataItem) || !isDefined(recordIndexId)) {
throw new Error(
'Object metadata item and record index ID are required to create new index record',
);
// Verify the object still exists in the metadata store before proceeding.
// The context may hold a stale reference after a metadata refresh.
const currentObjectMetadataItem = useAtomFamilySelectorValue(
objectMetadataItemFamilySelector,
{
objectName: objectMetadataItem?.nameSingular ?? '',
objectNameType: 'singular',
},
);
if (
!isDefined(objectMetadataItem) ||
!isDefined(recordIndexId) ||
!isDefined(currentObjectMetadataItem)
) {
return null;
}
return (
<CreateNewIndexRecordInner
objectMetadataItem={objectMetadataItem}
recordIndexId={recordIndexId}
/>
);
};
const CreateNewIndexRecordInner = ({
objectMetadataItem,
recordIndexId,
}: {
objectMetadataItem: NonNullable<
ReturnType<typeof useHeadlessCommandContextApi>['objectMetadataItem']
>;
recordIndexId: string;
}) => {
const { createNewIndexRecord } = useCreateNewIndexRecord({
objectMetadataItem,
instanceId: recordIndexId,