Show cached passives in the library and match IC PDFs by family MPN.

Decode EIA/Yageo/AVX chip codes into library specs, and find datasheets when the file is stored under the orderable catalog name.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-28 02:27:27 +02:00
co-authored by Cursor
parent 3be7d2fc21
commit 08cbbdc422
11 changed files with 534 additions and 80 deletions
+70 -2
View File
@@ -632,7 +632,11 @@ function ComponentsPanel() {
if (!prev) return prev;
if (type === "ic") return { ...prev, ics: prev.ics.filter((c) => c.mpn !== name) };
if (type === "passive") return { ...prev, passives: prev.passives.filter((c) => c.mpn !== name) };
return { ...prev, simple: prev.simple.filter((c) => c.mpn !== name) };
return {
...prev,
simple: prev.simple.filter((c) => c.mpn !== name),
passive_parts: (prev.passive_parts ?? []).filter((c) => c.mpn !== name),
};
});
reload();
} catch (e) {
@@ -678,6 +682,12 @@ function ComponentsPanel() {
c.subtype.toLowerCase().includes(lf) ||
c.description.toLowerCase().includes(lf),
);
const filteredPassiveParts = (data.passive_parts ?? []).filter(
(c) =>
c.mpn.toLowerCase().includes(lf) ||
c.subtype.toLowerCase().includes(lf) ||
c.specs_type.toLowerCase().includes(lf),
);
const filteredSimple = (data.simple ?? []).filter(
(c) =>
c.mpn.toLowerCase().includes(lf) ||
@@ -699,6 +709,13 @@ function ComponentsPanel() {
<span className="font-medium">{data.passives.length}</span>
<span className="text-muted-foreground">Passive Patterns</span>
</div>
{(data.passive_parts?.length ?? 0) > 0 && (
<div className="flex items-center gap-1.5 text-sm">
<Zap className="h-4 w-4 text-amber-600 dark:text-amber-400" />
<span className="font-medium">{data.passive_parts.length}</span>
<span className="text-muted-foreground">Passive MPNs</span>
</div>
)}
{(data.simple?.length ?? 0) > 0 && (
<div className="flex items-center gap-1.5 text-sm">
<Zap className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
@@ -827,6 +844,57 @@ function ComponentsPanel() {
</div>
)}
{/* Per-MPN passive specs */}
{filteredPassiveParts.length > 0 && (
<div>
<h2 className="text-sm font-medium mb-2">Passive MPNs</h2>
<div className="rounded-lg border border-border overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/50 text-muted-foreground">
<th className="text-left px-3 py-2 font-medium">MPN</th>
<th className="text-left px-3 py-2 font-medium">Type</th>
<th className="text-left px-3 py-2 font-medium">Subtype</th>
<th className="text-center px-3 py-2 font-medium">Params</th>
<th className="w-10 px-3 py-2" />
</tr>
</thead>
<tbody className="divide-y divide-border">
{filteredPassiveParts.map((s) => (
<tr key={s.mpn} className="hover:bg-muted/30 cursor-pointer" onClick={() => handleRowClick("simple", s.mpn)}>
<td className="px-3 py-2 font-mono text-xs">{s.mpn}</td>
<td className="px-3 py-2">
<Badge variant="secondary">{s.specs_type || "passive"}</Badge>
</td>
<td className="px-3 py-2">
{s.subtype ? (
<Badge variant="outline">{s.subtype}</Badge>
) : (
<span className="text-muted-foreground">-</span>
)}
</td>
<td className="px-3 py-2 text-center font-mono">{s.param_count}</td>
<td className="px-3 py-2 text-center">
<button
onClick={(e) => { e.stopPropagation(); handleDelete("simple", s.mpn); }}
disabled={deleting === s.mpn}
className="text-muted-foreground hover:text-destructive transition-colors disabled:opacity-50"
>
{deleting === s.mpn ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<Trash2 className="h-3.5 w-3.5" />
)}
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Simple component specs table */}
{filteredSimple.length > 0 && (
<div>
@@ -878,7 +946,7 @@ function ComponentsPanel() {
</div>
)}
{filteredICs.length === 0 && filteredPassives.length === 0 && filteredSimple.length === 0 && (
{filteredICs.length === 0 && filteredPassives.length === 0 && filteredPassiveParts.length === 0 && filteredSimple.length === 0 && (
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
<Package className="h-8 w-8 mb-2" />
<p className="text-sm">
+123 -35
View File
@@ -55,6 +55,17 @@ export default function LibraryPage() {
),
[data, lf],
);
const passiveParts = useMemo(
() =>
(data?.passive_parts ?? []).filter(
(c) =>
!lf ||
c.mpn.toLowerCase().includes(lf) ||
c.subtype.toLowerCase().includes(lf) ||
c.specs_type.toLowerCase().includes(lf),
),
[data, lf],
);
const simple = useMemo(
() =>
(data?.simple ?? []).filter(
@@ -108,6 +119,7 @@ export default function LibraryPage() {
!data ||
(data.ics.length === 0 &&
data.passives.length === 0 &&
(data.passive_parts ?? []).length === 0 &&
data.simple.length === 0 &&
data.datasheets.length === 0);
@@ -143,8 +155,15 @@ export default function LibraryPage() {
</span>
<span className="flex items-center gap-1.5">
<Zap className="h-4 w-4 text-amber-600 dark:text-amber-400" />
<span className="font-medium">
{(data.passive_parts ?? []).length}
</span>
<span className="text-muted-foreground">passives</span>
</span>
<span className="flex items-center gap-1.5">
<Zap className="h-4 w-4 text-amber-600/70 dark:text-amber-400/70" />
<span className="font-medium">{data.passives.length}</span>
<span className="text-muted-foreground">passive series</span>
<span className="text-muted-foreground">series</span>
</span>
<span className="flex items-center gap-1.5">
<Zap className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
@@ -169,7 +188,7 @@ export default function LibraryPage() {
<TabsList>
<TabsTrigger value="ics">ICs ({ics.length})</TabsTrigger>
<TabsTrigger value="passives">
Passives ({passives.length})
Passives ({passiveParts.length + passives.length})
</TabsTrigger>
<TabsTrigger value="simple">
Discrete ({simple.length})
@@ -225,40 +244,109 @@ export default function LibraryPage() {
)}
</TabsContent>
<TabsContent value="passives" className="pt-4">
{passives.length === 0 ? (
<EmptyFilter label="passive series" />
<TabsContent value="passives" className="pt-4 space-y-6">
{passiveParts.length === 0 && passives.length === 0 ? (
<EmptyFilter label="passives" />
) : (
<div className="rounded-lg border border-border overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/50 text-muted-foreground">
<th className="text-left px-3 py-2 font-medium">Series</th>
<th className="text-left px-3 py-2 font-medium">Type</th>
<th className="text-left px-3 py-2 font-medium">
Description
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{passives.map((p) => (
<tr key={p.mpn} className="hover:bg-muted/30">
<td className="px-3 py-2 font-mono text-xs">{p.mpn}</td>
<td className="px-3 py-2">
{p.subtype ? (
<Badge variant="secondary">{p.subtype}</Badge>
) : (
<span className="text-muted-foreground"></span>
)}
</td>
<td className="px-3 py-2 text-muted-foreground">
{p.description || "—"}
</td>
</tr>
))}
</tbody>
</table>
</div>
<>
{passiveParts.length > 0 && (
<div className="rounded-lg border border-border overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/50 text-muted-foreground">
<th className="text-left px-3 py-2 font-medium">
MPN
</th>
<th className="text-left px-3 py-2 font-medium">
Type
</th>
<th className="text-center px-3 py-2 font-medium">
Params
</th>
<th className="text-left px-3 py-2 font-medium">
PDF
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{passiveParts.map((p) => (
<tr key={p.mpn} className="hover:bg-muted/30">
<td className="px-3 py-2 font-mono text-xs">
{p.mpn}
</td>
<td className="px-3 py-2">
<Badge variant="secondary">
{p.subtype || p.specs_type || "passive"}
</Badge>
</td>
<td className="px-3 py-2 text-center">
{p.param_count}
</td>
<td className="px-3 py-2">
{p.has_datasheet ? (
<DatasheetLink
mpn={p.mpn}
opening={opening}
onOpen={openDatasheet}
/>
) : (
<span className="text-muted-foreground">
</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{passives.length > 0 && (
<div>
<p className="text-xs text-muted-foreground mb-2">
Series patterns one regex covers cousin MPNs
</p>
<div className="rounded-lg border border-border overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/50 text-muted-foreground">
<th className="text-left px-3 py-2 font-medium">
Series
</th>
<th className="text-left px-3 py-2 font-medium">
Type
</th>
<th className="text-left px-3 py-2 font-medium">
Description
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{passives.map((p) => (
<tr key={p.mpn} className="hover:bg-muted/30">
<td className="px-3 py-2 font-mono text-xs">
{p.mpn}
</td>
<td className="px-3 py-2">
{p.subtype ? (
<Badge variant="secondary">{p.subtype}</Badge>
) : (
<span className="text-muted-foreground">
</span>
)}
</td>
<td className="px-3 py-2 text-muted-foreground">
{p.description || "—"}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</>
)}
</TabsContent>
+11
View File
@@ -389,6 +389,15 @@ export interface LibraryPassive {
regex: string;
}
export interface LibraryPassivePart {
mpn: string;
type: "passive_part";
specs_type: string;
subtype: string;
param_count: number;
has_datasheet: boolean;
}
export interface LibrarySimple {
mpn: string;
type: "simple";
@@ -408,6 +417,7 @@ export interface LibraryDatasheet {
export interface LibraryCatalog {
ics: LibraryIC[];
passives: LibraryPassive[];
passive_parts?: LibraryPassivePart[];
simple: LibrarySimple[];
datasheets: LibraryDatasheet[];
}
@@ -606,6 +616,7 @@ export interface AdminSimple {
export interface AdminComponents {
ics: AdminIC[];
passives: AdminPassive[];
passive_parts?: AdminSimple[];
simple: AdminSimple[];
}