Fix project sidebar stuck on the report page.
Open finished projects on the hub instead of /report, and hard-navigate when leaving nested report/progress/placement routes for Dashboard, Library, and hub tabs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
What's new in Pinscope.
|
What's new in Pinscope.
|
||||||
|
|
||||||
|
## 2.28.2 — 2026-09-13 — Fix project sidebar navigation
|
||||||
|
|
||||||
|
Opening a finished project no longer dumps you on the report with a stuck left menu. Hub first; Report stays in the sidebar. Leaving `/report` for BOM/tabs uses a full navigation so the page actually changes.
|
||||||
|
|
||||||
|
- [Fixed] Dashboard project cards/table open `/project/{id}` (not `/report`).
|
||||||
|
- [Fixed] Sidebar Dashboard / Library / BOM tabs escape nested report/progress/placement routes.
|
||||||
|
|
||||||
## 2.28.1 — 2026-09-12 — Placement pipeline (parallel)
|
## 2.28.1 — 2026-09-12 — Placement pipeline (parallel)
|
||||||
|
|
||||||
Dedicated Placement job builds the routing-first topology plan without touching the analysis pipeline status or spending credits. No millimetres — domains, IC groups, satellites only.
|
Dedicated Placement job builds the routing-first topology plan without touching the analysis pipeline status or spending credits. No millimetres — domains, IC groups, satellites only.
|
||||||
|
|||||||
@@ -155,8 +155,9 @@ export function ProjectCard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the report by default — that's what users want to see for a finished
|
// Open the project hub by default so BOM / derating / settings in the
|
||||||
// project. In-flight or paused runs go to the progress page where the SSE
|
// sidebar work immediately. Report stays one click away in ProjectNav.
|
||||||
|
// In-flight or paused runs go to the progress page where the SSE
|
||||||
// stepper and resume controls live.
|
// stepper and resume controls live.
|
||||||
const inFlight =
|
const inFlight =
|
||||||
project.status === "running"
|
project.status === "running"
|
||||||
@@ -164,6 +165,6 @@ export function ProjectCard({
|
|||||||
|| project.status === "paused_by_user";
|
|| project.status === "paused_by_user";
|
||||||
const href = inFlight
|
const href = inFlight
|
||||||
? `/project/${project.id}/progress`
|
? `/project/${project.id}/progress`
|
||||||
: `/project/${project.id}/report`;
|
: `/project/${project.id}`;
|
||||||
return <Link href={href}>{card}</Link>;
|
return <Link href={href}>{card}</Link>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ function ProjectRow({
|
|||||||
|| project.status === "paused_by_user";
|
|| project.status === "paused_by_user";
|
||||||
const href = inFlight
|
const href = inFlight
|
||||||
? `/project/${project.id}/progress`
|
? `/project/${project.id}/progress`
|
||||||
: `/project/${project.id}/report`;
|
: `/project/${project.id}`;
|
||||||
|
|
||||||
const nameCell = (
|
const nameCell = (
|
||||||
<div className="flex items-center gap-1.5 min-w-0">
|
<div className="flex items-center gap-1.5 min-w-0">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname, useSearchParams } from "next/navigation";
|
import { usePathname, useSearchParams } from "next/navigation";
|
||||||
import { useState, useEffect } from "react";
|
import { Suspense, useState, useEffect, type ReactNode } from "react";
|
||||||
import {
|
import {
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
Cpu,
|
Cpu,
|
||||||
@@ -77,7 +77,14 @@ export function Sidebar() {
|
|||||||
|
|
||||||
<div className="flex-1 min-h-0 overflow-y-auto flex flex-col">
|
<div className="flex-1 min-h-0 overflow-y-auto flex flex-col">
|
||||||
{projectId ? (
|
{projectId ? (
|
||||||
<ProjectNav pathname={pathname} projectId={projectId} project={project} isAdmin={isAdmin} />
|
<Suspense fallback={<nav className="flex-1 px-2 py-3" aria-hidden />}>
|
||||||
|
<ProjectNav
|
||||||
|
pathname={pathname}
|
||||||
|
projectId={projectId}
|
||||||
|
project={project}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
) : (
|
) : (
|
||||||
<DefaultNav pathname={pathname} isAdmin={isAdmin} />
|
<DefaultNav pathname={pathname} isAdmin={isAdmin} />
|
||||||
)}
|
)}
|
||||||
@@ -185,6 +192,37 @@ const PROJECT_NAV_ITEMS: NavItem[] = [
|
|||||||
{ type: "tab", tab: "settings", label: "Settings", icon: Settings },
|
{ type: "tab", tab: "settings", label: "Settings", icon: Settings },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function NavLink({
|
||||||
|
href,
|
||||||
|
active,
|
||||||
|
children,
|
||||||
|
forceDocument,
|
||||||
|
}: {
|
||||||
|
href: string;
|
||||||
|
active?: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
forceDocument?: boolean;
|
||||||
|
}) {
|
||||||
|
const className = cn(
|
||||||
|
"flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors",
|
||||||
|
active
|
||||||
|
? "bg-accent text-accent-foreground"
|
||||||
|
: "text-muted-foreground hover:text-foreground hover:bg-accent/50"
|
||||||
|
);
|
||||||
|
if (forceDocument) {
|
||||||
|
return (
|
||||||
|
<a href={href} className={className}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Link href={href} className={className}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ProjectNav({
|
function ProjectNav({
|
||||||
pathname,
|
pathname,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -201,11 +239,18 @@ function ProjectNav({
|
|||||||
const currentTab = searchParams.get("tab");
|
const currentTab = searchParams.get("tab");
|
||||||
const isRunning = project?.status === "running";
|
const isRunning = project?.status === "running";
|
||||||
const isOnProgress = pathname === `${base}/progress`;
|
const isOnProgress = pathname === `${base}/progress`;
|
||||||
|
// Nested routes (report / progress / placement): soft-nav to `?tab=` can
|
||||||
|
// leave the report page mounted — use a full document navigation instead.
|
||||||
|
const onNestedRoute = pathname.startsWith(`${base}/`);
|
||||||
|
|
||||||
function isActive(item: NavItem): boolean {
|
function isActive(item: NavItem): boolean {
|
||||||
if (item.type === "route") {
|
if (item.type === "route") {
|
||||||
return pathname === `${base}${item.path}` && !currentTab;
|
return pathname === `${base}${item.path}` && !currentTab;
|
||||||
}
|
}
|
||||||
|
// Hub with no tab defaults to BOM in the project page.
|
||||||
|
if (item.tab === "bom") {
|
||||||
|
return pathname === base && (currentTab === "bom" || currentTab === null);
|
||||||
|
}
|
||||||
return pathname === base && currentTab === item.tab;
|
return pathname === base && currentTab === item.tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,20 +261,14 @@ function ProjectNav({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="flex-1 px-2 py-3 space-y-1">
|
<nav className="flex-1 px-2 py-3 space-y-1">
|
||||||
<Link
|
<NavLink href="/dashboard" forceDocument={onNestedRoute}>
|
||||||
href="/dashboard"
|
|
||||||
className="flex items-center gap-2 px-3 py-2 rounded-md text-sm text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
Dashboard
|
Dashboard
|
||||||
</Link>
|
</NavLink>
|
||||||
<Link
|
<NavLink href="/library" forceDocument={onNestedRoute}>
|
||||||
href="/library"
|
|
||||||
className="flex items-center gap-2 px-3 py-2 rounded-md text-sm text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
|
||||||
>
|
|
||||||
<Library className="h-4 w-4" />
|
<Library className="h-4 w-4" />
|
||||||
Library
|
Library
|
||||||
</Link>
|
</NavLink>
|
||||||
|
|
||||||
<div className="px-3 pt-3 pb-1">
|
<div className="px-3 pt-3 pb-1">
|
||||||
<p className="text-xs font-semibold text-foreground truncate">
|
<p className="text-xs font-semibold text-foreground truncate">
|
||||||
@@ -239,22 +278,16 @@ function ProjectNav({
|
|||||||
|
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
{isRunning && (
|
{isRunning && (
|
||||||
<Link
|
<NavLink href={`${base}/progress`} active={isOnProgress}>
|
||||||
href={`${base}/progress`}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors",
|
|
||||||
isOnProgress
|
|
||||||
? "bg-accent text-accent-foreground"
|
|
||||||
: "text-muted-foreground hover:text-foreground hover:bg-accent/50"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
Processing
|
Processing
|
||||||
</Link>
|
</NavLink>
|
||||||
)}
|
)}
|
||||||
{PROJECT_NAV_ITEMS.filter((item) => !item.adminOnly || isAdmin).map((item) => {
|
{PROJECT_NAV_ITEMS.filter((item) => !item.adminOnly || isAdmin).map((item) => {
|
||||||
const active = isActive(item);
|
const active = isActive(item);
|
||||||
const disabled = isRunning;
|
const disabled = isRunning;
|
||||||
|
const href = getHref(item);
|
||||||
|
const forceDocument = item.type === "tab" && onNestedRoute;
|
||||||
return disabled ? (
|
return disabled ? (
|
||||||
<span
|
<span
|
||||||
key={item.label}
|
key={item.label}
|
||||||
@@ -262,23 +295,23 @@ function ProjectNav({
|
|||||||
>
|
>
|
||||||
<item.icon className="h-4 w-4" />
|
<item.icon className="h-4 w-4" />
|
||||||
{item.label}
|
{item.label}
|
||||||
{item.adminOnly && <Shield className="h-3 w-3 ml-auto text-amber-600/60 dark:text-amber-500/60" />}
|
{item.adminOnly && (
|
||||||
|
<Shield className="h-3 w-3 ml-auto text-amber-600/60 dark:text-amber-500/60" />
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<Link
|
<NavLink
|
||||||
key={item.label}
|
key={item.label}
|
||||||
href={getHref(item)}
|
href={href}
|
||||||
className={cn(
|
active={active}
|
||||||
"flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors",
|
forceDocument={forceDocument}
|
||||||
active
|
|
||||||
? "bg-accent text-accent-foreground"
|
|
||||||
: "text-muted-foreground hover:text-foreground hover:bg-accent/50"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<item.icon className="h-4 w-4" />
|
<item.icon className="h-4 w-4" />
|
||||||
{item.label}
|
{item.label}
|
||||||
{item.adminOnly && <Shield className="h-3 w-3 ml-auto text-amber-600/60 dark:text-amber-500/60" />}
|
{item.adminOnly && (
|
||||||
</Link>
|
<Shield className="h-3 w-3 ml-auto text-amber-600/60 dark:text-amber-500/60" />
|
||||||
|
)}
|
||||||
|
</NavLink>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user