Skip to content

feat: compare the targets side by side on the statistics page - #38

Merged
i7Gamer merged 1 commit into
developmentfrom
feat/target-comparison
Aug 31, 2026
Merged

feat: compare the targets side by side on the statistics page#38
i7Gamer merged 1 commit into
developmentfrom
feat/target-comparison

Conversation

@i7Gamer

@i7Gamer i7Gamer commented Aug 31, 2026

Copy link
Copy Markdown
Owner

📋 Description

The payoff the target chips only filtered towards: a tenth, full-width card on the statistics page that puts the targets side by side. Collapsed it is a per-target summary table — average download/upload/ping and failure rate, each row's dot in its chip's colour — and expanded it adds an overlaid chart of the targets' series with a Download/Upload/Ping switcher, one line per target, plus the table.

Lazy by design. The per-target statistics are fetched only when the card is first opened and cached against the exact query+node+targets key they answer for: N requests per range change would spend the same fixed-window rate budget the page's own request lives on, and a 429 there blanks the whole page. The cache is handed to renders only while its key matches, so a Back-button range change under the open panel shows a loading line rather than the previous range's series, and the fetch keeps its own generation counter so it can never strand the page's stale-response guard.

Honest overlay. Each target keeps its own instants on the linear time axis (resampling onto a merged timeline would shred every line into dashes under the deliberate spanGaps-off rule); interaction is nearest-point with the tooltip titled from the point's own timestamp, no failed-test branch and no legend filter (a target may literally be named "Failed test"). Marker density follows the total drawn points while curve tension follows the longest series. The colour cycle gains one home — targetSeriesToken — so a line and its chip dot cannot disagree, and roundIndexById's -1 still resolves.

The table separates a fetch that failed ("Couldn't load") from a target that measured nothing (honest N/A). The reflow suite models the tenth card rather than being routed around (cards list, spanning model, wide-class rules restated per stage), the new chart joined the palette and point-density guard lists, and the six new strings ship translated in all 23 locales.

Suites: npm test and the integration suite green, lint 0 errors, vite build clean. Design went through an adversarial review round (9 defects fixed pre-implementation, incl. the rate-budget and tooltip-index-mode traps).

🚀 Changes made to ...

  • 🔧 Server
  • 🖥️ Client
  • 🌐 Web
  • 📚 Documentation
  • 🔄 Other: ___

✅ Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have tested my changes locally
  • I have looked for similar pull requests in the repository and found none
  • This pull request does not contain translations (they are managed on Crowdin, only en.json is edited here)

🤖 Generated with Claude Code

The chips only ever narrowed the pages to one target; the payoff of having
several is seeing them against each other. A tenth, full-width card on the
statistics page holds a per-target summary table - average speeds, latency
and failure rate, each dot wearing its chip's colour - and opens into an
overlay of the targets' series with a metric switcher, one line per target.

Client-only, and lazy on purpose: the per-target statistics are fetched
when the card is first opened, cached against the exact query they answer
for, and never while collapsed - N requests per range change would spend
the fixed-window rate budget the page's own request lives on, and a 429
there blanks the whole page. The cache is handed to every render only
while its key matches, so the Back button under an open panel shows a
loading line rather than the previous range's series, and the fetch keeps
its own generation counter so it can never strand the page's stale guard.

The overlay keeps each series on its own instants - the x axis is linear
epoch time - and interacts by nearest point, since index mode assumes one
shared label array; the tooltip titles from the point's own parsed x. The
colour cycle gains one home: targetSeriesToken carries the normalisation,
targetColour derives from it, and the canvas resolves the same token
through the chart theme, so a line and its dot cannot disagree. Marker
density follows the drawn total while tension follows the longest series -
one count for both drew the same data two ways. The table separates a
fetch that failed from a target that measured nothing, and the six new
strings ship translated in all 23 locales.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@i7Gamer
i7Gamer merged commit 0140173 into development Aug 31, 2026
6 of 7 checks passed
@i7Gamer
i7Gamer deleted the feat/target-comparison branch September 4, 2026 05:11
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.

1 participant