Production-grade visual upgrade. Pulls the violet→blue gradient from
the H glass app icon up into a coherent design system: brand color,
focus rings, button gradients, sidebar accents, selection highlight,
shadow tints all derive from the same palette. Result: login surface
no longer clashes with the app icon, post-login UI feels like a
single product instead of a Tauri shell.
Changes
-------
src/theme/globals.css — Iris design tokens:
- --color-primary #C5A572 (gold) → #7B6BE3 (violet, from logo)
- --color-secondary slate → #6B7CE0 (blue from logo)
- --color-tertiary gold → #9A8DEC (lighter violet)
- Surface stack rebased to slight cool/blue undertone (#13131A,
#181820, …) so violet accents pop without clashing
- Borders softened: --color-border 0.08 → 0.06, separator 0.06 → 0.04
- Radii loosened: sm 4 → 6, md 8 → 10, lg 12 → 14, xl 16 → 18
- --gradient-btn-primary: 3-stop violet→primary→blue (replaces flat
gold gradient); CTA glow shadow tinted violet
- --gradient-brand-wordmark exposed as a token so wordmark / avatar
gradient stays consistent across surfaces
- --shadow-dropdown: 3-stop layered, dark stop tinted violet so
floating menus feel of-a-piece with brand
- --shadow-focus-ring: violet 55% so input focus is visible
- --color-selection-bg: violet 32%
- Sidebar item hover/active: rgba violet base instead of plain white
- Sidebar panel backdrop: 2-stop radial (violet top-left + blue
bottom-right) replacing plain gold radial
- Type scale tokens: --text-xxs … --text-display
- Body font-feature-settings: 'kern','liga','cv11' +
text-rendering: optimizeLegibility for crisp Inter / Manrope
- .tabular-nums utility for status counters
src/components/login/HeicodeLoginPage.tsx — login redesign:
- Embeds /app-icon.png (the H glass logo) above wordmark
- Drop-shadow violet-tinted so icon feels continuous with backdrop
- Layered backdrop: violet halo top, blue glow bottom, faint SVG
grain texture for tactile premium feel
- Wordmark + tagline use --gradient-brand-wordmark token
- Card max-width tightened to sm (was md) for taller, more focused
composition (Linear / Vercel / Raycast desktop login style)
- Chrome strip: blurred translucent surface
src/components/login/ProviderLoginCard.tsx:
- Card gets faint violet→neutral surface gradient (lifts off backdrop
without heavy 1px border)
- Top hairline accent (centered violet line) at card top edge
- CTA button uses --gradient-btn-primary; hover glow stronger;
active scale 0.99 microinteraction
- Removed hardcoded rgba(197,165,114,…) gold artifact
src/components/layout/TitleBar.tsx:
- UserPill avatar gradient now uses --gradient-brand-wordmark token
(was inline hardcoded violet) so future palette tweaks propagate
src-tauri (Cargo.toml + src/lib.rs):
- Add tauri-plugin-single-instance and register it as the FIRST
plugin in the Builder chain. Subsequent Heicode launches focus
the existing window via show_main_window() instead of spawning
a new sidecar / new server port. Fixes "every shortcut click
starts a new instance" behavior.
Verification
------------
- bunx tsc -b --noEmit (desktop) clean
- cargo check (src-tauri) clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
140 lines
5.4 KiB
TypeScript
140 lines
5.4 KiB
TypeScript
import { useUIStore } from '../../stores/uiStore'
|
|
import { useTranslation } from '../../i18n'
|
|
import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore'
|
|
import { WindowControls } from './WindowControls'
|
|
|
|
const isMacHost = typeof navigator !== 'undefined'
|
|
&& /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || '')
|
|
|
|
export function TitleBar() {
|
|
const { activeView, setActiveView } = useUIStore()
|
|
const t = useTranslation()
|
|
|
|
return (
|
|
<div
|
|
className="h-[var(--titlebar-height)] flex items-center border-b border-[var(--color-border)] bg-[var(--color-surface)] select-none"
|
|
data-tauri-drag-region
|
|
>
|
|
{/* macOS traffic-light spacer (native overlay buttons appear here) */}
|
|
{isMacHost ? <div className="w-[78px] flex-shrink-0" data-tauri-drag-region /> : <div className="w-3" data-tauri-drag-region />}
|
|
|
|
{/* Logo */}
|
|
<div className="flex items-center gap-2 mr-4" data-tauri-drag-region>
|
|
<span className="text-xs font-bold tracking-[0.22em] text-[var(--color-brand)] uppercase">Heicode</span>
|
|
</div>
|
|
|
|
{/* Navigation arrows */}
|
|
<div className="flex items-center gap-1 mr-4">
|
|
<button className="p-1 rounded-[var(--radius-md)] text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M15 18l-6-6 6-6" />
|
|
</svg>
|
|
</button>
|
|
<button className="p-1 rounded-[var(--radius-md)] text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M9 18l6-6-6-6" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Center tabs */}
|
|
<div className="flex-1 flex items-center justify-center gap-1" data-tauri-drag-region>
|
|
<TabButton
|
|
active={activeView === 'code'}
|
|
onClick={() => setActiveView('code')}
|
|
icon="code"
|
|
>
|
|
{t('titlebar.code')}
|
|
</TabButton>
|
|
<TabButton
|
|
active={activeView === 'terminal'}
|
|
onClick={() => setActiveView('terminal')}
|
|
icon="terminal"
|
|
>
|
|
{t('titlebar.terminal')}
|
|
</TabButton>
|
|
<TabButton
|
|
active={activeView === 'history'}
|
|
onClick={() => setActiveView('history')}
|
|
icon="history"
|
|
>
|
|
{t('titlebar.history')}
|
|
</TabButton>
|
|
</div>
|
|
|
|
{/* Right: User pill + Settings + Window controls */}
|
|
<div className="flex items-center h-full">
|
|
<UserPill />
|
|
<button className="p-1.5 mr-2 rounded-[var(--radius-md)] text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] transition-colors">
|
|
<span className="material-symbols-outlined text-[18px]">settings</span>
|
|
</button>
|
|
<WindowControls />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Tiny avatar + display name + role badge. Sourced from
|
|
// useHeicodeAuthStore.status.user, populated by /api/heicode-auth/status.
|
|
// Hidden until auth is loaded so the bar doesn't flicker on app boot.
|
|
function UserPill() {
|
|
const user = useHeicodeAuthStore((s) => s.status?.user ?? null)
|
|
if (!user) return null
|
|
const name = user.displayName?.trim() || user.email?.trim() || 'You'
|
|
const initial = (name.slice(0, 1) || '?').toUpperCase()
|
|
// Show role badge only when meaningful (admin/root); plain "user" is noise.
|
|
const roleBadge =
|
|
user.role && user.role !== 'user' && user.role !== '1'
|
|
? user.role
|
|
: null
|
|
return (
|
|
<div
|
|
className="mr-2 flex items-center gap-2 rounded-full border border-[var(--color-border)] bg-[var(--color-surface-container)] py-0.5 pl-1 pr-3 text-xs text-[var(--color-text-secondary)] hover:border-[var(--color-border-hi,rgba(255,255,255,0.14))]"
|
|
title={user.email || user.userId || ''}
|
|
>
|
|
<span
|
|
className="flex h-5 w-5 items-center justify-center rounded-full text-[10px] font-bold text-white"
|
|
style={{ backgroundImage: 'var(--gradient-brand-wordmark)' }}
|
|
>
|
|
{initial}
|
|
</span>
|
|
<span className="max-w-[120px] truncate font-medium text-[var(--color-text-primary)]">
|
|
{name}
|
|
</span>
|
|
{roleBadge ? (
|
|
<span className="rounded-[var(--radius-sm)] border border-[var(--color-border)] px-1 text-[9px] uppercase tracking-wider text-[var(--color-text-tertiary)]">
|
|
{roleBadge}
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function TabButton({
|
|
active,
|
|
onClick,
|
|
icon,
|
|
children,
|
|
}: {
|
|
active: boolean
|
|
onClick: () => void
|
|
icon: string
|
|
children: React.ReactNode
|
|
}) {
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
className={`
|
|
flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-[var(--radius-md)] transition-colors duration-200
|
|
${active
|
|
? 'bg-[var(--color-surface-selected)] text-[var(--color-text-primary)]'
|
|
: 'text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'
|
|
}
|
|
`}
|
|
>
|
|
<span className="material-symbols-outlined text-[16px]">{icon}</span>
|
|
{children}
|
|
</button>
|
|
)
|
|
}
|