[object Object]

← back to Model Arena

model-arena: bulk re-run a SUBSET of a battle's models with checkboxes

fcff4496b4940cd96db5be0e75248c61fa2e1819 · 2026-09-23 17:48:34 -0700 · Steve Abrams

Adds a "Re-run selected" checkbox bar to the battle detail view — check any number
of the battle's models (including errored ones) and re-run ONLY those in one click,
the missing middle ground between the per-row ↻ (one at a time) and "Re-run entire
challenge" (all of them). Reuses the existing guarded /api/challenges/:id/retry/:model
endpoint. Default = none checked (re-running re-bills metered models, so opt-in is the
safe default) and the checked set is preserved across the 4s detail poll, same pattern
as the build-picker fix. Confirms a metered-model re-bill before firing.

Steve-clarified scope: he wanted the build launcher (done), THIS bulk-rerun subset, and
the new-battle checkboxes (verified already-correct) — all three.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01166faVcwiztzWqhHwD9yer

Files touched

Diff

commit fcff4496b4940cd96db5be0e75248c61fa2e1819
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 17:48:34 2026 -0700

    model-arena: bulk re-run a SUBSET of a battle's models with checkboxes
    
    Adds a "Re-run selected" checkbox bar to the battle detail view — check any number
    of the battle's models (including errored ones) and re-run ONLY those in one click,
    the missing middle ground between the per-row ↻ (one at a time) and "Re-run entire
    challenge" (all of them). Reuses the existing guarded /api/challenges/:id/retry/:model
    endpoint. Default = none checked (re-running re-bills metered models, so opt-in is the
    safe default) and the checked set is preserved across the 4s detail poll, same pattern
    as the build-picker fix. Confirms a metered-model re-bill before firing.
    
    Steve-clarified scope: he wanted the build launcher (done), THIS bulk-rerun subset, and
    the new-battle checkboxes (verified already-correct) — all three.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01166faVcwiztzWqhHwD9yer
---
 public/index.html | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/public/index.html b/public/index.html
index 90ccece..ad89ff1 100644
--- a/public/index.html
+++ b/public/index.html
@@ -337,6 +337,10 @@ table{border-collapse:separate;border-spacing:0;border:1px solid var(--line);bor
     <span class="back" id="back">← back to challenges</span>
     <a class="back" id="export" style="margin-left:16px" download>⬇ export battle as standalone HTML</a>
     <button class="btn pink" id="rerun-all" style="margin-left:16px" title="Re-run EVERY model in this battle from scratch (re-spends any metered models)">↻ Re-run entire challenge</button>
+    <span id="d-rerunbar" hidden style="margin-left:16px">
+      <span id="d-rerun-picks" class="build-picks" title="Check every model you want to re-run — regenerates ONLY those models' artifacts (the middle ground between the per-row ↻ and 'Re-run entire challenge'). Metered models re-bill."></span>
+      <button class="btn pink" id="d-rerun-go" title="Re-run ONLY the checked models">↻ Re-run selected</button>
+    </span>
     <span id="d-buildbar" hidden>
       <span id="d-build-picks" class="build-picks" title="Check every model whose artifact you want built — EACH checked model gets its OWN independent iTerm2 Claude window + its own landing page"></span>
       <button class="btn pink" id="d-build-go" title="Launch one independent iTerm2 Claude session + landing page for EACH checked model">⚒ Build in iTerm2</button>
@@ -627,6 +631,43 @@ async function renderDetail(id, statusOnly){
       };
     } else { bbar.hidden=true; }
   }
+  // Bulk re-run a SUBSET — check any number of the battle's models, hit one button, and
+  // re-run ONLY those (the middle ground between the per-row ↻ and 'Re-run entire challenge').
+  // Lists ALL runs so a failed/errored model can be re-run too. Same preserve-across-poll
+  // pattern as the build-picker: default = none checked (re-running re-bills metered models,
+  // so an opt-in default is the safe/reversible choice), then keep the user's set on rebuild.
+  const rbar=$('#d-rerunbar'), rpicks=$('#d-rerun-picks'), rgo=$('#d-rerun-go');
+  if (rbar){
+    if (c.runs.length){
+      rbar.hidden=false;
+      const firstRerunRender = !rpicks.querySelector('input');
+      const prevRerun = new Set([...rpicks.querySelectorAll('input:checked')].map(i=>i.value));
+      rpicks.innerHTML = c.runs.map(r=>{
+        const on = !firstRerunRender && prevRerun.has(r.model);   // default: none checked
+        const badge = r.status==='error' ? ' ✗' : (r.status==='done' ? '' : ' ⏳');
+        return `<label title="re-run ${mLabel(r.model)}${r.status==='error'?' (currently errored)':''}"><input type="checkbox" value="${r.model}"${on?' checked':''}> ${mLabel(r.model)}${badge}</label>`;
+      }).join('');
+      const meteredIds = new Set(c.runs.filter(r=>{ const m=(models||[]).find(x=>x.id===r.model); return m && m.kind!=='local' && m.kind!=='cli'; }).map(r=>r.model));
+      const updateRerunGo = ()=>{
+        const checked = [...rpicks.querySelectorAll('input:checked')].map(i=>i.value);
+        const nm = checked.filter(id=>meteredIds.has(id)).length;
+        rgo.textContent = checked.length ? `↻ Re-run selected (${checked.length})` : '↻ Re-run selected';
+        rgo.disabled = checked.length===0;
+        rgo.title = checked.length ? ('Re-run '+checked.length+' model(s)'+(nm?' — '+nm+' metered, will re-bill':'')) : 'Check at least one model';
+      };
+      rpicks.onchange = updateRerunGo;
+      updateRerunGo();
+      rgo.onclick = async ()=>{
+        const ids = [...rpicks.querySelectorAll('input:checked')].map(i=>i.value);
+        if (!ids.length) return;
+        const nm = ids.filter(id=>meteredIds.has(id)).length;
+        if (!confirm('Re-run '+ids.length+' selected model'+(ids.length===1?'':'s')+' in "'+c.title+'"?'+(nm?'\n\n'+nm+' metered model(s) will be re-billed.':''))) return;
+        rgo.disabled = true; rgo.textContent = '↻ re-running…';
+        for (const id of ids) { try { await fetch(API+'/api/challenges/'+c.id+'/retry/'+id, {method:'POST'}); } catch {} }
+        openDetail(c.id);   // reloads + restarts polling; the re-queued models animate back to running
+      };
+    } else { rbar.hidden=true; }
+  }
   // Continue with battles — jump to the New Battle form (mirrors the + New Battle nav)
   { const cont=$('#d-continue'); if (cont) cont.onclick = ()=> show('new'); }
   const arena = $('#d-arena');

← b94f38e Fix PT-06 stamp/label overlap and PT-03 ticket/face collisio  ·  back to Model Arena  ·  auto-data-snapshot: 2026-09-24T02:22:01 (1 data files) — dat 00f276b →