Skip to content

Feat/mar 1888 manual search results saving and recall#1555

Merged
william-schlegel merged 24 commits into
mainfrom
feat/MAR-1888-manual-search-results-saving-and-recall
May 28, 2026
Merged

Feat/mar 1888 manual search results saving and recall#1555
william-schlegel merged 24 commits into
mainfrom
feat/MAR-1888-manual-search-results-saving-and-recall

Conversation

@william-schlegel
Copy link
Copy Markdown
Contributor

@william-schlegel william-schlegel commented May 20, 2026

What has been done

  • Save and read searchs and filters (in a memory Map for the moment, waiting for the endpoint to be ready)
  • fix the language problem (RTL, not used in formatting)
  • new format function for localized country names (based on ISO2 code)
Screenshot 2026-05-27 at 17 08 23 Screenshot 2026-05-27 at 17 08 47 Screenshot 2026-05-27 at 17 09 04
  • remove opensanction external links
  • fix overflow bug with very long url without hyphens
  • manage topics with special presentation (Switch, Global)
Screenshot 2026-05-27 at 17 11 16 Screenshot 2026-05-27 at 17 11 23 Screenshot 2026-05-27 at 17 12 14
  • translations for the pep category topics

Save manual search

Implement the "save manual search" and read the saved manual search logic but hide the button as the end point is not ready yet (I am using a local Map to mock the endpoint ftm)
Screenshot 2026-05-27 at 17 15 29
Screenshot 2026-05-27 at 17 15 47
Screenshot 2026-05-27 at 17 15 59

New design component

// Interface
export interface ExpandableGroupTagLineProps {
  items: ReactNode[];
  moreButton?: (overflow: number, onExpand: () => void) => ReactNode;
  lessButton?: (onCollapse: () => void) => ReactNode;
}
// usage
<ExpandableGroupTagLine items={iiems} / >

result:

Screenshot 2026-05-21 at 13 49 43 Screenshot 2026-05-21 at 13 52 22 Screenshot 2026-05-21 at 14 04 04 Screenshot 2026-05-21 at 14 04 21

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ability to save freeform screening searches with results and retrieve saved searches with filtering and pagination
    • Country names now display in the user's selected language
    • Added read-only view mode for dataset selection in screening details
  • Improvements

    • Enhanced dataset and topic selection with better scoping and organization
    • Language switcher now properly updates UI immediately after language change
    • Improved word wrapping for entity property display
    • Global topic filtering now better integrated into selection workflow
  • Localization

    • Added translations for new filtering options, dataset labels, and saved search UI across supported languages

Review Change Stack

@linear
Copy link
Copy Markdown

linear Bot commented May 20, 2026

MAR-1888

@william-schlegel william-schlegel force-pushed the feat/MAR-1888-manual-search-results-saving-and-recall branch 3 times, most recently from 184bc47 to a3e90a2 Compare May 27, 2026 07:33
@william-schlegel william-schlegel marked this pull request as ready for review May 27, 2026 15:22
@coderabbitai coderabbitai Bot added the L large label May 27, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Warning

Review limit reached

@william-schlegel, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 55 minutes and 17 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fbe0d3a9-9727-4385-8e84-d08c6e8cabbc

📥 Commits

