Compare commits

...
Author SHA1 Message Date
TudorandClaude Opus 5.5 49ac96b487 refactor(search): drop the applied-filter row above the results
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m12s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 19s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 21s
The row repeated the search, local authority and school type above the
results list, and nothing else: no phase, distance, gender, sixth form,
admissions or faith. The filter bar already shows every one of them, in
its selects and More filters count on desktop and as removable chips on
phones, so the row was a partial second copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 14:27:53 +01:00
TudorandClaude Opus 5.5 214c80663e docs(spec): describe the API filters as they are built
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m13s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m18s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 26s
The /api/schools section still described filtering on stored
school_type_group and faith_groups columns, which the architecture
correction had already dropped: the filters look up the name columns at
request time. Also trims a trailing blank line from two test files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:25:20 +01:00
5 changed files with 19 additions and 59 deletions

No files matched your search

@@ -131,13 +131,20 @@ names. The names come from the same dictionaries, so the lookup is exact.
### `/api/schools`
- `school_type`: if the value is a type group key, filter on
`school_type_group`. Otherwise filter on the raw label exactly as today, so
an old `?school_type=Community+school` link keeps working.
- `faith` (new, optional, `max_length=40`, sanitised like the others): filter to
rows whose `faith_groups` contains the key. An unknown key returns no
schools rather than being ignored, so a typo does not silently show
everything.
Both filters work on the name columns at request time. `_names_in_group`
collects the distinct `school_type` or `religious_denomination` names the
group accepts, once per distinct name rather than per row, and the rows are
kept with `isin`. No group column is stored.
- `school_type`: if the value is a type group key (any case), keep the rows
whose `school_type` name `type_group_for` puts in that group. Otherwise
filter on the raw label exactly as today, so an old
`?school_type=Community+school` link keeps working.
- `faith` (new, optional, `max_length=40`, sanitised like the others): keep the
rows whose `religious_denomination` name `faith_groups_for` puts in that
faith (any case); for `none`, rows with a missing name too. An unknown key
returns no schools rather than being ignored, so a typo does not silently
show everything.
### `/api/filters`
@@ -135,4 +135,3 @@ describe('a URL value the options do not spell the same way', () => {
expect(screen.getByRole('combobox', { name: 'Faith' })).toHaveValue('roman_catholic');
});
});
@@ -167,13 +167,11 @@ describe('the folded search', () => {
});
});
describe('the results list filter chips', () => {
it('name a school type group by its label, not its key', () => {
params = new URLSearchParams('search=southmead&school_type=council');
describe('the results list', () => {
it('repeats no applied filters above the results; the filter bar shows them', () => {
params = new URLSearchParams('search=southmead&school_type=council&local_authority=Wandsworth');
const { container } = render(<HomeView initialSchools={results()} filters={filters} />);
const chips = container.querySelector('[class*="activeFilters"]')!;
expect(chips).toHaveTextContent('State school: council-run');
expect(chips).not.toHaveTextContent(/\bcouncil\b(?!-run)/);
expect(container.querySelector('[class*="activeFilters"]')).toBeNull();
expect(screen.queryByText('Search: southmead')).not.toBeInTheDocument();
});
});
-31
View File
@@ -1533,37 +1533,6 @@
cursor: pointer;
}
.activeFilters {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
.filterChip {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.625rem;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 999px;
font-size: 0.8rem;
color: var(--text-secondary);
}
.chipRemove {
color: var(--text-muted);
text-decoration: none;
font-size: 0.9rem;
line-height: 1;
transition: color var(--transition, 0.2s ease);
}
.chipRemove:hover {
color: var(--text-primary);
}
.loadMoreSection {
display: flex;
flex-direction: column;
-13
View File
@@ -292,11 +292,6 @@ export function HomeView({ initialSchools, filters, totalSchools, howItWorks, ed
const hasSearch = searchParams.get('search') || searchParams.get('postcode');
const isLocationSearch = !!searchParams.get('postcode');
const isSearchActive = !!(hasSearch || searchParams.get('local_authority') || searchParams.get('school_type'));
// The URL carries a type group's key; the chip reads its label. An old link's
// raw GIAS type reads as itself.
const schoolTypeParam = searchParams.get('school_type');
const schoolTypeChip = (schoolTypeParam && filters.school_type_groups?.find(
(o) => o.value.toLowerCase() === schoolTypeParam.toLowerCase())?.label) || schoolTypeParam;
const currentPhase = searchParams.get('phase') || '';
const secondaryCount = allSchools.filter(s => s.attainment_8_score != null).length;
const primaryCount = allSchools.filter(s => s.rwm_expected_pct != null).length;
@@ -812,14 +807,6 @@ export function HomeView({ initialSchools, filters, totalSchools, howItWorks, ed
{hasSearch && !isMapView && resultsHeader}
{isSearchActive && !isMapView && (
<div className={styles.activeFilters}>
{searchParams.get('search') && <span className={styles.filterChip}>Search: {searchParams.get('search')}<a href="/" className={styles.chipRemove} onClick={e => { e.preventDefault(); }}>×</a></span>}
{searchParams.get('local_authority') && <span className={styles.filterChip}>{searchParams.get('local_authority')}</span>}
{schoolTypeParam && <span className={styles.filterChip}>{schoolTypeChip}</span>}
</div>
)}
{initialSchools.schools.length === 0 && isSearchActive ? (
<EmptyState
title="No schools found"