Files
school_compare/nextjs-app
TudorandClaude Opus 5 2175dccb7c
PR Checks / Frontend Typecheck + Tests (pull_request) Canceled after 9s
PR Checks / Backend Smoke (pull_request) Canceled after 0s
PR Checks / Build Backend (no push) (pull_request) Canceled after 0s
PR Checks / Build Frontend (no push) (pull_request) Canceled after 0s
PR Checks / Build Pipeline (no push) (pull_request) Canceled after 0s
PR Checks / AI Code Review (Claude) (pull_request) Canceled after 0s
fix(api): treat "16 plus" as secondary, the way PHASE_GROUPS already does
GIAS phase 6 is "16 plus", and PHASE_GROUPS deliberately files it in the
secondary group. The payload helper decided the same question with
`"secondary" in phase_text`, which that value does not satisfy — so a
sixth-form college was handed the primary bucket and offered infant schools
as its peers, with the KS2 metric key to label them. No crash; just a page
confidently showing the wrong schools.

The decision now lives in similar_schools.is_secondary_phase, beside the
PHASE_GROUPS bucket it selects from, so the two cannot drift again. A test
pins them together.

The same binary assumption had a second output. computeSchoolFlags tests for
the substring too, so a 16-plus school renders the primary template, and the
composer was labelling the section from the template: "Other primary schools
near <sixth form college>" above a row of secondaries. The section now
derives its noun from the school's own phase, which also removes the
duplicated wording from both composers. A 16-plus school's candidates span
the whole secondary group, so no single noun fits and it gets the honest
general one.

Reported in review on #150.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 06:41:49 +01:00
..

SchoolCompare frontend and CMS

Next.js App Router with React, TypeScript, CSS Modules, Chart.js, Leaflet and Payload CMS. It serves school search, comparisons, rankings, school/place detail pages and editorial content across England.

Start with the repository overview, architecture and development checks.

Source map

Path Purpose
app/(frontend)/ Public root layout, server pages and FastAPI proxy
app/(payload)/ Payload root layout, /admin and /cms-api
app/robots.ts, app/opengraph-image.tsx, root icons Site-wide metadata endpoints
components/ Client views and reusable display components
components/school/ School detail sections
lib/api.ts, lib/types.ts Fetch wrappers and manual school API types
lib/schoolSections.ts, lib/compareLogic.ts Presentation decisions and data preparation
context/, hooks/ Comparison state, suggestion state and responsive behaviour
collections/, blocks/, migrations/ CMS schema and production migrations
__tests__/ Jest and React Testing Library tests

Do not introduce a shared app/layout.tsx: public pages and Payload have separate root layouts. Keep root metadata files outside the route groups.

Data and state

Server pages fetch initial data directly from FASTAPI_URL. Browser fetches use /api by default, forwarded by app/(frontend)/api/[...path]/route.ts. FASTAPI_URL must include /api. See .env.example for CMS and API settings.

State uses React hooks/context, URL search parameters and localStorage for the comparison basket. SWR is not installed. Maps use dynamic Leaflet wrappers. Revalidation intervals are configured in fetch wrappers and pages; they vary by resource. Backend reloads do not automatically invalidate every Next.js cache.

Commands

npm ci
npm run typecheck
npm test -- --runInBand
npm run build

test:watch and test:coverage are also available. There is no lint script. A running application needs the backend/data environment described in the development guide.

After CMS field or editor changes, run npm run generate:importmap. Keep payload-types.ts generated from the CMS schema rather than editing it by hand. The build must work without a database connection; avoid module-scope CMS queries and DB-backed generateStaticParams functions.

See publishing for CMS operations and deployment for staging and production promotion.