Reviewing files that changed from the base of the PR and between b1859ff and 8e03839.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • packages/marble-api/src/generated/marblecore-api.ts is excluded by !**/generated/**
📒 Files selected for processing (65)
  • packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx
  • packages/app-builder/src/components/ContinuousScreening/EditionValidationPanel.tsx
  • packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx
  • packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx
  • packages/app-builder/src/components/ContinuousScreening/validation/DatasetSelectionSection.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
  • packages/app-builder/src/components/DevLanguageShortcut.tsx
  • packages/app-builder/src/components/LanguagePicker.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/context/ListAndTopicDatasetConfiguration.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-selection-provider-utils.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/index.ts
  • packages/app-builder/src/components/Scenario/Screening/FieldDataset.tsx
  • packages/app-builder/src/components/Scenario/Screening/FieldEntityType.tsx
  • packages/app-builder/src/components/ScreeningThreshold.tsx
  • packages/app-builder/src/components/Screenings/DatasetTag.tsx
  • packages/app-builder/src/components/Screenings/EntityProperties.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformMatchCard.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchForm.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchPrint/PrintResultCard.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/SaveSearch.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx
  • packages/app-builder/src/components/Screenings/MatchResult.tsx
  • packages/app-builder/src/components/Screenings/ScreeningPanel/InlineRefineSearch.tsx
  • packages/app-builder/src/components/Screenings/ScreeningPanel/PanelSearchDetails.tsx
  • packages/app-builder/src/components/Screenings/ScreeningQueryDetail.tsx
  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/app-builder/src/hooks/useEntityName.ts
  • packages/app-builder/src/locales/ar/continuous-screening.json
  • packages/app-builder/src/locales/ar/scenarios.json
  • packages/app-builder/src/locales/ar/screenings.json
  • packages/app-builder/src/locales/en/continuous-screening.json
  • packages/app-builder/src/locales/en/scenarios.json
  • packages/app-builder/src/locales/en/screenings.json
  • packages/app-builder/src/locales/fr/continuous-screening.json
  • packages/app-builder/src/locales/fr/scenarios.json
  • packages/app-builder/src/locales/fr/screenings.json
  • packages/app-builder/src/models/continuous-screening.ts
  • packages/app-builder/src/models/data-model.ts
  • packages/app-builder/src/models/screening-config.ts
  • packages/app-builder/src/models/screening.ts
  • packages/app-builder/src/queries/screening/freeform-search.ts
  • packages/app-builder/src/queries/screening/lists-config.ts
  • packages/app-builder/src/repositories/ScreeningRepository.ts
  • packages/app-builder/src/routes/__root.tsx
  • packages/app-builder/src/routes/_app/_builder/detection/analytics/$scenarioId.tsx
  • packages/app-builder/src/routes/_app/_builder/detection/scenarios/$scenarioId/i/$iterationId/screenings.$screeningId.tsx
  • packages/app-builder/src/routes/_app/_builder/screening-search/index.tsx
  • packages/app-builder/src/server-fns/continuous-screening.ts
  • packages/app-builder/src/server-fns/screenings.ts
  • packages/marble-api/openapis/marblecore-api/screenings.yml
  • packages/ui-design-system/package.json
  • packages/ui-design-system/src/Collapsible/Collapsible.tsx
  • packages/ui-design-system/src/ExpandableGroupTagLine/ExpandableGroupTagLine.tsx
  • packages/ui-design-system/src/SelectCountry/SelectCountry.tsx
  • packages/ui-design-system/src/Tag/Tag.tsx
  • packages/ui-design-system/src/ThresholdRange/ThresholdRange.tsx
  • packages/ui-design-system/src/contexts/I18nContext.tsx
  • packages/ui-design-system/src/index.ts
  • packages/ui-design-system/src/utils/formatCountryName.ts
📝 Walkthrough

Walkthrough

This PR refactors i18n infrastructure to derive language state from react-i18next, redesigns dataset/topic selection using section-scoped composite keys, introduces width-aware tag expansion UI components, and adds comprehensive save/view workflows for freeform screening searches. Global screening category support is added throughout the system, with localization strings updated for multiple languages.

Changes

I18n Provider and Localization Infrastructure

Layer / File(s) Summary
I18n provider subscription to react-i18next
packages/ui-design-system/src/contexts/I18nContext.tsx, packages/ui-design-system/src/utils/formatCountryName.ts, packages/ui-design-system/package.json
I18nProvider now derives locale and translation function from react-i18next via useSyncExternalStore listening to languageChanged events, rather than accepting a prebuilt value prop. New formatCountryName(iso2, language) utility using Intl.DisplayNames for language-aware country labels. Package adds i18next/react-i18next as dev and peer dependencies.
Localized country selection
packages/ui-design-system/src/SelectCountry/SelectCountry.tsx, packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
SelectCountry now derives country list/sorting/filtering from current language via useI18n() and helper functions (getCountryName, buildCountryList, itemFilterValue); stores and renders localized country names. DataField.StringCountry renderer now uses formatCountryName(country.isoAlpha2, language) instead of hardcoded English names.

Dataset/Topic Selection Core Refactoring

Layer / File(s) Summary
Composite wire key format for dataset/topic selections
packages/app-builder/src/components/ListAndTopicConfiguration/dataset-selection-provider-utils.ts, packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts, packages/app-builder/src/components/ListAndTopicConfiguration/context/ListAndTopicDatasetConfiguration.ts, packages/app-builder/src/components/ListAndTopicConfiguration/index.ts
Replaces flat dataset name keys with section-scoped composite keys (sectionKey:datasetName/sectionKey:topicGroup:topicName); removes alias expansion logic and special global key handling. Removes withGlobalTopics config option. Refactors special-topic lookups to require sectionKey parameter. Simplifies global topic selection to read/write only two composite keys without listConfig iteration. Exports new sanitizeTruthyDatasets helper and updated getSectionLeafKeys function.
DatasetSelectionContent refactored for section-scoped keys and i18n
packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
Major refactor using section-scoped dataset/topic keys throughout, adds GlobalTopicSwitch component for global topics, uses ExpandableGroupTagLine for tag rendering with formatItemName/hasTranslation i18n support, filters out global section from main list, updates all selection/grouping logic and special-topic handling to be section-aware.

UI Component Library Enhancements

Layer / File(s) Summary
Width-constrained tag expansion and collapsible icon positioning
packages/ui-design-system/src/ExpandableGroupTagLine/ExpandableGroupTagLine.tsx, packages/ui-design-system/src/Collapsible/Collapsible.tsx, packages/ui-design-system/src/index.ts
New ExpandableGroupTagLine component measures items in hidden container via ResizeObserver to determine maximum visible tags; shows "more" control with overflow count when expanded is false, and "less" control when expanded. Collapsible.Title now accepts iconPosition prop (left/right/hidden) instead of boolean hideIcon.
Tag appearance variants and styling updates
packages/ui-design-system/src/Tag/Tag.tsx, packages/ui-design-system/src/ThresholdRange/ThresholdRange.tsx
Tag component adds appearance variant (including monospace) and color: white option. ThresholdRange rail styling enhanced with pseudo-element borders. Screening threshold adds explicit value: 0 option.

Freeform Search Refactoring and Saved Search Feature

Layer / File(s) Summary
Entity search form context for centralized state
packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx
New context module defining EntitySearchFormValues/EntitySearchFormInstance types, context, provider, and hooks (useEntitySearchForm, useEntitySearchFormStore) to centralize entity type/fields state across freeform search components.
FreeformSearchForm simplified without manual_search list config
packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchForm.tsx
Removes useListConfigQuery and completeGlobalTopicSelections usage, wraps form UI in EntitySearchFormProvider, derives datasets directly from listSharp state, computes canonical dataset keys without global-topic auto-completion, passes only disabled prop to EntityTypePopover.
EntityTypePopover refactored to use form context and language-aware country handling
packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
Reads entityType/fields from entity-search form context instead of props; props signature reduced to only disabled. AdditionalEntityTypePopover refactored to use local draft state synced from form store, manages birth-date validation errors locally, uses language-aware country conversion via countryFormStringToValue(..., i18n.language) and formatCountryName.
Related freeform components updated
packages/app-builder/src/components/Screenings/FreeformSearch/FreeformMatchCard.tsx, packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx, packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx, packages/app-builder/src/components/Screenings/ScreeningPanel/InlineRefineSearch.tsx
FreeformMatchCard removes "OpenSanctions" link, refactors collapsible styling. DatasetsPopover filters global datasets and uses getSectionLeafKeys. LimitPopover removes completeGlobalTopicSelections and simplifies global topic switch rendering. InlineRefineSearch wraps entity search UI in EntitySearchFormProvider.
SaveSearch modal for persisting searches
packages/app-builder/src/components/Screenings/FreeformSearch/SaveSearch.tsx
New component rendering a modal form with TanStack Form validation (name 1–120 chars), summary of search inputs/results/dataset count, submission via useSaveFreeformSearchMutation, success/error toast handling.
ViewSavedResults modal with filtering and pagination
packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
New modal UI with debounced name filter, date-range picker with static/dynamic options, owner filter, pagination controls, and paginated display of saved searches with expandable SavedSearchRow cards showing search name/owner/creation date/results count and matched entities. Includes supporting components: InputTags, PeriodFilter, OwnerFilter, ViewSavedResultsPaginationRow, FilterPill.
Backend: server functions and query hooks
packages/app-builder/src/server-fns/screenings.ts, packages/app-builder/src/queries/screening/freeform-search.ts
Adds saveFreeformSearchFn/listSavedFreeformSearchesFn/deleteSavedFreeformSearchFn server functions with Zod validation. Adds query hooks useSaveFreeformSearchMutation/useSavedFreeformSearchesQuery/useDeleteSavedFreeformSearchMutation with cache invalidation.
Backend: repository persistence and model types
packages/app-builder/src/repositories/ScreeningRepository.ts, packages/app-builder/src/models/screening.ts
Extends ScreeningRepository with CRUD methods (saveScreeningSearch, listSavedScreeningSearches, deleteSavedScreeningSearch) using in-memory mock storage. Adds exported interfaces: SavedScreeningSearchInputs, SavedScreeningSearch, SavedScreeningSearchFilters, SavedScreeningSearchPage.

Screening Category and Data Model Updates

Layer / File(s) Summary
Global category support in screening system
packages/app-builder/src/models/screening.ts, packages/app-builder/src/models/screening-config.ts, packages/app-builder/src/models/data-model.ts, packages/app-builder/src/components/Screenings/DatasetTag.tsx, packages/marble-api/openapis/marblecore-api/screenings.yml
Extends ScreeningCategory to include 'global'; introduces RiskTagCategory excluding global. Updates color/i18n/ranking mappings. screening-config.ts initializes global filter object and treats global as implicit-on-wire. data-model.ts uses RiskTagCategory for risk tag annotations. DatasetTag adds global case. OpenAPI schema includes global in both ScreeningAvailableFilters and ScreeningConfigBodyFiltersDto.

Continuous Screening and Data Visualization

Layer / File(s) Summary
Continuous screening UI with ExpandableGroupTagLine
packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx, packages/app-builder/src/components/ContinuousScreening/EditionValidationPanel.tsx, packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx, packages/app-builder/src/components/ContinuousScreening/validation/DatasetSelectionSection.tsx, packages/app-builder/src/models/continuous-screening.ts, packages/app-builder/src/server-fns/continuous-screening.ts
ConfigurationsPage now renders datasets/object types using ExpandableGroupTagLine with formatDatasetTitle. EditionValidationPanel sanitizes datasets before mutation via sanitizeTruthyDatasets. DatasetSelectionRecap uses getSectionLeafKeys for leaf-count logic. DatasetSelectionSection calls formatDatasetTitle(title, t) for i18n. Server functions sanitize datasets before creating/updating configs.

Localization and Route Providers

Layer / File(s) Summary
Locale JSON translations
packages/app-builder/src/locales/*/continuous-screening.json, packages/app-builder/src/locales/*/screenings.json, packages/app-builder/src/locales/*/scenarios.json
Added comprehensive i18n strings across English, French, Arabic: "Datasets/Topics" validation labels, PEP filter categories, entity property short forms, freeform search save/results UI, and global sanction list key.
Language picker and i18n provider routing
packages/app-builder/src/components/LanguagePicker.tsx, packages/app-builder/src/components/DevLanguageShortcut.tsx, packages/app-builder/src/routes/__root.tsx, packages/app-builder/src/routes/_app/_builder/detection/analytics/$scenarioId.tsx
Updates language picker/shortcut to call changeLanguage(nextLang) after setting preferred language. Replaces I18nextProvider with I18nProvider in root shell. Analytics route uses FormattingProvider instead of I18nProvider.
Screening form and search route updates
packages/app-builder/src/routes/_app/_builder/detection/scenarios/$scenarioId/i/$iterationId/screenings.$screeningId.tsx, packages/app-builder/src/routes/_app/_builder/screening-search/index.tsx
Screening detail form passes readOnly prop to FieldDataset. Screening search route organizes print view in container with commented-out save/view results features.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

This PR introduces substantial interconnected changes across i18n infrastructure, dataset/topic selection mechanics (composite keys throughout), multiple new UI components, complete freeform search refactoring with saved search persistence, and screening category system extensions. The complexity stems from the pervasive impact of the key refactoring on many dependent layers, numerous cross-file coordinations, and the interplay between form context, language awareness, and selection state management.

Possibly related PRs

  • checkmarble/marble-frontend#1535: This PR's refactoring of section-scoped dataset selection keys directly impacts freeform search components like DatasetsPopover and EntityTypePopover that depend on the updated leaf-name/key helpers.
  • checkmarble/marble-frontend#1554: Both PRs modify continuous-screening dataset derivation, with the retrieved PR using canonical keys and this PR adding sanitization before those keys are computed.
  • checkmarble/marble-frontend#1504: Both modify FreeformMatchCard.tsx—the retrieved PR changes the collapsible body to fetch enriched entity data while this PR removes the external OpenSanctions link and refactors the trigger styling.

Suggested reviewers

  • OrriMandarin
  • apognu
  • Pascal-Delange

