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
62 changes: 62 additions & 0 deletions apps/desktop-ui/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# DESIGN — MyDevTools Desktop ("Pro Instrument")

The desktop app is a **pro instrument**, not a web dashboard. Reference the things
that make Figma, Linear, Raycast, and Adobe apps feel native and expensive:
custom window chrome, dense information, one confident accent, keyboard-first
navigation, and motion that only ever conveys state.

Register: **product** (design serves the task). The tool disappears into the work.

## Anti-references (what it must NOT look like)
- shadcn/Vercel starter dark mode (neutral zinc). The current dark theme IS this — replace it.
- A 247px white sidebar + uniform card grid + `0.5rem` radius everywhere (SaaS template silhouette).
- User-selectable accent colors (blue/purple/green picker). One brand accent only.
- A browser-style OS title bar sitting above web content.

## Theme
- **Dark is the signature and default.** One genuinely-refined **Light** exists (manual toggle). No `system`, no color picker.
- Graphite blue-black surfaces (hue ~225, low chroma), stepped by lightness — never pure black, never zinc-neutral.

## Color (HSL tokens, dark)
| Role | Token | Value | Use |
|---|---|---|---|
| App base | `--background` | `225 14% 8%` | behind panels, titlebar |
| Panel | `--surface-1` / `--card` | `225 13% 11%` | content surface |
| Raised | `--surface-2` | `225 12% 13%` | toolbars, tab strip |
| Hover/input | `--surface-3` | `225 11% 16%` | inputs, hovered rows |
| Ink | `--foreground` | `220 16% 96%` | primary text |
| Muted ink | `--muted-foreground` | `222 10% 64%` | labels (≥4.5:1) |
| Hairline | `--border` | `225 9% 20%` | 1px separators |
| **Accent** | `--primary` / `--ring` | `235 84% 67%` | **electric indigo — selection + primary action + state only, never decoration** |

- Per-**category** accent colors stay (they help navigate 80 tools). The single brand accent is separate.
- Semantic: destructive red kept; success/warning/info to be added as tokens when needed.
- Mono (`--font-geist-mono`) for all values: hashes, JSON, timestamps, byte counts. Values feel like data.

## Typography
- One family: **Geist Sans** (UI) + **Geist Mono** (data). No display pairing.
- Fixed rem scale, tight ratio (~1.2). UI is dense: ~13px controls, 12px secondary labels, 11px micro. Reading prose 14px, capped 65–75ch.
- Headings tighten letter-spacing slightly; never fluid/clamp in-app.

## Shape, density, elevation
- `--radius: 0.375rem` (6px) base; controls tighter. No 8px-everywhere.
- Dense spacing on a 4px grid. Rely on **surface steps + hairline borders**, not shadows. One soft shadow reserved for popovers / the ⌘K palette.

## Chrome architecture
- **Custom unified titlebar+toolbar** (Tauri, decorations off): inset macOS traffic lights, app mark, current-tool breadcrumb, ⌘K trigger, actions. No OS strip.
- **Left nav**: dense sidebar (~220px) collapsible to a ~52px icon rail. Not a white 247px web sidebar.
- **⌘K command palette**: the primary way to move across 80 tools.

## Tabs (Figma-style)
- **Not pills.** Square tabs, softly rounded **top** corners only (`rounded-t`, square bottom).
- Tabs sit on a raised strip (`--surface-2`) and align to its bottom edge.
- **Dividers** (1px) between inactive tabs; suppressed next to the active tab.
- **Active tab is welded to the content**: its background matches the content surface and it breaks the strip's bottom border (the seam), with a 2px accent bar on top. Clear top-strip / content separation.
- Monochrome tool icon (accent-tinted when active) — no random rainbow chips.

## Motion
- 120–200ms, ease-out (quart/expo). State only: selection, panel open, palette, feedback. No page-load choreography, no decorative motion.
- Every animation has a `prefers-reduced-motion` fallback.

