Field analysis readability: the plan, and phase 1 — title every chart's axes - #501
Merged
Merged
Conversation
Measures the density problem on both field-analysis pages (the task page is 19,664px and 8,998 words at 1440px; 24,489px on a phone), then plans progressive disclosure on top of the mechanisms that already exist there rather than a redesign. Seven work items: axis titles on every chart (ConsistencyMap is the only one that has them today, and RankScatter's figcaption is doing its axes' job); a generalised Explain popover so section-level legends move where MetricExplanation's 48 metric-level ones already are; collapsing the reference layer; a derived summary card; a plain-English/with-statistics detail toggle; heatmap label vocabulary; and giving the comp page the info affordances it has none of. Records the constraints any of it must respect — explainability, print, SSR/hydration, RAC grid semantics, and FIELD_ANALYSIS_VERSION for stored labels. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UyMHUij8wpMk6rZzX3yqaY
Phase 1 of docs/2026-07-28-field-analysis-readability-plan.md (§A). ConsistencyMap was the only chart on these pages that named its axes. Everywhere else the job had fallen to the figcaption — RankScatter's opened "Each dot is a pilot: across is what was measured, up is a better rank", a sentence that exists only because its x axis had no title and its y axis said "rank" in 10px grey in a corner. A caption is read once; an axis is consulted continuously, and a screenshot or a printed page separates the two. New charts/AxisTitle.tsx carries XAxisTitle, YAxisTitle and ChartTitle — ConsistencyMap's pattern extracted, aria-hidden like the tick labels already are (every chart is a role="img" whose accessible name states its reading in words, with a table below carrying the values). Applied: - RankScatter: x names the metric and spells its unit out; y is "rank — 1 is the winner". The caption's axis clause is gone. - HorseraceLines: "behind the leader (minutes)" and "speed-section turnpoints, in order", replacing a bare "behind" in the corner. - LegWaterfall: it had no y ticks at all, so nothing said what a bar's height meant or which side of the line was the bad one. - PercentileHeatmap: both axes are SORTED and that ordering is the whole reading of the picture, but it lived six sentences into the caption. - Day profile: the three pilot-derived charts had no titles while the modelled ones beside them did, so a reader scrolling the stack lost track of the quantity. They get titles in the modelled charts' own idiom, and the axis all of them share is named ONCE at the foot of the stack rather than five times over. TaskWeatherPanel gets the same. Margins and viewBox heights grew with the titles, so every plot keeps the area it had. The climb chart gained enough headroom for four stacked rows (title, the task clock's two alternating rule rows, the best-climbs marker) so none can land on another whatever the day's timings are; the wind chart's title was printing through its "direction" lane label until it got MetWindChart's headroom. DistributionStrip is deliberately left alone: the plan called for a title there on the premise that it appears in the ⓘ popover without context, and that premise was wrong — the popover states the metric name and "Measured in kilometres per hour" directly above it, as does MetricDetailPanel above the strip. A title would have been a third copy. Verified against the seeded Corryong Cup 2026 T1: unit tests, 46 e2e, 30 SSR e2e (both analysis pages hydrate clean), and every chart read in the running app. The task page is 227 px taller and 42 words longer for it — §C is where the height comes back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UyMHUij8wpMk6rZzX3yqaY
…is-readability-6wtlrw
…is-readability-6wtlrw
…rywhere Follows the move master just made with scale.ts. The report card's score charts draw the same axis furniture the field-analysis charts do, and two chart families spelling it two ways is the drift a shared module exists to prevent. field-analysis/charts/AxisTitle.tsx -> charts/AxisTitle.tsx, and adopted by every hand-rolled SVG chart that draws any: - ConsistencyMap hand-rolled both of its titles and was the pattern the helper was extracted FROM; it now draws them from the helper rather than remaining a copy the others can drift from. - ScoreCurve, TrackCleaningChart and the report card's DistributionStrip had theirs written out inline. Adds AxisUnit for the corner stamp, because "pts" and "m" are NOT undersized y-axis titles and converting them would have been the wrong read of consistency. A stamp says what the NUMBERS are; a title says what the AXIS is. On a score curve the y axis is points and nothing else, and the section heading above already names the component being scored — so "pts" is complete, and a rotated title would restate the heading. Where a corner label was hiding a real ambiguity instead (which end of "rank" is good?), naming the axis is a title's job and the corner form was the bug. The module documents which to reach for. Placement is fixed inside AxisUnit rather than passed, so every chart's stamp lands in the same spot relative to its plot. No visual change anywhere: every adopted call site resolves to the same coordinates and the same type styles it had. Verified by reading the report card (all three chart kinds) and the comp analysis page in the running app, plus typecheck, 1,173 engine and 476 frontend unit tests, 46 e2e and 30 SSR e2e. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UyMHUij8wpMk6rZzX3yqaY
…is-readability-6wtlrw
18 commits, of which #519 ("Simplified Technical English across the field analysis, and metric labels that name the quantity") lands squarely on the charts this branch changed. Git auto-merged all four overlapping files — their edits were to caption prose, ours to axis geometry — but the merge being clean is not the same as the work still being right, so: - #519 rewrote every metric's FULL label. RankScatter's x-axis title is built from `metric.label`, so this is where phase 1 could have broken: a label that already carried its unit would have produced "Glide speed (km/h) (kilometres per hour)". None do. Checked by walking all 22 rows of the ranking in the running app and reading every title the scatter drew: the longest is 69 characters ("How much of the thermal the pilot climbed before leaving it (percent)") and still fits the plot without clipping, and the ratio-unit metrics correctly get no parenthetical. The new labels are a straight improvement to the axis titles. - The heatmap's axis titles and the day-profile stack's shared time-axis title survived intact. - #521 added a thermals chart with its own inline x title. Left alone on purpose — 9px and left-anchored are deliberate choices for a 320-unit panel, and adopting the shared helper would silently overturn them. Plan doc updated where master has overtaken it: §D is half-answered (full labels done by #519/#520, FIELD_ANALYSIS_VERSION now 20; the cryptic SHORT labels in the heatmap remain), §A is marked shipped with the two places it came out differently, and the baseline table is marked as a snapshot — the same page now measures 17,523 px / 9,081 words, so a phase claiming a reduction must prove it against a fresh measurement. §B, §C, §E, §F, §G, §H are untouched: the legends #519 reworded are still inline, and the comp page still has zero ⓘ. Verified: typecheck:all, 1,238 engine and 533 frontend unit tests, 52 e2e, 33 SSR e2e. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UyMHUij8wpMk6rZzX3yqaY
pokle
marked this pull request as ready for review
August 1, 2026 03:17
…is-readability-6wtlrw
One commit, no overlap with this branch's files. Re-checked the one thing it could have touched: ThermalsPanel's ClimbProfile still draws its own inline x-axis title at 9px, left-anchored, so the note in the plan doc about deliberately leaving it out of the shared AxisTitle helper is still accurate. FIELD_ANALYSIS_VERSION is 21 now; the plan doc said 20. Replaced the quoted number with a pointer to read it, since it has moved three times in four days. Verified: typecheck:all, 1,239 engine and 533 frontend unit tests, 52 e2e, 33 SSR e2e. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UyMHUij8wpMk6rZzX3yqaY
|
Preview Deployment |
pokle
pushed a commit
that referenced
this pull request
Aug 2, 2026
Merges master (2cb994c) and audits MetricLanes across the whole glidecomp-archive: 184 tasks, 1,104 families, 3,239 lanes. New web/scripts/audit-metric-lanes.ts, following audit-score-charts.ts: it scores every task in a comp library with the real engine, builds the real field-analysis report, runs buildLanes over each family the way MetricFamilySection does, and checks the invariants the drawing code relies on — dots pair by trackFile, every percentile sits strictly inside (0,100), the value printed at a lane's end is the value of the dot at that end (the one most likely to be silently wrong, since a 'lower'-is-better lane is flipped), the order is deterministic under reversed input, and no outcome metric gets a lane. Zero invariant violations across the archive. The three fixes below all come from things the audit COUNTED rather than things it failed on — each renders as a blank or an average-looking dot, which is why no test was going to catch them. 1. A lane measuring ONE pilot is dropped (136 of 3,401 lanes). Midrank puts a lone value at exactly 50, so the lane stated "this pilot was dead average" when it meant "only one pilot could be measured". There is no field for a percentile to sit within. The value is still in the per-pilot table, which is where a single reading belongs. This also halved the all-identical lanes, 283 -> 127. 2. The default profile falls past a leader with nothing to draw (5 of 861 charts). It was rank 1 unconditionally; a winner who never shared a thermal has no gaggle metrics at all, so those charts drew no connector — the rows-of-dots state the default exists to prevent. Now the best-ranked pilot appearing in at least two lanes, because a single dot is not a profile. Zero such charts remain. 3. An all-identical lane says so. 127 lanes stack every dot on P50 under a bare figure, which reads as a broken chart; it now reads "every pilot 0.4 m/s". Also adopts two conventions master landed while this was open: the shared XAxisTitle/YAxisTitle from charts/AxisTitle.tsx (#501 — every chart names its axes in the plot, because a caption is read once and an axis continuously), and nothing needed for #520's decision.altitude_floor going 'higher' -> 'neutral', since the orientation is data-driven. bun run test:all (1279 engine/script, 584 frontend, 508 comp, 92 auth), typecheck:all, and test:e2e:ssr (33 passed). Re-driven in a browser: axis titles clear the gutter, connector and readout unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R1Zrf2tMEP2QHK6jWboAQq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
docs/2026-07-28-field-analysis-readability-plan.mdand implements its phase 1 (§A — axis titles), then promotes the helper phase 1 introduced into the shared chart directory so the report card's charts use it too.Branch preview: https://claude-field-analysis-readab.glidecomp.pages.dev
The problem, measured
Rendered against the bundled Corryong Cup 2026, Task 1 (Open) — 32 pilots, mid-sized field:
The pages aren't badly designed — they're fully explained at all times. Every legend, caveat, method note and provenance line is permanently expanded, and all of it wears the same
text-xs text-muted-foregroundstyle, so the finding and the fine print look identical.The plan (7 phases)
Progressive disclosure built on the mechanisms already there (
MetricExplanation's ⓘ popover,MetricGlossary,Footnotes,Disclosure), not a redesign. §A axis titles · §B a generalisedExplainpopover for section- and column-level legends · §C collapse the reference layer · §D heatmap label vocabulary · §E a derived summary card · §F a plain-English/with-statistics toggle · §G comp-page ⓘs · §H narrow screens.Constraints recorded for each: explainability, print, SSR/hydration, RAC grid semantics for hidden columns, and
FIELD_ANALYSIS_VERSIONfor labels stored inside cached reports.Sub-issue material for #450, alongside the open #452 and #458 (both flagged as seams, neither done here).
Phase 1, implemented
ConsistencyMapwas the only chart on these pages that named its axes. Everywhere else the job had fallen to the figcaption —RankScatter's opened "Each dot is a pilot: across is what was measured, up is a better rank", a sentence that exists only because its x axis had no title and its y axis saidrankin 10px grey in a corner. A caption is read once; an axis is consulted continuously, and a screenshot or a printed page separates the two.RankScatterrankin a cornerrank — 1 is the winner. Caption's axis clause deletedHorseraceLinesbehindin a cornerbehind the leader (minutes)/speed-section turnpoints, in orderLegWaterfalltime against the winner (m:ss)/speed-section legs, in orderPercentileHeatmappilots, best first ↓/behaviours, most explanatory first →From tracks: wind/: climb/: wind by leg, in the modelled charts' own idiomtime of day (AEDT), once at the foot of the stack — not five times.TaskWeatherPaneltooMargins and viewBox heights grew with the titles so every plot keeps its area. Two collisions found by reading the rendered app and fixed: the wind chart's title printed straight through its
directionlane label, and the climb chart's title shared a row with the task clock's gate labels (it now has room for four stacked rows, so none can land on another whatever the day's timings are).The helper is shared, not field-analysis-local
charts/AxisTitle.tsxsits besidescale.tsin the shared chart directory — the same promotion master made for the scale helpers when the report card needed them. It carriesXAxisTitle/YAxisTitle/ChartTitle/AxisUnit, and every hand-rolled SVG chart that draws axis furniture now uses it: the field-analysis charts above,ConsistencyMap(the chart the pattern was extracted from, so it can't drift from its own descendants), and the report card'sScoreCurve,TrackCleaningChartandDistributionStrip, which had theirs written out inline.AxisUnitexists becauseptsandmare not undersized y-axis titles. A stamp says what the NUMBERS are; a title says what the AXIS is. On a score curve the y axis is points and nothing else, and the section heading already names the component — soptsis complete. Where a corner label was hiding a real ambiguity instead (which end ofrankis good?), naming the axis was a title's job and the corner form was the bug. The module documents which to reach for.Deliberate omissions
field-analysis/DistributionStripgot no title. The plan asked for one on the premise that it appears in the ⓘ popover without context — wrong: the popover prints the metric name and "Measured in kilometres per hour" immediately above it, as doesMetricDetailPanel. A third copy.ThermalsPanel'sClimbProfile(landed in Reconstructed thermals: 3D shapes from every pilot's track, on the analysis page and in the replay #521/Thermal rose: legible extent ring, a legend, and per-pilot climbs #526) keeps its own inline x title. 9px and left-anchored are deliberate choices for a 320-unit panel that adopting the helper would silently overturn.Re-validated against master
Master moved 24 commits under this branch while it was open, including #519 "Simplified Technical English across the field analysis, and metric labels that name the quantity" — which rewrote the very labels
RankScatter's x-axis title is built from. That's where phase 1 could have broken: a label carrying its own unit would render "Glide speed (km/h) (kilometres per hour)".Checked by walking all 22 rows of the ranking in the running app and reading every title the scatter drew. No duplication; the longest is 69 characters ("How much of the thermal the pilot climbed before leaving it (percent)") and still fits the plot; ratio- and count-unit metrics correctly get no parenthetical. The new labels are a straight improvement to the axis titles.
The plan doc has been updated where master overtook it: §D is now half-answered (#519/#520 fixed the full labels; the cryptic short labels heading the heatmap remain), and the baseline table is marked a snapshot — the same page now measures 17,523 px / 9,081 words, so a later phase claiming a reduction must prove it against a fresh measurement. §B, §C, §E, §F, §G, §H are unaffected: the legends #519 reworded are still inline, and the comp page still has zero ⓘ.
Verification
bun run typecheck:allclean; 1,239 engine/worker and 533 frontend unit tests pass (incl. newaxisTitleForcases).🤖 Generated with Claude Code
https://claude.ai/code/session_01UyMHUij8wpMk6rZzX3yqaY