Skip to content

Add configurable custom frontends for Trackio#531

Merged
abidlabs merged 16 commits into
mainfrom
feat/custom-frontends-491
Apr 23, 2026
Merged

Add configurable custom frontends for Trackio#531
abidlabs merged 16 commits into
mainfrom
feat/custom-frontends-491

Conversation

@abidlabs
Copy link
Copy Markdown
Member

@abidlabs abidlabs commented Apr 22, 2026

Closes #491

  • add configurable custom frontend resolution for Trackio with CLI arg, Python API arg, env var, and persistent user config
  • serve arbitrary static frontend directories locally and deploy the selected frontend to Gradio and static Spaces

Usage:

  1. Run trackio show --frontend ./my-trackio-frontend.
  2. Ask your LLM to edit the files in that directory.
  3. Keep the browser open while Trackio live reloads the frontend as those files change.

If the directory passed to --frontend does not exist, or exists but is empty, Trackio copies in the starter frontend automatically, prints that it did so, and then serves that directory. The starter is a complete plain-HTML/CSS/JS template: it calls the Trackio API, loads projects and runs, fetches metric values, and draws simple charts that you can replace with your own UI.

Example Frontends:

Signal Console

Signal Console

Sunrise Cards

Sunrise Cards

Editorial Grid

Editorial Grid

@gradio-pr-bot
Copy link
Copy Markdown
Contributor

gradio-pr-bot commented Apr 22, 2026

🪼 branch checks and previews

Name Status URL
🦄 Changes detected! Details

@gradio-pr-bot
Copy link
Copy Markdown
Contributor

🦄 change detected

This Pull Request includes changes to the following packages.

Package Version
trackio minor

  • Add configurable custom frontends for Trackio

‼️ Changeset not approved. Ensure the version bump is appropriate for all packages before approving.

  • Maintainers can approve the changeset by checking this checkbox.

Something isn't right?

  • Maintainers can change the version label to modify the version bump.
  • If the bot has failed to detect any changes, or if this pull request needs to update multiple packages to different versions or requires a more comprehensive changelog entry, maintainers can update the changelog file directly.

@HuggingFaceDocBuilderDev
Copy link
Copy Markdown

HuggingFaceDocBuilderDev commented Apr 22, 2026

🪼 branch checks and previews

Name Status URL
Spaces ready! Spaces preview

Install Trackio from this PR (includes built frontend)

pip install "https://huggingface.co/buckets/trackio/trackio-wheels/resolve/88ca781fe1808b2529cf61ed69317c92cd92194d/trackio-0.24.2-py3-none-any.whl"

