| SUGGESTION |
Code |
F0Select/F0Select.tsx:179-187 |
useEffect syncing value → localValue suppresses react-hooks/exhaustive-deps |
Audit intentionally omitted deps and document the invariant, or refactor to derived state |
| SUGGESTION |
Code |
F0Select/F0Select.tsx:228-229 |
useMemo deps array contains "searchFn" in props && props.searchFn — string literal in deps array is a code smell; React hooks lint tooling may not track it reliably |
Move the conditional into the memo body; put only reactive values in deps |
| SUGGESTION |
Code |
F0Select/F0Select.tsx:376-410 |
getDisplayItemsForSelection name implies a factory function, but it holds the computed array |
Rename to displayItemsForSelection |
| SUGGESTION |
Code |
F0Select/F0Select.tsx:916-923 |
displayName never set on F0SelectComponent after the generic cast |
Add F0SelectComponent.displayName = "F0Select" before export |
| SUGGESTION |
Code |
F0Select/components/SelectAll.tsx:71 |
id="select-all" hardcoded — duplicate IDs on multi-instance pages |
Accept a scoped id prop or call useId() |
| SUGGESTION |
Code |
F0Select/components/SelectionPreview.tsx:51 |
aria-label hardcoded English (also a BLOCKING a11y item) |
Use useI18n() |
| SUGGESTION |
Code |
F0Select/types.ts |
inputFieldStatus const array is referenced in stories but not re-exported from the component's public surface |
Re-export from types.ts or the barrel index.tsx |
| SUGGESTION |
Code |
F0Select/__tests__/F0Select.test.tsx:50 |
defaultSelectProps sets size: "md" but component default is "sm" |
Change to "sm" or remove the prop |
| SUGGESTION |
Code |
F0Select/__tests__/F0Select.test.tsx:228 |
it.skip "maintains focus on search input during data loading" has no linked issue |
Fix and unskip, or add a tracking issue reference |
| SUGGESTION |
A11y |
F0Select/F0Select.tsx:773-812 (asList path) |
<Label htmlFor={id}> targets useId() id but selectPrimitiveProps does not forward that id to the trigger — label→control link is broken |
Pass id={id} into selectPrimitiveProps or use aria-labelledby |
| SUGGESTION |
A11y |
F0Select/components/Arrow.tsx |
transition-transform duration-200 animation has no useReducedMotion() guard |
Apply duration-0 when useReducedMotion() returns true |
| SUGGESTION |
A11y |
F0Select/components/SelectTopActions.tsx |
<motion.div> for active-filter chips has no useReducedMotion() guard |
Set duration: 0, bounce: 0 when useReducedMotion() is true |
| SUGGESTION |
A11y |
F0Select/__tests__/F0Select.test.tsx |
No axe / accessibility assertions in test suite |
Add vitest-axe checks or axe-playwright story tests for default, multiple, asList, and custom-trigger paths |
| SUGGESTION |
Storybook |
F0Select/__stories__/F0Select.stories.tsx:224-226 |
Decorator uses SelectedItemsDetailedStatus<any, any> with eslint-disable |
Use SelectedItemsDetailedStatus<RecordType, FiltersDefinition> (already imported) |
| SUGGESTION |
Storybook |
F0Select/__stories__/F0Select.stories.tsx |
No dedicated WithDisabled, WithLoading, or WithReadonly interactive stories (they appear only inside Snapshot) |
Add standalone stories for each |
| SUGGESTION |
Storybook |
F0Select/__stories__/F0Select.stories.tsx:583-588 |
LargeList spreads WithSearchBox.args but WithSearchBox sets showSearchBox in its render fn, not args — LargeList does not actually show the search box |
Add showSearchBox: true to WithSearchBox.args or set it explicitly in LargeList.args |
| SUGGESTION |
Storybook |
F0Select/__stories__/F0Select.stories.tsx:337-348 |
Only WithDataTestId has a play function, limited to DOM presence assertion; no interaction or keyboard-nav play tests |
Add play functions for open/select/close, keyboard navigation, and onChange assertion |
| SUGGESTION |
Tests |
— |
Missing: keyboard navigation tests (Space/Enter to open, ArrowUp/Down, Escape) |
Add await user.keyboard(...) tests |
| SUGGESTION |
Tests |
— |
Missing: multiple={true} selection tests (select, deselect, displayed value) |
Add a describe block for multiple mode |
| SUGGESTION |
Tests |
— |
Missing: SelectAll checkbox behavior tests |
Add tests for check-all, uncheck-all, indeterminate state |
| SUGGESTION |
Tests |
— |
Missing: loading={true} state test |
Assert loading indicator is present |
| SUGGESTION |
Tests |
— |
Missing: asList={true} render mode tests |
Verify options visible without trigger click |
| SUGGESTION |
Tests |
— |
Missing: source (async/paginated) data loading tests |
Add tests for initial load, pagination trigger |
| SUGGESTION |
Tests |
— |
Missing: error / status / hint display tests |
Assert status message and hint text appear |
| SUGGESTION |
Tests |
— |
Missing: actions prop test |
Pass actions and assert rendered action element |
| SUGGESTION |
Tests |
— |
Missing: onOpenChange callback test |
Assert called with true on open, false on close |
| SUGGESTION |
Tests |
— |
Missing: readonly prop test |
Assert trigger is non-interactive when readonly |
Stability Audit — F0Select
Findings
BLOCKING
F0Select/__tests__/F0Select.test.tsx:1screenimported from@testing-library/reactinstead of@/testing/test-utilsimport { screen } from "@/testing/test-utils"F0Select/__tests__/F0Select.test.tsx:3import "@testing-library/jest-dom/vitest"is forbidden per F0 conventionF0Select/__tests__/F0Select.test.tsx:62-64console.log(value)indefaultSelectProps.onChangetest helpervi.fn()F0Select/__tests__/F0Select.test.tsx:97user.click(...)inopenSelecthelper is not awaitedawaitbeforeuser.click(...)F0Select/__tests__/F0Select.test.tsx:102fireEvent.animationStartinside sharedopenSelecthelper — affects every testuserEventcall or document whyfireEventis requiredF0Select/__tests__/F0Select.test.tsx:410container.querySelector("button[data-testid='clear-button']")— non-accessible DOM queryscreen.getByRole("button", { name: /clear/i })F0Select/__tests__/F0Select.test.tsx:416await fireEvent.click(clearButton)—fireEventis synchronous,awaitis a no-op; preferuserEventawait user.click(clearButton)F0Select/__stories__/F0Select.stories.tsx:316console.log(...)in the story decorator — runs on every story renderconsole.logcallF0Select/__stories__/F0Select.stories.tsx:544console.log("searchFn", ...)insideWithSearchBoxrender functionF0Select/__stories__/F0Select.stories.tsx:773,814,858console.log("selectionStatus", ...)in threeonSelectItemscallbacksselectionStatuson screenF0Select/__stories__/F0Select.stories.tsx:865,880<F0Select {...(args as any)} />—anycast inMultiplePaginatedAsListandAsListrender functionsargscorrectly; use the story's inferredArgstypeF0Select/F0Select.tsx:581-583handleApplydeclared withuseCallback(fn, [])(empty deps) but closes overhandleChangeOpenLocal— stale closurehandleChangeOpenLocalto deps arrayF0Select/F0Select.tsx:820-825<SelectTrigger asChild>merges Radix combobox role onto a<div>, which is non-interactive;aria-labelon a<div>is ignored by ATchildrenin a<button>so Radix merges onto a real interactive hostF0Select/F0Select.tsx:879-884<button>hasaria-label={label || placeholder}whileInputFieldalready injects the samearia-labelonto its child — produces a conflicting labelaria-labelfrom the inner<button>; rely onInputField'shtmlFor/idlinkageF0Select/components/SelectAll.tsx:71id="select-all"hardcoded — multipleF0Selectinstances on the same page produce duplicateidattributes, breaking ARIA associationsuseId()insideSelectAllor accept a scoped id from the parentF0Select/components/SelectionPreview.tsx:51aria-label={`Remove ${item.label}`}is hardcoded English — untranslatableuseI18n()with an interpolated translation keyF0Select/components/SelectItem.tsx:27-29<div>lacksaria-hidden="true"— SVG content may corrupt the Radixoptionaccessible namearia-hidden="true"to the icon wrapper<div>SUGGESTION
F0Select/F0Select.tsx:179-187useEffectsyncingvalue→localValuesuppressesreact-hooks/exhaustive-depsF0Select/F0Select.tsx:228-229useMemodeps array contains"searchFn" in props && props.searchFn— string literal in deps array is a code smell; React hooks lint tooling may not track it reliablyF0Select/F0Select.tsx:376-410getDisplayItemsForSelectionname implies a factory function, but it holds the computed arraydisplayItemsForSelectionF0Select/F0Select.tsx:916-923displayNamenever set onF0SelectComponentafter the generic castF0SelectComponent.displayName = "F0Select"before exportF0Select/components/SelectAll.tsx:71id="select-all"hardcoded — duplicate IDs on multi-instance pagesuseId()F0Select/components/SelectionPreview.tsx:51aria-labelhardcoded English (also a BLOCKING a11y item)useI18n()F0Select/types.tsinputFieldStatusconst array is referenced in stories but not re-exported from the component's public surfacetypes.tsor the barrelindex.tsxF0Select/__tests__/F0Select.test.tsx:50defaultSelectPropssetssize: "md"but component default is"sm""sm"or remove the propF0Select/__tests__/F0Select.test.tsx:228it.skip"maintains focus on search input during data loading" has no linked issueF0Select/F0Select.tsx:773-812(asListpath)<Label htmlFor={id}>targetsuseId()id butselectPrimitivePropsdoes not forward thatidto the trigger — label→control link is brokenid={id}intoselectPrimitivePropsor usearia-labelledbyF0Select/components/Arrow.tsxtransition-transform duration-200animation has nouseReducedMotion()guardduration-0whenuseReducedMotion()returnstrueF0Select/components/SelectTopActions.tsx<motion.div>for active-filter chips has nouseReducedMotion()guardduration: 0, bounce: 0whenuseReducedMotion()istrueF0Select/__tests__/F0Select.test.tsxvitest-axechecks oraxe-playwrightstory tests for default, multiple, asList, and custom-trigger pathsF0Select/__stories__/F0Select.stories.tsx:224-226SelectedItemsDetailedStatus<any, any>with eslint-disableSelectedItemsDetailedStatus<RecordType, FiltersDefinition>(already imported)F0Select/__stories__/F0Select.stories.tsxWithDisabled,WithLoading, orWithReadonlyinteractive stories (they appear only inside Snapshot)F0Select/__stories__/F0Select.stories.tsx:583-588LargeListspreadsWithSearchBox.argsbutWithSearchBoxsetsshowSearchBoxin its render fn, not args —LargeListdoes not actually show the search boxshowSearchBox: truetoWithSearchBox.argsor set it explicitly inLargeList.argsF0Select/__stories__/F0Select.stories.tsx:337-348WithDataTestIdhas aplayfunction, limited to DOM presence assertion; no interaction or keyboard-nav play testsplayfunctions for open/select/close, keyboard navigation, andonChangeassertionawait user.keyboard(...)testsmultiple={true}selection tests (select, deselect, displayed value)describeblock for multiple modeloading={true}state testasList={true}render mode testssource(async/paginated) data loading testserror/status/hintdisplay testsactionsprop testactionsand assert rendered action elementonOpenChangecallback testtrueon open,falseon closereadonlyprop testreadonlyReady-to-run agent fix prompt