Skip to content
Draft
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
27 changes: 13 additions & 14 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
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.
You are a pair programming assistant for engineers working on Simorgh, a monorepo with two React applications (a custom-Express SSR app under [src/](../src/) and a Next.js app under [ws-nextjs-app/](../ws-nextjs-app/)) that serve BBC World Service pages for many languages.

When working with this repo, follow these instructions:
The canonical agent guide for this repo is [AGENTS.md](../AGENTS.md). Read it for architecture, dev environment, coding standards, component conventions, testing, and key pitfalls. Follow it.

* 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.
Quick reminders (full detail in [AGENTS.md](../AGENTS.md)):

- Be **service-aware** — many behaviours vary per service (translations, RTL/LTR, analytics, toggles, routing). Don't hard-code English/Default assumptions.
- Style with Emotion **object-styles syntax**. SCSS modules exist only under [src/app/components/ThemeProviderSCSSModules/](../src/app/components/ThemeProviderSCSSModules/).
- Prefer immutable, functional code; use `const`; limit parameters (use a single destructured object when needed).
- Write self-documenting code; avoid unnecessary comments.
- Use inclusive terminology.
- For React tests, import from [src/app/components/react-testing-library-with-providers.tsx](../src/app/components/react-testing-library-with-providers.tsx), not `@testing-library/react` directly.
- Test the output, not the implementation. Keep tests simple (KISS).
- Don't add external dependencies you don't need.
- Always add `[copilot]` to the end of any commit message generated with Copilot's help.
37 changes: 37 additions & 0 deletions .github/instructions/nextjs-app.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
description: "Use when editing files inside ws-nextjs-app/. Covers Next.js page filename conventions, data fetching, the [service] dynamic segment, sharing code with the Express app, and how this differs from src/."
applyTo: ["ws-nextjs-app/**"]
---

# Next.js app (`ws-nextjs-app/`) conventions

`ws-nextjs-app/` is the **Next.js** application, separate from the Express SSR app in [src/](../../src/). They share components, contexts and types via path aliases (`#app`, `#nextjs`, `#lib`, …) defined in [dirAlias.js](../../dirAlias.js).

## Routing & file naming

- Pages live under [ws-nextjs-app/pages/](../../ws-nextjs-app/pages/). Per-service routes are under [ws-nextjs-app/pages/[service]/](../../ws-nextjs-app/pages/%5Bservice%5D/) (articles, av-embeds, homepage, live, my-news, onDemandAudio, onDemandTv, popular, send, topics, watch, wrapped, …).
- Page files use the **`*.page.tsx`** suffix (e.g. `wrapped.page.tsx`, `_app.page.tsx`, `_document.page.tsx`, `_error.page.tsx`). The Next config `pageExtensions` filters to this suffix so non-page tsx files are ignored by routing — name accordingly.
- API routes live under [ws-nextjs-app/pages/api/](../../ws-nextjs-app/pages/api/).

## Data fetching

- Use `getServerSideProps` for pages that need request-time data (most BBC pages). Avoid `getStaticProps` unless the content is genuinely static across services.
- Resolve the service from the dynamic `[service]` route param, validate it against the `Services` union from [src/app/models/types/global.ts](../../src/app/models/types/global.ts), and 404 unknown services.
- Use shared utilities in [ws-nextjs-app/utilities/](../../ws-nextjs-app/utilities/) before reaching for new dependencies.

## Sharing with the Express app

- Import shared React components from `#app/components/...` rather than duplicating them.
- Service config (`#app/lib/config/services`) and feature toggles (`#app/lib/config/toggles`) are shared — don't fork them here.
- The custom RTL wrapper at [src/app/components/react-testing-library-with-providers.tsx](../../src/app/components/react-testing-library-with-providers.tsx) is also used for tests in this app.

## Tests

- Unit tests are colocated and run via the root `yarn test:unit`.
- Integration tests for this app live under [ws-nextjs-app/integration/](../../ws-nextjs-app/integration/) and are picked up by [jest.config.js](../../jest.config.js).
- The app has its own `jest.config.ts` and `next.config.js` — read them before changing build behaviour.

## Pitfalls

- Don't add Express-specific middleware or `src/server/` imports here; this app is served by Next, not the custom Express server.
- The Next app and Express app have **independent build outputs** — verify your change builds in both if it touches shared code.
45 changes: 45 additions & 0 deletions .github/instructions/service-config.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
description: "Use when adding, editing, or reviewing per-service configuration files under src/app/lib/config/services. Covers the DefaultServiceConfig shape, RTL/LTR direction, translations, font script, analytics, and registration in the services index."
applyTo: ["src/app/lib/config/services/**", "src/app/lib/config/toggles/**"]
---

