feat(tracker-web): show job prompt + PR/target config on the detail page

The fleet job detail page never rendered the prompt (bodyMd) or the repo/
verify/auto-merge/capabilities/deps config. Add a Prompt card (verbatim body,
scrollable) + a target/config grid, with a read-only badge once the job leaves
queued/draft (a factory may already be acting on it). Expose verify/autoMerge/
deps on the FleetJob client type.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
saravanakumardb1 2026-06-01 00:29:29 -07:00
parent 78c4e47460
commit cb4f7a7606
2 changed files with 65 additions and 0 deletions

View File

@ -255,6 +255,9 @@ export default function FleetJobDetailPage() {
<MetaCard label="Attempts" value={String(job.attempts)} />
</section>
{/* Prompt (the job body) + PR/target config */}
<PromptCard job={job} />
{/* Review gate (multi-reviewer human gate) */}
{job.stage === 'review' && (
<ReviewGateCard
@ -497,6 +500,62 @@ function MetaCard({ label, value }: { label: string; value: string }) {
);
}
/**
* The job's prompt (verbatim `bodyMd`) + its PR/target config. The prompt is only
* mutable before a factory picks the job up; once it leaves `queued`/`draft` it is
* locked (a worker may already be acting on it) so it renders read-only.
*/
function PromptCard({ job }: { job: FleetJob }) {
const locked = job.stage !== 'queued' && job.stage !== 'draft';
const cfg: Array<[string, string | undefined]> = [
['Repo', job.repo],
['Base branch', job.repo ? (job.baseBranch ?? 'main') : undefined],
['Verify', job.verify],
['Auto-merge', job.repo ? (job.autoMerge ? 'yes' : 'no') : undefined],
['Capabilities', job.capabilities?.length ? job.capabilities.join(', ') : undefined],
['Deps', job.deps?.length ? job.deps.join(', ') : undefined],
['Idempotency key', job.idempotencyKey],
];
const shown = cfg.filter(([, v]) => v != null && v !== '');
return (
<section className="space-y-3" aria-label="Prompt and target">
<div className="flex items-center gap-2">
<h2 className="text-lg font-semibold">Prompt</h2>
<span
className={`rounded-full px-2 py-0.5 text-xs font-medium ${
locked
? 'bg-muted text-muted-foreground'
: 'bg-blue-500/15 text-blue-700 dark:text-blue-300'
}`}
title={
locked
? 'The job has been picked up — its prompt is locked.'
: 'Still queued — not yet picked up by a factory.'
}
>
{locked ? 'read-only — picked up' : 'queued — not yet picked up'}
</span>
</div>
<pre
className="max-h-96 overflow-auto whitespace-pre-wrap rounded-lg border bg-muted/30 p-3 text-sm font-mono"
aria-label="Job prompt body"
>
{job.bodyMd?.trim() ? job.bodyMd : 'No prompt body.'}
</pre>
{shown.length > 0 && (
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{shown.map(([k, v]) => (
<div key={k} className="rounded-lg border p-3">
<p className="text-xs text-muted-foreground">{k}</p>
<p className="text-sm font-medium break-words font-mono">{v}</p>
</div>
))}
</div>
)}
</section>
);
}
/** Compact stat chip for the per-job cost/token/time totals. */
function Stat({ label, value }: { label: string; value: string }) {
return (

View File

@ -27,6 +27,12 @@ export interface FleetJob {
reviewDecisions?: ReviewDecision[];
repo?: string;
baseBranch?: string;
/** PR mode: verify command run in the checkout before the PR opens. */
verify?: string;
/** PR mode: squash-merge the PR automatically when verify passes. */
autoMerge?: boolean;
/** Job dependencies (idempotency keys this job is gated on). */
deps?: string[];
}
export interface FleetFactory {