review column: 'approve above' on hover, wrap long audio titles
All checks were successful
Build and Push Docker Image / build (push) Successful in 55s
All checks were successful
Build and Push Docker Image / build (push) Successful in 55s
each top-level card now shows a secondary button on hover ('↑ approve
above') that approves every card listed above this one in one
round-trip. uses a new POST /api/review/approve-batch { itemIds } that
ignores non-pending items so stale client state can't 409. series cards
get the same affordance scoped via a named tailwind group so it
doesn't collide with the inner episode cards' own hover state.
fix the horizontal-scroll glitch: long unbreakable audio titles (e.g.
the raw release filename) now line-wrap inside the card via
[overflow-wrap:anywhere] + min-w-0 on the span. previously
break-words was a no-op since there were no whitespace break points
in the release string.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -575,6 +575,44 @@ app.post("/approve-all", (c) => {
|
||||
return c.json({ ok: true, count: pending.length });
|
||||
});
|
||||
|
||||
// ─── Batch approve (by item id list) ─────────────────────────────────────────
|
||||
// Used by the "approve up to here" affordance in the review column. The
|
||||
// client knows the visible order (movies + series sort-key) and passes in
|
||||
// the prefix of item ids it wants approved in one round-trip. Items that
|
||||
// aren't pending (already approved / skipped / done) are silently ignored
|
||||
// so the endpoint is idempotent against stale client state.
|
||||
app.post("/approve-batch", async (c) => {
|
||||
const db = getDb();
|
||||
const body = await c.req.json<{ itemIds?: unknown }>().catch(() => ({ itemIds: undefined }));
|
||||
if (
|
||||
!Array.isArray(body.itemIds) ||
|
||||
!body.itemIds.every((v) => typeof v === "number" && Number.isInteger(v) && v > 0)
|
||||
) {
|
||||
return c.json({ ok: false, error: "itemIds must be an array of positive integers" }, 400);
|
||||
}
|
||||
const ids = body.itemIds as number[];
|
||||
if (ids.length === 0) return c.json({ ok: true, count: 0 });
|
||||
|
||||
const placeholders = ids.map(() => "?").join(",");
|
||||
const pending = db
|
||||
.prepare(
|
||||
`SELECT rp.*, mi.id as item_id FROM review_plans rp JOIN media_items mi ON mi.id = rp.item_id
|
||||
WHERE rp.status = 'pending' AND rp.is_noop = 0 AND mi.id IN (${placeholders})`,
|
||||
)
|
||||
.all(...ids) as (ReviewPlan & { item_id: number })[];
|
||||
|
||||
let count = 0;
|
||||
for (const plan of pending) {
|
||||
db.prepare("UPDATE review_plans SET status = 'approved', reviewed_at = datetime('now') WHERE id = ?").run(plan.id);
|
||||
const { item, streams, decisions } = loadItemDetail(db, plan.item_id);
|
||||
if (item) {
|
||||
enqueueAudioJob(db, plan.item_id, buildCommand(item, streams, decisions));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return c.json({ ok: true, count });
|
||||
});
|
||||
|
||||
// ─── Auto-approve high-confidence ────────────────────────────────────────────
|
||||
// Approves every pending plan whose original language came from an authoritative
|
||||
// source (radarr/sonarr). Anything with low confidence keeps needing a human.
|
||||
|
||||
Reference in New Issue
Block a user