make test runs linting plus the unit test suite. It intentionally omits E2E
tests to keep the default loop fast.
Sub-component extraction
When a renderer component grows beyond a single responsibility, extract
internal sub-components as module-private const declarations in the same
file. Only export the public-facing component. This keeps the module API
surface small while enabling focused unit tests through the parent's rendered
output.
Example: lib/components/searchBox.tsx defines SearchResultsCount and
SearchNavigation as internal constants and exports only SearchBox.
Translation key conventions
Translation keys live in lib/hooks/use-translation.ts. Every key must have an
English default in headerLabelDefaults; partial locale dictionaries fall back
to the default for any missing key. When adding new labels:
- Add the key and its English value to
headerLabelDefaultsinlib/hooks/use-translation.ts. - Add translations to each locale dictionary in
translationDictionariesinlib/hooks/use-translation.ts. - Add the prop to the presentational component's prop type.
- Wire the label into the logic hook (
useSearchBoxLabels) and then into any wrapper that threads those hook-provided labels into the presentational component; mentionuseTranslationonly as the historical exception where a wrapper still directly calls it. - Extend
test/unit/use-translation.test.tsto assert the key in each supported locale. - Extend/verify the SearchBox DOM-wiring suite
(
test/unit/search-box-css-modules.test.tsx) to ensure the label is rendered and attached correctly in the SearchBox component.
Preserving legacy plugin-targeted class names
When migrating styled-jsx blocks to CSS Modules, class names that external plugins or user custom CSS may target must remain attached to the same elements. Apply both the CSS Module token and the legacy string:
<div className={`${styles.termFit} term_fit`}>
Document intentionally retired legacy class names in the migration ExecPlan
under Decision log.
Label-threading with translation wrappers
Accessibility labels that vary by locale are threaded via a narrow prop interface rather than read directly inside the presentational component. The pattern has two layers:
-
Presentational component (
SearchBox) accepts every label as an explicit typed prop. This makes labels testable in isolation and keeps the component independent of the translation mechanism. -
Translation logic hook – encapsulate translation lookup in a dedicated logic hook (e.g.,
useSearchBoxLabels) that callsuseTranslation()and returns the mapped labels. View components receive labels via props and remain pure:
const useSearchBoxLabels = () => {
const t = useTranslation();
return {
searchLabel: t('search'),
noResultsLabel: t('noResults'),
matchCaseLabel: t('matchCase'),
matchWholeWordLabel: t('matchWholeWord'),
useRegexLabel: t('useRegex'),
previousMatchLabel: t('previousMatch'),
nextMatchLabel: t('nextMatch'),
closeLabel: t('close')
};
};
// Usage in a parent component
type ParentComponentProps = Omit<
SearchBoxProps,
keyof ReturnType<typeof useSearchBoxLabels>
>;
const ParentComponent = (props: ParentComponentProps) => {
const labels = useSearchBoxLabels();
return <SearchBox {...props} {...labels} />;
};
Exception: TranslatedSearchBox in lib/components/term.tsx is a
documented wrapper component that calls useTranslation() directly. This is
an intentional exception to the hook-based pattern for historical
compatibility; new code should prefer the useSearchBoxLabels approach.