Hark! What complex refactoring doth take flight,
With keys composite, scoped in section's light,
And searches saved with filters fair and true,
I18n's touch makes all the world seem new.
From dataset tags to form's collected state,
This PR doth grandly renovate!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/MAR-1888-manual-search-results-saving-and-recall

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx (1)

94-94: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use i18n interpolation for the count/empty suffix instead of string concatenation.

At Line 94, concatenating (${count}) / (${...}) after t(...) can break RTL ordering; move these into translation keys with interpolation ({ count }) so each locale controls placement.

Based on learnings: “when rendering translated strings that include dynamic count values, always use i18n interpolation … avoid rendering t('key') followed by (${count}) as separate nodes, since this can break RTL layout.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx`
at line 94, The JSX currently concatenates a translated base string with a count
suffix (`{isEmpty ? \` (${t('scenarios:sanction.lists.no_lists_selected')})\` :
\` (${count})\`}`) which breaks RTL; change this to use i18n interpolation keys
so locale controls placement. Create/consume translation keys that include the
suffix with an interpolated value (e.g.
`scenarios:sanction.lists.selected_count` -> `({count})` or a localized
equivalent, and `scenarios:sanction.lists.no_lists_selected_count` for the empty
case), then replace the concatenation with a single `t(...)` call passing `{
count }` (or no vars for the explicit empty key) where `isEmpty` chooses the
appropriate key; update the JSX in DatasetsPopover.tsx to call those keys
instead of concatenating `t(...)` + `(${count})`.
🧹 Nitpick comments (6)
packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx (1)

45-47: ⚡ Quick win

Tighten form/store typing instead of casting through unknown/never.

These casts hide integration errors that TypeScript could catch at compile-time. A typed provider contract here will save thee from subtle runtime woes.

