Leftover from the mechanical extraction: the shell still called
computeSchoolFlags() and derived isReportCard / ofstedInspectedDate /
oeifAreas / oeifAllSameGrade / deprivationDesc / primaryAvg / secondaryAvg on
every render, duplicating work page.tsx already does. None of those values
were referenced in its JSX anymore -- that logic moved to the section
composers.
The chrome needs only four locally-derived values (latestResults, phase,
isAllThrough, hasLocation), all one-liners over props it already owns.
Removing them made seven props dead, which TypeScript caught at both call
sites: absenceData, ofsted, admissions, admissionsHistory, deprivation,
finance and nationalAvg now go straight to the section composers and never
reach the client component. The shell's surface is down to schoolInfo,
yearlyData, census, navItems and children.
No behaviour change: 155 tests pass and the characterization tests remain
byte-identical to the commit that introduced them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
page.tsx now composes the sections and passes them through SchoolDetailShell
as children, so ~1,300 lines of static markup stop shipping as client
JavaScript. The shell keeps what is genuinely interactive: back link, header
reveal, hero map, compare CTA, sticky nav and scroll-spy.
The scroll-spy already located sections via document.getElementById, so it
works unchanged against server-rendered children.
Charts needed a client wrapper: next/dynamic with ssr:false is illegal in a
Server Component, so components/school/charts.tsx is the boundary that keeps
Chart.js (64 KB gz) lazy and browser-only.
Measured on this build:
- school route client chunk: 8 KB gz (33 KB raw)
- total static JS across all chunks: 380.6 -> 350.7 KB gz
- section markup is absent from every client chunk ("Got their first choice",
"Ofsted reports", "Most deprived" etc. all return 0 hits); shell strings
still present, as expected
- shared baseline unchanged at 172 KB gz -- out of scope, as designed
The 14 characterization tests pass byte-identical to the commit that
introduced them. Only the render helper changed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Moves ~1,300 lines of section markup out of the two client views into
components/school/, mirroring the components/compare/ layout. Twelve section
components plus shared primitives, all server components. The only client
file is AdmissionsViewToggle, which owns the hidden/aria-pressed state and
receives both views as server-rendered children.
JSX was extracted mechanically rather than retyped, so the markup the CSS
modules depend on is verbatim.
Sharing follows measured similarity, not assumption:
- Finances (91%) shared. The secondary premises-cost card is gated behind a
prop so primary pages are unchanged; enabling it is a one-line follow-up.
- Ofsted (80%) shared, but behind a variant prop. The headline similarity hid
a real fork: on a school with no overall grade the primary page shows a
"Not rated" badge while the secondary shows a four-area OEIF panel, and the
disclaimer copy differs. Both preserved exactly; reconciling them is a
human decision, not a side effect of a move.
- Admissions (14%) and History (40%) kept separate.
Not yet wired up -- the old views still render.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Builds components/school/schoolSections.module.css from the primary module,
plus 35 secondary-only rules, so shared section components have one stylesheet.
Three categories of difference, handled separately so nothing changes visually:
- 7 incidental-drift properties unioned in (overflow-wrap, word-break,
min-width, user-select) -- fixes that landed on one page and were never
back-ported. Also fixes latent long-school-name overflow on the page that
lacked them.
- 4 genuinely visual class differences kept as explicit variants:
genderBar, heroStatValue, genderSplitBoys, genderSplitGirls. font-weight on
the genderSplit pair was reclassified from drift to visual -- unioning it
would have bolded the primary page.
- 17 rules that exist only in the secondary stylesheet but target shared class
names (.card, .sectionTitle, .metricCard). Applied flat these would restyle
the primary page, so they are scoped under .secondaryScope, which only
SecondarySchoolSections will carry.
Audited: every rule in both original stylesheets is a subset of the merged one
(0 MISSING, 0 LOST), excluding the documented variants.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>