feat(tracker-web): factory dropdown routes job to the selected factory's product

New Job form: pick a Factory (2 hardcoded for this machine) -> the job is submitted
to that factory's product (submitJob productId override) and the dashboard view
switches to it so the job is visible. Confirmation shows factory + product.

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-05-31 06:28:28 -07:00
parent c239abeec9
commit e9c1714c13
2 changed files with 57 additions and 19 deletions

View File

@ -48,6 +48,13 @@ const FLEET_REPOS = [
];
const FLEET_BASE_BRANCH = 'main';
// Factories set up on this machine. Selecting one routes the job to that factory's
// product (the factory polling that product claims it).
const FLEET_FACTORIES = [
{ id: 'mac-1', label: 'mac-1 — LysnrAI (autoship + PR)', productId: 'lysnrai' },
{ id: 'mac-2', label: 'mac-2 — ChronoMind (human gate + PR)', productId: 'chronomind' },
];
export default function FleetJobsPage() {
const { token } = useAuth();
const [jobs, setJobs] = useState<FleetJob[]>([]);
@ -56,6 +63,7 @@ export default function FleetJobsPage() {
// New-job form state
const [showForm, setShowForm] = useState(false);
const [factoryId, setFactoryId] = useState(FLEET_FACTORIES[0].id);
const [body, setBody] = useState('');
const [priority, setPriority] = useState<'critical' | 'high' | 'medium' | 'low'>('high');
const [caps, setCaps] = useState('build');
@ -97,24 +105,32 @@ export default function FleetJobsPage() {
.split(',')
.map(c => c.trim())
.filter(Boolean);
const { job } = await submitJob({
idempotencyKey: `ui-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
bodyMd: body.trim(),
priority,
capabilities,
...(repo
? {
repo,
baseBranch: FLEET_BASE_BRANCH,
...(verifyCmd.trim() ? { verify: verifyCmd.trim() } : {}),
...(autoMerge ? { autoMerge: true } : {}),
}
: {}),
});
const factory = FLEET_FACTORIES.find(f => f.id === factoryId) ?? FLEET_FACTORIES[0];
const { job } = await submitJob(
{
idempotencyKey: `ui-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
bodyMd: body.trim(),
priority,
capabilities,
...(repo
? {
repo,
baseBranch: FLEET_BASE_BRANCH,
...(verifyCmd.trim() ? { verify: verifyCmd.trim() } : {}),
...(autoMerge ? { autoMerge: true } : {}),
}
: {}),
},
factory.productId
);
// Route the dashboard view to the factory's product so the job is visible.
if (typeof window !== 'undefined') {
localStorage.setItem('tracker_selected_product', factory.productId);
}
setSubmitMsg({
ok: true,
text:
`Submitted ${job.id} (stage: ${job.stage})` +
`Submitted ${job.id} to ${factory.id} (${factory.productId}, stage: ${job.stage})` +
(repo
? ` — PR mode: ${repo}@${FLEET_BASE_BRANCH}${verifyCmd.trim() ? ' +verify' : ''}${autoMerge ? ' +auto-merge' : ''}`
: ' — no PR (plain job)') +
@ -128,7 +144,7 @@ export default function FleetJobsPage() {
} finally {
setSubmitting(false);
}
}, [body, caps, priority, repo, verifyCmd, autoMerge, refresh]);
}, [body, caps, priority, repo, verifyCmd, autoMerge, factoryId, refresh]);
return (
<div className="p-6 space-y-6">
@ -147,6 +163,23 @@ export default function FleetJobsPage() {
</button>
{showForm && (
<div className="space-y-3 border-t px-4 py-4">
<div>
<label htmlFor="job-factory" className="mb-1 block text-sm font-medium">
Factory
</label>
<select
id="job-factory"
value={factoryId}
onChange={e => setFactoryId(e.target.value)}
className="rounded border bg-background px-2 py-1 text-sm"
>
{FLEET_FACTORIES.map(f => (
<option key={f.id} value={f.id}>
{f.label}
</option>
))}
</select>
</div>
<div>
<label htmlFor="job-body" className="mb-1 block text-sm font-medium">
Task (markdown)

View File

@ -207,9 +207,14 @@ export interface SubmitJobBody {
autoMerge?: boolean;
}
/** Submit a new fleet job. Returns the created job. */
export async function submitJob(body: SubmitJobBody): Promise<{ job: FleetJob }> {
return apiFetch(`/jobs`, { method: 'POST', body: JSON.stringify(body) });
/** Submit a new fleet job. Optionally target a specific product (factory's product),
* overriding the dashboard's selected product for this submission. */
export async function submitJob(
body: SubmitJobBody,
productId?: string
): Promise<{ job: FleetJob }> {
const headers = productId ? { 'x-product-id': productId } : undefined;
return apiFetch(`/jobs`, { method: 'POST', body: JSON.stringify(body), headers });
}
/** WIP checkpoint a factory carries across lease re-assignments (server schema). */