# Service config conventions

Each Simorgh **service** (language/edition such as `arabic`, `mundo`, `portuguese`, `news`, `pidgin`) has one config file under [src/app/lib/config/services/](../../src/app/lib/config/services/) that exports a `service` object keyed by variant (commonly `default`).

## Shape

A service config typically includes (see [arabic.ts](../../src/app/lib/config/services/arabic.ts) and [mundo.ts](../../src/app/lib/config/services/mundo.ts) for full examples):

- `lang` — BCP-47 code (e.g. `ar`, `es`, `pt-BR`).
- `dir` — `'rtl'` or `'ltr'`. Drives layout, mirroring, and bidirectional handling. Don't omit.
- `articleAuthor`, `articleTimestampPrefix`, `atiAnalyticsAppName`, `atiAnalyticsProducerId`, `atiAnalyticsProducerName`, `chartbeatDomain` — analytics & content metadata. Match existing services exactly; these are wired into BBC analytics pipelines.
- `brandName`, `product`, `serviceLocalizedName` — branding strings shown in UI.
- `script` — imported font script (e.g. `arabicFontScript`, `latinDiacriticsFontScript`). Determines GEL font stack.
- `translations` — large object of localized strings. Add new keys to **all** services, not just the one you're editing, to avoid runtime gaps.
- `navigation` — top-nav items.
- `mostRead` — limits, header text, frequency.
- Per-service feature flags (e.g. `showAdPlaceholder`, `showRelatedTopics`).

## Required workflow when adding a new service