Suggested typing hardening
-import { useStore } from '`@tanstack/react-form`';
+import { type FormApi, useStore } from '`@tanstack/react-form`';
@@
-export function EntitySearchFormProvider({ form, children }: { form: unknown; children: ReactNode }) {
+export function EntitySearchFormProvider({
+  form,
+  children,
+}: {
+  form: EntitySearchFormInstance;
+  children: ReactNode;
+}) {
   return (
-    <EntitySearchFormContext.Provider value={form as EntitySearchFormInstance}>
+    <EntitySearchFormContext.Provider value={form}>
       {children}
     </EntitySearchFormContext.Provider>
   );
 }
@@
 export function useEntitySearchFormStore<TSelected>(selector: (state: EntitySearchFormState) => TSelected): TSelected {
   const { store } = useEntitySearchForm();
-  return useStore(store as never, selector);
+  return useStore(store as FormApi<EntitySearchFormValues>['store'], selector);
 }

Also applies to: 57-59

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx`
around lines 45 - 47, Change the loose typing on EntitySearchFormProvider to
accept a properly typed form prop instead of using unknown/never casts: update
the provider signature to require the concrete interface
(EntitySearchFormInstance) for the form param and ensure children is typed as
ReactNode, then pass the form directly into EntitySearchFormContext.Provider
without "as" casting; likewise, replace similar casts at the other occurrence
(the block around lines that referenced the same pattern) so all uses
consistently accept and expose EntitySearchFormInstance rather than
unknown/never.
packages/app-builder/src/server-fns/continuous-screening.ts (1)

2-2: ⚡ Quick win

Import sanitizeTruthyDatasets from a utility module, not a component barrel.

For server-fns, keep imports on server-safe utility boundaries to avoid accidental UI coupling as the barrel evolves.

Suggested import adjustment
-import { sanitizeTruthyDatasets } from '`@app-builder/components/ListAndTopicConfiguration`';
+import { sanitizeTruthyDatasets } from '`@app-builder/components/ListAndTopicConfiguration/dataset-utils`';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app-builder/src/server-fns/continuous-screening.ts` at line 2, The
import of sanitizeTruthyDatasets in continuous-screening.ts currently comes from
the component barrel '`@app-builder/components/ListAndTopicConfiguration`', which
may pull UI code into server fns; change the import to the server-safe utility
module that actually exports sanitizeTruthyDatasets (replace the barrel import
with the direct path to the utility that defines sanitizeTruthyDatasets) so
continuous-screening.ts only depends on server-safe utilities instead of
components.
packages/app-builder/src/queries/screening/freeform-search.ts (1)

52-61: ⚡ Quick win

Align query hook naming with the repository convention.

useSavedFreeformSearchesQuery should follow the useGetXyzQuery pattern (e.g., useGetSavedFreeformSearchesQuery) for consistency with app-builder query hooks.

As per coding guidelines: “Use TanStack Query hooks with naming convention useGetXyzQuery for data fetching operations.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app-builder/src/queries/screening/freeform-search.ts` around lines
52 - 61, The exported hook useSavedFreeformSearchesQuery doesn't follow the
repository naming convention; rename it to useGetSavedFreeformSearchesQuery
(update the function declaration/export from useSavedFreeformSearchesQuery to
useGetSavedFreeformSearchesQuery) and keep the implementation unchanged (still
calling listSavedFreeformSearchesFn via listSavedSearches and returning the same
useQuery). Also update all imports/usages across the codebase to the new name so
references to useSavedFreeformSearchesQuery are replaced with
useGetSavedFreeformSearchesQuery.
packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts (1)

149-152: 💤 Low value

Consider using Object.hasOwn or in operator for key check.

The current implementation uses Object.keys().includes() which creates an intermediate array on every call. While not a performance-critical path, a simpler approach would be cleaner.

♻️ Suggested refactor
 export function hasTranslation(key: string) {
-  const hasKey = Object.keys(FILTER_TRANSLATION_MAP).includes(key);
-  return hasKey ? FILTER_TRANSLATION_MAP[key as keyof typeof FILTER_TRANSLATION_MAP] : undefined;
+  return key in FILTER_TRANSLATION_MAP
+    ? FILTER_TRANSLATION_MAP[key as keyof typeof FILTER_TRANSLATION_MAP]
+    : undefined;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts`
around lines 149 - 152, The hasTranslation function currently checks keys by
building an array via Object.keys(...).includes(...); change this to a direct
key existence check (e.g., Object.hasOwn or the in operator) against
FILTER_TRANSLATION_MAP to avoid the intermediate array and simplify the logic in
hasTranslation; update the return to still return FILTER_TRANSLATION_MAP[key as
keyof typeof FILTER_TRANSLATION_MAP] when present and undefined otherwise,
keeping the function name hasTranslation and the FILTER_TRANSLATION_MAP
reference unchanged.
packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx (1)

131-137: 💤 Low value

The eslint-disable for exhaustive-deps may cause stale closure issues.

The syncLocalFromForm function captures entityTypeFields and fields in its closure. When called from this effect, if entityTypeFields or fields have changed since the last render but openRequest hasn't, the function will use outdated values.

However, since syncLocalFromForm reads fields directly from useEntitySearchFormStore and entityTypeFields is derived from entityType (also from the store), the values should be current when the effect executes. The lint disable is acceptable here, but consider extracting the logic inline or adding a comment explaining why these dependencies are safe to omit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx`
around lines 131 - 137, The effect currently disables exhaustive-deps and calls
syncLocalFromForm which can close over stale entityTypeFields/fields; inline the
syncLocalFromForm logic directly inside the useEffect that references
openRequest/disabled/lastProcessedOpenRequest so it reads the latest
entityTypeFields and fields at call time, then remove the
eslint-disable-next-line; alternatively, if you must keep syncLocalFromForm as a
separate function, add a concise explanatory comment above the disabled lint
rule stating that syncLocalFromForm reads current values from
useEntitySearchFormStore and entityType is derived from the store (so omitting
deps is intentional) and keep the disable, but prefer the inline approach to
avoid stale closures.
packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx (1)

800-821: 💤 Low value

Review useMemo dependency array for potential issues.

The dependency array includes both selectedItems and selectedKey. Since selectedKey is derived from selectedItems (line 798), including both is intentional—selectedKey serves as a stable primitive to trigger re-computation when selection changes, since selectedItems array reference may be unstable even when contents are the same.

However, listConfig, sectionKey, topicGroup, and onAfterChange are also captured but only listConfig is potentially unstable. The RemovableTag callback captures these in a closure. This is likely fine since these values don't change frequently, but be aware that stale closures could occur if parent re-renders with new onAfterChange callbacks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx`
around lines 800 - 821, The useMemo for tagItems currently lists both
selectedItems and selectedKey and captures the whole listConfig object which may
be unstable; change the dependency array to rely on the stable primitive
selectedKey (remove selectedItems) and reference the stable updater instead of
the whole config by replacing listConfig with listConfig.update (i.e. use
[selectedKey, mode, listConfig.update, sectionKey, topicGroup, onAfterChange]);
this keeps the memo triggered by real selection changes and prevents unnecessary
recomputations or stale closures for the RemovableTag onRemove which calls
listConfig.update and setTopicKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts`:
- Around line 111-114: Remove the debug console.log by deleting the
console.log('last', last) call in the formatDatasetTitle flow; locate the block
where last is computed and checked with hasTranslation(last) (symbols:
formatDatasetTitle, last, hasTranslation, t) and simply remove the console.log
line (or replace it with a proper debug/logger statement if persistent logging
is required).

In
`@packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx`:
- Around line 800-821: The useMemo inside DatasetSelectionContent's tagItems
contains a leftover debug console.log({ item }) which should be removed; edit
the tagItems computation (the selectedItems.map callback that calls
formatItemName and returns RemovableTag or ViewTag) to delete the console.log
line so no debugging output is emitted, then save/format and run linters to
ensure no unused variables warnings remain.

In
`@packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx`:
- Around line 96-117: The popover trigger currently uses a clickable <div> which
breaks keyboard accessibility; replace the root <div className="flex
items-center gap-v2-sm cursor-pointer"> with a semantic interactive element like
<button type="button"> (or your design-system's button-primitive) and move any
onClick/ref/disabled logic to that element so keyboard/aria behavior works; keep
the existing class names (flex items-center gap-v2-sm) on the new button, set
disabled={disabled} or aria-disabled={disabled} accordingly, ensure tagRef (and
any focus handling) is attached to the appropriate element (button or contained
Tag) and preserve rendering of includeDeceasedSelected,
committedLimit/DEFAULT_LIMIT and hasCustomValue exactly as before.

In
`@packages/app-builder/src/components/Screenings/FreeformSearch/SaveSearch.tsx`:
- Line 17: The current Zod schema for the saved-search "name" field uses
z.string().min(1).max(120) which still accepts whitespace-only values; update
the "name" validator to trim input before validation by using
z.string().trim().min(1).max(120) (reference the "name" schema in
SaveSearch.tsx) so that names like "   " are rejected—ensure any downstream
UI/error handling still displays a clear validation message after this change.

In
`@packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx`:
- Around line 96-101: The close control currently uses an Icon with an onClick
handler which is not keyboard-accessible; update the ViewSavedResults component
to render a real <button> (type="button") that contains the Icon instead, move
the onClick={() => setOpen(false)} to the button, keep the accessible aria-label
(t('common:close')) on the button, preserve visual classes (e.g., "size-5
cursor-pointer text-grey-secondary hover:text-grey-primary") on the button or
Icon as appropriate, and remove the onClick from the Icon so the control is
focusable and works with keyboard and assistive tech (refer to the Icon usage
and setOpen in ViewSavedResults.tsx).

In `@packages/app-builder/src/constants/screening-entity.tsx`:
- Line 267: Replace the invalid Tailwind class on the Wikidata ExternalLink:
find the ExternalLink element that renders the wikidataId (e.g., ExternalLink
href={`https://wikidata.org/wiki/${value}`} className="normal-case break-al")
and change the typo "break-al" to a valid class such as "break-all" (or
"break-words") so long wikidataId values wrap correctly and adhere to the
tailwind-preset styling.

In `@packages/app-builder/src/locales/ar/continuous-screening.json`:
- Around line 71-75: The localization key
edition.validation.datasetSelection.no_removed is inconsistent with the
others—update its Arabic value to match the "lists/topics" wording used by
edition.validation.datasetSelection.added.title and
edition.validation.datasetSelection.removed.title (i.e., change "لم تتم إزالة
قوائم" to include "/المواضيع" so it reads like the others).

In `@packages/app-builder/src/locales/en/continuous-screening.json`:
- Line 100: Update the JSON value for the key
"filter.pep.category.law_enforce_authority" to read "Law enforcement authority"
(replace the current "Law enforce authority") ensuring the string remains valid
JSON; also search for and update any other locale files or usages that mirror
this key to keep translations consistent.

In `@packages/app-builder/src/repositories/ScreeningRepository.ts`:
- Around line 72-73: The mock saved-search implementation uses a fixed
MOCK_SAVED_SEARCH_OWNER_ID which causes cross-user exposure; change the mock to
scope saved searches to the authenticated user by replacing the global
MOCK_SAVED_SEARCH_OWNER_ID with a per-request/argument ownerId (or a helper like
getCurrentUserId) and update all affected functions—createSavedScreeningSearch,
listSavedScreeningSearches, getSavedScreeningSearch, deleteSavedScreeningSearch
(and any helpers that reference MOCK_SAVED_SEARCH_OWNER_ID)—to accept and
persist/filter by that ownerId so saved searches are only returned for the
requesting user.

In `@packages/app-builder/src/server-fns/screenings.ts`:
- Line 121: The field schema using "results: z.array(z.any())" trusts arbitrary
client payloads; replace it with a concrete Zod item schema (e.g. ResultItem =
z.object({...}) with explicit fields and constraints) and then use
z.array(ResultItem).max(N) to bound array length and apply per-field limits
(e.g. z.string().max(...), z.number().int().min(...)/max(...),
z.record(z.string(), z.any()).optional() only if needed) so malformed or
oversized blobs are rejected; update both occurrences (the "results" schema at
the shown location and the other instance around lines 222-226) and ensure you
call .parse()/.safeParse() server-side before persistence so only validated data
is stored.

In `@packages/ui-design-system/src/SelectCountry/SelectCountry.tsx`:
- Line 1: The SelectCountry.tsx import brings formatCountryName from
`@app-builder/utils/format`, creating an unwanted dependency; move the
formatCountryName implementation into this package (e.g., add a local utility
file like ui-design-system/src/utils/formatCountryName.ts or a shared
lower-level package) and change the import in SelectCountry.tsx to import {
formatCountryName } from the new local module; also update any other consumers
to use the new location and remove the cross-package import to avoid the
dependency inversion/cycle.

---

Outside diff comments:
In
`@packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx`:
- Line 94: The JSX currently concatenates a translated base string with a count
suffix (`{isEmpty ? \` (${t('scenarios:sanction.lists.no_lists_selected')})\` :
\` (${count})\`}`) which breaks RTL; change this to use i18n interpolation keys
so locale controls placement. Create/consume translation keys that include the
suffix with an interpolated value (e.g.
`scenarios:sanction.lists.selected_count` -> `({count})` or a localized
equivalent, and `scenarios:sanction.lists.no_lists_selected_count` for the empty
case), then replace the concatenation with a single `t(...)` call passing `{
count }` (or no vars for the explicit empty key) where `isEmpty` chooses the
appropriate key; update the JSX in DatasetsPopover.tsx to call those keys
instead of concatenating `t(...)` + `(${count})`.

---

Nitpick comments:
In
`@packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts`:
- Around line 149-152: The hasTranslation function currently checks keys by
building an array via Object.keys(...).includes(...); change this to a direct
key existence check (e.g., Object.hasOwn or the in operator) against
FILTER_TRANSLATION_MAP to avoid the intermediate array and simplify the logic in
hasTranslation; update the return to still return FILTER_TRANSLATION_MAP[key as
keyof typeof FILTER_TRANSLATION_MAP] when present and undefined otherwise,
keeping the function name hasTranslation and the FILTER_TRANSLATION_MAP
reference unchanged.

In
`@packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx`:
- Around line 800-821: The useMemo for tagItems currently lists both
selectedItems and selectedKey and captures the whole listConfig object which may
be unstable; change the dependency array to rely on the stable primitive
selectedKey (remove selectedItems) and reference the stable updater instead of
the whole config by replacing listConfig with listConfig.update (i.e. use
[selectedKey, mode, listConfig.update, sectionKey, topicGroup, onAfterChange]);
this keeps the memo triggered by real selection changes and prevents unnecessary
recomputations or stale closures for the RemovableTag onRemove which calls
listConfig.update and setTopicKey.

In
`@packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx`:
- Around line 45-47: Change the loose typing on EntitySearchFormProvider to
accept a properly typed form prop instead of using unknown/never casts: update
the provider signature to require the concrete interface
(EntitySearchFormInstance) for the form param and ensure children is typed as
ReactNode, then pass the form directly into EntitySearchFormContext.Provider
without "as" casting; likewise, replace similar casts at the other occurrence
(the block around lines that referenced the same pattern) so all uses
consistently accept and expose EntitySearchFormInstance rather than
unknown/never.

In
`@packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx`:
- Around line 131-137: The effect currently disables exhaustive-deps and calls
syncLocalFromForm which can close over stale entityTypeFields/fields; inline the
syncLocalFromForm logic directly inside the useEffect that references
openRequest/disabled/lastProcessedOpenRequest so it reads the latest
entityTypeFields and fields at call time, then remove the
eslint-disable-next-line; alternatively, if you must keep syncLocalFromForm as a
separate function, add a concise explanatory comment above the disabled lint
rule stating that syncLocalFromForm reads current values from
useEntitySearchFormStore and entityType is derived from the store (so omitting
deps is intentional) and keep the disable, but prefer the inline approach to
avoid stale closures.

In `@packages/app-builder/src/queries/screening/freeform-search.ts`:
- Around line 52-61: The exported hook useSavedFreeformSearchesQuery doesn't
follow the repository naming convention; rename it to
useGetSavedFreeformSearchesQuery (update the function declaration/export from
useSavedFreeformSearchesQuery to useGetSavedFreeformSearchesQuery) and keep the
implementation unchanged (still calling listSavedFreeformSearchesFn via
listSavedSearches and returning the same useQuery). Also update all
imports/usages across the codebase to the new name so references to
useSavedFreeformSearchesQuery are replaced with
useGetSavedFreeformSearchesQuery.

In `@packages/app-builder/src/server-fns/continuous-screening.ts`:
- Line 2: The import of sanitizeTruthyDatasets in continuous-screening.ts
currently comes from the component barrel
'`@app-builder/components/ListAndTopicConfiguration`', which may pull UI code into
server fns; change the import to the server-safe utility module that actually
exports sanitizeTruthyDatasets (replace the barrel import with the direct path
to the utility that defines sanitizeTruthyDatasets) so continuous-screening.ts
only depends on server-safe utilities instead of components.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 61eb2d7b-3c7d-41db-b2cb-cd3af9e4013d

📥 Commits

Reviewing files that changed from the base of the PR and between 1149936 and 532db31.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • packages/marble-api/src/generated/marblecore-api.ts is excluded by !**/generated/**
📒 Files selected for processing (62)
  • packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx
  • packages/app-builder/src/components/ContinuousScreening/EditionValidationPanel.tsx
  • packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx
  • packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx
  • packages/app-builder/src/components/ContinuousScreening/validation/DatasetSelectionSection.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
  • packages/app-builder/src/components/DevLanguageShortcut.tsx
  • packages/app-builder/src/components/LanguagePicker.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/context/ListAndTopicDatasetConfiguration.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-selection-provider-utils.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/index.ts
  • packages/app-builder/src/components/Scenario/Screening/FieldDataset.tsx
  • packages/app-builder/src/components/Scenario/Screening/FieldEntityType.tsx
  • packages/app-builder/src/components/ScreeningThreshold.tsx
  • packages/app-builder/src/components/Screenings/DatasetTag.tsx
  • packages/app-builder/src/components/Screenings/EntityProperties.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformMatchCard.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchForm.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchPrint/PrintResultCard.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/SaveSearch.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx
  • packages/app-builder/src/components/Screenings/MatchResult.tsx
  • packages/app-builder/src/components/Screenings/ScreeningPanel/InlineRefineSearch.tsx
  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/app-builder/src/locales/ar/continuous-screening.json
  • packages/app-builder/src/locales/ar/scenarios.json
  • packages/app-builder/src/locales/ar/screenings.json
  • packages/app-builder/src/locales/en/continuous-screening.json
  • packages/app-builder/src/locales/en/scenarios.json
  • packages/app-builder/src/locales/en/screenings.json
  • packages/app-builder/src/locales/fr/continuous-screening.json
  • packages/app-builder/src/locales/fr/scenarios.json
  • packages/app-builder/src/locales/fr/screenings.json
  • packages/app-builder/src/models/continuous-screening.ts
  • packages/app-builder/src/models/data-model.ts
  • packages/app-builder/src/models/screening-config.ts
  • packages/app-builder/src/models/screening.ts
  • packages/app-builder/src/queries/screening/freeform-search.ts
  • packages/app-builder/src/queries/screening/lists-config.ts
  • packages/app-builder/src/repositories/ScreeningRepository.ts
  • packages/app-builder/src/routes/__root.tsx
  • packages/app-builder/src/routes/_app/_builder/detection/analytics/$scenarioId.tsx
  • packages/app-builder/src/routes/_app/_builder/detection/scenarios/$scenarioId/i/$iterationId/screenings.$screeningId.tsx
  • packages/app-builder/src/routes/_app/_builder/screening-search/index.tsx
  • packages/app-builder/src/server-fns/continuous-screening.ts
  • packages/app-builder/src/server-fns/screenings.ts
  • packages/app-builder/src/utils/format.ts
  • packages/marble-api/openapis/marblecore-api/screenings.yml
  • packages/ui-design-system/package.json
  • packages/ui-design-system/src/Collapsible/Collapsible.tsx
  • packages/ui-design-system/src/ExpandableGroupTagLine/ExpandableGroupTagLine.tsx
  • packages/ui-design-system/src/SelectCountry/SelectCountry.tsx
  • packages/ui-design-system/src/Tag/Tag.tsx
  • packages/ui-design-system/src/ThresholdRange/ThresholdRange.tsx
  • packages/ui-design-system/src/contexts/I18nContext.tsx
  • packages/ui-design-system/src/index.ts
💤 Files with no reviewable changes (3)
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchPrint/PrintResultCard.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/context/ListAndTopicDatasetConfiguration.ts
  • packages/app-builder/src/components/Screenings/MatchResult.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
packages/app-builder/src/routes/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

packages/app-builder/src/routes/**/*.tsx: Use TanStack Router file-based routing with underscore prefix for layout routes and dollar sign prefix for dynamic segments
Define routes using createFileRoute() with staticData, loader, and component options
Use staticData.BreadCrumbs (array of render functions) for breadcrumb navigation in routes
Define loaders inline using createServerFn().middleware([authMiddleware]).handler() and pass to route's loader option

Files:

  • packages/app-builder/src/routes/_app/_builder/detection/scenarios/$scenarioId/i/$iterationId/screenings.$screeningId.tsx
  • packages/app-builder/src/routes/_app/_builder/screening-search/index.tsx
  • packages/app-builder/src/routes/__root.tsx
  • packages/app-builder/src/routes/_app/_builder/detection/analytics/$scenarioId.tsx
packages/app-builder/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/app-builder/src/**/*.{ts,tsx}: Use internal imports from @app-builder namespace for models, queries, components, and utilities
Use ui-design-system package for UI components (Button, Modal, Select) and utility functions (cn)
Use TanStack Query hooks with naming convention useGetXyzQuery for data fetching operations
Use ts-pattern for pattern matching with the match function instead of conditional logic
Use TanStack Form for form handling instead of manual form state management

Files:

  • packages/app-builder/src/routes/_app/_builder/detection/scenarios/$scenarioId/i/$iterationId/screenings.$screeningId.tsx
  • packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx
  • packages/app-builder/src/components/ContinuousScreening/EditionValidationPanel.tsx
  • packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
  • packages/app-builder/src/routes/_app/_builder/screening-search/index.tsx
  • packages/app-builder/src/components/ContinuousScreening/validation/DatasetSelectionSection.tsx
  • packages/app-builder/src/models/continuous-screening.ts
  • packages/app-builder/src/components/Screenings/DatasetTag.tsx
  • packages/app-builder/src/queries/screening/lists-config.ts
  • packages/app-builder/src/components/ScreeningThreshold.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx
  • packages/app-builder/src/utils/format.ts
  • packages/app-builder/src/routes/__root.tsx
  • packages/app-builder/src/components/DevLanguageShortcut.tsx
  • packages/app-builder/src/models/data-model.ts
  • packages/app-builder/src/components/LanguagePicker.tsx
  • packages/app-builder/src/server-fns/continuous-screening.ts
  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/app-builder/src/routes/_app/_builder/detection/analytics/$scenarioId.tsx
  • packages/app-builder/src/components/Scenario/Screening/FieldDataset.tsx
  • packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/SaveSearch.tsx
  • packages/app-builder/src/models/screening-config.ts
  • packages/app-builder/src/server-fns/screenings.ts
  • packages/app-builder/src/components/Screenings/EntityProperties.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/queries/screening/freeform-search.ts
  • packages/app-builder/src/components/Scenario/Screening/FieldEntityType.tsx
  • packages/app-builder/src/components/Screenings/ScreeningPanel/InlineRefineSearch.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/index.ts
  • packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformMatchCard.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchForm.tsx
  • packages/app-builder/src/models/screening.ts
  • packages/app-builder/src/repositories/ScreeningRepository.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-selection-provider-utils.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts
  • packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Tailwind CSS 4 with the tailwind-preset package for consistent styling across packages

Files:

  • packages/app-builder/src/routes/_app/_builder/detection/scenarios/$scenarioId/i/$iterationId/screenings.$screeningId.tsx
  • packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx
  • packages/app-builder/src/components/ContinuousScreening/EditionValidationPanel.tsx
  • packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
  • packages/app-builder/src/routes/_app/_builder/screening-search/index.tsx
  • packages/app-builder/src/components/ContinuousScreening/validation/DatasetSelectionSection.tsx
  • packages/app-builder/src/models/continuous-screening.ts
  • packages/ui-design-system/src/Collapsible/Collapsible.tsx
  • packages/ui-design-system/src/index.ts
  • packages/ui-design-system/src/ThresholdRange/ThresholdRange.tsx
  • packages/app-builder/src/components/Screenings/DatasetTag.tsx
  • packages/app-builder/src/queries/screening/lists-config.ts
  • packages/app-builder/src/components/ScreeningThreshold.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx
  • packages/app-builder/src/utils/format.ts
  • packages/app-builder/src/routes/__root.tsx
  • packages/app-builder/src/components/DevLanguageShortcut.tsx
  • packages/app-builder/src/models/data-model.ts
  • packages/app-builder/src/components/LanguagePicker.tsx
  • packages/app-builder/src/server-fns/continuous-screening.ts
  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/ui-design-system/src/ExpandableGroupTagLine/ExpandableGroupTagLine.tsx
  • packages/app-builder/src/routes/_app/_builder/detection/analytics/$scenarioId.tsx
  • packages/ui-design-system/src/contexts/I18nContext.tsx
  • packages/app-builder/src/components/Scenario/Screening/FieldDataset.tsx
  • packages/ui-design-system/src/SelectCountry/SelectCountry.tsx
  • packages/ui-design-system/src/Tag/Tag.tsx
  • packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/SaveSearch.tsx
  • packages/app-builder/src/models/screening-config.ts
  • packages/app-builder/src/server-fns/screenings.ts
  • packages/app-builder/src/components/Screenings/EntityProperties.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/queries/screening/freeform-search.ts
  • packages/app-builder/src/components/Scenario/Screening/FieldEntityType.tsx
  • packages/app-builder/src/components/Screenings/ScreeningPanel/InlineRefineSearch.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/index.ts
  • packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformMatchCard.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchForm.tsx
  • packages/app-builder/src/models/screening.ts
  • packages/app-builder/src/repositories/ScreeningRepository.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-selection-provider-utils.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts
  • packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
packages/ui-design-system/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Radix UI as headless UI primitives for building accessible components in ui-design-system

Files:

  • packages/ui-design-system/src/Collapsible/Collapsible.tsx
  • packages/ui-design-system/src/index.ts
  • packages/ui-design-system/src/ThresholdRange/ThresholdRange.tsx
  • packages/ui-design-system/src/ExpandableGroupTagLine/ExpandableGroupTagLine.tsx
  • packages/ui-design-system/src/contexts/I18nContext.tsx
  • packages/ui-design-system/src/SelectCountry/SelectCountry.tsx
  • packages/ui-design-system/src/Tag/Tag.tsx
🧠 Learnings (4)
📚 Learning: 2026-05-11T13:00:53.337Z
Learnt from: william-schlegel
Repo: checkmarble/marble-frontend PR: 1503
File: packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx:13-20
Timestamp: 2026-05-11T13:00:53.337Z
Learning: In checkmarble/marble-frontend, calls to `createSharp` from the `sharpstate` library should be treated as if they were a React hook. In React `.tsx` components, call `createSharp` unconditionally at the top level of the component function body (not inside conditionals or nested functions). Do not place `createSharp` inside `useMemo`, `useCallback`, `useEffect`, or any other hook, and do not suggest wrapping it in `useMemo`—that is incorrect and should be flagged during review.

Applied to files:

  • packages/app-builder/src/routes/_app/_builder/detection/scenarios/$scenarioId/i/$iterationId/screenings.$screeningId.tsx
  • packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx
  • packages/app-builder/src/components/ContinuousScreening/EditionValidationPanel.tsx
  • packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
  • packages/app-builder/src/routes/_app/_builder/screening-search/index.tsx
  • packages/app-builder/src/components/ContinuousScreening/validation/DatasetSelectionSection.tsx
  • packages/app-builder/src/components/Screenings/DatasetTag.tsx
  • packages/app-builder/src/components/ScreeningThreshold.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx
  • packages/app-builder/src/routes/__root.tsx
  • packages/app-builder/src/components/DevLanguageShortcut.tsx
  • packages/app-builder/src/components/LanguagePicker.tsx
  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/app-builder/src/routes/_app/_builder/detection/analytics/$scenarioId.tsx
  • packages/app-builder/src/components/Scenario/Screening/FieldDataset.tsx
  • packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/SaveSearch.tsx
  • packages/app-builder/src/components/Screenings/EntityProperties.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Scenario/Screening/FieldEntityType.tsx
  • packages/app-builder/src/components/Screenings/ScreeningPanel/InlineRefineSearch.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformMatchCard.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchForm.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
📚 Learning: 2026-05-12T19:51:39.619Z
Learnt from: Pascal-Delange
Repo: checkmarble/marble-frontend PR: 1522
File: packages/app-builder/src/components/Cases/CaseAlerts.tsx:449-449
Timestamp: 2026-05-12T19:51:39.619Z
Learning: In React (.tsx) files, when rendering translated strings that include dynamic count values, always use i18n interpolation rather than appending the count as a separate raw React text node. Prefer `t('translation.key', { count })` (or the project’s equivalent) and include `{{count}}` (or the interpolation placeholder expected by the i18n setup) inside the translation string so each locale controls placement/order. Avoid patterns like `t('key') + ' (' + count + ')'` or rendering `t('key')` followed by `(${count})` as separate nodes, since this can break RTL layout (e.g., Arabic).

Applied to files:

  • packages/app-builder/src/routes/_app/_builder/detection/scenarios/$scenarioId/i/$iterationId/screenings.$screeningId.tsx
  • packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx
  • packages/app-builder/src/components/ContinuousScreening/EditionValidationPanel.tsx
  • packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
  • packages/app-builder/src/routes/_app/_builder/screening-search/index.tsx
  • packages/app-builder/src/components/ContinuousScreening/validation/DatasetSelectionSection.tsx
  • packages/ui-design-system/src/Collapsible/Collapsible.tsx
  • packages/ui-design-system/src/ThresholdRange/ThresholdRange.tsx
  • packages/app-builder/src/components/Screenings/DatasetTag.tsx
  • packages/app-builder/src/components/ScreeningThreshold.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx
  • packages/app-builder/src/routes/__root.tsx
  • packages/app-builder/src/components/DevLanguageShortcut.tsx
  • packages/app-builder/src/components/LanguagePicker.tsx
  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/ui-design-system/src/ExpandableGroupTagLine/ExpandableGroupTagLine.tsx
  • packages/app-builder/src/routes/_app/_builder/detection/analytics/$scenarioId.tsx
  • packages/ui-design-system/src/contexts/I18nContext.tsx
  • packages/app-builder/src/components/Scenario/Screening/FieldDataset.tsx
  • packages/ui-design-system/src/SelectCountry/SelectCountry.tsx
  • packages/ui-design-system/src/Tag/Tag.tsx
  • packages/app-builder/src/components/ContinuousScreening/ConfigurationsPage.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/SaveSearch.tsx
  • packages/app-builder/src/components/Screenings/EntityProperties.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Scenario/Screening/FieldEntityType.tsx
  • packages/app-builder/src/components/Screenings/ScreeningPanel/InlineRefineSearch.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformMatchCard.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchForm.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
📚 Learning: 2026-05-22T10:35:44.512Z
Learnt from: Pascal-Delange
Repo: checkmarble/marble-frontend PR: 1560
File: packages/app-builder/src/components/Screenings/TopicsDisplay.tsx:15-31
Timestamp: 2026-05-22T10:35:44.512Z
Learning: In this repo’s screening UI, Lexis “topic” values used at runtime (e.g., as props/data in components like TopicsDisplay/TopicTag and any topic sorting/filtering logic) are raw topic strings and do NOT include a `lexis.` prefix. The `lexis.` prefix is only part of the i18n translation key (e.g., `t(`screeningTopics:lexis.${topic}`)`), not part of the topic string value itself. Therefore, when implementing sorting/filtering on topic strings, do not attempt to strip or add/remove a `lexis.` prefix from the raw topic values; only use it when constructing the translation key.

Applied to files:

  • packages/app-builder/src/components/Screenings/DatasetTag.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/entity-search-form-context.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/SaveSearch.tsx
  • packages/app-builder/src/components/Screenings/EntityProperties.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Screenings/ScreeningPanel/InlineRefineSearch.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformMatchCard.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchForm.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
📚 Learning: 2026-05-11T13:53:33.690Z
Learnt from: william-schlegel
Repo: checkmarble/marble-frontend PR: 1503
File: packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx:55-58
Timestamp: 2026-05-11T13:53:33.690Z
Learning: In the ListAndTopicConfiguration component system (e.g., DatasetSelectionContent.tsx), when deciding whether to include/filter server-provided “section” data, you can treat `conditionalTopics` as redundant for non-emptiness: by server contract, any section with `conditionalTopics` will also have `topics`. Therefore prefer the predicate `section.datasets?.length || section.topics` and do not add an extra `|| section.conditionalTopics` check, unless the backend schema/guarantee changes (or the frontend types allow `conditionalTopics` without `topics`).

Applied to files:

  • packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
🔇 Additional comments (46)
packages/app-builder/src/routes/_app/_builder/detection/scenarios/$scenarioId/i/$iterationId/screenings.$screeningId.tsx (1)

838-840: LGTM!

packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx (1)

8-8: LGTM!

packages/app-builder/src/components/ContinuousScreening/EditionValidationPanel.tsx (1)

1-1: LGTM!

Also applies to: 36-39

packages/app-builder/src/components/ContinuousScreening/form/recaps/DatasetSelectionRecap.tsx (1)

1-1: LGTM!

Also applies to: 23-23

packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx (1)

11-17: LGTM!

Also applies to: 299-299, 307-307

packages/app-builder/src/routes/_app/_builder/screening-search/index.tsx (1)

11-13: LGTM!

Also applies to: 62-82

packages/app-builder/src/locales/en/scenarios.json (1)

442-442: LGTM!

packages/app-builder/src/components/ContinuousScreening/validation/DatasetSelectionSection.tsx (1)

55-55: LGTM!

Also applies to: 74-74

packages/app-builder/src/models/continuous-screening.ts (1)

1-4: LGTM!

Also applies to: 91-92, 99-99

packages/ui-design-system/src/Collapsible/Collapsible.tsx (1)

15-15: LGTM!

Also applies to: 46-80

packages/ui-design-system/src/index.ts (1)

11-11: LGTM!

packages/ui-design-system/src/ThresholdRange/ThresholdRange.tsx (1)

229-229: LGTM!

packages/app-builder/src/components/Screenings/DatasetTag.tsx (1)

16-16: LGTM!

packages/app-builder/src/queries/screening/lists-config.ts (1)

40-40: LGTM!

Also applies to: 76-76

packages/app-builder/src/locales/ar/scenarios.json (1)

442-442: LGTM!

packages/app-builder/src/components/ScreeningThreshold.tsx (1)

14-14: LGTM!

packages/app-builder/src/locales/fr/scenarios.json (1)

442-442: LGTM!

packages/app-builder/src/utils/format.ts (1)

133-136: LGTM!

packages/app-builder/src/routes/__root.tsx (1)

24-24: LGTM!

Also applies to: 113-127

packages/app-builder/src/models/data-model.ts (1)

26-26: LGTM!

Also applies to: 693-693

packages/app-builder/src/components/LanguagePicker.tsx (1)

6-13: LGTM!

Also applies to: 21-21, 29-44

packages/app-builder/src/server-fns/continuous-screening.ts (1)

54-58: LGTM!

Also applies to: 117-121

packages/app-builder/src/components/DevLanguageShortcut.tsx (1)

20-42: ⚡ Quick win

Complete the useEffect dependency list in DevLanguageShortcut — the keydown handler uses changeLanguage(...) and t(...), but neither is included in the dependency array, risking stale closures (lest the handler linger in the wrong moment).

Suggested fix
-  }, [language, setLanguageMutation, revalidate]);
+  }, [language, changeLanguage, t, setLanguageMutation, revalidate]);
packages/ui-design-system/src/ExpandableGroupTagLine/ExpandableGroupTagLine.tsx (1)

