book.html: list the trailers already run, because there was no way back

Once you navigated away from a trailer, the only route back to it was the URL.
The page offered a way to start work and no way to return to it, which meant
every finished run was effectively lost the moment you closed the tab.

The list leads with what someone actually wants to know at a glance - is it
finished, did every writer survive, has anything been drawn, what has it cost -
rather than with an id and a timestamp. A run still working shows what it is
doing and keeps polling, so this is also the page you land on to check
progress.

trailer.html gets a link back, so the two pages are a loop rather than a
one-way trip.
This commit is contained in:
gator 2026-09-17 02:48:38 +00:00
parent fa1992f200
commit 31b3474f8f

View file

@ -34,6 +34,18 @@
.wr.on{border-color:var(--accent)}
.wr input{margin:0}
.lead{margin-left:6px;font-size:12px;color:var(--muted)}
.run{display:flex;gap:14px;align-items:center;padding:14px 0;
border-top:1px solid var(--line);flex-wrap:wrap}
.run:first-child{border-top:0}
.run .meta{flex:1;min-width:220px}
.run h3{margin:0 0 3px;font-size:16px}
.run .sub{color:var(--muted);font-size:13px}
.run .who{color:var(--muted);font-size:12px;margin-top:2px}
.dot{font-size:12px;padding:2px 8px;border-radius:999px;border:1px solid var(--line);
color:var(--muted)}
.dot.done{color:var(--accent2);border-color:var(--accent2)}
.dot.work{color:var(--accent);border-color:var(--accent)}
.dot.bad{color:#e0685f;border-color:#e0685f}
</style>
</head>
<body>
@ -44,6 +56,13 @@
<p>Upload the manuscript and say what you want the trailer to do. Several models each read the whole book and write their own cinematic trailer &mdash; script, cast and shot list. You read them side by side, mark up the shots you want changed, and let them critique each other before one gets filmed.</p>
</header>
<section id="runsec" style="display:none">
<div class="card">
<h2 class="sec">Trailers you have run</h2>
<div id="runs"></div>
</div>
</section>
<section>
<div class="card">
<div class="msg" id="msg"></div>
@ -129,7 +148,45 @@ function readFileB64(file) {
navBar("trailer");
var BOOKS = [], PICKED = null, WRITERS = [], CHOSEN = {}, LEAD = "", EDIT = null;
var pollTimer = null;
var pollTimer = null, runTimer = null;
// ---------------------------------------------------------------- runs
function runHtml(r) {
var bits = [];
if (r.ready) bits.push(r.ready + " written");
if (r.drawn) bits.push(r.drawn + " drawn");
if (r.failed) bits.push(r.failed + " failed");
if (r.cost_usd) bits.push("$" + Number(r.cost_usd).toFixed(2));
if (r.created_at) bits.push(when(r.created_at));
var cls = r.busy ? "work" : (r.failed && !r.ready ? "bad" : "done");
var word = r.busy ? "working" : (r.stage || "");
return '<div class="run">'
+ '<div class="meta"><h3>' + esc(r.book_title || "Untitled") + '</h3>'
+ '<div class="sub">' + esc(bits.join(" · ")) + '</div>'
+ '<div class="who">' + esc(r.writers || "")
+ (r.lead_writer ? ' &middot; lead: ' + esc(r.lead_writer) : '') + '</div>'
+ '</div>'
+ '<span class="dot ' + cls + '">' + esc(word) + '</span>'
+ '<a class="btn" href="/trailer.html?run=' + encodeURIComponent(r.id) + '">'
+ (r.busy ? "Watch" : "Review") + '</a>'
+ '</div>';
}
async function loadRuns() {
var r = await api("list_runs", {});
if (!r.ok) return;
var runs = r.runs || [];
document.getElementById("runsec").style.display = runs.length ? "" : "none";
document.getElementById("runs").innerHTML = runs.map(runHtml).join("");
// A run that is still writing or drawing is worth watching from here, so
// this doubles as the progress page.
var busy = runs.some(function (x) { return x.busy; });
if (busy && !runTimer) runTimer = setInterval(loadRuns, 8000);
if (!busy && runTimer) { clearInterval(runTimer); runTimer = null; }
}
// ---------------------------------------------------------------- books
@ -207,7 +264,7 @@ function readFileB64(file) {
t.disabled = true;
var u = await api("update_book", { book_id: save, title: title, author: author });
if (!u.ok) { showMsg("msg", u.error || "That did not save."); t.disabled = false; return; }
hideMsg("msg"); EDIT = null; loadBooks();
hideMsg("msg"); EDIT = null; loadBooks(); loadRuns();
return;
}
if (pick) { PICKED = pick; hideMsg("msg"); paintBooks(); paintQuote(); return; }
@ -344,6 +401,7 @@ function readFileB64(file) {
});
loadBooks();
loadRuns();
})();
</script>
</body>