29 KiB
Drop verify/checkmarks, merge jobs view into item details — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Rip out the post-job verification path entirely (DB column, SSE event, handoff function), delete the standalone /execute page, and surface per-item job info (status, command, log, run/cancel) on the item details page. Batch queue controls move into the Pipeline column headers.
Architecture: Rescan becomes the single source of truth for "is this file still done?" — the verified flag and the Jellyfin refresh handoff are no longer needed. The Jobs page disappears; its per-item info is enriched onto GET /api/review/:id and rendered inline on the details page. Batch controls (Run all, Clear queue, Clear) sit in the existing ColumnShell actions slot.
Tech Stack: Bun + Hono (server), React 19 + TanStack Router (client), bun:sqlite.
File Structure
Backend:
server/db/index.ts— addDROP COLUMN verifiedmigrationserver/db/schema.ts— removeverifiedfromreview_plansDDLserver/services/rescan.ts— removeverifiedfrom INSERT/UPDATE logicserver/api/review.ts— droprp.verifiedfrom pipeline SELECT, dropverified = 0from unapprove, enrichloadItemDetailwith latest jobserver/api/execute.ts— deletehandOffToJellyfin,emitPlanUpdate,POST /verify-unverified,GET /(list endpoint) and the plan_update emissions from job lifecycleserver/types.ts— dropverifiedfromReviewPlan, add job shape on detail responseserver/services/__tests__/webhook.test.ts— delete thewebhook_verified flagdescribe block
Frontend:
src/routes/execute.tsx— delete (file)src/features/execute/ExecutePage.tsx— delete (file)src/shared/lib/types.ts— dropverifiedfromPipelineJobItemandReviewPlan; addjobfield toDetailDatasrc/routes/__root.tsx— removeJobsnav linksrc/features/pipeline/PipelinePage.tsx— removeplan_updateSSE listener, remove theStart queueheader buttonsrc/features/pipeline/DoneColumn.tsx— remove verify button,unverifiedCount,verified/✓✓glyphsrc/features/pipeline/QueueColumn.tsx— addRun all+Clear queueactionssrc/features/review/AudioDetailPage.tsx— add JobSection
Plan ordering rationale: Backend DB migration first (Task 1) so the schema drift from the verified column doesn't break tests. Then server logic deletions (Task 2). Then server additions (Task 3). Frontend follows in dependency order: types → route deletion → column updates → details enrichment.
Task 1: Drop verified column from DB + backend references
Files:
-
Modify:
server/db/index.ts(migration block) -
Modify:
server/db/schema.ts:77 -
Modify:
server/services/rescan.ts:233-270 -
Modify:
server/api/review.ts:330, 773 -
Modify:
server/types.ts(ReviewPlan interface) -
Modify:
server/services/__tests__/webhook.test.ts:186-240 -
Step 1: Add idempotent migration in
server/db/index.ts
Locate the existing block of alter(...) calls (around line 76 where webhook_verified was added and renamed). Append a new call at the end so it runs on the next startup for existing databases:
alter("ALTER TABLE review_plans DROP COLUMN verified");
The alter() helper wraps each statement in try/catch, so on a fresh DB (where verified never existed because we'll remove it from schema.ts) the DROP is a no-op, and on an existing DB it removes the column once.
- Step 2: Remove
verifiedfrom schema.ts
Open server/db/schema.ts. Find the review_plans CREATE TABLE block (around line 77) and delete the line:
verified INTEGER NOT NULL DEFAULT 0,
- Step 3: Remove
verifiedfrom rescan.ts INSERT/UPDATE
Open server/services/rescan.ts around lines 223–272.
First, trim the block comment immediately above the db.prepare(...) call. Delete the paragraph that starts `verified` tracks whether we have independent confirmation... (lines 232–238 in the current file). Keep the "Status transition rules" paragraph above it.
Then replace the INSERT/ON CONFLICT statement and its .run(...) args with the variant that has no verified column:
db
.prepare(`
INSERT INTO review_plans (item_id, status, is_noop, confidence, apple_compat, job_type, notes)
VALUES (?, 'pending', ?, ?, ?, ?, ?)
ON CONFLICT(item_id) DO UPDATE SET
status = CASE
WHEN excluded.is_noop = 1 THEN 'done'
WHEN review_plans.status = 'done' AND ? = 'webhook' THEN 'pending'
WHEN review_plans.status = 'done' THEN 'done'
WHEN review_plans.status = 'error' THEN 'pending'
ELSE review_plans.status
END,
is_noop = excluded.is_noop,
confidence = excluded.confidence,
apple_compat = excluded.apple_compat,
job_type = excluded.job_type,
notes = excluded.notes
`)
.run(
itemId,
analysis.is_noop ? 1 : 0,
confidence,
analysis.apple_compat,
analysis.job_type,
analysis.notes.length > 0 ? analysis.notes.join("\n") : null,
source, // for the CASE WHEN ? = 'webhook' branch
);
Note: the parameter list drops the two verified-related bindings (was analysis.is_noop ? 1 : 0 passed twice for the verified CASE, and the source passed twice). Verify by counting ? placeholders in the SQL (7) matches .run() argument count (7).
- Step 4: Remove
rp.verifiedfrom the pipeline SELECT
Open server/api/review.ts around line 330. In the done query, change:
SELECT j.*, mi.name, mi.series_name, mi.type,
rp.job_type, rp.apple_compat, rp.verified
to:
SELECT j.*, mi.name, mi.series_name, mi.type,
rp.job_type, rp.apple_compat
- Step 5: Remove
verified = 0from unapprove UPDATE
Open server/api/review.ts around line 773. Change:
db.prepare("UPDATE review_plans SET status = 'pending', verified = 0, reviewed_at = NULL WHERE id = ?").run(plan.id);
to:
db.prepare("UPDATE review_plans SET status = 'pending', reviewed_at = NULL WHERE id = ?").run(plan.id);
- Step 6: Remove
verifiedfrom theReviewPlantype
Open server/types.ts. Find the ReviewPlan interface and delete the verified: number; line (around line 68).
- Step 7: Delete the webhook_verified test block
Open server/services/__tests__/webhook.test.ts. Find the block describe("processWebhookEvent — webhook_verified flag", …) starting at line 186 and delete through its closing }); at line 240.
- Step 8: Run the test suite
Run: bun test
Expected: PASS with the full suite green (the remaining webhook tests, analyzer tests, etc.).
If any test fails with "no such column: verified", grep for remaining references:
rg "verified" server/
and remove each occurrence.
- Step 9: Commit
git add server/db/index.ts server/db/schema.ts server/services/rescan.ts server/api/review.ts server/types.ts server/services/__tests__/webhook.test.ts
git commit -m "drop review_plans.verified column and all its references"
Task 2: Delete verification path in server/api/execute.ts
Files:
-
Modify:
server/api/execute.ts -
Step 1: Delete
handOffToJellyfinfunction
Open server/api/execute.ts. Delete the entire handOffToJellyfin function and its JSDoc, spanning roughly lines 28–98 (from the block comment starting /**\n * Post-job verification… through the closing brace of the function).
Also delete the now-unused imports at the top that only this function used:
import { getItem, refreshItem } from "../services/jellyfin";
import { loadRadarrLibrary, radarrUsable } from "../services/radarr";
import { loadSonarrLibrary, sonarrUsable } from "../services/sonarr";
import { upsertJellyfinItem } from "../services/rescan";
import type { RescanConfig } from "../services/rescan";
import { getAllConfig } from "../db";
(Only delete the ones not used elsewhere in the file. Run the TS check in Step 6 to catch any that are still needed.)
- Step 2: Delete
emitPlanUpdatefunction
In the same file, find emitPlanUpdate (around line 183) and delete the function and its block comment (lines 176–186).
- Step 3: Remove calls to
handOffToJellyfinfrom the job lifecycle
There are two call sites at lines 492 and 609, each wrapped in .catch(...). Find both instances that look like:
handOffToJellyfin(job.item_id).catch((err) =>
logError(`handOffToJellyfin for item ${job.item_id} failed:`, err),
);
Delete both blocks entirely.
- Step 4: Delete the
/verify-unverifiedendpoint
In the same file, find and delete the whole block starting with the comment // ─── Verify all unverified done plans ─── and the app.post("/verify-unverified", …) handler below it (approximately lines 357–389).
- Step 5: Delete the
GET /list endpoint
Find the handler mounted at app.get("/", (c) => { ... }) that returns the filtered jobs list (the one used by the Execute page). Delete the whole block including its preceding comment.
To locate: it reads filter from c.req.query("filter"), runs a SELECT joining jobs with media_items, and returns { jobs, filter, totalCounts }.
- Step 6: Run TypeScript compile
Run: bun --bun tsc --noEmit --project tsconfig.server.json
Expected: PASS with no unused import warnings.
If the compiler complains about unused imports, remove them.
- Step 7: Run lint
Run: bun run lint
Expected: PASS.
- Step 8: Commit
git add server/api/execute.ts
git commit -m "rip out jellyfin handoff verification path and verify-unverified endpoint"
Task 3: Enrich loadItemDetail with the latest job
Files:
-
Modify:
server/api/review.ts:111-126 -
Modify:
server/types.ts(add exportedDetailJobshape or similar if helpful) -
Step 1: Add latest-job query to
loadItemDetail
Open server/api/review.ts around line 111. Replace the body with the job enrichment:
function loadItemDetail(db: ReturnType<typeof getDb>, itemId: number) {
const item = db.prepare("SELECT * FROM media_items WHERE id = ?").get(itemId) as MediaItem | undefined;
if (!item) return { item: null, streams: [], plan: null, decisions: [], command: null, job: null };
const streams = db
.prepare("SELECT * FROM media_streams WHERE item_id = ? ORDER BY stream_index")
.all(itemId) as MediaStream[];
const plan = db.prepare("SELECT * FROM review_plans WHERE item_id = ?").get(itemId) as ReviewPlan | undefined | null;
const decisions = plan
? (db.prepare("SELECT * FROM stream_decisions WHERE plan_id = ?").all(plan.id) as StreamDecision[])
: [];
const command = plan && !plan.is_noop ? buildCommand(item, streams, decisions) : null;
const job = db
.prepare(
`SELECT id, item_id, command, job_type, status, output, exit_code,
created_at, started_at, completed_at
FROM jobs WHERE item_id = ? ORDER BY created_at DESC LIMIT 1`,
)
.get(itemId) as Job | undefined;
return { item, streams, plan: plan ?? null, decisions, command, job: job ?? null };
}
Add the Job type import at the top of the file if not already imported:
import type { Job, MediaItem, MediaStream, ReviewPlan, StreamDecision } from "../types";
- Step 2: Run the test suite
Run: bun test
Expected: PASS.
- Step 3: Smoke-test the endpoint manually
Start the server: bun run dev:server
In another terminal:
curl -s http://localhost:3000/api/review/1 | jq '.job'
Expected: either null (no jobs ever) or a job object with the fields above.
Kill the dev server with Ctrl-C after confirming.
- Step 4: Commit
git add server/api/review.ts
git commit -m "enrich GET /api/review/:id with the latest job row"
Task 4: Update client types (drop verified, add job on DetailData)
Files:
-
Modify:
src/shared/lib/types.ts -
Modify:
src/features/review/AudioDetailPage.tsx:13-19(localDetailDatainterface) -
Step 1: Remove
verifiedfromReviewPlan
In src/shared/lib/types.ts, find the ReviewPlan interface (lines 44–56). This client-side type doesn't currently include verified — confirm by reading lines 44–56. If it does, delete the line. If not, skip this sub-step.
- Step 2: Remove
verifiedfromPipelineJobItem
In the same file around line 161, delete:
// 1 when an independent post-hoc check confirms the on-disk file matches
// the plan (ffprobe after a job, or is_noop=1 on the very first scan).
// Renders as the second checkmark in the Done column.
verified?: number;
- Step 3: Update
DetailDatainAudioDetailPage.tsx
Open src/features/review/AudioDetailPage.tsx at line 13 and replace the interface with:
interface DetailData {
item: MediaItem;
streams: MediaStream[];
plan: ReviewPlan | null;
decisions: StreamDecision[];
command: string | null;
job: Job | null;
}
Add Job to the imports at line 9:
import type { Job, MediaItem, MediaStream, ReviewPlan, StreamDecision } from "~/shared/lib/types";
- Step 4: Run lint
Run: bun run lint
Expected: PASS.
- Step 5: Commit
git add src/shared/lib/types.ts src/features/review/AudioDetailPage.tsx
git commit -m "client types: drop verified, add job on DetailData"
Task 5: Delete the Execute page, route, and nav link
Files:
-
Delete:
src/features/execute/ExecutePage.tsx -
Delete:
src/routes/execute.tsx -
Modify:
src/routes/__root.tsx:72 -
Delete (if empty after file removal):
src/features/execute/ -
Step 1: Delete the files
rm src/features/execute/ExecutePage.tsx src/routes/execute.tsx
rmdir src/features/execute 2>/dev/null || true
- Step 2: Remove the
Jobsnav link
Open src/routes/__root.tsx at line 72 and delete:
<NavLink to="/execute">Jobs</NavLink>
- Step 3: Regenerate the TanStack Router tree
The router typegen runs in dev. Start the dev client briefly to regenerate src/routeTree.gen.ts:
Run: bun run dev:client &
Wait 3 seconds for Vite to finish the initial build and regenerate the tree, then kill it:
sleep 3 && kill %1
Alternatively, if TSR has a CLI: bunx @tanstack/router-cli generate. Either works.
- Step 4: Run build to confirm no dangling imports
Run: bun run build
Expected: PASS with no errors about missing /execute route or missing ExecutePage import.
- Step 5: Commit
git add -A src/
git commit -m "delete /execute page, route, and Jobs nav link"
Task 6: Simplify DoneColumn (remove verify button + checkmark glyph)
Files:
-
Modify:
src/features/pipeline/DoneColumn.tsx -
Step 1: Rewrite DoneColumn with the glyph and verify button removed
Replace the entire file contents with:
import { Link } from "@tanstack/react-router";
import { Badge } from "~/shared/components/ui/badge";
import { api } from "~/shared/lib/api";
import type { PipelineJobItem } from "~/shared/lib/types";
import { ColumnShell } from "./ColumnShell";
interface DoneColumnProps {
items: PipelineJobItem[];
onMutate: () => void;
}
export function DoneColumn({ items, onMutate }: DoneColumnProps) {
const clear = async () => {
await api.post("/api/execute/clear-completed");
onMutate();
};
const reopen = async (itemId: number) => {
await api.post(`/api/review/${itemId}/reopen`);
onMutate();
};
const actions = items.length > 0 ? [{ label: "Clear", onClick: clear }] : undefined;
return (
<ColumnShell title="Done" count={items.length} actions={actions}>
{items.map((item) => (
<div key={item.id} className="group rounded border bg-white p-2">
<Link
to="/review/audio/$id"
params={{ id: String(item.item_id) }}
className="text-xs font-medium truncate block hover:text-blue-600 hover:underline"
>
{item.name}
</Link>
<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>
))}
{items.length === 0 && <p className="text-sm text-gray-400 text-center py-8">No completed items</p>}
</ColumnShell>
);
}
- Step 2: Run lint
Run: bun run lint
Expected: PASS.
- Step 3: Commit
git add src/features/pipeline/DoneColumn.tsx
git commit -m "done column: drop checkmark glyph and verify-unverified button"
Task 7: Add batch controls to QueueColumn header, remove Start queue from Pipeline header
Files:
-
Modify:
src/features/pipeline/QueueColumn.tsx -
Modify:
src/features/pipeline/PipelinePage.tsx -
Step 1: Update QueueColumn to expose
Run all+Clear queue
Replace the entire file with:
import { api } from "~/shared/lib/api";
import type { PipelineJobItem } from "~/shared/lib/types";
import { ColumnShell } from "./ColumnShell";
import { PipelineCard } from "./PipelineCard";
interface QueueColumnProps {
items: PipelineJobItem[];
jellyfinUrl: string;
onMutate: () => void;
}
export function QueueColumn({ items, jellyfinUrl, onMutate }: QueueColumnProps) {
const runAll = async () => {
await api.post("/api/execute/start");
onMutate();
};
const clear = async () => {
if (!confirm(`Cancel all ${items.length} pending jobs?`)) return;
await api.post("/api/execute/clear");
onMutate();
};
const unapprove = async (itemId: number) => {
await api.post(`/api/review/${itemId}/unapprove`);
onMutate();
};
const actions =
items.length > 0
? [
{ label: "Run all", onClick: runAll, primary: true },
{ label: "Clear", onClick: clear },
]
: undefined;
return (
<ColumnShell title="Queued" count={items.length} actions={actions}>
<div className="space-y-2">
{items.map((item) => (
<PipelineCard key={item.id} item={item} jellyfinUrl={jellyfinUrl} onUnapprove={() => unapprove(item.item_id)} />
))}
{items.length === 0 && <p className="text-sm text-gray-400 text-center py-8">Queue empty</p>}
</div>
</ColumnShell>
);
}
- Step 2: Remove
Start queuebutton from PipelinePage header
Open src/features/pipeline/PipelinePage.tsx. In the header JSX around line 89–97, delete the Start queue <Button> and the startQueue callback (around lines 34–37). The header should become:
<div className="flex items-center justify-between px-6 py-3 border-b shrink-0">
<h1 className="text-lg font-semibold">Pipeline</h1>
<span className="text-sm text-gray-500">{data.doneCount} files in desired state</span>
</div>
Also remove the Button import at the top of the file if it's no longer used:
import { Button } from "~/shared/components/ui/button";
- Step 3: Run lint
Run: bun run lint
Expected: PASS (no unused-import errors).
- Step 4: Commit
git add src/features/pipeline/QueueColumn.tsx src/features/pipeline/PipelinePage.tsx
git commit -m "pipeline: batch controls move to queued column header"
Task 8: Remove plan_update SSE listener
Files:
-
Modify:
src/features/pipeline/PipelinePage.tsx -
Step 1: Delete the plan_update listener
In src/features/pipeline/PipelinePage.tsx around lines 67–72, delete:
// plan_update lands ~15s after a job finishes — the post-job jellyfin
// verification writes verified=1 (or flips the plan back to pending).
// Without refreshing here the Done column would never promote ✓ to ✓✓.
es.addEventListener("plan_update", () => {
scheduleReload();
});
The other listeners (job_update, job_progress, queue_status) stay untouched.
- Step 2: Run lint
Run: bun run lint
Expected: PASS.
- Step 3: Commit
git add src/features/pipeline/PipelinePage.tsx
git commit -m "pipeline: remove plan_update SSE listener (feature gone)"
Task 9: Add JobSection to AudioDetailPage
Files:
-
Modify:
src/features/review/AudioDetailPage.tsx -
Step 1: Add a
JobSectioncomponent in the same file
Near the bottom of src/features/review/AudioDetailPage.tsx, after the TitleInput component and before the AudioDetailPage export, add:
interface JobSectionProps {
itemId: number;
job: Job;
onMutate: () => void;
}
function JobSection({ itemId, job, onMutate }: JobSectionProps) {
const [showCmd, setShowCmd] = useState(false);
const [showLog, setShowLog] = useState(job.status === "error");
const [liveStatus, setLiveStatus] = useState(job.status);
const [liveOutput, setLiveOutput] = useState(job.output ?? "");
const [progress, setProgress] = useState<{ seconds: number; total: number } | null>(null);
// Keep local state in sync when parent fetches fresh data
useEffect(() => {
setLiveStatus(job.status);
setLiveOutput(job.output ?? "");
}, [job.status, job.output, job.id]);
// Subscribe to SSE for live updates on this specific job id
useEffect(() => {
const es = new EventSource("/api/execute/events");
es.addEventListener("job_update", (e) => {
const d = JSON.parse((e as MessageEvent).data) as { id: number; status: string; output?: string };
if (d.id !== job.id) return;
setLiveStatus(d.status as Job["status"]);
if (d.output !== undefined) setLiveOutput(d.output);
if (d.status === "done" || d.status === "error") onMutate();
});
es.addEventListener("job_progress", (e) => {
const d = JSON.parse((e as MessageEvent).data) as { id: number; seconds: number; total: number };
if (d.id !== job.id) return;
setProgress({ seconds: d.seconds, total: d.total });
});
return () => es.close();
}, [job.id, onMutate]);
const runJob = async () => {
await api.post(`/api/execute/job/${job.id}/run`);
onMutate();
};
const cancelJob = async () => {
await api.post(`/api/execute/job/${job.id}/cancel`);
onMutate();
};
const stopJob = async () => {
await api.post("/api/execute/stop");
onMutate();
};
const typeLabel = job.job_type === "transcode" ? "Audio Transcode" : "Audio Remux";
const exitBadge = job.exit_code != null && job.exit_code !== 0 ? job.exit_code : null;
return (
<div className="mt-6 pt-4 border-t border-gray-200">
<div className="text-gray-400 text-[0.75rem] uppercase tracking-[0.05em] mb-2">Job</div>
<div className="flex items-center gap-2 flex-wrap mb-3">
<Badge variant={liveStatus}>{liveStatus}</Badge>
<Badge variant={job.job_type === "transcode" ? "manual" : "noop"}>{typeLabel}</Badge>
{exitBadge != null && <Badge variant="error">exit {exitBadge}</Badge>}
{job.started_at && (
<span className="text-gray-500 text-[0.72rem]">started {job.started_at}</span>
)}
{job.completed_at && (
<span className="text-gray-500 text-[0.72rem]">completed {job.completed_at}</span>
)}
<div className="flex-1" />
<Button size="sm" variant="secondary" onClick={() => setShowCmd((v) => !v)}>
Cmd
</Button>
{liveOutput && (
<Button size="sm" variant="secondary" onClick={() => setShowLog((v) => !v)}>
Log
</Button>
)}
{liveStatus === "pending" && (
<>
<Button size="sm" onClick={runJob}>
▶ Run
</Button>
<Button size="sm" variant="secondary" onClick={cancelJob}>
✕ Cancel
</Button>
</>
)}
{liveStatus === "running" && (
<Button size="sm" variant="secondary" onClick={stopJob}>
✕ Stop
</Button>
)}
</div>
{liveStatus === "running" && progress && progress.total > 0 && (
<div className="h-1.5 bg-gray-200 rounded mb-3 overflow-hidden">
<div
className="h-full bg-blue-500 transition-[width] duration-500"
style={{ width: `${Math.min(100, (progress.seconds / progress.total) * 100).toFixed(1)}%` }}
/>
</div>
)}
{showCmd && (
<div className="font-mono text-[0.74rem] bg-gray-50 text-gray-700 px-3 py-2 rounded max-h-[120px] overflow-y-auto whitespace-pre-wrap break-all mb-2">
{job.command}
</div>
)}
{showLog && liveOutput && (
<div className="font-mono text-[0.74rem] bg-[#1a1a1a] text-[#d4d4d4] px-3 py-2 rounded max-h-[260px] overflow-y-auto whitespace-pre-wrap break-all">
{liveOutput}
</div>
)}
</div>
);
}
Note: Badge's variant prop must accept each of "pending" | "running" | "done" | "error" | "manual" | "noop". Verify by opening src/shared/components/ui/badge.tsx — these variants already exist per the Execute page's use. If any are missing, add them there.
- Step 2: Render
JobSectioninsideAudioDetailPage
In the same file, in the AudioDetailPage component's JSX, place the JobSection between the FFmpeg command textarea and the Approve/Skip buttons. Locate the existing block around lines 338–348 (the {command && (...)} section with the textarea) and add immediately below it:
{data.job && <JobSection itemId={item.id} job={data.job} onMutate={load} />}
- Step 3: Run lint
Run: bun run lint
Expected: PASS.
- Step 4: Run the dev server and verify manually
Run: bun run dev
Open http://localhost:5173:
- Navigate to an item that has a pending job (approve one from Review, then go to its details page via the Queued card link) → confirm the Job section shows status
pendingand working▶ Run/✕ Cancelbuttons. - Click
▶ Run→ the status badge flips torunningand the progress bar appears. - When the job finishes → status flips to
doneand the Log button becomes available. - Navigate to a done item → confirm Job section shows status
done,CmdandLogtoggles work.
Kill the dev server with Ctrl-C.
- Step 5: Commit
git add src/features/review/AudioDetailPage.tsx
git commit -m "details: surface job status, command, log, and run/cancel inline"
Task 10: Version bump, final build, CalVer commit
Files:
-
Modify:
package.json(version field) -
Step 1: Bump the CalVer version
Today is 2026-04-15. Read the current version in package.json; if it's already 2026.04.15.N, increment N. Otherwise, set it to 2026.04.15.1.
Edit package.json:
"version": "2026.04.15.1"
(Use the next free .N suffix if .1 was already used today.)
- Step 2: Run the full build
Run: bun run build
Expected: PASS — Vite produces dist/ cleanly.
- Step 3: Run tests once more
Run: bun test
Expected: PASS.
- Step 4: Run lint
Run: bun run lint
Expected: PASS.
- Step 5: Commit
git add package.json
git commit -m "v2026.04.15.1 — drop verify/checkmarks, merge jobs view into item details"
Guided Gates (user-verified after deploy)
- GG-1: Done column shows cards with only a
done/errorbadge — no ✓ or ✓✓ glyph. - GG-2: Clicking a Done item → details page shows Job section below the FFmpeg command box, with
CmdandLogtoggles. - GG-3: Clicking a Queued item → details page shows a pending job with working
▶ Runand✕ Cancel; running it updates the badge live. - GG-4:
/executereturns 404 in the browser. - GG-5:
Run all+Clearbuttons appear in the Queued column header;Clearstays in the Done column header; the previousStart queuebutton in the Pipeline page header is gone. - GG-6:
PRAGMA table_info(review_plans);in the SQLite DB no longer listsverified.