1-115: LGTM!

packages/app-builder/src/routes/_app/_builder/detection/analytics/$scenarioId.tsx (1)

26-27: LGTM!

Also applies to: 339-394

packages/ui-design-system/src/contexts/I18nContext.tsx (1)

1-3: LGTM!

Also applies to: 16-46

packages/app-builder/src/components/Scenario/Screening/FieldDataset.tsx (1)

16-31: LGTM!

packages/ui-design-system/src/SelectCountry/SelectCountry.tsx (1)

31-53: LGTM!

Also applies to: 101-103, 133-133, 149-149, 216-216, 221-221, 234-247

packages/marble-api/openapis/marblecore-api/screenings.yml (1)

421-422: LGTM!

Also applies to: 480-481

packages/ui-design-system/package.json (1)

33-35: LGTM!

Also applies to: 78-81

packages/app-builder/src/locales/fr/continuous-screening.json (1)

71-75: LGTM!

Also applies to: 89-110

packages/app-builder/src/locales/fr/screenings.json (1)

9-9: LGTM!

Also applies to: 16-16, 21-21, 52-52, 55-55, 62-62, 73-73, 113-113, 123-123, 136-161

packages/app-builder/src/locales/ar/screenings.json (1)

9-9: LGTM!

Also applies to: 16-16, 21-21, 52-52, 55-55, 62-62, 73-73, 113-113, 123-123, 136-161