@HuggingFaceDocBuilderDev
Copy link
Copy Markdown

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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a configurable “custom frontend directory” mechanism to Trackio so users can swap the dashboard UI (locally and on Spaces) while continuing to use the existing /api/* backend.

Changes:

  • Introduces frontend resolution (arg → env → persisted config → bundled → starter fallback) and a persisted config file under HF_HOME.
  • Refactors frontend serving to mount an arbitrary static directory (SPA-style fallback to index.html).
  • Wires frontend_dir/--frontend through trackio.show(), CLI commands, and Spaces/static deploy flows; ships starter + example theme frontends with docs/tests.

Reviewed changes

Copilot reviewed 37 out of 41 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
trackio/server.py Passes frontend selection into Starlette app build.
trackio/frontend_server.py Serves arbitrary static frontend directories (SPA fallback).
trackio/frontend_config.py Adds resolver + persisted config for frontend directories.
trackio/init.py Exposes frontend_dir on trackio.show() and resolves it.
trackio/cli.py Adds --frontend flags and trackio config subcommands.
trackio/deploy.py Deploys selected frontend to Gradio/static Spaces flows.
trackio/frontend_templates/starter/index.html Starter fallback frontend HTML template.
trackio/frontend_templates/starter/styles.css Starter fallback frontend styles.
trackio/frontend_templates/starter/app.js Starter fallback frontend JS (calls /api/*).
tests/unit/test_frontend_config.py Tests for persisted config + resolver precedence/fallback.
tests/unit/test_deploy.py Tests for deploying custom frontends to Spaces/static Spaces.
scripts/launch_custom_frontend_themes.py Script to run demo themes locally on multiple ports.
pyproject.toml Packages frontend templates into wheel artifacts.
plans/issue-491-custom-frontends-plan.md Implementation plan document for issue #491.
docs/source/quickstart.md Documents --frontend / frontend_dir usage.
docs/source/launch.md Documents custom frontend + persistent config CLI.
docs/source/environment_variables.md Documents TRACKIO_FRONTEND_DIR.
docs/source/deploy_embed.md Documents deploying custom frontend via sync/freeze.
README.md README updates for custom frontend + deploy flows.
.changeset/little-cats-bet.md Minor version changeset for the feature.
examples/custom-frontends/README.md Explains example frontends (non-runtime).
examples/custom-frontends/brutalist-lab/frontend/index.html Demo theme frontend markup.
examples/custom-frontends/brutalist-lab/frontend/styles.css Demo theme styling.
examples/custom-frontends/brutalist-lab/frontend/app.js Demo theme bootstrap JS.
examples/custom-frontends/brutalist-lab/frontend/shared-theme.js Demo theme logic + plotting.
examples/custom-frontends/signal-console/frontend/index.html Demo theme frontend markup.
examples/custom-frontends/signal-console/frontend/styles.css Demo theme styling.
examples/custom-frontends/signal-console/frontend/app.js Demo theme bootstrap JS.
examples/custom-frontends/signal-console/frontend/shared-theme.js Demo theme logic + plotting.
examples/custom-frontends/sunrise-cards/frontend/index.html Demo theme frontend markup.
examples/custom-frontends/sunrise-cards/frontend/styles.css Demo theme styling.
examples/custom-frontends/sunrise-cards/frontend/app.js Demo theme bootstrap JS.
examples/custom-frontends/sunrise-cards/frontend/shared-theme.js Demo theme logic + plotting.
examples/custom-frontends/editorial-grid/frontend/index.html Demo theme frontend markup.
examples/custom-frontends/editorial-grid/frontend/styles.css Demo theme styling.
examples/custom-frontends/editorial-grid/frontend/app.js Demo theme bootstrap JS.
examples/custom-frontends/editorial-grid/frontend/shared-theme.js Demo theme logic + plotting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread trackio/frontend_config.py Outdated
Comment on lines +107 to +126
for source, candidate in _configured_frontend_candidates(frontend_dir):
if is_valid_frontend_dir(candidate):
if source == "config" and announce:
_announce_config_frontend(candidate)
return ResolvedFrontend(
path=candidate,
source=source,
is_custom=True,
)
print(
f"* Trackio frontend from {source} is invalid: {candidate}. "
f"Falling back to starter template at {STARTER_FRONTEND_DIR}."
)
return ResolvedFrontend(
path=STARTER_FRONTEND_DIR,
source="starter",
is_custom=True,
used_fallback=True,
requested_path=candidate,
)
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolve_frontend_dir() returns immediately on the first invalid candidate inside the loop, so later sources (env/config) are never considered. For example, if TRACKIO_FRONTEND_DIR is set but invalid, a valid persisted config (or bundled frontend) will never be used.

Consider continuing to the next candidate when a higher-precedence source is invalid, and only doing an immediate starter fallback for an explicitly provided argument (if that’s the intended behavior).

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +56
for (const record of runRecords.slice(0, 8)) {
const item = document.createElement("div");
item.className = "item";
item.innerHTML = `
<strong>${record.name || "Unnamed run"}</strong>
<div class="meta">Created: ${record.created_at || "unknown"} | Updated: ${record.updated_at || "unknown"}</div>
`;
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This starter template renders run names/timestamps using innerHTML with values coming from the Trackio API (record.name, record.created_at, etc.). Since run names are user-controlled, this enables XSS in the fallback UI (including when deployed on Spaces).

Use textContent (or build DOM nodes) for untrusted values, or HTML-escape them before inserting into innerHTML.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +106
} catch (error) {
projectTitle.textContent = "Frontend error";
runsEl.innerHTML = `<div class="item"><strong>Could not load Trackio data</strong><div class="meta">${error.message}</div></div>`;
metricsEl.innerHTML = "";
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error.message is interpolated directly into innerHTML in the error handler. If the backend returns an error string containing HTML, this becomes another XSS vector.

Prefer setting the message via textContent (or escaping) instead of inserting it into innerHTML.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +56
function renderRuns(runRecords) {
runsEl.innerHTML = "";
if (!runRecords.length) {
runsEl.innerHTML = '<div class="item"><strong>No runs yet</strong><div class="meta">Log a run and refresh this page.</div></div>';
return;
}

for (const record of runRecords.slice(0, 8)) {
const item = document.createElement("div");
item.className = "item";
item.innerHTML = `
<strong>${record.name || "Unnamed run"}</strong>
<div class="meta">Created: ${record.created_at || "unknown"} | Updated: ${record.updated_at || "unknown"}</div>
`;
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UI labels this section as "Latest Runs", but get_runs_for_project returns runs ordered by created_at ASC (oldest-first), and the template displays runRecords.slice(0, 8) (the oldest runs). Also, run records from the API only include created_at (no updated_at), so record.updated_at will always render as "unknown".

Either adjust the backend/API to return the needed fields/order, or update the template copy/logic to match what the API actually provides (e.g., reverse/sort client-side and remove/rename the updated timestamp).

Copilot uses AI. Check for mistakes.
Comment thread trackio/cli.py
Comment on lines +131 to +135
if args.config_command == "set":
frontend_dir = set_persisted_frontend_dir(args.frontend)
print(f"Saved Trackio default frontend: {frontend_dir}")
print("Reset with `trackio config unset frontend`.")
return
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trackio config set frontend ... can raise a ValueError from set_persisted_frontend_dir() for invalid directories, which will currently bubble up as a traceback. Other CLI flows often convert user errors into a clean message/exit code.

Consider catching ValueError here and routing it through error_exit(...) (or equivalent) for a friendlier CLI experience.

Copilot uses AI. Check for mistakes.
abidlabs and others added 4 commits April 21, 2026 23:07
Preserve frontend source precedence when env/config paths are invalid, harden starter frontend rendering against XSS, and surface config-set validation errors as clean CLI messages.

Made-with: Cursor
@qgallouedec
Copy link
Copy Markdown
Collaborator

nice! can we also set e.g. the width of the plots?

@abidlabs
Copy link
Copy Markdown
Member Author

nice! can we also set e.g. the width of the plots?

For sure, the frontend is just vanilla js/css/html, so you can modify anything now. I'll vary up these 4 examples a bit more so we can see what's possible

Reformat touched frontend server and related script/test files to satisfy the format check without changing behavior.

Made-with: Cursor
Apply Ruff import sorting so the format CI job passes.

Made-with: Cursor
@abidlabs
Copy link
Copy Markdown
Member Author

abidlabs commented Apr 22, 2026

PR should be ready now!

@abidlabs
Copy link
Copy Markdown
Member Author

Going to do a release to get the Windows fix out. Will go ahead and merge this in (I've tested this fairly robustly & it doesn't interact much with core Trackio library so its unlikely to have cause issues)

@abidlabs abidlabs merged commit 27a50a3 into main Apr 23, 2026
11 checks passed
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.

Allow easily vibecoding custom frontends for Trackio logs

5 participants