Accept a KiCad project drop so hierarchical sheets and the board upload in one step.

A single .kicad_sch was parsed in isolation, so child sheets looked like an empty netlist. Report 404s now surface the real API error instead of a generic fetch failure.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-11 07:26:27 +02:00
co-authored by Cursor
parent b3a47f0bec
commit a56dda2358
18 changed files with 965 additions and 116 deletions
+24 -1
View File
@@ -41,6 +41,7 @@ import {
} from "lucide-react";
import { useOptionalUser } from "@/hooks/use-optional-auth";
import { ImpedancePanel } from "@/components/project/impedance-panel";
import { PcbUploadButton } from "@/components/project/pcb-upload";
export default function ProjectDetailPage({
params,
@@ -190,6 +191,11 @@ export default function ProjectDetailPage({
<h1 className="text-lg font-semibold">{project.name}</h1>
<p className="text-sm text-muted-foreground">
{new Date(project.created).toLocaleDateString()}
{project.hasPcb ? (
<span className="ml-2">· PCB uploaded</span>
) : (
<span className="ml-2">· no PCB</span>
)}
{typeof project.totalCostUsd === "number" && project.totalCostUsd > 0 && (
<span className="ml-2 font-mono tabular-nums text-foreground">
${project.totalCostUsd.toFixed(4)}
@@ -348,7 +354,11 @@ export default function ProjectDetailPage({
)}
{tab === "impedance" && (
<ImpedancePanel projectId={id} hasPcb={Boolean(project.hasPcb)} />
<ImpedancePanel
projectId={id}
hasPcb={Boolean(project.hasPcb)}
onPcbUploaded={reload}
/>
)}
{tab === "logs" && (
@@ -357,6 +367,19 @@ export default function ProjectDetailPage({
{tab === "settings" && (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-sm">KiCad PCB</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<p className="text-sm text-muted-foreground">
{project.hasPcb
? "A .kicad_pcb is on this project. Replace it here, then re-run the pipeline for layout and net Z0."
: "No board yet. Schematic review does not need one. Layout checks and net Z0 do."}
</p>
<PcbUploadButton projectId={id} onUploaded={reload} />
</CardContent>
</Card>
<CollaboratorsSection projectId={id} />
<SkippedComponentsSection skipped={project.skippedComponents} />
<ReportVersionSection pinscopeVersion={project.pinscopeVersion} />
@@ -20,7 +20,7 @@ import {
import { PipelineStepper } from "@/components/progress/pipeline-stepper";
import { PausedRunBanner } from "@/components/billing/paused-run-banner";
import { usePipelineProgress } from "@/hooks/use-pipeline-progress";
import { cancelPipeline, fetchProject, resumePipeline, reprocessPipeline } from "@/lib/api";
import { cancelPipeline, fetchProject, fetchReport, resumePipeline, reprocessPipeline } from "@/lib/api";
import type { PauseCheckpoint } from "@/lib/types";
import {
AlertTriangle,
@@ -107,14 +107,24 @@ export default function ProgressPage({
}
};
// Auto-navigate to report when pipeline completes successfully
// Auto-navigate to report when pipeline completes and the file exists
useEffect(() => {
if (done && !error && !cancelled && !projectPaused) {
const timer = setTimeout(() => {
router.push(`/project/${id}/report`);
}, 1500);
return () => clearTimeout(timer);
}
if (!done || error || cancelled || projectPaused) return;
let stopped = false;
(async () => {
for (let i = 0; i < 8; i++) {
try {
await fetchReport(id);
if (!stopped) router.push(`/project/${id}/report`);
return;
} catch {
await new Promise((r) => setTimeout(r, 500));
}
}
})();
return () => {
stopped = true;
};
}, [done, error, cancelled, projectPaused, id, router]);
// Auto-navigate to dashboard when pipeline is cancelled
@@ -220,8 +220,18 @@ function ReportContent({ projectId }: { projectId: string }) {
if (error || !report || !graph) {
return (
<div className="p-6 max-w-5xl mx-auto w-full text-center py-12 text-sm text-muted-foreground">
{error ?? "Report not found."}
<div className="p-6 max-w-5xl mx-auto w-full text-center py-12 space-y-3">
<p className="text-sm text-muted-foreground">
{error ?? "Report not found."}
</p>
<p className="text-sm">
<a
href={`/project/${projectId}/progress`}
className="text-blue-600 dark:text-blue-500 hover:underline"
>
Open pipeline progress
</a>
</p>
</div>
);
}