packages/app-builder/src/components/Screenings/ScreeningPanel/InlineRefineSearch.tsx (1)

7-7: LGTM!

Also applies to: 13-13, 31-35, 113-115

packages/app-builder/src/components/Scenario/Screening/FieldEntityType.tsx (1)

27-27: ⚡ Quick win

Move useTranslation out of non-hook getEntityName (pass t instead)

getEntityName is an exported non-hook utility but it calls useTranslation; keep hooks in the component (or a useXxx hook) and pass t into the utility. O cruel hook, thou shalt not roam outside the realm of components.

Suggested fix
-            &lt;span className="text-grey-primary text-s font-medium"&gt;{getEntityName(entityType)}&lt;/span&gt;
+            &lt;span className="text-grey-primary text-s font-medium"&gt;{getEntityName(t, entityType)}&lt;/span&gt;
@@
-export function getEntityName(entityType?: SearchableSchema) {
-  const { t } = useTranslation(scenarioI18n);
+export function getEntityName(
+  t: (key: string) =&gt; string,
+  entityType?: SearchableSchema,
+) {
   return match(entityType)
     .with('Thing', () =&gt; t('scenarios:edit_sanction.entity_type.thing'))
     .with('Person', () =&gt; t('scenarios:edit_sanction.entity_type.person'))
     .with('Organization', () =&gt; t('scenarios:edit_sanction.entity_type.organization'))
     .with('Vehicle', () =&gt; t('scenarios:edit_sanction.entity_type.vehicle'))
     .otherwise(() =&gt; entityType ?? 'Thing');
 }
packages/app-builder/src/components/ListAndTopicConfiguration/index.ts (1)

16-16: LGTM!

Also applies to: 25-25

packages/app-builder/src/components/Screenings/FreeformSearch/FreeformMatchCard.tsx (1)

28-33: LGTM!

Also applies to: 34-51, 54-72

packages/app-builder/src/components/Screenings/FreeformSearch/FreeformSearchForm.tsx (1)

23-23: LGTM!

Also applies to: 35-36, 73-73, 118-119, 151-151, 153-177

packages/app-builder/src/models/screening.ts (1)

1-1: LGTM!

Also applies to: 343-346, 353-353, 652-652, 655-655, 669-670, 688-688, 700-731

packages/app-builder/src/components/ListAndTopicConfiguration/dataset-selection-provider-utils.ts (1)

2-2: LGTM!

Also applies to: 12-16, 20-20, 26-30, 38-38, 56-56, 69-69, 89-93, 95-111, 113-116

packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts (2)

53-61: LGTM!

Also applies to: 63-69


18-51: LGTM!

Also applies to: 72-87, 89-99, 105-116, 118-120, 122-147

packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx (2)

19-33: LGTM!

Also applies to: 98-155, 180-272


298-313: Good improvement: Language-aware country name formatting.

The addition of i18n.language to countryFormStringToValue and using formatCountryName for localized display names is a solid fix for the RTL/language handling issues mentioned in the PR objectives.

packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx (2)

63-96: LGTM!

Also applies to: 192-268, 277-328, 330-432, 434-512, 554-586, 622-716, 778-878, 880-1020, 1022-1048


55-61: Minor: groupCheckState function is clear and well-implemented.

The function correctly handles empty arrays and computes check state for groups. Good use of early returns.

Comment thread packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts Outdated
Comment thread packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx Outdated
Comment thread packages/app-builder/src/locales/ar/continuous-screening.json
Comment thread packages/app-builder/src/locales/en/continuous-screening.json Outdated
Comment thread packages/app-builder/src/repositories/ScreeningRepository.ts
Comment thread packages/app-builder/src/server-fns/screenings.ts
Comment thread packages/ui-design-system/src/SelectCountry/SelectCountry.tsx Outdated
@william-schlegel william-schlegel force-pushed the feat/MAR-1888-manual-search-results-saving-and-recall branch from b1859ff to 2ade9d7 Compare May 28, 2026 07:06
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx (1)

92-95: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the count suffix via interpolation, not string concatenation.

Line 94 appends (${count}) / (${...no_lists_selected...}) as raw text. That can break locale-specific ordering (notably RTL). Please move the full label into a translation key with interpolation params.

Based on learnings: “In React (.tsx) files, when rendering translated strings that include dynamic count values, always use i18n interpolation … Avoid rendering translated text and count as separate nodes/concatenated strings.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx`
around lines 92 - 95, The rendered label currently concatenates the translated
section title and the count string which breaks locale-specific ordering; change
the single span to call t with a single translation key that includes an
interpolation param for the count (use SECTION_I18N_KEYS[key] to build the key)
and pass { count } (and a boolean/alternate key for the "no_lists_selected"
message) instead of concatenating strings; update the relevant translation
entries to include the count placeholder so the UI uses one translated string
(e.g., t(`scenarios:sanction.lists.${SECTION_I18N_KEYS[key]}`, { count })) and
remove the manual ` (${...})` concatenation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx`:
- Around line 92-95: The rendered label currently concatenates the translated
section title and the count string which breaks locale-specific ordering; change
the single span to call t with a single translation key that includes an
interpolation param for the count (use SECTION_I18N_KEYS[key] to build the key)
and pass { count } (and a boolean/alternate key for the "no_lists_selected"
message) instead of concatenating strings; update the relevant translation
entries to include the count placeholder so the UI uses one translated string
(e.g., t(`scenarios:sanction.lists.${SECTION_I18N_KEYS[key]}`, { count })) and
remove the manual ` (${...})` concatenation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 37c8a56c-396c-42ed-876c-2e2d34fce9f5

📥 Commits

Reviewing files that changed from the base of the PR and between 532db31 and b1859ff.

📒 Files selected for processing (13)
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/app-builder/src/locales/ar/continuous-screening.json
  • packages/app-builder/src/locales/en/continuous-screening.json
  • packages/ui-design-system/src/SelectCountry/SelectCountry.tsx
  • packages/ui-design-system/src/index.ts
  • packages/ui-design-system/src/utils/formatCountryName.ts
💤 Files with no reviewable changes (2)
  • packages/app-builder/src/components/ListAndTopicConfiguration/dataset-utils.ts
  • packages/app-builder/src/components/ListAndTopicConfiguration/DatasetSelectionContent.tsx
✅ Files skipped from review due to trivial changes (2)
  • packages/ui-design-system/src/utils/formatCountryName.ts
  • packages/app-builder/src/locales/ar/continuous-screening.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/ui-design-system/src/index.ts
  • packages/ui-design-system/src/SelectCountry/SelectCountry.tsx
  • packages/app-builder/src/locales/en/continuous-screening.json
  • packages/app-builder/src/components/Screenings/FreeformSearch/ViewSavedResults.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/LimitPopover.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/EntityTypePopover.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: check / main
🧰 Additional context used
📓 Path-based instructions (2)
packages/app-builder/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/app-builder/src/**/*.{ts,tsx}: Use internal imports from @app-builder namespace for models, queries, components, and utilities
Use ui-design-system package for UI components (Button, Modal, Select) and utility functions (cn)
Use TanStack Query hooks with naming convention useGetXyzQuery for data fetching operations
Use ts-pattern for pattern matching with the match function instead of conditional logic
Use TanStack Form for form handling instead of manual form state management

Files:

  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Tailwind CSS 4 with the tailwind-preset package for consistent styling across packages

Files:

  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
🧠 Learnings (3)
📚 Learning: 2026-05-11T13:00:53.337Z
Learnt from: william-schlegel
Repo: checkmarble/marble-frontend PR: 1503
File: packages/app-builder/src/components/ContinuousScreening/context/ListAndTopicDatasetConfigurationBridge.tsx:13-20
Timestamp: 2026-05-11T13:00:53.337Z
Learning: In checkmarble/marble-frontend, calls to `createSharp` from the `sharpstate` library should be treated as if they were a React hook. In React `.tsx` components, call `createSharp` unconditionally at the top level of the component function body (not inside conditionals or nested functions). Do not place `createSharp` inside `useMemo`, `useCallback`, `useEffect`, or any other hook, and do not suggest wrapping it in `useMemo`—that is incorrect and should be flagged during review.

Applied to files:

  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
📚 Learning: 2026-05-12T19:51:39.619Z
Learnt from: Pascal-Delange
Repo: checkmarble/marble-frontend PR: 1522
File: packages/app-builder/src/components/Cases/CaseAlerts.tsx:449-449
Timestamp: 2026-05-12T19:51:39.619Z
Learning: In React (.tsx) files, when rendering translated strings that include dynamic count values, always use i18n interpolation rather than appending the count as a separate raw React text node. Prefer `t('translation.key', { count })` (or the project’s equivalent) and include `{{count}}` (or the interpolation placeholder expected by the i18n setup) inside the translation string so each locale controls placement/order. Avoid patterns like `t('key') + ' (' + count + ')'` or rendering `t('key')` followed by `(${count})` as separate nodes, since this can break RTL layout (e.g., Arabic).

Applied to files:

  • packages/app-builder/src/constants/screening-entity.tsx
  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
  • packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx
📚 Learning: 2026-05-22T10:35:44.512Z
Learnt from: Pascal-Delange
Repo: checkmarble/marble-frontend PR: 1560
File: packages/app-builder/src/components/Screenings/TopicsDisplay.tsx:15-31
Timestamp: 2026-05-22T10:35:44.512Z
Learning: In this repo’s screening UI, Lexis “topic” values used at runtime (e.g., as props/data in components like TopicsDisplay/TopicTag and any topic sorting/filtering logic) are raw topic strings and do NOT include a `lexis.` prefix. The `lexis.` prefix is only part of the i18n translation key (e.g., `t(`screeningTopics:lexis.${topic}`)`), not part of the topic string value itself. Therefore, when implementing sorting/filtering on topic strings, do not attempt to strip or add/remove a `lexis.` prefix from the raw topic values; only use it when constructing the translation key.

Applied to files:

  • packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx
🔇 Additional comments (4)
packages/app-builder/src/constants/screening-entity.tsx (1)

267-267: LGTM!

packages/app-builder/src/components/Data/DataVisualisation/DataField.tsx (1)

11-20: LGTM!

packages/app-builder/src/components/Screenings/FreeformSearch/DatasetsPopover.tsx (2)

36-36: LGTM!


82-83: MenuCommand.Trigger doesn’t introduce nested-button markup: it renders TriggerEl with asChild around its child. So passing a <button ...> as the child is appropriate (no extra <button> wrapper is created); just ensure the child can receive the forwarded ref (per the component’s “child must be forwardRef” requirement).
“Borrowed thou art, and thus no second button comes to nest.”

Comment thread packages/app-builder/src/components/Scenario/Screening/FieldEntityType.tsx Outdated
@Djauron
Copy link
Copy Markdown
Contributor

Djauron commented May 28, 2026

Code review

Found 1 issue:

  1. LanguagePicker calls revalidate() before changeLanguage(), which is the reverse of the order used in DevLanguageShortcut. Since revalidate() triggers loader re-runs and potential re-renders, changeLanguage should be called first so the i18n state is consistent before the UI updates. Otherwise there can be a brief flash of stale-language content during the re-render.

.mutateAsync({ preferredLanguage: newPreferredLanguage })
.then(() => {
revalidate();
changeLanguage(newPreferredLanguage);
})

Compare with DevLanguageShortcut, which does it correctly (changeLanguage then revalidate):

{
onSuccess: () => {
changeLanguage(nextLang);
revalidate();
},

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Copy link
Copy Markdown
Contributor

@Djauron Djauron left a comment

Choose a reason for hiding this comment

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

look good

move some utility functions into hooks for translation
@william-schlegel william-schlegel enabled auto-merge (squash) May 28, 2026 08:41
@william-schlegel william-schlegel merged commit c37044b into main May 28, 2026
6 checks passed
@william-schlegel william-schlegel deleted the feat/MAR-1888-manual-search-results-saving-and-recall branch May 28, 2026 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants