diff --git a/cc-haha/desktop/src/api/heicodeAuth.ts b/cc-haha/desktop/src/api/heicodeAuth.ts index c5cb3f9..5c2d22c 100644 --- a/cc-haha/desktop/src/api/heicodeAuth.ts +++ b/cc-haha/desktop/src/api/heicodeAuth.ts @@ -29,6 +29,14 @@ export type HeicodeAuthStatus = { baseUrl: string models: { main: string; haiku: string; sonnet: string; opus: string } } | null + /** Logged-in user profile (Manager-side identity). Null until login. */ + user: { + userId?: string + email?: string + displayName?: string + role?: string + channelId?: string + } | null } export type HeicodeLoginInput = { diff --git a/cc-haha/desktop/src/components/layout/TitleBar.tsx b/cc-haha/desktop/src/components/layout/TitleBar.tsx index 3241297..8ad0dfc 100644 --- a/cc-haha/desktop/src/components/layout/TitleBar.tsx +++ b/cc-haha/desktop/src/components/layout/TitleBar.tsx @@ -1,5 +1,6 @@ import { useUIStore } from '../../stores/uiStore' import { useTranslation } from '../../i18n' +import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore' import { WindowControls } from './WindowControls' const isMacHost = typeof navigator !== 'undefined' @@ -61,8 +62,9 @@ export function TitleBar() { - {/* Right: Settings + Window controls */} + {/* Right: User pill + Settings + Window controls */}
+ @@ -72,6 +74,44 @@ export function TitleBar() { ) } +// 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 ( +
+ + {initial} + + + {name} + + {roleBadge ? ( + + {roleBadge} + + ) : null} +
+ ) +} + function TabButton({ active, onClick, diff --git a/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx b/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx index 9a3cc4b..2fcb3b0 100644 --- a/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx +++ b/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx @@ -43,7 +43,10 @@ export function HeicodeLoginPage() { data-tauri-drag-region >
- + Heicode
@@ -56,14 +59,20 @@ export function HeicodeLoginPage() { className="pointer-events-none absolute inset-0" style={{ backgroundImage: - 'radial-gradient(circle at 50% 30%, rgba(197,165,114,0.06), transparent 55%)', + 'radial-gradient(circle at 50% 30%, rgba(123,107,227,0.08), transparent 55%)', }} /> {/* Content */}
-

+ {/* Wordmark uses the same purple→blue gradient as the H glass logo + so the login surface visually matches the app icon (window + tray icon, taskbar, dock). */} +

Heicode

{ data?: { token?: string refreshToken?: string - user?: { id?: string; email?: string; channelId?: string } + user?: { + id?: string + email?: string + channelId?: string + // mcp-server may return either `name` or `display_name` depending on + // version; try both. role lets the UI badge admin/root. + name?: string + display_name?: string + role?: string + } } } = {} try { mgrJson = (await mgrRes.json()) as typeof mgrJson } catch { /* */ } @@ -270,6 +292,10 @@ async function handleLoginWithCredentials(req: Request): Promise { } const mcpUserId = mgrJson.data?.user?.id const mcpChannelId = mgrJson.data?.user?.channelId + const mcpEmail = mgrJson.data?.user?.email + const mcpDisplayName = + mgrJson.data?.user?.display_name || mgrJson.data?.user?.name + const mcpRole = mgrJson.data?.user?.role // Decode JWT exp claims (no signature verification — we already trust the // platform we just authenticated against). Used downstream to know when to @@ -289,6 +315,9 @@ async function handleLoginWithCredentials(req: Request): Promise { managerLoginUrl: managerBase, ...(mcpUserId !== undefined && { userId: mcpUserId }), ...(mcpChannelId !== undefined && { channelId: mcpChannelId }), + ...(mcpEmail && { email: mcpEmail }), + ...(mcpDisplayName && { displayName: mcpDisplayName }), + ...(mcpRole && { role: mcpRole }), } // ── Step 2: POST /api/user/session/from-agnet ───────── diff --git a/cc-haha/src/server/types/provider.ts b/cc-haha/src/server/types/provider.ts index 1748f2b..542d825 100644 --- a/cc-haha/src/server/types/provider.ts +++ b/cc-haha/src/server/types/provider.ts @@ -39,6 +39,12 @@ export const McpAuthSchema = z.object({ userId: z.string().optional(), /** Manager-side channelId for this user. */ channelId: z.string().optional(), + /** Email captured from /api/auth/login response (used for header user pill). */ + email: z.string().optional(), + /** Human-readable display name from /api/auth/login response (header user pill). */ + displayName: z.string().optional(), + /** Role string returned by Manager (e.g. 'user'/'admin'/'root'). */ + role: z.string().optional(), }) export type McpAuth = z.infer