Skip to content

Conversation

@anadi45
Copy link
Contributor

@anadi45 anadi45 commented Oct 30, 2025

Fixes - #14995

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.

Greptile Overview

Greptile Summary

Fixed crash in AI settings page when no AI models are configured by adding optional chaining to SelectControl component.

  • Added optional chaining (?.) when accessing selectedOption.Icon and selectedOption.label
  • Prevented crash when Select component passes undefined for selectedOption (occurs when options array is empty and no emptyOption provided)
  • Used nullish coalescing (??) to provide empty string fallback for label

Issue found:

  • Type signature for selectedOption prop doesn't match runtime behavior - declares as required but can receive undefined

Confidence Score: 3/5

  • Safe to merge with one type safety issue to address post-merge
  • The defensive fix resolves the immediate crash, but leaves a type mismatch between the declared interface and actual runtime behavior that should be corrected for better type safety
  • No files require immediate attention, but consider updating SelectControlProps type definition as a follow-up

Important Files Changed

File Analysis

Filename Score Overview
packages/twenty-front/src/modules/ui/input/components/SelectControl.tsx 3/5 Fixed crash by adding optional chaining, but type signature needs update to match runtime behavior

Sequence Diagram

sequenceDiagram
    participant User
    participant SettingsAIAgentForm
    participant useAiModelOptions
    participant Select
    participant SelectControl

    User->>SettingsAIAgentForm: Navigate to AI settings page
    SettingsAIAgentForm->>useAiModelOptions: Get AI model options
    useAiModelOptions-->>SettingsAIAgentForm: Returns empty array []
    SettingsAIAgentForm->>Select: Render with options=[], no emptyOption
    Select->>Select: Calculate selectedOption
    Note over Select: options.find() || emptyOption || options[0]<br/>= undefined || undefined || undefined<br/>= undefined
    Select->>SelectControl: Pass selectedOption=undefined
    
    alt Before Fix
        SelectControl->>SelectControl: Access selectedOption.Icon
        Note over SelectControl: TypeError: Cannot read properties<br/>of undefined (reading 'Icon')
        SelectControl-->>User: Crash!
    else After Fix
        SelectControl->>SelectControl: Access selectedOption?.Icon
        Note over SelectControl: Returns undefined (safe)
        SelectControl->>SelectControl: Render label with selectedOption?.label ?? ''
        SelectControl-->>User: Renders empty select (no crash)
    end
Loading

Additional Comments (1)

  1. packages/twenty-front/src/modules/ui/input/components/SelectControl.tsx, line 64 (link)

    syntax: Type signature claims selectedOption is required but Select.tsx can pass undefined when options is empty and no emptyOption provided. Update type to reflect reality:

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@anadi45
Copy link
Contributor Author

anadi45 commented Oct 30, 2025

@charlesBochet please check this as well

Copy link
Contributor

@prastoin prastoin left a comment

Choose a reason for hiding this comment

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

Hello there thanks for your contribution, left a comment

/>
) : null}
<OverflowingTextWithTooltip text={selectedOption.label} />
<OverflowingTextWithTooltip text={selectedOption?.label ?? ''} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Remark: Either redundant or would need to update type for this to be optional, about to double check

Copy link
Contributor

@prastoin prastoin left a comment

Choose a reason for hiding this comment

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

Indeed the issue comes from the label being undefined, this fix is not enough

);

const selectedOption =
options.find(({ value: key }) => key === value) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Remark: this did not handle '' or 0 atomic values correctly before same for null

Copy link
Contributor

@prastoin prastoin left a comment

Choose a reason for hiding this comment

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

Hey @lucasbordeau could you please have a look to this when you have some free time ?

options.find(({ value: key }) => key === value) ||
emptyOption ||
options[0];
if (fromMatchingOption !== undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

use !isDefined()

Copy link
Contributor

Choose a reason for hiding this comment

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

Would involve a regression,fromMatchingOption is nullable

Copy link
Contributor

Choose a reason for hiding this comment

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

export type SelectOption<
  Value extends string | number | boolean | null = string,
> = {
  Icon?: IconComponent | null;
  label: string;
  value: Value;
  disabled?: boolean;
  color?: ThemeColor | 'transparent';
};

Copy link
Contributor

Choose a reason for hiding this comment

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

Why ? We treat null and undefined the same in the application, we shouldn't make a difference, that's why we have isDefined

Copy link
Contributor

Choose a reason for hiding this comment

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

Creating different code paths for null and undefined is not a good idea as we have already discussed a few months back, the default way should be to use isDefined everywhere and in some very rare edge cases to differentiate between null and undefined, not the other way around.

Copy link
Contributor

Choose a reason for hiding this comment

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

I modified the file and applied the correct typeguards that should be used everywhere so you can see what we expect in the app.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also I hope that we agree on that very soon because that's how we handle nullable values since 2 years, so better not introducing and favoring another way to work with nullable now.

Copy link
Contributor

@lucasbordeau lucasbordeau Nov 3, 2025

Choose a reason for hiding this comment

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

To go in your direction, we could separate here indeed the option.value treatment between null and undefined, but this is not required here and the code that was checking for undefined wasn't checking option.value but option object, which shouldn't be treated as undefined only but nullable with isDefined

Copy link
Contributor

@lucasbordeau lucasbordeau Nov 3, 2025

Choose a reason for hiding this comment

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

For example in filteredOptions we could add a option.value !== null but using a isDefined(option.value) would also work great, I don't see a precise use case that would require handling null value separately here, using isDefined would work also.

That's the thing with undefined and null, from experience they should not be separated because very often they can indeed be treated the same and they should be from a maintenance and cognitive load perspective.

Copy link
Contributor

@lucasbordeau lucasbordeau Nov 3, 2025

Choose a reason for hiding this comment

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

And you're right for empty option array, but that's again another use case than null | undefined.

See this issue : twentyhq/core-team-issues#1826

return fromMatchingOption;
}

if (emptyOption !== undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same

Copy link
Contributor

Choose a reason for hiding this comment

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

Same answer

Copy link
Contributor

@lucasbordeau lucasbordeau left a comment

Choose a reason for hiding this comment

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

LGTM

@lucasbordeau lucasbordeau merged commit 4541149 into twentyhq:main Nov 3, 2025
54 checks passed
@github-actions
Copy link
Contributor

github-actions bot commented Nov 3, 2025

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:30291

This environment will automatically shut down when the PR is closed or after 5 hours.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 3, 2025

Thanks @anadi45 for your contribution!
This marks your 6th PR on the repo. You're top 5% of all our contributors 🎉
See contributor page - Share on LinkedIn - Share on Twitter

Contributions

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