Fix run list order and legend overflow#516
Conversation
- Sort runs newest-first in the sidebar - Fix color mismatch between sidebar and legend on System Metrics page - Collapse legend with "+N more" toggle when many runs are selected Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🪼 branch checks and previews
|
🦄 change detectedThis Pull Request includes changes to the following packages.
|
🪼 branch checks and previews
Install Trackio from this PR (includes built frontend) pip install "https://huggingface.co/buckets/trackio/trackio-wheels/resolve/a1ad5fb5555eeb114e5dfcf54d165a7f4710ab95/trackio-0.23.0-py3-none-any.whl" |
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
There was a problem hiding this comment.
Pull request overview
This PR improves the metrics browsing experience by adjusting run ordering, stabilizing run-to-color mapping on the System Metrics page, and preventing plot legends from overwhelming charts when many runs are selected. It also adds client/server support for batched log fetching and introduces host-aware polling/rate-limit backoff behavior.
Changes:
- Show runs newest-first in the sidebar and fix System Metrics run color mapping by basing colors on the full run list.
- Add
/get_logs_batchAPI + storage support and switch the Metrics page to fetch logs in batches. - Add legend collapsing (“+N more” / “Show less”) for
LinePlotandBarPlot, including fullscreen, plus host-aware polling/backoff helpers.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| trackio/utils.py | Updates write-token sharing warning text shown to users. |
| trackio/sqlite_storage.py | Refactors metric log fetching helpers and adds get_logs_batch() storage method. |
| trackio/server.py | Exposes get_logs_batch through the API registry. |
| trackio/frontend/src/pages/SystemMetrics.svelte | Fixes run color mapping source and adds host-aware polling guards/intervals. |
| trackio/frontend/src/pages/Metrics.svelte | Switches to batched log fetching and adds host-aware polling guards/intervals. |
| trackio/frontend/src/lib/hostPolling.js | New shared utilities for poll intervals, tab-hidden detection, and 429 cooldown. |
| trackio/frontend/src/lib/api.js | Registers 429 hits for cooldown and adds getLogsBatch() client API. |
| trackio/frontend/src/components/LinePlot.svelte | Collapses legends with a toggle to prevent overflow (normal + fullscreen). |
| trackio/frontend/src/components/BarPlot.svelte | Collapses legends with a toggle to prevent overflow (normal + fullscreen). |
| trackio/frontend/src/App.svelte | Reverses run list to newest-first and makes polling host-aware; passes new props to pages. |
| examples/fake-training-many-runs.py | Adds an example script to generate many runs for UI testing. |
| .changeset/shy-places-wish.md | Adds a minor changeset entry for the release. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _subsample_metric_rows(rows: list[Any], max_points: int | None) -> list[Any]: | ||
| if max_points is None or len(rows) <= max_points: | ||
| return rows | ||
| step = len(rows) / max_points | ||
| indices = {int(i * step) for i in range(max_points)} | ||
| indices.add(len(rows) - 1) | ||
| return [rows[i] for i in sorted(indices)] |
There was a problem hiding this comment.
_subsample_metric_rows() divides by max_points without guarding against 0 or negative values. Since get_logs_batch exposes max_points via the API, passing 0 (or a negative number) would raise or produce incorrect indexing. Consider validating max_points (e.g., treat <= 0 as returning an empty list or raising a 400-level error higher up).
| export async function getLogsBatch(project, runs) { | ||
| if (await isStaticMode()) { | ||
| const out = []; | ||
| for (const run of runs) { | ||
| const logs = await staticApi.getLogs(project, run); | ||
| out.push({ | ||
| run: run?.name ?? null, | ||
| run_id: run?.id ?? null, | ||
| logs, | ||
| }); | ||
| } | ||
| return out; | ||
| } | ||
| const payload = { | ||
| project, | ||
| runs: runs.map((run) => ({ | ||
| run: run?.name ?? null, | ||
| run_id: run?.id ?? null, | ||
| })), | ||
| }; | ||
| return await callApi("/get_logs_batch", payload); | ||
| } |
There was a problem hiding this comment.
getLogsBatch() currently assumes each entry in runs is an object with {name,id}. Elsewhere in this module, normalizeRun() explicitly supports run being null or a string (run name). If a caller passes strings, the payload will send {run: null, run_id: null} and the server will return empty logs. Consider reusing normalizeRun() when building the batch payload (and for the static-mode out metadata) to keep the API consistent with getLogs()/getMetricsForRun().
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
# Conflicts: # trackio/frontend/src/pages/Metrics.svelte # trackio/frontend/src/pages/SystemMetrics.svelte # trackio/server.py # trackio/sqlite_storage.py
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
+N more/Show lesstoggle to avoid taking over the chart when many runs are selected (applied to bothLinePlotandBarPlot, including fullscreen)Screen.Recording.2026-04-17.at.4.09.20.PM.mov