Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code b4c665d17e chore: improve monitoring for fix(command-menu): handle missing record store dat
Enhanced `CommandMenuItemErrorBoundary`'s Sentry capture with three improvements:

1. **Added `commandMenuItemId` tag** — enables filtering and debugging by specific command menu item in Sentry, making it easy to identify which command is causing errors.

2. **Set severity level to `warning`** — command menu errors are recoverable (the boundary renders null), not fatal crashes. Using `warning` instead of the default `error` level reduces alert fatigue and correctly categorizes these as non-critical issues.

3. **Added fingerprint `['command-menu-item-error', error.message]`** — Sentry groups events by message rather than creating duplicate issues per stack trace variation. Since minified stack traces vary between builds, this ensures all instances of the same command menu error are grouped into one Sentry issue.

These improvements match the exact pattern established in commit 6ce23a4115.
2026-04-27 12:12:40 +00:00
Sonarly Claude Code fff0667c9b fix(command-menu): handle missing record store data in navigate commands
https://sonarly.com/issue/31631?type=bug

The "Navigate to next/previous record" command throws an unhandled error when the user clicks the navigation button before the current record's data has been loaded into the Jotai record store, resulting in an error snackbar and a failed navigation.

Fix: Changed `throw new Error(...)` to `return null` in both `NavigateToNextRecordSingleRecordCommand` and `NavigateToPreviousRecordSingleRecordCommand`.

**Why:** When the user rapidly clicks "Navigate to next/previous Person" after navigating to a new record page (especially during auth token renewal), the record data may not yet be loaded into `recordStoreFamilyState`. The `buildHeadlessCommandContextApi` reads the record store synchronously — if the record hasn't been loaded via GraphQL yet, `recordStoreFamilyState.atomFamily(id)` returns `null`, which gets filtered out by `isDefined`, leaving `selectedRecords` empty. The component then hits the guard and throws.

Throwing is the wrong behavior here because:
1. The error boundary (`CommandMenuItemErrorBoundary`) catches it, renders `null` as fallback, and shows an error snackbar to the user — but the error isn't actionable by the user
2. It fires a Sentry event for what is actually a transient data-loading race condition, not a true application error
3. The sibling command `SeeVersionWorkflowRunSingleRecordCommand` already uses `return null` for the same scenario (established in commit 6ce23a4115)

Returning `null` makes the command a silent no-op when data isn't ready, avoiding the error snackbar and Sentry noise. The `HeadlessEngineCommandWrapperEffect` that would have been rendered will simply not execute, and the user can retry the navigation once the page fully loads.
2026-04-27 12:12:40 +00:00
3 changed files with 8 additions and 6 deletions
@@ -29,6 +29,12 @@ export const CommandMenuItemErrorBoundary = ({
const { captureException } = await import('@sentry/react');
captureException(error, (scope) => {
scope.setTag('component', 'CommandMenuItem');
scope.setTag('commandMenuItemId', commandMenuItemId);
scope.setLevel('warning');
scope.setFingerprint([
'command-menu-item-error',
error.message,
]);
return scope;
});
@@ -10,9 +10,7 @@ export const NavigateToNextRecordSingleRecordCommand = () => {
const recordId = selectedRecords[0]?.id;
if (!isDefined(recordId) || !isDefined(objectMetadataItem)) {
throw new Error(
'Record ID and object metadata are required to navigate to next record',
);
return null;
}
const { navigateToNextRecord, isLoadingPagination } =
@@ -10,9 +10,7 @@ export const NavigateToPreviousRecordSingleRecordCommand = () => {
const recordId = selectedRecords[0]?.id;
if (!isDefined(recordId) || !isDefined(objectMetadataItem)) {
throw new Error(
'Record ID and object metadata are required to navigate to previous record',
);
return null;
}
const { navigateToPreviousRecord, isLoadingPagination } =