runs list: mark older runs with duplicate-stem output as stale, hide their compare checkbox

When two runs share identical params they write to the same figs/<stem>.html,
and the most recent overwrites the earlier. Previously both got a compare
checkbox with the same data-stem, so toggling one toggled both via the JS
Set. Now we flag older duplicates server-side (first occurrence wins — Prefect
returns runs START_TIME_DESC), drop their checkbox, fade the row, and mark
'overwritten' on the outputs line.
This commit is contained in:
Michael Pilosov 2026-04-22 15:35:05 -06:00
parent fc1ae9dbc9
commit a5614ac371
5 changed files with 33 additions and 4 deletions

View File

@ -611,6 +611,22 @@ def _run_view(run: Dict[str, Any]) -> Dict[str, Any]:
} }
def _mark_stale_views(views: List[Dict[str, Any]]) -> None:
"""Flag runs whose emb HTML was overwritten by a newer sibling with
identical params. Prefect returns runs sorted by START_TIME_DESC, so the
first occurrence of each stem is authoritative; later ones are stale.
Mutates views in place."""
seen: set = set()
for v in views:
stem = v["emb_file"][:-5] if v.get("emb_file") else None
if stem and stem in seen:
v["stale"] = True
else:
v["stale"] = False
if stem:
seen.add(stem)
def _reducer_choices() -> List[Dict[str, str]]: def _reducer_choices() -> List[Dict[str, str]]:
return [ return [
{"key": k, "label": spec["label"], "blurb": spec["blurb"]} {"key": k, "label": spec["label"], "blurb": spec["blurb"]}
@ -632,6 +648,7 @@ async def index(request: Request) -> HTMLResponse:
runs = await PREFECT.recent_runs(client, limit=10) runs = await PREFECT.recent_runs(client, limit=10)
dep_id = await PREFECT.deployment_id(client) dep_id = await PREFECT.deployment_id(client)
views = [_run_view(r) for r in runs] views = [_run_view(r) for r in runs]
_mark_stale_views(views)
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"index.html", "index.html",
@ -668,6 +685,7 @@ async def runs_partial(request: Request) -> HTMLResponse:
async with httpx.AsyncClient(timeout=5.0) as client: async with httpx.AsyncClient(timeout=5.0) as client:
runs = await PREFECT.recent_runs(client, limit=10) runs = await PREFECT.recent_runs(client, limit=10)
views = [_run_view(r) for r in runs] views = [_run_view(r) for r in runs]
_mark_stale_views(views)
return templates.TemplateResponse( return templates.TemplateResponse(
request, "_runs.html", {"runs": views} request, "_runs.html", {"runs": views}
) )
@ -770,6 +788,7 @@ async def submit(request: Request) -> HTMLResponse:
async with httpx.AsyncClient(timeout=5.0) as client: async with httpx.AsyncClient(timeout=5.0) as client:
runs = await PREFECT.recent_runs(client, limit=10) runs = await PREFECT.recent_runs(client, limit=10)
views = [_run_view(r) for r in runs] views = [_run_view(r) for r in runs]
_mark_stale_views(views)
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"_runs.html", "_runs.html",

View File

@ -518,6 +518,15 @@ button.submit:disabled { background: var(--faint); border-color: var(--faint); c
padding-left: 0.55rem; padding-left: 0.55rem;
margin-left: -0.55rem; margin-left: -0.55rem;
} }
.runs li.run.stale {
opacity: 0.55;
}
.runs li.run.stale .stale-note {
color: var(--alarm);
font-style: italic;
font-size: 0.72rem;
margin-left: 0.3rem;
}
.run .stamp { .run .stamp {
font-family: var(--mono); font-family: var(--mono);

View File

@ -7,8 +7,8 @@
{% else %} {% else %}
<ul class="runs"> <ul class="runs">
{% for r in runs %} {% for r in runs %}
<li class="run {% if just_submitted is defined and r.id == just_submitted %}just-submitted{% endif %}"> <li class="run {% if just_submitted is defined and r.id == just_submitted %}just-submitted{% endif %}{% if r.stale %} stale{% endif %}">
{% if r.emb_exists %} {% if r.emb_exists and not r.stale %}
<input type="checkbox" class="compare-cb" data-stem="{{ r.emb_file[:-5] }}" aria-label="select run for comparison" /> <input type="checkbox" class="compare-cb" data-stem="{{ r.emb_file[:-5] }}" aria-label="select run for comparison" />
{% else %} {% else %}
<span class="compare-cb-slot" aria-hidden="true"></span> <span class="compare-cb-slot" aria-hidden="true"></span>
@ -47,6 +47,7 @@
<div class="outputs"> <div class="outputs">
<span class="tag">fig</span> <span class="tag">fig</span>
{% if r.stale %}<span class="stale-note" title="a newer run with identical params overwrote this output">overwritten</span>{% endif %}
{% if r.ref_file %} {% if r.ref_file %}
{% if r.ref_exists %} {% if r.ref_exists %}
<a href="/figs/{{ r.ref_file }}" target="_blank" rel="noopener">reference</a> <a href="/figs/{{ r.ref_file }}" target="_blank" rel="noopener">reference</a>

View File

@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>embedding notebook &middot; compare</title> <title>embedding notebook &middot; compare</title>
<link rel="stylesheet" href="/static/style.css?v=26" /> <link rel="stylesheet" href="/static/style.css?v=27" />
<script type="importmap"> <script type="importmap">
{ {
"imports": { "imports": {

View File

@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>embedding notebook</title> <title>embedding notebook</title>
<link rel="stylesheet" href="/static/style.css?v=26" /> <link rel="stylesheet" href="/static/style.css?v=27" />
<script src="https://unpkg.com/htmx.org@2.0.4"></script> <script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script type="importmap"> <script type="importmap">
{ {