Skip to content

Field analysis readability: the plan, and phase 1 — title every chart's axes - #501

Merged
pokle merged 9 commits into
masterfrom
claude/field-analysis-readability-6wtlrw
Aug 1, 2026
Merged

Field analysis readability: the plan, and phase 1 — title every chart's axes#501
pokle merged 9 commits into
masterfrom
claude/field-analysis-readability-6wtlrw

Conversation

@pokle

@pokle pokle commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Adds docs/2026-07-28-field-analysis-readability-plan.md and 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:

Task field analysis Comp field analysis
Height @1440px 19,664 px (~18 screens) 7,515 px
Height @400px 24,489 px (~28 screens)
Words 8,998 ~1,400
Prose paragraphs >12 words 59 (3,588 words) 6
Table cells 1,985 ~330
ⓘ affordances 48 0

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-foreground style, 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 generalised Explain popover 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_VERSION for 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

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.

Chart Was Now
RankScatter ticks only; rank in a corner x names the metric and spells its unit out; y is rank — 1 is the winner. Caption's axis clause deleted
HorseraceLines behind in a corner behind the leader (minutes) / speed-section turnpoints, in order
LegWaterfall no y ticks at all time against the winner (m:ss) / speed-section legs, in order
PercentileHeatmap ordering explained 6 sentences into the caption pilots, best first ↓ / behaviours, most explanatory first →
Day profile (flown) no titles, while the modelled charts beside them had them From tracks: wind / : climb / : wind by leg, in the modelled charts' own idiom
Day profile (shared x) none time of day (AEDT), once at the foot of the stack — not five times. TaskWeatherPanel too

Margins 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 direction lane 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.tsx sits beside scale.ts in the shared chart directory — the same promotion master made for the scale helpers when the report card needed them. It carries XAxisTitle / 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's ScoreCurve, TrackCleaningChart and DistributionStrip, which had theirs written out inline.

AxisUnit exists because pts and m are 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 — so pts is complete. Where a corner label was hiding a real ambiguity instead (which end of rank is 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

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:all clean; 1,239 engine/worker and 533 frontend unit tests pass (incl. new axisTitleFor cases).
  • 52 e2e and 33 SSR e2e pass — both analysis pages hydrate clean.
  • Every changed chart, in both families, read in the running app against seeded Corryong Cup 2026 T1.
  • Cost of §A: the charts are taller and the page carries ~40 more words. §A buys legibility, not brevity; §C is where the height comes back.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UyMHUij8wpMk6rZzX3yqaY

claude added 2 commits July 28, 2026 22:17
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
@pokle pokle changed the title docs: plan for making the field analysis pages less intimidating Field analysis readability: the plan, and phase 1 — title every chart's axes Jul 28, 2026
claude added 5 commits July 29, 2026 02:40
…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
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
pokle marked this pull request as ready for review August 1, 2026 03:17
claude added 2 commits August 1, 2026 03:18
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
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Preview Deployment
https://7e89b9ea.glidecomp.pages.dev
Commit: 40f43d6

@pokle
pokle merged commit 54852d8 into master Aug 1, 2026
3 checks passed
@pokle
pokle deleted the claude/field-analysis-readability-6wtlrw branch August 1, 2026 03:56
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants