feat(compare): limit the basket to five per phase, not five overall
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 22s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m17s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 12s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 20s

A parent choosing a primary and a secondary school at once hit the old
cap of five total. The basket now holds up to five primary and five
secondary schools (ten in all), matching the compare page's phase tabs.

Schools that could land in either tab (all-through, special schools with
phase "Not applicable", unknown phase) count against both groups, so no
tab ever exceeds the five-slot chart palette and point styles.

- lib/compareLogic: compareGroups, fullGroupFor, fitToGroupLimits
- search modal disables only the full group and says which one
- rankings rows carry the phase of the tab they are ranked under
- shared ?urns= links are trimmed per group
- copy: compare metadata, homepage value prop, How it works card now
  name primary and secondary schools (also better for search intent)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
TudorandClaude Opus 5.5 committed 2026-09-30 11:56:54 +01:00
1 parent 343b40c645
commit 0a4c051ee5
12 files changed
+145 -25

No files matched your search

+3 -1
View File
@@ -6,6 +6,7 @@
'use client';
import { createContext, useContext } from 'react';
import type { CompareGroup } from '@/lib/compareLogic';
import type { School } from '@/lib/types';
interface ComparisonContextType {
@@ -18,7 +19,8 @@ interface ComparisonContextType {
replaceSchools: (schools: School[]) => void;
clearAll: () => void;
isSelected: (urn: number) => boolean;
canAddMore: boolean;
/** The comparison group with no room for this school, or null. */
fullGroupFor: (school: Pick<School, 'phase'>) => CompareGroup | null;
isInitialized: boolean;
mutate: () => void;
}
+11 -5
View File
@@ -7,11 +7,11 @@
import { useState, useEffect, useCallback } from 'react';
import { getFromLocalStorage, setToLocalStorage } from '@/lib/utils';
import { MAX_PER_GROUP, fitToGroupLimits, fullGroupFor } from '@/lib/compareLogic';
import type { School } from '@/lib/types';
import { ComparisonContext } from './ComparisonContext';
const STORAGE_KEY = 'selectedSchools';
const MAX_SCHOOLS = 5;
export function ComparisonProvider({ children }: { children: React.ReactNode }) {
const [selectedSchools, setSelectedSchools] = useState<School[]>([]);
@@ -53,8 +53,9 @@ export function ComparisonProvider({ children }: { children: React.ReactNode })
if (prev.some((s) => s.urn === school.urn)) {
return prev;
}
if (prev.length >= MAX_SCHOOLS) {
alert(`Maximum ${MAX_SCHOOLS} schools can be compared`);
const fullGroup = fullGroupFor(prev, school);
if (fullGroup) {
alert(`You can compare up to ${MAX_PER_GROUP} ${fullGroup} schools at once. Remove one to add another.`);
return prev;
}
return [...prev, school];
@@ -68,7 +69,7 @@ export function ComparisonProvider({ children }: { children: React.ReactNode })
// Replace the whole selection — used when a shared /compare?urns=… link
// must take precedence over whatever this visitor had stored.
const replaceSchools = useCallback((schools: School[]) => {
setSelectedSchools(schools.slice(0, MAX_SCHOOLS));
setSelectedSchools(fitToGroupLimits(schools));
}, []);
const clearAll = useCallback(() => {
@@ -80,6 +81,11 @@ export function ComparisonProvider({ children }: { children: React.ReactNode })
[selectedSchools]
);
const fullGroupForSchool = useCallback(
(school: Pick<School, 'phase'>) => fullGroupFor(selectedSchools, school),
[selectedSchools]
);
// Placeholder mutate - actual SWR mutate is in useComparison hook
const mutate = useCallback(() => {}, []);
@@ -95,7 +101,7 @@ export function ComparisonProvider({ children }: { children: React.ReactNode })
replaceSchools,
clearAll,
isSelected,
canAddMore: selectedSchools.length < MAX_SCHOOLS,
fullGroupFor: fullGroupForSchool,
isInitialized,
mutate,
}}