Imported from wellcomecollection/wellcomecollection.org (
AGENTS.md). Install upstream withnpx skills add wellcomecollection/wellcomecollection.org. Copyright stays with the author.
Wellcome Collection Development Guidelines
These guidelines are for active development and coding tasks. Shared coding standards and PR review guidelines are in .github/copilot-instructions.md.
Guidelines for AI assistants working on the Wellcome Collection codebase. Shared coding standards (naming, accessibility, duplication, workspace structure, Prismic conventions) live in .github/copilot-instructions.md. The line below is a Claude Code import directive, not a GitHub @-mention — it pulls that file's contents into context automatically:
@.github/copilot-instructions.md
The sections below cover agent-specific operational guidance.
Working Style
Ask questions before implementing. If requirements are unclear, the approach is ambiguous, or there are multiple reasonable solutions, ask rather than guessing. This saves rework and ensures we build the right thing.
Consider our version of Node by consulting .nvmrc.
Complete your work properly. Before considering a task done:
- Fix all linting errors
- If necessary, run
yarn tscfrom the root to check TypeScript errors - Test your changes locally where possible
- Check that imports and dependencies are correct
Write casually and keep explanations clear. Don't use emojis or bold text in responses.
Build and Test
Running apps locally
yarn install
yarn content # runs content webapp, http://localhost:3000
yarn identity # runs identity webapp
yarn cardigan # runs the design system
yarn dash # runs the internal dashboard (yarn workspace @weco/dash run dev)
Both apps can run together: (yarn content & yarn identity). Both need matching NEXT_PUBLIC_API_ENV_OVERRIDE values in their .env files.
Run yarn config-local-apis to configure nginx proxying so the webapps can hit local dev APIs (see README.md for the full override precedence rules).
To deploy a local build straight to staging, bypassing CI (coordinate with the team first, and restore afterwards): yarn deploy-dev content / yarn deploy-dev restore content.
Type checking
yarn tsc # from root - checks TypeScript across all packages
Testing
yarn test:common # jest in common/
yarn test:content # jest in content/webapp
yarn test:identity # jest in identity/webapp
yarn test:all:unit # all three of the above
yarn test:playwright # e2e against staging
yarn test:playwright:mobile # e2e against staging, mobile viewport
To run a single test file, cd into the relevant workspace (e.g. content/webapp or common) and run:
yarn test path/to/File.test.ts
Documentation
Key documentation files:
- playwright/README.md - E2E testing with Playwright, user stories approach
- docs/testing-the-site-on-dates-that-arent-right-now.md - How to test date logic
- common/README.md - About shared code structure, data directory, Prismic conventions
- docs/git-hooks.md - Git hooks with Husky (pre-commit linting)
- docs/ - Additional feature-specific documentation
Architecture
Yarn Workspaces monorepo. Main workspaces:
common/— shared code (@weco/common): components, hooks, services, utils, types, and the Prismic model (customtypes/,views/slices/) generated via SliceMachine. Also owns the koa-middleware and server-data pipeline used by both webapps.content/webapp/— the main public site (@weco/content): stories, exhibitions, events, catalogue/works, search, concepts, guides. Next.js app with a custom Koa server (app.ts).identity/webapp/— account management (@weco/identity): login, registration, library membership.toggles/webapp/— feature flag / A-B test definitions, compiled and deployed to S3, served attoggles.wellcomecollection.organd displayed on the toggles dashboard.dash/webapp/— internal dashboard, including the toggles UI.cardigan/— the design system.cache/— CloudFront / Lambda@Edge config, including the A/B test cohort assignment (cache/edge_lambdas/src/toggler.ts).deploy/— deployment tooling (yarn deploy-dev).infrastructure/— Terraform for the experience AWS account.prismic-model/— shared Prismic type definitions.
Server architecture (content & identity)
Both webapps run Next.js behind a custom Koa server (see content/webapp/app.ts):
require('@weco/common/services/apm/initApm')(...)must be the first thing loaded (APM instrumentation).- Koa middleware chain:
apmErrorMiddlewarethenwithCachedValues(fromcommon/koa-middleware), which currently composeswithPrismicPreviewStatus— this is where request-scoped data (like Prismic preview state) gets attached before Next.js handles the request. initServerData()(common/server-data) runs once at boot and is the source of theserverDataobject (includingserverData.toggles) available ingetServerSideProps.- Routing is case-sensitive (
sensitive: trueon the Koa router) — deliberate, because Prismic generates near-identical document IDs differing only in case. /rssis served directly by this Koa server (backed byrss.wellcomecollection.orgvia CloudFront) rather than through a Next.js page.
Toggles (feature flags, A/B tests & modes)
Three categories, all defined in toggles/webapp/toggles.ts:
- Feature flags (
experimental/permanent/stage) — boolean, with adefaultValue. Add one by appending tofeatureFlagswithinitialValue: false, thenyarn deployfromtoggles/webappto publish to S3/the dashboard. - A/B tests — cohort assignment happens in the Lambda@Edge layer (
cache/edge_lambdas/src/toggler.ts), not in the webapp. - Modes — like a feature flag but the value is a selected option string rather than a boolean, activated via a cookie holding the chosen option's
id. Defined in themodesarray (e.g.kioskModefor Reading Room / Tenderness & Rage devices,cataloguePipelinefor routing catalogue API queries to a specific Elasticsearch cluster via anelasticClusterparam).
Access patterns:
- Client-side:
useFeatureFlags()/useABTest()/useModes()from@weco/common/server-data/Context. - Server-side:
serverData.toggles.someFlag.value(orserverData.toggles.modes.someMode) insidegetServerSideProps; the sameserverData.togglesobject is threaded into API clients (catalogue, etc.) that need toggle- or mode-gated behaviour. - Server-side toggle cookie resolution (
common/server-data/toggles.ts) validates against the deployedtoggles.wellcomecollection.org/toggles.json— a flag or mode added locally totoggles.tswon't resolve from its cookie until the toggles package has actually been deployed.
API environment overrides (local dev against real APIs)
Which environment (prod/stage/dev) each backend API is hit from, precedence high to low:
toggles?.stagingApiunset → everything isprod.toggles?.stagingApitrue → everything isstage.NEXT_PUBLIC_API_ENV_OVERRIDE→ applies to content, concepts, and catalogue APIs together.- Per-API overrides:
NEXT_PUBLIC_CONTENT_API_ENV_OVERRIDE,NEXT_PUBLIC_CONCEPTS_API_ENV_OVERRIDE,NEXT_PUBLIC_CATALOGUE_API_ENV_OVERRIDE.
Prismic integration
Content is authored in Prismic. Key layers, all under content/webapp/services/prismic/:
fetch/— Prismic client fetch helpers,fetch-links.tsfor GraphQuery fetch-link configuration.transformers/— convert raw Prismic documents into the app's view models.events.ts— event-specific Prismic queries/logic.
common/customtypes and common/views/slices are the SliceMachine-managed Prismic model (custom types and slices); regenerate/edit these via SliceMachine (yarn slicemachine), not by hand, when adding fields. See the "Future API ID naming" conventions in .github/copilot-instructions.md before adding new custom types, slices, or fields.
Catalogue / IIIF
content/webapp/services/wellcome/catalogue talks to the Catalogue API (works, images). content/webapp/services/iiif fetches and transforms IIIF manifests for viewing digitised items.
CI / deployment
Buildkite pipelines live in .buildkite/ (pipeline.yml main, pipeline.deployment.yml, pipeline.e2e-universal.yml). To reproduce a CI build step locally, run the matching docker compose service (see README.md for the full walkthrough of mounting AWS credentials and using the experience-ci role).
Server vs Client Code
Next.js code runs in both Node.js (server-side rendering) and the browser (client-side). When using Node.js-only APIs or packages:
- Use
typeof window === 'undefined'to detect server-side context - Use dynamic imports
await import()for Node.js-only packages - Add webpack config to exclude server-only packages from client bundles
Example: When adding utilities that rely on server-only dependencies (for example HTTP clients or Node.js core modules), ensure they are only imported and executed in server contexts using runtime checks and appropriate webpack configuration.
Why this matters: Server-only packages that try to load in the browser will cause "Cannot find module" errors even with webpack exclusion. You need both webpack config AND runtime checks.
Backend Concepts
The team is strong on front-end but may benefit from extra explanation of backend/infrastructure concepts:
- HTTP connection pooling and keep-alive behaviour: Reusing connections reduces latency. Timeouts must be coordinated between client and server (client timeout < server timeout) to avoid connection resets.
- Docker and deployment: How code gets built and deployed to production.
When implementing backend code, explain the reasoning behind technical decisions, not just what the code does.
Pull Requests
See CONTRIBUTING.md for general contribution guidelines and repository conventions.
Our PR template (configured in GitHub's interface) asks for:
- What does this change? - Detail the problem, why the change is needed, and how it solves it
- How to test - Instructions to verify the change (e.g., "On PROD, do X and see Y. On this branch, do X and see Z.")
- Have we considered potential risks? - Potential risks, mitigations, and whether alarms are needed
When writing PR descriptions, follow this structure and provide enough detail for the PR to be understandable in the future. Keep the template's own headings verbatim.
When referencing GitHub issues, put - For #123 or - Refs #123 (never Closes #123, so merging doesn't auto-close the ticket) as the very first line of the description, before ## What does this change? starts.
Writing style
PR descriptions are for humans first - write them the way you'd explain the change to a teammate:
- Casual tone, not stiff or formal. British spelling in prose (see copilot-instructions.md).
- Terse: cut padding, hedging, and anything already obvious from the diff. Don't restate context.
- Plain words over jargon or vivid-metaphor shorthand ("soak", "load-bearing", etc.) - if a normal phrase says it, use that.
- One line per paragraph or bullet - don't hard-wrap markdown at a column width.
- In "How to test", keep specific example URLs/work IDs rather than generic placeholders, and link them inline on the descriptive phrase, e.g.
[View a work with an archive collection](https://www-dev.wellcomecollection.org/works/aegabdcp#contents). Usewww-dev.wellcomecollection.org, not prod. - In "Have we considered potential risks?", state the actual risk and stop - don't conclude "no alarms needed" or similar. Whether alarms/monitoring are needed is a call for the human reviewer, not something to assert.
- A
> [!NOTE]callout is a good place for a genuine open caveat you want a reviewer to weigh in on (e.g. "styling isn't final, pending a discussion") - skip it if there isn't one.
AI-assisted PRs
When an AI assistant drafts the PR description, on top of the above:
-
If there's a genuinely non-obvious decision, alternative considered, or open question worth recording for a future debugging session, add a collapsed section at the end of the body:
<details> <summary>For AI / future debugging</summary> Whatever's actually non-obvious - why this approach over another one, an edge case deliberately left unhandled, an assumption made, a gotcha hit while implementing. </details>Skip this entirely for straightforward PRs - it's for genuine signal, not a checklist item to fill in every time. Don't duplicate what's already stated plainly above the fold.
-
Commit messages can carry more of the "why" than the PR body does - humans rarely read them, but they're useful
git blame/git logcontext for AI-assisted debugging later. This doesn't change the general git workflow guidance elsewhere (new commits rather than amending, etc.). -
Append
_Written by Claude Code._(or the relevant tool's name) as a short italic line at the very end of the body, after any "For AI" section - GitHub's automatic attribution footer is suppressed in this repo's settings, so this has to be in the text itself.