Rebrand Pinscope to Periscope across product and codebase.

Rename the core package to periscopex, update UI/docs/Docker/deploy defaults to periscope.michelebigi.it, and keep legacy version/storage key aliases so existing projects keep working.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-13 20:02:04 +02:00
co-authored by Cursor
parent 556306bf4d
commit 8d2b85600f
177 changed files with 869 additions and 766 deletions
+1 -1
View File
@@ -234,7 +234,7 @@ function ProjectsPanel() {
// Stash the source project id and hand off to the dashboard, which
// mounts the create-project dialog. The dialog fetches the full
// Project on its end so we don't have to pass the entire object here.
window.sessionStorage.setItem("pinscopex:cloneAsNewProjectId", projectId);
window.sessionStorage.setItem("periscopex:cloneAsNewProjectId", projectId);
router.push("/dashboard");
}
+3 -3
View File
@@ -20,7 +20,7 @@ import { useCredits } from "@/components/billing/credits-context";
import { OnboardingSurvey } from "@/components/dashboard/onboarding-survey";
type ViewMode = "cards" | "table";
const VIEW_STORAGE_KEY = "pinscopex:dashboard:view";
const VIEW_STORAGE_KEY = "periscopex:dashboard:view";
export default function DashboardPage() {
return (
@@ -79,10 +79,10 @@ function DashboardContent() {
// Admin handoff: "Rerun as new project" stashes a project ID here.
const cloneId =
typeof window !== "undefined"
? window.sessionStorage.getItem("pinscopex:cloneAsNewProjectId")
? window.sessionStorage.getItem("periscopex:cloneAsNewProjectId")
: null;
if (cloneId) {
window.sessionStorage.removeItem("pinscopex:cloneAsNewProjectId");
window.sessionStorage.removeItem("periscopex:cloneAsNewProjectId");
fetchProject(cloneId)
.then(setCloneAsNewProject)
.catch(() => {
+1 -1
View File
@@ -104,7 +104,7 @@ export default function FeedbackPage() {
)}
{expanded === t.ticket_id && t.admin_notes && (
<div className="mt-3 rounded border border-emerald-500/20 bg-emerald-500/5 p-3">
<p className="text-xs text-emerald-600 dark:text-emerald-400 mb-1">Pinscope Team:</p>
<p className="text-xs text-emerald-600 dark:text-emerald-400 mb-1">Periscope Team:</p>
<p className="text-sm text-muted-foreground">{t.admin_notes}</p>
</div>
)}
+24 -11
View File
@@ -46,6 +46,13 @@ import { ImpedancePanel } from "@/components/project/impedance-panel";
import { TopologyPanel } from "@/components/project/topology-panel";
import { PcbUploadButton } from "@/components/project/pcb-upload";
import { PdfViewerSheet } from "@/components/pdf/pdf-viewer-sheet";
import {
deratingOverridesKey,
deratingSettingsKey,
legacyDeratingOverridesKey,
legacyDeratingSettingsKey,
migrateLocalKey,
} from "@/lib/storage-keys";
export default function ProjectDetailPage({
params,
@@ -61,7 +68,10 @@ export default function ProjectDetailPage({
const [deratingSettings, setDeratingSettings] = useState<DeratingSettings>(() => {
if (typeof window === "undefined") return { ceramic: 50, tantalum: 50, electrolytic: 50 };
try {
const stored = localStorage.getItem(`pinscopex:derating-settings:${id}`);
const stored = migrateLocalKey(
deratingSettingsKey(id),
legacyDeratingSettingsKey(id),
);
return stored ? JSON.parse(stored) : { ceramic: 50, tantalum: 50, electrolytic: 50 };
} catch {
return { ceramic: 50, tantalum: 50, electrolytic: 50 };
@@ -70,7 +80,10 @@ export default function ProjectDetailPage({
const [manualVoltages, setManualVoltages] = useState<Record<string, number>>(() => {
if (typeof window === "undefined") return {};
try {
const stored = localStorage.getItem(`pinscopex:derating-overrides:${id}`);
const stored = migrateLocalKey(
deratingOverridesKey(id),
legacyDeratingOverridesKey(id),
);
return stored ? JSON.parse(stored) : {};
} catch {
return {};
@@ -83,12 +96,12 @@ export default function ProjectDetailPage({
// Persist derating settings to localStorage
useEffect(() => {
localStorage.setItem(`pinscopex:derating-settings:${id}`, JSON.stringify(deratingSettings));
localStorage.setItem(deratingSettingsKey(id), JSON.stringify(deratingSettings));
}, [deratingSettings, id]);
// Persist manual voltage overrides to localStorage
useEffect(() => {
localStorage.setItem(`pinscopex:derating-overrides:${id}`, JSON.stringify(manualVoltages));
localStorage.setItem(deratingOverridesKey(id), JSON.stringify(manualVoltages));
}, [manualVoltages, id]);
const reload = useCallback(() => {
@@ -458,7 +471,7 @@ export default function ProjectDetailPage({
</Card>
<CollaboratorsSection projectId={id} />
<SkippedComponentsSection skipped={project.skippedComponents} />
<ReportVersionSection pinscopeVersion={project.pinscopeVersion} />
<ReportVersionSection periscopeVersion={project.periscopeVersion} />
</div>
)}
<PdfViewerSheet
@@ -1268,11 +1281,11 @@ function SkippedComponentsSection({
}
function ReportVersionSection({
pinscopeVersion,
periscopeVersion,
}: {
pinscopeVersion?: string | null;
periscopeVersion?: string | null;
}) {
if (!pinscopeVersion) return null;
if (!periscopeVersion) return null;
return (
<Card>
<CardHeader>
@@ -1281,9 +1294,9 @@ function ReportVersionSection({
<CardContent>
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">
Generated with Pinscope
Generated with Periscope
</p>
<span className="font-mono text-sm">v{pinscopeVersion}</span>
<span className="font-mono text-sm">v{periscopeVersion}</span>
</div>
</CardContent>
</Card>
@@ -1299,7 +1312,7 @@ function PipelineErrorBanner({
}) {
const [copied, setCopied] = useState(false);
const detail = message ?? "Unknown error — no details were recorded.";
const shareText = `PinscopeX project ${projectId} failed: ${detail}`;
const shareText = `PeriscopeX project ${projectId} failed: ${detail}`;
const handleCopy = async () => {
try {
@@ -6,7 +6,7 @@ import { pageMetadata } from "@/lib/site";
export const metadata = pageMetadata({
title: "Changelog",
description:
"Recent updates and improvements to Pinscope — new EDA tool support, review accuracy improvements, and platform changes.",
"Recent updates and improvements to Periscope — new EDA tool support, review accuracy improvements, and platform changes.",
path: "/changelog",
});
+1 -1
View File
@@ -14,7 +14,7 @@ export function Nav() {
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-6">
<Link href="/" className="flex items-center gap-2">
<Cpu className="h-5 w-5 text-blue-500" />
<span className="text-sm font-semibold tracking-tight">Pinscope</span>
<span className="text-sm font-semibold tracking-tight">Periscope</span>
</Link>
<nav className="hidden sm:flex items-center gap-6 text-sm text-muted-foreground">
<Link href="/#features" className="hover:text-foreground transition-colors">
@@ -7,7 +7,7 @@ import { pageMetadata } from "@/lib/site";
export const metadata = pageMetadata({
title: "Contact",
description:
"Talk to the Pinscope team — questions, account help, or enterprise deployment.",
"Talk to the Periscope team — questions, account help, or enterprise deployment.",
path: "/contact",
});
@@ -22,7 +22,7 @@ export default function ContactPage() {
Get in touch
</h1>
<p className="mt-4 text-muted-foreground max-w-lg leading-relaxed animate-fade-up [animation-delay:100ms]">
Have a question about Pinscope, need help with your account, or want to
Have a question about Periscope, need help with your account, or want to
discuss enterprise deployment? We&rsquo;d love to hear from you.
</p>
</section>
@@ -54,7 +54,7 @@ export default function ContactPage() {
<div className="mx-auto max-w-6xl px-6 py-8 flex items-center justify-between text-xs text-muted-foreground">
<div className="flex items-center gap-2">
<Cpu className="h-4 w-4 text-blue-500" />
<span>Pinscope</span>
<span>Periscope</span>
</div>
<div className="flex items-center gap-4">
<Link
@@ -6,7 +6,7 @@ import { pageMetadata } from "@/lib/site";
export const metadata = pageMetadata({
title: "File Upload Guide",
description:
"Step-by-step export instructions for KiCad, Altium, OrCAD, Cadence Allegro, Siemens Xpedition, EasyEDA, and Autodesk EAGLE — netlists, BOMs, and datasheets ready for Pinscope.",
"Step-by-step export instructions for KiCad, Altium, OrCAD, Cadence Allegro, Siemens Xpedition, EasyEDA, and Autodesk EAGLE — netlists, BOMs, and datasheets ready for Periscope.",
path: "/file-guide",
});
+4 -4
View File
@@ -117,7 +117,7 @@ export default function LandingPage() {
<Link href="/" className="flex items-center gap-2">
<Cpu className="h-5 w-5 text-blue-500" />
<span className="text-sm font-semibold tracking-tight">
Pinscope
Periscope
</span>
</Link>
<nav className="hidden sm:flex items-center gap-6 text-sm text-muted-foreground">
@@ -169,7 +169,7 @@ export default function LandingPage() {
<br className="hidden lg:block" /> first time
</h1>
<p className="mt-6 text-lg sm:text-xl text-muted-foreground max-w-xl leading-relaxed animate-fade-up [animation-delay:100ms]">
Pinscope reviews your schematic against every datasheet and
Periscope reviews your schematic against every datasheet and
catches the errors that would otherwise surface at bring-up.
</p>
<div className="mt-8 flex flex-col items-start gap-4 animate-fade-up [animation-delay:200ms]">
@@ -191,7 +191,7 @@ export default function LandingPage() {
<div className="rounded-xl border border-border overflow-hidden bg-card/40">
<Image
src="/report.png"
alt="Pinscope validation report"
alt="Periscope validation report"
width={2400}
height={1500}
className="w-full h-auto"
@@ -328,7 +328,7 @@ export default function LandingPage() {
Your designs stay yours
</h2>
<p className="mt-3 text-muted-foreground text-center max-w-lg mx-auto">
Hardware IP is sensitive. Pinscope is built to keep it that way.
Hardware IP is sensitive. Periscope is built to keep it that way.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mt-12">
{[
@@ -6,7 +6,7 @@ import { pageMetadata } from "@/lib/site";
export const metadata = pageMetadata({
title: "Privacy Policy",
description:
"How Pinscope collects, stores, and protects the schematics, datasheets, and BOMs you upload.",
"How Periscope collects, stores, and protects the schematics, datasheets, and BOMs you upload.",
path: "/privacy",
});
@@ -45,7 +45,7 @@ export default function SignInPage() {
<div className="mx-auto flex min-h-[70vh] max-w-md flex-col justify-center px-4 py-16">
<h1 className="font-display text-3xl tracking-tight">Sign in</h1>
<p className="mt-2 text-sm text-muted-foreground">
Pinscope account for this server. Invite collaborators from a project once they have an account.
Periscope account for this server. Invite collaborators from a project once they have an account.
</p>
<form onSubmit={onSubmit} className="mt-8 flex flex-col gap-4">
<div className="space-y-2">
+1 -1
View File
@@ -6,7 +6,7 @@ import { pageMetadata } from "@/lib/site";
export const metadata = pageMetadata({
title: "Terms of Service",
description:
"Terms governing your use of Pinscope, including account, payment, and acceptable-use rules.",
"Terms governing your use of Periscope, including account, payment, and acceptable-use rules.",
path: "/terms",
});
+3 -3
View File
@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
export const alt = "Pinscope — Agentic schematic validation";
export const alt = "Periscope — Agentic schematic validation";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";
@@ -51,7 +51,7 @@ export default function Image() {
letterSpacing: "-0.01em",
}}
>
Pinscope
Periscope
</div>
</div>
@@ -97,7 +97,7 @@ export default function Image() {
KiCad · Altium · OrCAD · Cadence · Siemens · EasyEDA · EAGLE
</div>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
pinscope.ai
periscope.michelebigi.it
</div>
</div>
</div>
@@ -152,7 +152,7 @@ function autoDetect(headers: string[], candidates: string[]): string | null {
}
// ---------------------------------------------------------------------------
// BOM classification — mirrors pinscopex/taxonomy.py REF_PREFIX_TO_TYPE
// BOM classification — mirrors periscopex/taxonomy.py REF_PREFIX_TO_TYPE
// ---------------------------------------------------------------------------
interface IcMpnEntry {
@@ -63,7 +63,7 @@ export function OnboardingSurvey() {
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent showCloseButton={false} className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Welcome to Pinscope</DialogTitle>
<DialogTitle>Welcome to Periscope</DialogTitle>
<DialogDescription>
Two quick questions to help us improve the product.
</DialogDescription>
+1 -1
View File
@@ -73,7 +73,7 @@ export function Sidebar() {
<div className="px-4 py-4 border-b border-border">
<Link href="/dashboard" className="flex items-center gap-2">
<Cpu className="h-5 w-5 text-blue-500" />
<span className="text-sm font-semibold tracking-tight">Pinscope</span>
<span className="text-sm font-semibold tracking-tight">Periscope</span>
</Link>
</div>
@@ -96,7 +96,7 @@ export function ChangelogTimeline({ content }: { content: string }) {
</Link>
<Link href="/" className="flex items-center gap-2">
<Cpu className="h-4 w-4 text-blue-500" />
<span className="text-sm font-medium">Pinscope</span>
<span className="text-sm font-medium">Periscope</span>
</Link>
</div>
</header>
@@ -104,7 +104,7 @@ export function ChangelogTimeline({ content }: { content: string }) {
<main className="mx-auto max-w-3xl px-6 py-12">
<div className="mb-12">
<h1 className="text-3xl font-bold tracking-tight mb-2">Changelog</h1>
<p className="text-sm text-muted-foreground">What's new in Pinscope.</p>
<p className="text-sm text-muted-foreground">What's new in Periscope.</p>
</div>
<ol className="relative">
+1 -1
View File
@@ -115,7 +115,7 @@ export function LegalPageShell({ children }: { children: ReactNode }) {
</Link>
<Link href="/" className="flex items-center gap-2">
<Cpu className="h-4 w-4 text-blue-500" />
<span className="text-sm font-medium">Pinscope</span>
<span className="text-sm font-medium">Periscope</span>
</Link>
</div>
</header>
@@ -40,7 +40,7 @@ interface PdfViewerPanelProps {
className?: string;
}
const HIGHLIGHT_CLASS = "pinscope-quote-hl";
const HIGHLIGHT_CLASS = "periscope-quote-hl";
// U+00B5 MICRO SIGN and U+03BC GREEK SMALL MU render identically but the
// model and the PDF often disagree on which one — fold them together.
@@ -131,7 +131,7 @@ export function ImpedancePanel({
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "pinscope.kicad_dru";
a.download = "periscope.kicad_dru";
a.click();
URL.revokeObjectURL(url);
}
+6 -11
View File
@@ -2,11 +2,11 @@
/**
* Open-core seam: cloud/gateway replaces this with Clerk hooks.
* Self-host local mode uses Pinscope JWT in localStorage.
* Self-host local mode uses Periscope JWT in localStorage.
*/
import { useCallback, useEffect, useState } from "react";
import { authEnabled, localAuthEnabled, TOKEN_STORAGE_KEY } from "@/lib/auth";
import { authEnabled, localAuthEnabled, TOKEN_STORAGE_KEY, readAuthToken } from "@/lib/auth";
export interface AppUser {
id: string;
@@ -36,12 +36,7 @@ const LOCAL_USER: AppUser = {
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "";
function readStoredToken(): string | null {
if (typeof window === "undefined") return null;
try {
return window.localStorage.getItem(TOKEN_STORAGE_KEY);
} catch {
return null;
}
return readAuthToken();
}
export function storeAuthToken(token: string | null) {
@@ -52,7 +47,7 @@ export function storeAuthToken(token: string | null) {
} catch {
/* ignore */
}
window.dispatchEvent(new Event("pinscope-auth-changed"));
window.dispatchEvent(new Event("periscope-auth-changed"));
}
export function useOptionalAuth(): OptionalAuth {
@@ -67,10 +62,10 @@ export function useOptionalAuth(): OptionalAuth {
const sync = () => setToken(readStoredToken());
sync();
setReady(true);
window.addEventListener("pinscope-auth-changed", sync);
window.addEventListener("periscope-auth-changed", sync);
window.addEventListener("storage", sync);
return () => {
window.removeEventListener("pinscope-auth-changed", sync);
window.removeEventListener("periscope-auth-changed", sync);
window.removeEventListener("storage", sync);
};
}, []);
+1 -1
View File
@@ -6,7 +6,7 @@ import { fetchReport, fetchGraph } from "@/lib/api";
function describeLoadError(e: unknown): string {
if (e instanceof TypeError) {
return "Could not reach the Pinscope API. Check that the backend is running and that /api is proxied to it.";
return "Could not reach the Periscope API. Check that the backend is running and that /api is proxied to it.";
}
return e instanceof Error ? e.message : "Failed to load report";
}
+9 -1
View File
@@ -1,6 +1,11 @@
"use client";
import { useState, useEffect } from "react";
import {
legacyReviewedFindingsKey,
migrateLocalKey,
reviewedFindingsKey,
} from "@/lib/storage-keys";
/**
* Lightweight hook that reads the reviewed-findings count from localStorage
@@ -12,7 +17,10 @@ export function useReviewedCount(projectId: string): number {
useEffect(() => {
try {
const stored = localStorage.getItem(`pinscopex:reviewed-findings:${projectId}`);
const stored = migrateLocalKey(
reviewedFindingsKey(projectId),
legacyReviewedFindingsKey(projectId),
);
if (stored) {
const arr: unknown[] = JSON.parse(stored);
setCount(arr.length);
+10 -2
View File
@@ -3,9 +3,14 @@
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
import type { Finding } from "@/lib/types";
import { getFindingKey } from "@/lib/utils";
import {
legacyReviewedFindingsKey,
migrateLocalKey,
reviewedFindingsKey,
} from "@/lib/storage-keys";
function storageKey(projectId: string) {
return `pinscopex:reviewed-findings:${projectId}`;
return reviewedFindingsKey(projectId);
}
export function useReviewedFindings(projectId: string, findings: Finding[]) {
@@ -18,7 +23,10 @@ export function useReviewedFindings(projectId: string, findings: Finding[]) {
const [reviewedIds, setReviewedIds] = useState<Set<string>>(() => {
if (typeof window === "undefined") return new Set();
try {
const stored = localStorage.getItem(storageKey(projectId));
const stored = migrateLocalKey(
reviewedFindingsKey(projectId),
legacyReviewedFindingsKey(projectId),
);
if (!stored) return new Set();
const arr: string[] = JSON.parse(stored);
// Load all stored keys as-is. Pruning here would wipe everything
+7 -4
View File
@@ -29,7 +29,7 @@ import type {
ValidationReport,
} from "./types";
// Mirror of backend.pinscopex.utils.safe_mpn — used to match MPNs
// Mirror of backend.periscopex.utils.safe_mpn — used to match MPNs
// against datasheet filename stems returned by the server.
export function safeMpn(mpn: string): string {
return mpn.replace(/\//g, "_").replace(/:/g, "_");
@@ -102,7 +102,10 @@ function mapProject(p: Record<string, unknown>): Project {
lcscToMpn: (p.lcsc_to_mpn as Record<string, string> | null) ?? null,
lcscPayloads: (p.lcsc_payloads as Record<string, LcscPayload> | null) ?? null,
componentMpns: (p.component_mpns as ComponentMpnBuckets | null) ?? null,
pinscopeVersion: (p.pinscope_version as string | null | undefined) ?? null,
periscopeVersion:
(p.periscope_version as string | null | undefined) ??
(p.pinscope_version as string | null | undefined) ??
null,
netlistFormat: (p.netlist_format as Project["netlistFormat"]) ?? null,
netlistSubdesigns: (p.netlist_subdesigns as string[] | null) ?? null,
placementStatus: (p.placement_status as Project["placementStatus"]) ?? "draft",
@@ -817,7 +820,7 @@ export async function downloadEcoCsv(projectId: string): Promise<void> {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "pinscope-eco.csv";
a.download = "periscope-eco.csv";
a.click();
URL.revokeObjectURL(url);
}
@@ -1125,7 +1128,7 @@ export interface LcscResolvePassiveResponse {
mpn: string;
safe_mpn: string;
// Shape mirrors the persisted passive specs model in
// backend/pinscopex/models.py — a discriminated union over `specs_type`.
// backend/periscopex/models.py — a discriminated union over `specs_type`.
// Keep loose at this layer; the wizard surfaces a small summary.
model: Record<string, unknown>;
cached: boolean;
+21 -2
View File
@@ -2,7 +2,7 @@
* Open-core auth switch.
*
* - Clerk: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY set (cloud build).
* - Local: NEXT_PUBLIC_AUTH_MODE=local (self-host Pinscope accounts).
* - Local: NEXT_PUBLIC_AUTH_MODE=local (self-host Periscope accounts).
* - Off: neither — signed-in stub user "local", matching backend.
*
* NEXT_PUBLIC_* vars are inlined at build time.
@@ -16,4 +16,23 @@ export const localAuthEnabled =
process.env.NEXT_PUBLIC_AUTH_MODE === "local" &&
!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
export const TOKEN_STORAGE_KEY = "pinscope_token";
export const TOKEN_STORAGE_KEY = "periscope_token";
/** Pre-rebrand localStorage key — read once and migrate. */
export const LEGACY_TOKEN_STORAGE_KEY = "pinscope_token";
export function readAuthToken(): string | null {
if (typeof window === "undefined") return null;
try {
const current = window.localStorage.getItem(TOKEN_STORAGE_KEY);
if (current) return current;
const legacy = window.localStorage.getItem(LEGACY_TOKEN_STORAGE_KEY);
if (legacy) {
window.localStorage.setItem(TOKEN_STORAGE_KEY, legacy);
window.localStorage.removeItem(LEGACY_TOKEN_STORAGE_KEY);
return legacy;
}
} catch {
/* ignore */
}
return null;
}
+3 -3
View File
@@ -1,12 +1,12 @@
export const SITE_URL = (
process.env.NEXT_PUBLIC_SITE_URL?.trim().replace(/\/$/, "") ||
"https://pinscope.ai"
"https://periscope.michelebigi.it"
);
export const SITE_NAME = "Pinscope";
export const SITE_NAME = "Periscope";
export const SITE_DESCRIPTION =
"Pinscope reviews your schematic against every datasheet and catches the errors that would otherwise surface at bring-up. Works with KiCad, Altium, OrCAD, Cadence, Siemens, EasyEDA, and EAGLE.";
"Periscope reviews your schematic against every datasheet and catches the errors that would otherwise surface at bring-up. Works with KiCad, Altium, OrCAD, Cadence, Siemens, EasyEDA, and EAGLE.";
export const SITE_TAGLINE = "Agentic schematic validation";
+42
View File
@@ -0,0 +1,42 @@
/** Migrate localStorage keys from pre-rebrand `pinscopex:` prefix. */
export function migrateLocalKey(newKey: string, legacyKey: string): string | null {
if (typeof window === "undefined") return null;
try {
const current = localStorage.getItem(newKey);
if (current != null) return current;
const legacy = localStorage.getItem(legacyKey);
if (legacy != null) {
localStorage.setItem(newKey, legacy);
localStorage.removeItem(legacyKey);
return legacy;
}
} catch {
/* ignore */
}
return null;
}
export function reviewedFindingsKey(projectId: string) {
return `periscopex:reviewed-findings:${projectId}`;
}
export function legacyReviewedFindingsKey(projectId: string) {
return `pinscopex:reviewed-findings:${projectId}`;
}
export function deratingSettingsKey(projectId: string) {
return `periscopex:derating-settings:${projectId}`;
}
export function legacyDeratingSettingsKey(projectId: string) {
return `pinscopex:derating-settings:${projectId}`;
}
export function deratingOverridesKey(projectId: string) {
return `periscopex:derating-overrides:${projectId}`;
}
export function legacyDeratingOverridesKey(projectId: string) {
return `pinscopex:derating-overrides:${projectId}`;
}
+1 -1
View File
@@ -272,7 +272,7 @@ export interface Project {
lcscToMpn?: Record<string, string> | null;
lcscPayloads?: Record<string, LcscPayload> | null;
componentMpns?: ComponentMpnBuckets | null;
pinscopeVersion?: string | null;
periscopeVersion?: string | null;
// "pads" | "edif" | "kicad_*" — netlist the user uploaded. null on legacy projects.
// projects predating EDIF support; treat null as PADS for rendering.
netlistFormat?: "pads" | "edif" | "kicad_xml" | "kicad_sexp" | "kicad_sch" | null;