Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
472 changes: 472 additions & 0 deletions PLAN.md

Large diffs are not rendered by default.

429 changes: 86 additions & 343 deletions docs/design/design-tokens.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/design/ui-backlog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# UI backlog

> **Predates the neutral EDA redesign (2026-09).** The mockups and token values
> referenced below describe the old violet-on-slate look, not the shipped chrome.
> Where this file and [`design-tokens.md`](design-tokens.md) disagree, design-tokens.md wins.

> Source note: this backlog was distilled from a UI/UX review that took place as a chat
> transcript against screen mockups. **The mockups survive.** They are preserved under
> [`mockups/`](mockups/): 13 standalone HTML mockups, plus the 17 PNG captures the review was
Expand Down
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/core/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"test:watch": "vitest --config vitest.config.ts"
},
"dependencies": {
"@fontsource/ibm-plex-mono": "^5.3.0",
"@fontsource/ibm-plex-sans": "^5.3.0",
"@radix-ui/react-context-menu": "^2.2.4",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
Expand Down
8 changes: 4 additions & 4 deletions src/core/frontend/src/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import { openContextMenu } from "@shared/frontend/context-menu";

function LoadingScreen() {
return (
<div className="flex h-full w-full items-center justify-center bg-slate-50 text-sm text-slate-600 dark:bg-slate-950 dark:text-slate-300">
<div className="flex h-full w-full items-center justify-center bg-surface-app text-sm text-text-secondary">
Initializing OpenPCB...
</div>
);
}

function ErrorScreen({ message }: { message: string }) {
return (
<div className="flex h-full w-full items-center justify-center bg-slate-50 p-6 dark:bg-slate-950">
<div className="max-w-xl rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-200">
<div className="flex h-full w-full items-center justify-center bg-surface-app p-6">
<div className="max-w-xl rounded-control border border-status-danger/30 bg-status-danger-soft px-4 py-3 text-sm text-status-danger">
{message}
</div>
</div>
Expand Down Expand Up @@ -56,7 +56,7 @@ export function AppShell() {

return (
<>
<div className="flex h-full w-full flex-col">
<div className="flex h-full w-full flex-col bg-surface-app text-text">
<TitleBar />
<div
className="grid min-h-0 flex-1 grid-cols-[80px_1fr]"
Expand Down
19 changes: 9 additions & 10 deletions src/core/frontend/src/components/AppContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,7 @@ export function AppContextMenu() {
tabIndex={-1}
role="menu"
className={cn(
"fixed z-[60] min-w-[10rem] rounded-md border border-slate-200 bg-white py-1.5 shadow-lg outline-none",
"dark:border-slate-700 dark:bg-slate-900",
"fixed z-[60] min-w-[10rem] rounded-float border border-border bg-surface-panel py-1.5 shadow-lg outline-none",
)}
style={{
left: clamped.x,
Expand All @@ -162,7 +161,7 @@ export function AppContextMenu() {
}}
>
{title && (
<div className="px-3 py-1 text-xs font-medium text-slate-500 dark:text-slate-400">
<div className="px-3 py-1 text-xs font-medium text-text-tertiary">
{title}
</div>
)}
Expand All @@ -174,7 +173,7 @@ export function AppContextMenu() {
<div
key={`${gi}-${ii}-sep`}
role="separator"
className="my-1 border-t border-slate-200 dark:border-slate-700"
className="my-1 border-t border-border"
/>
);
}
Expand All @@ -192,16 +191,16 @@ export function AppContextMenu() {
onClick={item.disabled ? undefined : () => handleItemClick(idx)}
className={cn(
"flex w-full items-center justify-between px-3 py-1.5 text-sm outline-none",
"text-slate-700 dark:text-slate-200",
"text-text",
item.disabled && "cursor-not-allowed opacity-50",
!item.disabled && "hover:bg-slate-100 dark:hover:bg-slate-800",
isFocused && !item.disabled && "bg-slate-100 dark:bg-slate-800",
item.destructive && "text-red-600 dark:text-red-400",
!item.disabled && "hover:bg-surface-hover",
isFocused && !item.disabled && "bg-surface-hover",
item.destructive && "text-status-danger",
)}
>
<span>{item.label}</span>
{item.shortcut && (
<span className="ml-4 text-xs text-slate-400 dark:text-slate-500">
<span className="ml-4 text-xs text-text-tertiary">
{item.shortcut}
</span>
)}
Expand All @@ -212,7 +211,7 @@ export function AppContextMenu() {
return (
<div key={`group-${gi}-${group.id}`} role="group">
{group.label && (
<div className="px-3 py-1 text-xs font-medium text-slate-500 dark:text-slate-400">
<div className="px-3 py-1 text-xs font-medium text-text-tertiary">
{group.label}
</div>
)}
Expand Down
30 changes: 13 additions & 17 deletions src/core/frontend/src/components/LeftSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,11 @@ interface LeftSidebarProps {
}

function navButtonClass(active: boolean): string {
return `flex w-16 cursor-pointer flex-col items-center justify-center rounded-2xl border border-transparent py-2 transition-colors ${
active
? "border-violet-600 bg-violet-100 text-violet-600 dark:border-violet-400 dark:bg-violet-900/40 dark:text-violet-300"
: "text-slate-400 hover:bg-slate-100 hover:text-slate-600 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
}`;
return `flex ${active ? "w-16 bg-surface-hover text-text-strong" : "w-[72px] text-text-tertiary hover:bg-surface-hover/60 hover:text-text"} cursor-pointer flex-col items-center gap-1 rounded-control py-2 pb-1.5 transition-colors`;
}

function navLabelClass(active: boolean): string {
return `mt-1 text-xs leading-tight text-center ${
active
? "font-medium text-violet-600 dark:text-violet-300"
: "text-slate-500 dark:text-slate-400"
}`;
return `text-2xs leading-tight text-center ${active ? "font-medium" : ""}`;
}

export function LeftSidebar({ onSettingsClick }: LeftSidebarProps) {
Expand Down Expand Up @@ -61,7 +53,7 @@ export function LeftSidebar({ onSettingsClick }: LeftSidebarProps) {

return (
<TooltipProvider delayDuration={300}>
<aside className="flex w-20 flex-col items-center justify-between border-r border-slate-200 bg-white py-3 dark:border-slate-700 dark:bg-slate-900">
<aside className="flex w-20 flex-col items-center justify-between border-r border-border bg-surface-rail py-2.5">
<div className="w-10 h-10">
<svg
xmlns="http://www.w3.org/2000/svg"
Expand Down Expand Up @@ -99,7 +91,7 @@ export function LeftSidebar({ onSettingsClick }: LeftSidebarProps) {
aria-label="Home"
onClick={navigateHome}
>
<LayoutGrid className="h-6 w-6" strokeWidth={1.8} />
<LayoutGrid className="h-5 w-5" strokeWidth={1.5} />
<span className={navLabelClass(currentRoute.kind === "home")}>
Home
</span>
Expand All @@ -120,7 +112,7 @@ export function LeftSidebar({ onSettingsClick }: LeftSidebarProps) {
aria-label={module.sidebar.label}
onClick={() => navigateToModule(module.id)}
>
<ModuleIcon className="h-6 w-6" strokeWidth={1.8} />
<ModuleIcon className="h-5 w-5" strokeWidth={1.5} />
<span className={navLabelClass(active)}>
{module.sidebar.label}
</span>
Expand All @@ -137,19 +129,23 @@ export function LeftSidebar({ onSettingsClick }: LeftSidebarProps) {
target="_blank"
rel="noreferrer"
aria-label="Report a bug or request a feature"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-600 dark:text-slate-500 dark:hover:bg-slate-800 dark:hover:text-slate-300"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-control text-text-tertiary transition-colors hover:bg-surface-hover hover:text-text"
>
<CircleDot className="h-4 w-4" strokeWidth={1.8} />
<CircleDot className="h-[18px] w-[18px]" strokeWidth={1.5} />
</a>
</Tooltip>
<button
type="button"
aria-label="Settings"
aria-current={currentRoute.kind === "settings" ? "page" : undefined}
className={navButtonClass(currentRoute.kind === "settings")}
className={`flex h-8 w-8 cursor-pointer items-center justify-center rounded-control transition-colors hover:bg-surface-hover ${
currentRoute.kind === "settings"
? "text-text-strong"
: "text-text-tertiary hover:text-text"
}`}
onClick={onSettingsClick}
>
<Settings className="h-6 w-6" strokeWidth={1.8} />
<Settings className="h-[18px] w-[18px]" strokeWidth={1.5} />
</button>
</div>
</aside>
Expand Down
2 changes: 1 addition & 1 deletion src/core/frontend/src/components/ModuleSpaceHost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export function ModuleSpaceHost({
}

if (!Component) {
return <div className="m-6 text-sm text-slate-500">Loading module…</div>;
return <div className="m-6 text-sm text-text-tertiary">Loading module…</div>;
}

return (
Expand Down
8 changes: 4 additions & 4 deletions src/core/frontend/src/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function ThemeToggle({
return (
<div
className={cn(
"inline-flex items-center gap-1 rounded-md border border-slate-200 bg-slate-50 p-1 shadow-sm dark:border-slate-700 dark:bg-slate-900",
"inline-flex items-center gap-1 rounded-control border border-border bg-surface-panel p-1",
className,
)}
>
Expand All @@ -41,11 +41,11 @@ export function ThemeToggle({
disabled={!isReady}
onClick={() => handleChange(value)}
className={cn(
"flex min-w-[3.5rem] flex-col items-center justify-center rounded-sm border border-transparent px-3 py-1 text-xs font-medium transition-colors",
"flex min-w-[3.5rem] flex-col items-center justify-center rounded-control border border-transparent px-3 py-1 text-xs font-medium transition-colors",
isReady ? "cursor-pointer" : "cursor-default",
isActive
? "bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900"
: "bg-transparent text-slate-700 hover:bg-slate-200 dark:text-slate-300 dark:hover:bg-slate-800",
? "bg-primary text-primary-foreground"
: "bg-transparent text-text hover:bg-surface-hover",
)}
>
<span>{label}</span>
Expand Down
10 changes: 5 additions & 5 deletions src/core/frontend/src/components/TitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const isMac =
typeof navigator !== "undefined" &&
navigator.platform.toUpperCase().includes("MAC");

// Mirrors index.css --surface-rail / --text-primary. Solid hex only — the
// Mirrors index.css --surface-rail / --text-strong. Solid hex only — the
// Window Controls Overlay rejects rgba() colors. Keyed on the resolved theme
// mode rather than read from getComputedStyle, because this child effect fires
// before ThemeProvider's .dark-class effect (React runs child effects first),
Expand All @@ -20,8 +20,8 @@ const OVERLAY: Record<
"light" | "dark",
{ color: string; symbolColor: string }
> = {
dark: { color: "#0b1018", symbolColor: "#f3f4f6" },
light: { color: "#ffffff", symbolColor: "#111827" },
dark: { color: "#0f0f10", symbolColor: "#f5f5f5" },
light: { color: "#ececee", symbolColor: "#111114" },
};

/**
Expand Down Expand Up @@ -55,9 +55,9 @@ export function TitleBar(): React.ReactElement | null {
paddingLeft: isMac ? 80 : 8,
} as React.CSSProperties
}
className="flex shrink-0 select-none items-center justify-center border-b border-slate-200 bg-surface-rail dark:border-slate-700"
className="flex shrink-0 select-none items-center justify-center border-b border-border bg-surface-rail"
>
<span className="text-xs font-medium tracking-wide text-text-secondary">
<span className="text-xs font-medium tracking-wide text-text-tertiary">
OpenPCB
</span>
</div>
Expand Down
7 changes: 3 additions & 4 deletions src/core/frontend/src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function DialogOverlay({
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn("fixed inset-0 z-50 bg-black/60", className)}
className={cn("fixed inset-0 z-50 bg-black/50", className)}
{...props}
/>
);
Expand All @@ -44,15 +44,14 @@ export function DialogContent({
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"fixed left-1/2 top-1/2 z-50 flex max-h-[90vh] w-[min(92vw,64rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg border border-slate-200 bg-white shadow-xl",
"dark:border-slate-700 dark:bg-slate-900",
"fixed left-1/2 top-1/2 z-50 flex max-h-[90vh] w-[min(92vw,64rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-float border border-border bg-surface-panel shadow-xl",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close className="absolute right-3 top-3 rounded p-1 text-slate-500 hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-100">
<DialogPrimitive.Close className="absolute right-3 top-3 rounded-control p-1 text-text-tertiary hover:bg-surface-hover hover:text-text-strong">
<XIcon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
Expand Down
2 changes: 1 addition & 1 deletion src/core/frontend/src/components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function ScrollBar({
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-slate-300 dark:bg-slate-700" />
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-surface-control" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
);
}
Loading
Loading