done column: hover 'back to review' to re-queue a done/errored plan
All checks were successful
Build and Push Docker Image / build (push) Successful in 45s
All checks were successful
Build and Push Docker Image / build (push) Successful in 45s
adds POST /api/review/:id/reopen that flips done or errored plans back to pending, clears the lingering job row, resets verified=0, and keeps the prior ffmpeg error summary in the plan's notes so the user has context for redeciding. done column cards grow a hover-only '← back to review' button next to the status badge — works identically for both the ✓/✓✓ and the ✗ rows, since the server accepts either. also hid the existing queue card's back-to-review behind the same hover affordance so the two columns behave consistently and the cards stay visually calm when not hovered. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "netfelix-audio-fix",
|
"name": "netfelix-audio-fix",
|
||||||
"version": "2026.04.14.21",
|
"version": "2026.04.14.22",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:server": "NODE_ENV=development bun --hot server/index.tsx",
|
"dev:server": "NODE_ENV=development bun --hot server/index.tsx",
|
||||||
"dev:client": "vite",
|
"dev:client": "vite",
|
||||||
|
|||||||
@@ -752,6 +752,30 @@ app.post("/:id/retry", (c) => {
|
|||||||
return c.json({ ok: true });
|
return c.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Reopen a completed or errored plan: flip it back to pending so the user
|
||||||
|
// can adjust decisions and re-approve. Used by the Done column's hover
|
||||||
|
// "Back to review" affordance. Unlike /unapprove (which rolls back an
|
||||||
|
// approved-but-not-yet-running plan), this handles the post-job states
|
||||||
|
// and drops the lingering job row so the pipeline doesn't show leftover
|
||||||
|
// history for an item that's about to be re-queued.
|
||||||
|
app.post("/:id/reopen", (c) => {
|
||||||
|
const db = getDb();
|
||||||
|
const id = parseId(c.req.param("id"));
|
||||||
|
if (id == null) return c.json({ error: "invalid id" }, 400);
|
||||||
|
const plan = db.prepare("SELECT * FROM review_plans WHERE item_id = ?").get(id) as ReviewPlan | undefined;
|
||||||
|
if (!plan) return c.notFound();
|
||||||
|
if (plan.status !== "done" && plan.status !== "error") {
|
||||||
|
return c.json({ ok: false, error: "Can only reopen plans with status done or error" }, 409);
|
||||||
|
}
|
||||||
|
db.transaction(() => {
|
||||||
|
// Leave plan.notes alone so the user keeps any ffmpeg error summary
|
||||||
|
// from the prior run — useful context when redeciding decisions.
|
||||||
|
db.prepare("UPDATE review_plans SET status = 'pending', verified = 0, reviewed_at = NULL WHERE id = ?").run(plan.id);
|
||||||
|
db.prepare("DELETE FROM jobs WHERE item_id = ? AND status IN ('done', 'error')").run(id);
|
||||||
|
})();
|
||||||
|
return c.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
app.post("/:id/unapprove", (c) => {
|
app.post("/:id/unapprove", (c) => {
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
const id = parseId(c.req.param("id"));
|
const id = parseId(c.req.param("id"));
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ export function DoneColumn({ items, onMutate }: DoneColumnProps) {
|
|||||||
onMutate();
|
onMutate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reopen = async (itemId: number) => {
|
||||||
|
await api.post(`/api/review/${itemId}/reopen`);
|
||||||
|
onMutate();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColumnShell
|
<ColumnShell
|
||||||
title="Done"
|
title="Done"
|
||||||
@@ -30,7 +35,7 @@ export function DoneColumn({ items, onMutate }: DoneColumnProps) {
|
|||||||
? "Done — awaiting post-job verification"
|
? "Done — awaiting post-job verification"
|
||||||
: "Error";
|
: "Error";
|
||||||
return (
|
return (
|
||||||
<div key={item.id} className="rounded border bg-white p-2">
|
<div key={item.id} className="group rounded border bg-white p-2">
|
||||||
<div className="flex items-start gap-1.5">
|
<div className="flex items-start gap-1.5">
|
||||||
<span
|
<span
|
||||||
title={markTitle}
|
title={markTitle}
|
||||||
@@ -48,7 +53,18 @@ export function DoneColumn({ items, onMutate }: DoneColumnProps) {
|
|||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
<Badge variant={item.status === "done" ? "done" : "error"}>{item.status}</Badge>
|
<div className="flex items-center gap-1.5 mt-0.5">
|
||||||
|
<Badge variant={item.status === "done" ? "done" : "error"}>{item.status}</Badge>
|
||||||
|
<div className="flex-1" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => reopen(item.item_id)}
|
||||||
|
title="Send this item back to the Review column to redecide and re-queue"
|
||||||
|
className="text-[0.68rem] px-1.5 py-0.5 rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
|
||||||
|
>
|
||||||
|
← Back to review
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export function PipelineCard({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onUnapprove}
|
onClick={onUnapprove}
|
||||||
className="text-xs px-3 py-1 rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100"
|
className="text-xs px-3 py-1 rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
>
|
>
|
||||||
← Back to review
|
← Back to review
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user