1. Add a TypeScript file `src/app/lib/config/services/<service>.ts` that exports a `service: DefaultServiceConfig` object.
2. Register it in [src/app/lib/config/services/index.ts](../../src/app/lib/config/services/index.ts) (the `services` record drives the `Services` union).
3. Add the service to the `Services` union in [src/app/models/types/global.ts](../../src/app/models/types/global.ts) if not already present.
4. Add per-service content fixtures under [data/<service>/](../../data/) (mirror an existing service's structure).
5. Add toggles (if needed) in [src/app/lib/config/toggles/](../../src/app/lib/config/toggles/) per environment.
6. Run `yarn test:unit` — service config integrity tests will fail loudly if a key is missing.

## Editing existing config

- Don't change `dir`, `lang`, `atiAnalyticsProducerId/Name`, or analytics domains without product owner sign-off.
- When you add a new translation key, add it across **every** service file, even if the value is the English fallback — partial coverage produces missing-string regressions.
- Reuse shared translation modules (e.g. [russianUkrainianSharedTranslations.ts](../../src/app/lib/config/services/russianUkrainianSharedTranslations.ts)) instead of duplicating.

## RTL services

- RTL services include `arabic`, `pashto`, `persian`, `urdu`. Any change to UI behaviour gated on a service should be tested against at least one RTL service.

## Type safety

- `DefaultServiceConfig` (defined in [defaultServiceVariants.ts](../../src/app/lib/config/services/defaultServiceVariants.ts) area) is the source of truth. Don't widen it with `as any`. Add the field to the type if you genuinely need it.
43 changes: 43 additions & 0 deletions .github/instructions/tests.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
description: "Use when writing or editing Jest unit tests, React component tests, or integration tests in Simorgh. Covers the custom RTL wrapper, service-aware assertions, snapshot policy, and KISS test style."
applyTo: ["**/*.test.{ts,tsx,js,jsx}", "**/*.client.test.{ts,tsx,js,jsx}", "src/integration/**", "src/testHelpers/**"]
---

# Simorgh test conventions

## Imports

- For React component tests **always** import `render`, `screen`, `act`, `waitFor`, etc. from the custom wrapper [src/app/components/react-testing-library-with-providers.tsx](../../src/app/components/react-testing-library-with-providers.tsx). It wires up Service, User, Toggle, EventTracking, Account and Theme providers. **Never** import from `@testing-library/react` directly.
- The wrapper's `render` accepts extra options (e.g. `service`, `toggles`, `pageData`, `bbcOrigin`) — use those instead of manually wrapping with providers.

## Style

- Test the **output**, not the implementation. Prefer queries like `screen.getByRole(...)` / `screen.getByText(...)` and assert on visible markup, attributes, or computed style.
- Keep logic out of tests. No loops, no per-test branching. If you need parameterised cases, use `it.each` with a small data table.
- One behaviour per `it`. Use clear `describe`/`it` titles that read like a sentence.
- Don't mock things you don't need to. Prefer the real component tree under the providers.

## Service-awareness

- When a behaviour varies per service (RTL/LTR, fonts, translations, analytics), add at least one assertion for a non-default service. The Paragraph tests are a good model:

```tsx
render(<Paragraph>Hello</Paragraph>, { service: 'arabic' });
expect(screen.getByText('Hello')).toHaveStyle({ fontFamily: '...' });
```
- Pick services that exercise the variation (e.g. `arabic` for RTL/Arabic script, `mundo` for Latin, `news` for English defaults).

## File layout

- Colocate tests next to the code: `index.test.tsx` beside `index.tsx`.
- Use `*.client.test.{ts,tsx}` for tests that must run in the happy-dom (browser-like) Jest project; default Jest project is jsdom.
- Integration tests live under [src/integration/](../../src/integration/) and run as **three separate Jest projects** (AMP, Canonical, Lite). A single test file may be skipped in some platforms via `testPathIgnorePatterns` in [jest.config.js](../../jest.config.js) — verify your new file is covered by the platform(s) you intend.

## Snapshots

- Avoid large/whole-component snapshots. Snapshot small, intentional pieces only.
- Update snapshots with `yarn test:unit:updatesnapshots` (unit) or `yarn test:integration:updatesnapshots` (integration). Review every diff before committing.

## Generated test files (do not commit)

- `yarn test:linkey` produces `src/app/lib/config/services/*.test.js` files. They are temporary; clean up with `yarn test:linkey:cleanup`. Never commit them.
51 changes: 51 additions & 0 deletions .github/skills/add-service/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: add-service
description: 'Add a new BBC World Service language/edition (a "service") to Simorgh. Creates the service config under src/app/lib/config/services, registers it in the services index and Services union, scaffolds matching data fixtures under data/, and adds default toggles. Use when the user asks to add or scaffold a new service, language, or edition.'
argument-hint: 'service-name BCP47-lang dir(rtl|ltr)'
---

# Add a new Simorgh service

Adding a service is a multi-file, cross-cutting change. Follow this procedure to keep all sites and tests consistent.

## When to use

- The user asks to "add a new service", "scaffold service X", "add a new language", or "add an edition".

## Inputs

- **service-name**: lowercase, no spaces (e.g. `swahili`, `mundo`, `portuguese`). Becomes the URL path segment and config key.
- **lang**: BCP-47 language code (e.g. `sw`, `pt-BR`, `ar`).
- **dir**: `rtl` or `ltr`.
- **brand & analytics fields**: confirm with the user — `brandName`, `atiAnalyticsProducerId`, `atiAnalyticsProducerName`, `chartbeatDomain`, fonts (`script`).

## Procedure

1. **Pick a sibling service to mirror.** For an LTR Latin service, copy [src/app/lib/config/services/mundo.ts](../../../src/app/lib/config/services/mundo.ts). For RTL, copy [src/app/lib/config/services/arabic.ts](../../../src/app/lib/config/services/arabic.ts). For non-Latin LTR, pick a closest match (e.g. `hindi`, `thai`).
2. **Create `src/app/lib/config/services/<service-name>.ts`** with the copied config. Update:
- `lang`, `dir`, `brandName`, `serviceLocalizedName`, `product`.
- All analytics IDs (`atiAnalyticsProducerId`, `atiAnalyticsProducerName`, `chartbeatDomain`, `twitterCreator`, …).
- `script` import (e.g. `arabicFontScript`, `latinDiacriticsFontScript`).
- `translations` — translate each value. **Do not leave English fallbacks silently** — flag any keys you can't translate so the developer can fill them in.
- `navigation`, `mostRead`, `radioSchedule`, etc. as appropriate; remove sections the new service doesn't need.
3. **Register the service** in [src/app/lib/config/services/index.ts](../../../src/app/lib/config/services/index.ts) (add `"<service-name>": ''` to the `services` record, alphabetically).
4. **Add to the `Services` union** in [src/app/models/types/global.ts](../../../src/app/models/types/global.ts) if not already a member.
5. **Scaffold data fixtures** under `data/<service-name>/` mirroring an existing service. At minimum copy the structure of `data/<sibling-service>/` and replace IDs/strings — do not commit copy-pasted English content as live data; mark fixtures clearly if they're placeholders.
6. **Toggles**: add the new service to per-environment files in [src/app/lib/config/toggles/](../../../src/app/lib/config/toggles/) only if a toggle requires per-service configuration.
7. **Run validation**:
- `yarn test:lint`
- `yarn test:unit` (service config integrity tests will catch missing keys)
- `yarn build:local` to verify it bundles
8. **Manual smoke tests**: visit `/<service-name>` in `yarn dev` and verify direction, fonts, and translations render.

## Don't

- Don't widen `DefaultServiceConfig` with `any` to fit a missing field. Add the field to the type.
- Don't fork shared translation modules (e.g. [russianUkrainianSharedTranslations.ts](../../../src/app/lib/config/services/russianUkrainianSharedTranslations.ts)); reuse them.
- Don't commit `yarn test:linkey`-generated `*.test.js` files.
- Don't ship analytics IDs you invented — confirm them with the developer.

## References

- Service config primer: [.github/instructions/service-config.instructions.md](../../instructions/service-config.instructions.md)
- Reference services: [arabic.ts](../../../src/app/lib/config/services/arabic.ts), [mundo.ts](../../../src/app/lib/config/services/mundo.ts), [news.ts](../../../src/app/lib/config/services/news.ts)
43 changes: 43 additions & 0 deletions .github/skills/new-component/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
name: new-component
description: 'Scaffold a new React component in src/app/components/ following the Simorgh Paragraph reference pattern: index.tsx with Emotion object styles, colocated Jest tests using the custom RTL wrapper, a Storybook story, and a README. Use when the user asks to create, scaffold, or add a new component.'
argument-hint: 'ComponentName (PascalCase)'
---

# New Simorgh component

Create a new component directory under [src/app/components/](../../../src/app/components/) following the convention used by [Paragraph](../../../src/app/components/Paragraph/).

## When to use

- The user asks to "create a new component", "scaffold a component", "add a component called X", etc.
- The change is in the Express app's shared component tree (`src/app/components/`). For Next.js-only components, the pattern is the same but the location may be `ws-nextjs-app/`.

## Inputs

- **ComponentName**: PascalCase (e.g. `Headline`, `MediaCaption`). The folder name and default export must match.

## Procedure

1. Confirm the ComponentName with the user if not given. Reject names that aren't PascalCase or that already exist in `src/app/components/`.
2. Create the directory `src/app/components/<ComponentName>/` with these four files (templates in [./assets/](./assets/)):
- `index.tsx` — `FC<Props>` with Emotion `css` prop using **object-styles** syntax. Props interface extends a relevant DOM type when the component renders an HTML element. Use a single destructured object parameter.
- `index.test.tsx` — imports `render`, `screen` from `../react-testing-library-with-providers` (never `@testing-library/react`). Includes at least one base test and one service-aware test (e.g. `service: 'arabic'` for RTL).
- `index.stories.tsx` — Storybook story exporting a `default` config and an `Example`. Mirror the structure of [Paragraph/index.stories.tsx](../../../src/app/components/Paragraph/index.stories.tsx).
- `README.md` — short description, props table, and a "How to use" code block.
3. Run `yarn test:unit -- <ComponentName>` and `yarn test:lint` to verify.
4. Do **not** add new external dependencies. Compose existing components from `src/app/components/` (e.g. `Text`) and Psammead from `src/app/legacy/psammead/` first.

## Conventions to enforce

- Emotion **object-styles** only. No string template CSS. Do not introduce `*.module.scss` (that path exists only for [ThemeProviderSCSSModules](../../../src/app/components/ThemeProviderSCSSModules/)).
- `const` everywhere; no reassignment.
- Limit props; if more than ~4, refactor into a single object param.
- Service-aware: don't hard-code English/LTR. Read service via context if needed.
- WCAG 2.1 AA: semantic HTML, accessible names, keyboard support.

## References

- Reference component: [src/app/components/Paragraph/](../../../src/app/components/Paragraph/)
- Custom RTL wrapper: [src/app/components/react-testing-library-with-providers.tsx](../../../src/app/components/react-testing-library-with-providers.tsx)
- Templates: [./assets/index.tsx.template](./assets/index.tsx.template), [./assets/index.test.tsx.template](./assets/index.test.tsx.template), [./assets/index.stories.tsx.template](./assets/index.stories.tsx.template), [./assets/README.md.template](./assets/README.md.template)
16 changes: 16 additions & 0 deletions .github/skills/new-component/assets/README.md.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Description

The `__COMPONENT_NAME__` component is used to ... (describe purpose, including any service-specific behaviour).

## Props

| Name | type | Description |
| ---------- | --------- | ---------------------------------------- |
| children | ReactNode | Content rendered inside the component. |
| className? | string | Optional class name forwarded to the root element. |

## How to use

```tsx
<__COMPONENT_NAME__>Some content</__COMPONENT_NAME__>
```
Loading
Loading