## Component contract
Every interactive element ships default/hover/focus/active/disabled/loading. Skeletons over spinners. Empty states teach. Same button/control vocabulary everywhere.
180 changes: 43 additions & 137 deletions apps/desktop-ui/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ body {
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--radius: 0.375rem;
--surface-1: 0 0% 100%;
--surface-2: 225 24% 97.5%;
--surface-3: 225 22% 94.5%;
Expand All @@ -61,144 +61,50 @@ body {
}

.dark {
/* Minimalist dark theme - shadcn neutral zinc defaults */
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
/* Chart colors remain balanced */
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--card: 240 10% 5.5%;
--surface-1: 240 8% 7%;
--surface-2: 240 7% 9%;
--surface-3: 240 6% 12%;
/* Sidebar aligned to the deck blue-black theme (was a desaturated grey) */
--sidebar-background: 240 10% 5%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 0 0% 98%;
--sidebar-primary-foreground: 240 5.9% 10%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 240 4.9% 83.9%;
}

/* Color Theme Overrides - Light Mode */
:root.blue {
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 0 0% 98%;
--ring: 217.2 91.2% 59.8%;
--sidebar-primary: 217.2 91.2% 59.8%;
}

:root.purple {
--primary: 258.3 89.5% 66.3%;
--primary-foreground: 0 0% 98%;
--ring: 258.3 89.5% 66.3%;
--sidebar-primary: 258.3 89.5% 66.3%;
}

:root.green {
--primary: 142.1 76.2% 36.3%;
--primary-foreground: 0 0% 98%;
--ring: 142.1 76.2% 36.3%;
--sidebar-primary: 142.1 76.2% 36.3%;
}

:root.orange {
--primary: 24.6 95% 53.1%;
--primary-foreground: 0 0% 98%;
--ring: 24.6 95% 53.1%;
--sidebar-primary: 24.6 95% 53.1%;
}

:root.red {
--primary: 0 72.2% 50.6%;
--primary-foreground: 0 0% 98%;
--ring: 0 72.2% 50.6%;
--sidebar-primary: 0 72.2% 50.6%;
}

:root.pink {
--primary: 340.7 82.1% 52.7%;
--primary-foreground: 0 0% 98%;
--ring: 340.7 82.1% 52.7%;
--sidebar-primary: 340.7 82.1% 52.7%;
}

:root.cyan {
--primary: 199.1 89.1% 48.2%;
--primary-foreground: 0 0% 98%;
--ring: 199.1 89.1% 48.2%;
--sidebar-primary: 199.1 89.1% 48.2%;
}

:root.indigo {
--primary: 239.4 83.9% 66.7%;
--primary-foreground: 0 0% 98%;
--ring: 239.4 83.9% 66.7%;
--sidebar-primary: 239.4 83.9% 66.7%;
}

/* Color Theme Overrides - Dark Mode */
.dark.blue {
--primary: 217.2 91.2% 59.8%;
--sidebar-primary: 217.2 91.2% 59.8%;
}

.dark.purple {
--primary: 258.3 89.5% 66.3%;
--sidebar-primary: 258.3 89.5% 66.3%;
}

.dark.green {
--primary: 142.1 70.6% 45.3%;
--sidebar-primary: 142.1 70.6% 45.3%;
}

.dark.orange {
--primary: 24.6 95% 53.1%;
--sidebar-primary: 24.6 95% 53.1%;
}

.dark.red {
--primary: 0 72.2% 50.6%;
--sidebar-primary: 0 72.2% 50.6%;
}

.dark.pink {
--primary: 340.7 82.1% 52.7%;
--sidebar-primary: 340.7 82.1% 52.7%;
}

.dark.cyan {
--primary: 199.1 89.1% 48.2%;
--sidebar-primary: 199.1 89.1% 48.2%;
/* Pro Instrument — graphite blue-black (hue ~225, low chroma). Surfaces
step by lightness; one electric-indigo accent; hairline borders. */
--background: 225 14% 8%; /* app base behind panels + titlebar */
--foreground: 220 16% 96%;
--card: 225 13% 11%; /* = surface-1 (content panels) */
--card-foreground: 220 16% 96%;
--popover: 225 14% 13%; /* menus / ⌘K palette, slightly lifted */
--popover-foreground: 220 16% 96%;
--primary: 235 84% 67%; /* signature accent — electric indigo */
--primary-foreground: 0 0% 100%;
--secondary: 225 10% 16%;
--secondary-foreground: 220 14% 92%;
--muted: 225 10% 15%;
--muted-foreground: 222 10% 64%; /* ≥4.5:1 on surfaces */
--accent: 225 11% 18%; /* hover tint (neutral, not brand) */
--accent-foreground: 220 16% 96%;
--destructive: 0 72% 57%;
--destructive-foreground: 0 0% 100%;
--border: 225 9% 20%; /* hairline */
--input: 225 9% 22%;
--ring: 235 84% 67%;
/* Chart / category palette (kept vivid for data + category accents) */
--chart-1: 235 84% 67%;
--chart-2: 190 80% 55%;
--chart-3: 160 60% 50%;
--chart-4: 40 90% 62%;
--chart-5: 330 75% 62%;
--surface-1: 225 13% 11%;
--surface-2: 225 12% 13%;
--surface-3: 225 11% 16%;
--sidebar-background: 225 14% 9%;
--sidebar-foreground: 222 11% 72%;
--sidebar-primary: 235 84% 67%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 225 12% 15%;
--sidebar-accent-foreground: 220 16% 96%;
--sidebar-border: 225 9% 18%;
--sidebar-ring: 235 84% 67%;
}

.dark.indigo {
--primary: 239.4 83.9% 66.7%;
--sidebar-primary: 239.4 83.9% 66.7%;
}
/* Pro Instrument commits to ONE brand accent (electric indigo, set above).
The user-selectable accent-color overrides were removed intentionally —
multiple brand colors dilute identity and read as a web-app template.
Per-category tool accents live in CATEGORY_ACCENT, not here. */
}

