Compare commits

..
Author SHA1 Message Date
TudorandClaude Fable 5 fef83b3bf2 fix(compare): sticky school bar hidden behind the site header
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m7s
PR Checks / Backend Smoke (pull_request) Successful in 6s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 47s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 2m3s
The Navigation header is position:sticky top:0 (z-index 1000). The school
bar was also sticky top:0 (z-index 10), so when scrolled it pinned at the
same top:0 *behind* the header — on mobile 57 of its 72px were covered,
leaving only a sliver, so you couldn't see which schools were selected.

Offset the bar's sticky top to the header height (65px desktop, 57px
mobile — the Navigation breakpoint is also 640px) so it pins just below.

e2e guard asserts the bar's sticky offset is at least the header height
(verified it fails against the pre-fix build).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
2026-07-15 12:32:36 +01:00
tudor cf458fe05c Merge pull request 'School name cutoff fix' (#43) from fix/compare-mockup-drift into main
Stage (build -> staging -> E2E gate) / Build Backend (FastAPI) (push) Successful in 13s
Stage (build -> staging -> E2E gate) / Build Frontend (Next.js) (push) Successful in 50s
Stage (build -> staging -> E2E gate) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Stage (build -> staging -> E2E gate) / Deploy to Staging (push) Successful in 1s
Stage (build -> staging -> E2E gate) / E2E Journeys against Staging (push) Successful in 44s
Reviewed-on: #43
2026-07-15 08:01:47 +00:00
Tudor f579630fab School name cutoff fix
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m2s
PR Checks / Backend Smoke (pull_request) Successful in 6s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 46s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 9s
PR Checks / AI Code Review (Claude) (pull_request) Failing after 9s
2026-07-15 08:57:21 +01:00
4 changed files with 34 additions and 11 deletions
+13
View File
@@ -227,6 +227,19 @@ test('compare chart on mobile shows school chips with tap-to-focus', async ({ pa
); );
expect(bodyOverflowsX).toBe(false); expect(bodyOverflowsX).toBe(false);
// The sticky school bar must pin *below* the sticky site header, not at
// top:0 where the header covers it and the selected schools are hidden.
// Assert the sticky offset directly (robust — no scroll timing needed).
const barTop = await page
.locator('[class*="schoolBar"]')
.first()
.evaluate((el) => parseFloat(getComputedStyle(el).top));
const headerHeight = await page
.locator('[class*="header"]')
.first()
.evaluate((el) => el.getBoundingClientRect().height);
expect(barTop).toBeGreaterThanOrEqual(headerHeight - 1);
// The trends chart still renders (inside the Explore trends section)… // The trends chart still renders (inside the Explore trends section)…
const chartCanvas = page.locator('canvas:visible').first(); const chartCanvas = page.locator('canvas:visible').first();
await expect(chartCanvas).toBeVisible({ timeout: 15_000 }); await expect(chartCanvas).toBeVisible({ timeout: 15_000 });
@@ -80,10 +80,12 @@
} }
/* Sticky school bar — column identity while scrolling; horizontal scroll on /* Sticky school bar — column identity while scrolling; horizontal scroll on
narrow screens */ narrow screens. Offset by the sticky site header's height (Navigation is
position: sticky, top: 0) so this bar pins just below it instead of
sliding underneath and being hidden. Header ≈ 65px desktop / 57px mobile. */
.schoolBar { .schoolBar {
position: sticky; position: sticky;
top: 0; top: 65px;
z-index: 10; z-index: 10;
background: var(--bg-primary, #faf7f2); background: var(--bg-primary, #faf7f2);
display: flex; display: flex;
@@ -173,6 +175,11 @@
pills with short names (matching the mobile mockup) instead of full-width pills with short names (matching the mobile mockup) instead of full-width
cards whose names wrap to several lines. */ cards whose names wrap to several lines. */
@media (max-width: 640px) { @media (max-width: 640px) {
/* The mobile Navigation header is shorter (≈57px). */
.schoolBar {
top: 57px;
}
.schoolChip { .schoolChip {
flex: 0 0 auto; flex: 0 0 auto;
min-width: 0; min-width: 0;
@@ -55,24 +55,27 @@
.cell { .cell {
display: flex; display: flex;
align-items: center; align-items: baseline;
gap: 0.5rem; gap: 0.35rem 0.5rem;
flex-wrap: wrap; flex-wrap: wrap;
padding: 0.45rem 0; padding: 0.5rem 0;
border-top: 1px solid var(--border-light); border-top: 1px solid var(--border-light);
margin-top: 0.45rem; margin-top: 0.5rem;
font-size: 0.95rem; font-size: 0.95rem;
} }
/* The school name gets its own full-width line above the value — real
school names are long and varied, so a fixed-width name column truncated
them ("Our Lady Queen of H…") or crowded the value. */
.cellTag { .cellTag {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.4rem; gap: 0.4rem;
width: 5rem; flex-basis: 100%;
flex: none;
font-size: 0.8rem; font-size: 0.8rem;
font-weight: 600; font-weight: 600;
color: var(--sc, var(--text-secondary)); color: var(--sc, var(--text-secondary));
margin-bottom: 0.15rem;
} }
.cellDot { .cellDot {
@@ -92,10 +95,9 @@
.small { .small {
display: block; display: block;
flex-basis: 100%; flex-basis: 100%;
padding-left: 5.5rem;
font-size: 0.8rem; font-size: 0.8rem;
color: var(--text-muted); color: var(--text-muted);
margin-top: -0.05rem; margin-top: 0;
} }
.chip { .chip {
@@ -187,6 +189,7 @@
display: flex; display: flex;
gap: 0.3rem; gap: 0.3rem;
flex-wrap: wrap; flex-wrap: wrap;
flex-basis: 100%;
margin-top: 0.3rem; margin-top: 0.3rem;
} }
+1 -1
View File
@@ -65,7 +65,7 @@ export function truncate(text: string, maxLength: number): string {
* "Barclay", "St Mary's Catholic Primary School" → "St Mary's". Falls back to * "Barclay", "St Mary's Catholic Primary School" → "St Mary's". Falls back to
* a length-capped truncation for names that don't carry a type suffix. * a length-capped truncation for names that don't carry a type suffix.
*/ */
export function shortName(name: string, maxLength = 20): string { export function shortName(name: string, maxLength = 32): string {
let s = name let s = name
.replace( .replace(
/\s+(primary|junior|infant|nursery|community|foundation|catholic|academy|school|college)\b.*$/i, /\s+(primary|junior|infant|nursery|community|foundation|catholic|academy|school|college)\b.*$/i,