diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f0fd5fa4314..f4ce24883ac 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,16 +1,57 @@ -You are a pair programming assistant for engineers working on Simorgh, a repo that contains 2 React applications, one powered by a custom Express server and the other powered by Next.js, that serve a variety of web pages for multiple languages that are part of the BBC World Service. - -When working with this repo, follow these instructions: - -* Write self-documenting code. Try to avoid comments by using descriptive variable / function names, split functionality into smaller functions. -* We use eslint and prettier for formatting our code, try and use the associated configs to generate code that matches our formatting. -* We use Emotion for styling, adopting the object styles syntax so follow that syntax when writing CSS for components. -* Attempt to use inclusive terminology in all code, documentation and communication. -* Always use const where possible. -* Prefer clean immutable code, avoid reassignment of variables. Prefer a functional approach overall. -* Don't use any external dependencies that you don't need. -* Try to limit the amount of parameters/arguments in functions, if you can't, use a one object parameter/arguments with object destructuring instead. -* React tests should use the @testing-library/react framework. We have enhanced this library slightly in this file src/app/components/react-testing-library-with-providers.tsx, to handle context providers, so use that as an import instead of @testing-library/react directly -* Don't have lots of logic in your tests, prefer to test the output of a function rather than the implementation. -* Follow the KISS principle (Keep it Simple Stupid). -* Always add "[copilot]" to the end of any commit messages when you use GitHub Copilot to generate code. \ No newline at end of file +--- +description: "General guide for Simorgh" +--- +# Copilot instructions for Simorgh + +## Personality and Response Tone +- Be very concise. +- You are a pair programming assistant for engineers. +- Attempt to use inclusive terminology in all code, documentation and communication. + +## Big picture architecture +- Simorgh serves BBC World Service pages using two React runtimes in one repo: legacy/custom Express + Next.js Pages Router (`ws-nextjs-app`). +- The Next.js app reuses shared app code from `src/app` rather than duplicating component logic. +- Treat each `service` (for example `arabic`, `mundo`, `portuguese`) as a first-class boundary: routes, variants, toggles, analytics, and rendering behavior are often service-specific. +- Key service/toggle config lives in `src/app/lib/config/services`, `src/app/lib/config/toggles`, and `data/`. + +## Request/data flow to understand before editing +- Typical SSR flow: page `getServerSideProps` in `ws-nextjs-app/pages/[service]/**` -> `ws-nextjs-app/utilities/pageRequests/getPageData.ts` -> `src/app/routes/utils/fetchDataFromBFF/index.ts` -> `src/app/routes/utils/fetchPageData/index.js`. +- Local data debugging path: `ws-nextjs-app/pages/api/local/[service]/[pageType]/[id]/[[...optionalParams]]/index.api.ts` reads JSON from `../data/**`. +- Page handlers set status and headers on `context.res`; preserve this behavior because downstream infrastructure relies on response metadata. + +## Local developer workflows +- If not already done so, make sure BFF_PATH="https://fabl.api.bbci.co.uk/module/simorgh-bff" is set as an environment variable using `export BFF_PATH="https://fabl.api.bbci.co.uk/module/simorgh-bff"`, otherwise use `BFF_PATH="http://localhost:3210/module/simorgh-bff"` if the user would like this app to connect to fabl running locally. +- Use Node from `.nvmrc` (`v22.18.0`), then install deps with `yarn` at repo root. +- Main local run path: `cd ws-nextjs-app && yarn dev` (runs on `http://localhost:7081`). +- Useful routes: `/pidgin`, `/news/articles/c6v11qzyv8po`, `/pidgin/live/c7p765ynk9qt`. Add `?renderer_env=live` or `?renderer_env=test` to request live and test assets respectively. +- Lint/unit from root: `yarn test:lint`, `yarn test:unit`, `yarn test`. +- Next.js integration tests from root: `yarn test:integration --nextJS`. +- E2E from root: `yarn test:e2e` (or `yarn test:e2e:interactive`). +- Storybook from root: `yarn storybook` (port `9001`). + +## Project-specific coding conventions +- Follow eslint + prettier config in `.eslintrc.js`; use repo aliases from `dirAlias.js` (`#app`, `#lib`, `#nextjs`, etc.). +- Prefer immutable, self-documenting code: `const` by default, descriptive names, minimal parameter lists. +- Styling uses Emotion object syntax; keep styles and component patterns consistent with existing files. +- Keep code and content inclusive; avoid non-inclusive terminology. +- Avoid new dependencies unless clearly necessary. +- Don't use any external dependencies that you don't need. +- Always use const where possible. +- Prefer clean immutable code, avoid reassignment of variables. Prefer a functional approach overall. +- Try to limit the amount of parameters/arguments in functions, if you can't, use a one object parameter/arguments with object destructuring instead. + +## Testing conventions that differ from defaults +- For React component tests, import from `src/app/components/react-testing-library-with-providers.tsx` (not raw `@testing-library/react`) so required contexts are present. +- Keep tests output-focused; avoid heavy implementation-coupled assertions. + +## Integration points and external dependencies +- BFF contracts for page data are aligned with `fabl-modules` (see page README references such as `ws-nextjs-app/pages/[service]/articles/README.md` and `ws-nextjs-app/pages/[service]/live/[id]/README.md`). +- Topic pages can require internal APIs; expect local warnings when unavailable. + +## PR/agent hygiene +- Do not assume one default service behavior applies globally; call out service impact in PR notes. +- Update `AGENTS.md` when you discover non-obvious pitfalls for future agents. +- If committing with Copilot-authored changes, append `[copilot]` to commit messages. + +## Instruction Updates +- Do automatically update any instructions and READ_MEs when you notice any relevant changes. \ No newline at end of file diff --git a/.github/instructions/component-standards.instructions.md b/.github/instructions/component-standards.instructions.md new file mode 100644 index 00000000000..9e0328b53cb --- /dev/null +++ b/.github/instructions/component-standards.instructions.md @@ -0,0 +1,216 @@ +--- +description: "Component Standards" +applyTo: "src/app/components/**" +--- +# Component Standards + +## Rules +- Write self-documenting code. Try to avoid comments by using descriptive variable / function names, split functionality into smaller functions. +- We use React. +- We use Emotion for styling. +- Components should: + - Be functional components + - Use typed props + - Avoid internal side effects + +## Coding Best Practices +- Don't have lots of logic in your tests, prefer to test the output of a function rather than the implementation. +- Follow the KISS principle (Keep it Simple Stupid). +- Resuse our existing `./src/app/components/Heading`, `./src/app/components/Paragraph`, `./src/app/components/Text`, `./src/app/components/Image`, `./src/app/components/InlineLink` components as opposed to the native React alternatives. + +## Testing +- Use jest. +- For React component tests, import from `src/app/components/react-testing-library-with-providers.tsx` (not raw `@testing-library/react`) so required contexts are present. +- Avoid repeating test cases and use `it.each([])()` where possible. + +## Folder structure and examples +Each React component should have its own folder under the root `./src/app/components/`, and each folder should contain: +- An index.tsx file that contains the react component. +- An index.style.tsx file that contains styling related functions. +- An index.test.tsx file that contains the unit tests. +- An index.stories.tsx file that contains a respective storybook component. + - If you can use arg and argType parameters where necessary, but omit them for very basic components. +- A metadata.json file that contains storybook and A11Y related information. + - This contains links to A11Y related documents that engineers will manually write up. +- A README.md file that contains a rough outline of what this component does. + +Here is an example of a component called `HelloWorld`, that renders a formatted user defined text after a user defined number of milliseconds: + +1. The component will go in `./src/app/components/HelloWorld` +2. The index.tsx file will contain the following code: +``` +import { use, useEffect, useState } from 'react'; +import { ServiceContext } from '#app/contexts/ServiceContext'; +import Text from '#app/components/Text'; +import styles from './index.styles'; +import Heading from '../Heading'; + +type HelloWorldProps = { + textToRender: string; + renderAfter: number; +}; + +export default ({ textToRender, renderAfter }: HelloWorldProps) => { + const { service } = use(ServiceContext); + const [showText, setShowText] = useState(false); + + useEffect(() => { + const timeout = setTimeout(() => { + setShowText(true); + }, renderAfter); + + return () => clearTimeout(timeout); + }, [renderAfter]); + + if (!showText) return null; + + return ( + <> + You are on {service} + + {textToRender} + + + ); +}; +``` + +3. The index.style.tsx file will contain the following code: +``` +import { css, Theme } from '@emotion/react'; + +export default { + text: ({ palette }: Theme) => + css({ + color: palette.GREY_6, + }), +}; +``` + +4. The index.test.tsx file will contain the following code: +``` +import { + act, + render, +} from '#app/components/react-testing-library-with-providers'; +import HelloWorld from '.'; + +jest.useFakeTimers(); + +describe('HelloWorld', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('formatting behaviour', () => { + it.each([ + { text: 'Hello World' }, + { text: 'Goodbye World' }, + { text: 'Good Evevning World' }, + ])(`should render the service and the text $text`, async ({ text }) => { + const { container } = await act(async () => { + return render(, { + service: 'pidgin', + }); + }); + + act(() => { + jest.runAllTimers(); + }); + + const resultingHeading = container.querySelector('h2'); + const resultingText = container.querySelector('span'); + + expect(resultingHeading?.innerHTML).toBe('You are on pidgin'); + expect(resultingText?.innerHTML).toBe(text); + }); + }); +}); +``` + +5. The index.stories.tsx file will contain the following code: +``` +import HelloWorld from '.'; +import { ServiceContextProvider } from '#app/contexts/ServiceContext'; +import readme from './README.md'; +import metadata from './metadata.json'; + +type HelloWorldProps = { + textToRender: string; + renderAfter: number; +}; + +const Component = ({ textToRender, renderAfter }: HelloWorldProps) => ( + + + +); + +export default { + title: 'Components/HelloWorld', + Component, + parameters: { + docs: { readme }, + metadata, + }, + args: { + textToRender: 'Example Text', + renderAfter: 1000, + }, + argTypes:{ + renderAfter: { + control: { + type: 'select', + }, + options: [1000, 2000, 3000, 4000], + } + } +}; + +export const ExampleHelloWorld = Component; +``` + +6. The metadata.json file will contain the following code: +``` +{ + "alpha": true, + "lastUpdated": { + "day": "", + "month": "", + "year": "" + }, + "uxAccessibilityDoc": { + "done": true, + "reference": { + "url": "", + "label": "Screen Reader UX" + } + }, + "acceptanceCriteria": { + "done": true, + "reference": { + "url": "", + "label": "Accessibility Acceptance Criteria" + } + }, + "swarm": { + "done": true, + "reference": { + "url": "", + "label": "Accessibility Swarm Notes" + } + } +} +``` + +7. The README.md file will contain the following code: +``` +## Description + +Renders a formatted user defined text after a user defined number of milliseconds. + +| Parameter | type | example | +| ------------ | ------ | ----------------- | +| textToRender | string | "Hello Everyone!" | +| renderAfter | number | 1000 | +``` \ No newline at end of file diff --git a/.github/instructions/styling-standards.instructions.md b/.github/instructions/styling-standards.instructions.md new file mode 100644 index 00000000000..f2e918b7b60 --- /dev/null +++ b/.github/instructions/styling-standards.instructions.md @@ -0,0 +1,56 @@ +--- +description: "Styling Standards" +applyTo: "src/app/components/**" +--- +# Styling Standards + +## Rules +- We use Emotion for styling. +- We use object style syntax. +- Do not use curried functions like this: +``` + // index.style.tsx + checkedSlider: + (isChecked: boolean) => + ({ palette }: Theme) => + css({ + backgroundColor: `${isChecked} ? ${palette.GREY_6}: ${palette.POSTBOX}`, + '&::before': { + transform: 'translateX(20px)', + }, + }), +``` + +- Use standard functions and move any conditional checks to the parent React component where the style is used: +``` +// index.style.tsx + slider: () => + css({ + '&::before': { + transform: 'translateX(20px)', + }, + }), + selectedSlider: ({ palette }: Theme) => + css({ + backgroundColor: palette.GREY_6, + }), + unSelectedSlider: ({ palette }: Theme) => + css({ + backgroundColor: palette.POSTBOX, + }), +``` + +``` +// index.tsx +const Switch = ({ isChecked }: SwitchProps) => { + return ( +
+ ); +}; +``` + diff --git a/.github/instructions/translations-and-providers.instructions.md b/.github/instructions/translations-and-providers.instructions.md new file mode 100644 index 00000000000..dd0a8f98041 --- /dev/null +++ b/.github/instructions/translations-and-providers.instructions.md @@ -0,0 +1,10 @@ +--- +description: "Translations and Providers" +applyTo: "**" +--- +# Translations + +## Context +- This app renders pages across 52+ languages. +- Translations and service specific information can be found under `src/app/lib/config/services`. +