/* Custom Scrollbar for Sidebar */
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop-ui/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ export default async function RootLayout({
<body suppressHydrationWarning className="min-h-screen bg-background font-sans antialiased">
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
defaultTheme="dark"
enableSystem={false}
disableTransitionOnChange
>
<NextIntlClientProvider messages={messages}>
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop-ui/src/components/dashboard/dashboard-hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function Stat({
'flex h-7 w-7 shrink-0 items-center justify-center rounded-lg ring-1 ring-inset ring-border/50',
isZero
? 'bg-muted text-muted-foreground'
: 'bg-gradient-to-br from-primary/15 to-violet-500/10 text-primary',
: 'bg-primary/[0.12] text-primary',
)}
>
<Icon size={14} className={isZero ? 'text-muted-foreground' : 'text-primary'} />
Expand Down Expand Up @@ -120,10 +120,10 @@ export function DashboardHero({
<div className="hidden md:flex flex-col gap-4">
<div className="space-y-1.5">
<p className="inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
<span className="h-1.5 w-1.5 rounded-full bg-gradient-to-br from-primary to-violet-500" />
<span className="h-1.5 w-1.5 rounded-full bg-primary" />
{dashboardGreeting(t)}
</p>
<h1 className="dash-greeting text-2xl sm:text-[1.75rem] font-bold tracking-tight leading-tight">
<h1 className="text-2xl sm:text-[1.75rem] font-bold tracking-tight leading-tight text-foreground">
{user?.displayName
? t('welcomeBackNamed', { name: user.displayName.split(' ')[0] })
: t('welcomeBack')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const ToolCard = React.memo(function ToolCard({
<Card
ref={cardRef}
className={cn(
'dash-card-sheen relative h-full overflow-hidden rounded-xl border border-border bg-gradient-to-br from-card to-card/80',
'dash-card-sheen relative h-full overflow-hidden rounded-lg border border-border bg-card',
'shadow-sm dark:shadow-none',
'transition-[colors,transform,box-shadow,border-color] duration-200 ease-out',
'hover:border-primary/40 hover:shadow-lg hover:shadow-primary/5 motion-safe:hover:-translate-y-0.5',
Expand Down
16 changes: 6 additions & 10 deletions apps/desktop-ui/src/components/global-command-palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ export function GlobalCommandPalette() {
const commandSurfaceClass =
'[&_[cmdk-group-heading]]:px-3 [&_[cmdk-group-heading]]:pt-2.5 [&_[cmdk-group-heading]]:pb-1 [&_[cmdk-group-heading]]:text-[10px] [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-[0.14em] [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-14 [&_[cmdk-input]]:text-base'

// Shared row treatment — gradient selection sweep + icon chip (Raycast-style).
// Shared row treatment — solid accent selection + icon chip.
const itemClass =
'group mx-1 flex items-center gap-3 rounded-xl px-2 py-2 transition-colors aria-selected:bg-gradient-to-r aria-selected:from-primary/[0.14] aria-selected:via-primary/[0.06] aria-selected:to-transparent aria-selected:ring-1 aria-selected:ring-inset aria-selected:ring-primary/20'
'group mx-1 flex items-center gap-3 rounded-lg px-2 py-2 transition-colors aria-selected:bg-primary/10 aria-selected:ring-1 aria-selected:ring-inset aria-selected:ring-primary/20'
const chipClass =
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-muted/60 text-muted-foreground ring-1 ring-inset ring-border/50 transition-colors group-aria-selected:bg-gradient-to-br group-aria-selected:from-primary/20 group-aria-selected:to-violet-500/10 group-aria-selected:text-primary group-aria-selected:ring-primary/20'
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[hsl(var(--surface-3))] text-muted-foreground ring-1 ring-inset ring-border/50 transition-colors group-aria-selected:bg-primary/15 group-aria-selected:text-primary group-aria-selected:ring-primary/30'

return (
<Dialog open={open} onOpenChange={setOpen}>
Expand All @@ -169,16 +169,12 @@ export function GlobalCommandPalette() {
className={cn(
'overflow-hidden p-0 gap-0 max-w-2xl',
'max-h-[min(85vh,640px)] flex flex-col',
'rounded-2xl border-border/60 bg-popover/90 backdrop-blur-2xl',
'shadow-2xl shadow-primary/10 ring-1 ring-inset ring-white/[0.04]'
'rounded-xl border-border bg-popover/95 backdrop-blur-xl',
'shadow-2xl shadow-black/40'
)}
>
<DialogTitle className="sr-only">Search tools and pages</DialogTitle>
{/* Gradient hairline — the palette's signature accent */}
<div
aria-hidden
className="h-px w-full shrink-0 bg-gradient-to-r from-transparent via-primary/50 to-transparent"
/>
<div aria-hidden className="h-px w-full shrink-0 bg-border" />
<Command
className={cn(
'rounded-none border-none bg-transparent shadow-none',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ export function JsonFormatterLayout() {
const state = pane === 'left' ? leftPane : rightPane
const isTextMode = state.mode === ('text' as Mode)
return (
<div className="flex flex-wrap items-center gap-1 border-b border-border/60 bg-muted/30 px-2 py-1.5">
<div className="flex flex-wrap items-center gap-1 border-b border-border bg-[hsl(var(--surface-2))] px-2 py-1.5">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 max-w-[180px] gap-1 px-2">
Expand Down Expand Up @@ -544,15 +544,15 @@ export function JsonFormatterLayout() {

{isMobile ? (
/* Mobile: single full-height pane, toggled by tabs */
<div className="min-h-0 flex-1 rounded-xl border border-border/70 bg-card shadow-sm overflow-hidden flex flex-col">
<div className="min-h-0 flex-1 rounded-lg border border-border bg-card overflow-hidden flex flex-col">
{renderPaneToolbar(activePane)}
<div className="min-h-0 flex-1">{renderEditor(activePane)}</div>
</div>
) : (
/* Desktop: side-by-side resizable panes */
<ResizablePanelGroup
direction="horizontal"
className="min-h-0 flex-1 rounded-xl border border-border/70 bg-card shadow-sm overflow-hidden"
className="min-h-0 flex-1 rounded-lg border border-border bg-card overflow-hidden"
>
<ResizablePanel defaultSize={50} minSize={20} className="min-h-0">
<div className="flex h-full min-h-0 flex-col">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export function RegexTesterLayout() {
</Card>

<Card className="flex flex-col flex-1 min-h-0 overflow-hidden p-0">
<div className="flex items-center justify-between px-3 py-2 border-b border-border/50 bg-muted/30 shrink-0">
<div className="flex items-center justify-between px-3 py-2 border-b border-border bg-[hsl(var(--surface-2))] shrink-0">
<Label htmlFor="regex-text" className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
{t('testTextLabel')}
</Label>
Expand Down
Loading