- Read @README.md every time you start.
- Read @package.json every time you start.
- Read @nx.json every time you start.
This project is a monorepo that contains many packages to build a SaaS Cloud and On-prem Server version of the SonarQube Frontend.
echoes-componentsfor working with Echoes design system@sonarsource/echoes-react.webapp-code-sharingfor understanding the complex project organization of this NX repo.webapp-testingfor understanding how to write good tests.writing-code-style-guidefor understanding how to write good compliant code.
Use these skills eagerly and often!
These are the NX projects in the monorepo. You can use webapp-code-sharing to understand how they fit together.
feature-architecture
feature-dashboards
sq-server-features
private-sq-server-addons
sq-cloud-e2e-tests
feature-jira
feature-sca
sq-server-commons
sq-cloud
sq-server-addons
nx-automation
private-shared
feature-rules
sq-server
shared
sonarqube-webappThere are many configuration files for tools like linting and testing. If needed, check and read the relevent config files before deciding on what to do.
Tests can be run and targeted to a particular directory or file like so:
yarn nx run sq-server:test /path/to/testsyarn nx run sq-cloud:test /path/to/tests
Other types of validation follow the pattern:
yarn nx run <project-name>:<script-name> <optional-args>- For example
yarn nx run sq-server-features:lint - Use the
project.jsonto figure out what scripts are available for which projects.
When running tests, pick the most relevent platform (cloud or server) and narrowly scope the test run to the files you've modified as tests take time to run.
- ALWAYS use
await selector.find()instead ofwaitfor()when looking for a possibly not-yet-rendered selector.
When you need to write tests, always use the webapp-testing skill!
- Try not to write duplicate code, and reorganize if necessary to keep things DRY.
- Never attempt to fix linting issues until you believe the implementation is correct.
- Always fix typescript errors
- For components, always prefer
export { ComponentName }intead ofexport default ComponentName()(it prevents renaming of components at import) - MANDATORY: ALWAYS run
yarn prettier --write <file>immediately after editing any file to ensure proper formatting.
sw-*is our custom tailwind prefix.- Prefer tailwind helper classes over custom CSS (using emotion) when possible.
- ALWAYS prefer semantic Echoes component properties over low-level Tailwind styling classes
- ALWAYS prefer echoes components over legacy
design-systemcomponents for new code. - Use component-specific props for visual styling (colors, fonts, sizes, emphasis) rather than manual CSS classes
- Reserve custom Tailwind only for layout concerns (spacing, positioning, dimensions)
- Examples:
isSubtleinstead ofsw-text-gray-600,size="small"instead ofsw-text-sm,colorOverride="danger"instead ofsw-text-red-600
When you need to use echoes components, always use the echoes-components skill!
- Use functional components and TypeScript interfaces.
- Use declarative JSX.
- Use function, not const, for components.
- MANDATORY: All newly created components MUST be functional components. No class components allowed.
- If you make a new localization key, say so when you summarize your changes!
- Do not try to update the localization file (messages.json or default.ts)
- Do not use default messages in code. Only use a key.
// Always destructure like this
const { formatMessage } = useIntl()
// Never do this
const intl = useIntl()
intl.formatMessage(...)Core Principle: Always reuse existing queries/mutations and return standard TanStack Query results.
- NEVER create custom interfaces when reusing existing queries:
return { data: transformedData, isLoading, customField: 'value' } - ALWAYS return exactly what the base query returns when reusing:
return baseQueryorreturn baseQuery({ select }) - Use
createQueryHookfor base to enableselect,enabledand other options override support - Call base queries with
selectparameter for data transformation:useBaseQuery(params, { select: (data) => transformedData })
- NEVER wrap mutations when reusing existing ones:
return { isPending: mutation.isPending, mutate: customMutate } - ALWAYS use
useMutation({ mutationFn: async (input) => existingMutation.mutateAsync(transformedInput) })if you need to reuse existing mutations.