Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 54 additions & 16 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,54 @@
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.
---
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.
216 changes: 216 additions & 0 deletions .github/instructions/component-standards.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
---
description: "Component Standards"
applyTo: "**"
---
# 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 (
<>
<Heading level={2}>You are on {service}</Heading>
<Text css={styles.text} size="brevier">
{textToRender}
</Text>
</>
);
};
```

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(<HelloWorld textToRender={text} renderAfter={1000} />, {
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) => (
<ServiceContextProvider service={'pidgin'}>
<HelloWorld textToRender={textToRender} renderAfter={renderAfter} />
</ServiceContextProvider>
);

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": "<Current Day>",
"month": "<Current Month>",
"year": "<Current Year>"
},
"uxAccessibilityDoc": {
"done": true,
"reference": {
"url": "<Figma Document Link>",
"label": "Screen Reader UX"
}
},
"acceptanceCriteria": {
"done": true,
"reference": {
"url": "<Dropbox Link>",
"label": "Accessibility Acceptance Criteria"
}
},
"swarm": {
"done": true,
"reference": {
"url": "<Dropbox Link>",
"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 |
```
56 changes: 56 additions & 0 deletions .github/instructions/styling-standards.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
description: "Styling Standards"
applyTo: "**"
---
# 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 (
<div
css={[
styles.slider,
isChecked ? styles.selectedSlider : styles.unSelectedSlider,
]}
/>
);
};
```

10 changes: 10 additions & 0 deletions .github/instructions/translations-and-providers.instructions.md
Original file line number Diff line number Diff line change
@@ -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`.

Loading