Show BOM, schematic, and PCB as three upload boxes on the first wizard screen.
The extra board step made the flow look like only two files were required. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
What's new in Pinscope.
|
What's new in Pinscope.
|
||||||
|
|
||||||
|
## 2.26.2 — 2026-09-11 — Three file boxes
|
||||||
|
|
||||||
|
New project: BOM, schematic, and PCB on the first screen. PCB is optional.
|
||||||
|
|
||||||
|
- [Changed] No extra wizard step for the board.
|
||||||
|
|
||||||
## 2.26.1 — 2026-09-11 — Ask for the KiCad board
|
## 2.26.1 — 2026-09-11 — Ask for the KiCad board
|
||||||
|
|
||||||
The create-project wizard has a dedicated PCB step (skip still allowed). Report load errors show the API detail instead of a generic fetch failure.
|
The create-project wizard has a dedicated PCB step (skip still allowed). Report load errors show the API detail instead of a generic fetch failure.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "2.26.1",
|
"version": "2.26.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"sync-version": "node scripts/sync-version.mjs",
|
"sync-version": "node scripts/sync-version.mjs",
|
||||||
|
|||||||
@@ -352,7 +352,6 @@ function summarizeLcscModel(model: Record<string, unknown>): string {
|
|||||||
|
|
||||||
type WizardStep =
|
type WizardStep =
|
||||||
| "details"
|
| "details"
|
||||||
| "pcb"
|
|
||||||
| "columns"
|
| "columns"
|
||||||
| "subdesigns"
|
| "subdesigns"
|
||||||
| "lcsc-passives"
|
| "lcsc-passives"
|
||||||
@@ -362,7 +361,6 @@ type WizardStep =
|
|||||||
|
|
||||||
const ALL_STEPS: { key: WizardStep; label: string }[] = [
|
const ALL_STEPS: { key: WizardStep; label: string }[] = [
|
||||||
{ key: "details", label: "Project Details" },
|
{ key: "details", label: "Project Details" },
|
||||||
{ key: "pcb", label: "Board (optional)" },
|
|
||||||
{ key: "columns", label: "BOM Columns" },
|
{ key: "columns", label: "BOM Columns" },
|
||||||
{ key: "subdesigns", label: "Sub-designs" },
|
{ key: "subdesigns", label: "Sub-designs" },
|
||||||
{ key: "lcsc-passives", label: "Resolving Passive Specs" },
|
{ key: "lcsc-passives", label: "Resolving Passive Specs" },
|
||||||
@@ -592,9 +590,6 @@ export function CreateProjectDialog({
|
|||||||
const [netlistFiles, setNetlistFiles] = useState<File[]>([]);
|
const [netlistFiles, setNetlistFiles] = useState<File[]>([]);
|
||||||
const netlistFile = netlistFiles[0] ?? null;
|
const netlistFile = netlistFiles[0] ?? null;
|
||||||
const [pcbFile, setPcbFile] = useState<File | null>(null);
|
const [pcbFile, setPcbFile] = useState<File | null>(null);
|
||||||
const skipPcbStep =
|
|
||||||
Boolean(pcbFile) ||
|
|
||||||
netlistFiles.some((f) => f.name.toLowerCase().endsWith(".zip"));
|
|
||||||
const [netlistNetCount, setNetlistNetCount] = useState<number | null>(null);
|
const [netlistNetCount, setNetlistNetCount] = useState<number | null>(null);
|
||||||
const [netlistError, setNetlistError] = useState<string | null>(null);
|
const [netlistError, setNetlistError] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -885,7 +880,6 @@ export function CreateProjectDialog({
|
|||||||
// lcsc-passives drains to zero before the auto-advance effect
|
// lcsc-passives drains to zero before the auto-advance effect
|
||||||
// moves us off the step on the next tick).
|
// moves us off the step on the next tick).
|
||||||
if (s.key === step) return true;
|
if (s.key === step) return true;
|
||||||
if (s.key === "pcb") return !skipPcbStep;
|
|
||||||
if (s.key === "subdesigns") return hasSubdesignChoice;
|
if (s.key === "subdesigns") return hasSubdesignChoice;
|
||||||
if (s.key === "lcsc-passives") return hasLcscPassives;
|
if (s.key === "lcsc-passives") return hasLcscPassives;
|
||||||
if (s.key === "datasheets") return hasIcs;
|
if (s.key === "datasheets") return hasIcs;
|
||||||
@@ -893,7 +887,7 @@ export function CreateProjectDialog({
|
|||||||
if (s.key === "passives") return hasPassives;
|
if (s.key === "passives") return hasPassives;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}, [step, skipPcbStep, hasSubdesignChoice, hasLcscPassives, hasIcs, hasSimple, hasPassives]);
|
}, [step, hasSubdesignChoice, hasLcscPassives, hasIcs, hasSimple, hasPassives]);
|
||||||
|
|
||||||
const stepIndex = activeSteps.findIndex((s) => s.key === step);
|
const stepIndex = activeSteps.findIndex((s) => s.key === step);
|
||||||
|
|
||||||
@@ -928,9 +922,8 @@ export function CreateProjectDialog({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleNetlistChange = useCallback((incoming: File[]) => {
|
const handleNetlistChange = useCallback((incoming: File[]) => {
|
||||||
const { netlist, pcb, bom } = splitProjectFiles(incoming);
|
const { netlist, pcb } = splitProjectFiles(incoming);
|
||||||
if (pcb[0]) setPcbFile(pcb[0]);
|
if (pcb[0]) setPcbFile(pcb[0]);
|
||||||
if (bom[0]) handleBomChange([bom[0]]);
|
|
||||||
const files = netlist;
|
const files = netlist;
|
||||||
const file = files[0] || null;
|
const file = files[0] || null;
|
||||||
setNetlistFiles(files);
|
setNetlistFiles(files);
|
||||||
@@ -976,7 +969,7 @@ export function CreateProjectDialog({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [handleBomChange]);
|
}, []);
|
||||||
|
|
||||||
const resetAndClose = useCallback(() => {
|
const resetAndClose = useCallback(() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -1746,7 +1739,6 @@ export function CreateProjectDialog({
|
|||||||
|
|
||||||
const canAdvance = (): boolean => {
|
const canAdvance = (): boolean => {
|
||||||
if (step === "details") return !!(name.trim() && bomFile && netlistFile && !netlistError);
|
if (step === "details") return !!(name.trim() && bomFile && netlistFile && !netlistError);
|
||||||
if (step === "pcb") return true;
|
|
||||||
if (step === "columns") return !!(refCol && mpnCol);
|
if (step === "columns") return !!(refCol && mpnCol);
|
||||||
if (step === "subdesigns")
|
if (step === "subdesigns")
|
||||||
return !!(selectedSubdesignIds && selectedSubdesignIds.size > 0);
|
return !!(selectedSubdesignIds && selectedSubdesignIds.size > 0);
|
||||||
@@ -1787,9 +1779,6 @@ export function CreateProjectDialog({
|
|||||||
const res = await runEarlyNetlistUpload();
|
const res = await runEarlyNetlistUpload();
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
}
|
}
|
||||||
setStep(skipPcbStep ? "columns" : "pcb");
|
|
||||||
}
|
|
||||||
else if (step === "pcb") {
|
|
||||||
setStep("columns");
|
setStep("columns");
|
||||||
}
|
}
|
||||||
else if (step === "columns") {
|
else if (step === "columns") {
|
||||||
@@ -1867,8 +1856,7 @@ export function CreateProjectDialog({
|
|||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
setError(null);
|
setError(null);
|
||||||
if (step === "pcb") setStep("details");
|
if (step === "columns") setStep("details");
|
||||||
else if (step === "columns") setStep(skipPcbStep ? "details" : "pcb");
|
|
||||||
else if (step === "subdesigns") setStep("columns");
|
else if (step === "subdesigns") setStep("columns");
|
||||||
else if (step === "lcsc-passives") {
|
else if (step === "lcsc-passives") {
|
||||||
if (hasSubdesignChoice) setStep("subdesigns");
|
if (hasSubdesignChoice) setStep("subdesigns");
|
||||||
@@ -2046,7 +2034,7 @@ export function CreateProjectDialog({
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
)}
|
)}
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="sm:max-w-2xl"
|
className="sm:max-w-3xl"
|
||||||
showCloseButton={!creating}
|
showCloseButton={!creating}
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
@@ -2124,7 +2112,7 @@ export function CreateProjectDialog({
|
|||||||
onKeyDown={(e) => e.key === "Enter" && canAdvance() && goNext()}
|
onKeyDown={(e) => e.key === "Enter" && canAdvance() && goNext()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<FileUploadZone
|
<FileUploadZone
|
||||||
label="BOM"
|
label="BOM"
|
||||||
@@ -2133,15 +2121,7 @@ export function CreateProjectDialog({
|
|||||||
onFilesChange={handleBomChange}
|
onFilesChange={handleBomChange}
|
||||||
/>
|
/>
|
||||||
<p className="text-[11px] text-muted-foreground leading-tight px-1">
|
<p className="text-[11px] text-muted-foreground leading-tight px-1">
|
||||||
CSV or Excel.{" "}
|
.csv or .xlsx
|
||||||
<a
|
|
||||||
href="/file-guide#the-bom"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-blue-600 dark:text-blue-500 hover:underline"
|
|
||||||
>
|
|
||||||
How to export →
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
@@ -2159,48 +2139,36 @@ export function CreateProjectDialog({
|
|||||||
</p>
|
</p>
|
||||||
) : netlistNetCount !== null ? (
|
) : netlistNetCount !== null ? (
|
||||||
<p className="text-[11px] text-muted-foreground leading-tight px-1">
|
<p className="text-[11px] text-muted-foreground leading-tight px-1">
|
||||||
{netlistNetCount} nets detected
|
{netlistNetCount} nets
|
||||||
</p>
|
|
||||||
) : netlistFiles.length > 0 ? (
|
|
||||||
<p className="text-[11px] text-muted-foreground leading-tight px-1">
|
|
||||||
{pcbFile
|
|
||||||
? "Board included."
|
|
||||||
: netlistFiles.length > 1
|
|
||||||
? `${netlistFiles.length} schematic files.`
|
|
||||||
: "Ready."}
|
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-[11px] text-muted-foreground leading-tight px-1">
|
<p className="text-[11px] text-muted-foreground leading-tight px-1">
|
||||||
Drop the KiCad folder, or one netlist.{" "}
|
Netlist or .kicad_sch
|
||||||
<a
|
|
||||||
href="/file-guide#the-netlist"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-blue-600 dark:text-blue-500 hover:underline"
|
|
||||||
>
|
|
||||||
How to export →
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<FileUploadZone
|
||||||
|
label="PCB"
|
||||||
|
accept=".kicad_pcb"
|
||||||
|
files={pcbFile ? [pcbFile] : []}
|
||||||
|
onFilesChange={(files) => {
|
||||||
|
const picked = splitProjectFiles(files).pcb[0] ?? files[0] ?? null;
|
||||||
|
setPcbFile(
|
||||||
|
picked?.name.toLowerCase().endsWith(".kicad_pcb")
|
||||||
|
? picked
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p className="text-[11px] text-muted-foreground leading-tight px-1">
|
||||||
|
Optional .kicad_pcb
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{step === "pcb" && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Optional. Skip if you only want schematic review.
|
|
||||||
</p>
|
|
||||||
<FileUploadZone
|
|
||||||
label="KiCad board (.kicad_pcb)"
|
|
||||||
accept=".kicad_pcb"
|
|
||||||
files={pcbFile ? [pcbFile] : []}
|
|
||||||
onFilesChange={(files) => setPcbFile(files[0] ?? null)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ---- Step 2: BOM Columns ---- */}
|
{/* ---- Step 2: BOM Columns ---- */}
|
||||||
{step === "columns" && csvData && (
|
{step === "columns" && csvData && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// AUTO-GENERATED by scripts/sync-version.mjs from content/changelog.md.
|
// AUTO-GENERATED by scripts/sync-version.mjs from content/changelog.md.
|
||||||
// Do not edit by hand — change the top "## X.Y.Z" heading in the changelog.
|
// Do not edit by hand — change the top "## X.Y.Z" heading in the changelog.
|
||||||
export const APP_VERSION = "2.26.1";
|
export const APP_VERSION = "2.26.2";
|
||||||
export const APP_VERSION_DATE = "2026-09-11";
|
export const APP_VERSION_DATE = "2026-09-11";
|
||||||
|
|||||||
Reference in New Issue
Block a user